Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow-ify ReactPartialRenderer #11251

Merged
merged 6 commits into from
Oct 26, 2017

Conversation

iamdustan
Copy link
Contributor

@iamdustan iamdustan commented Oct 17, 2017

Closes #11175

cc @gaearon

childIndex: number,
context: Object,
footer: string,
debugElementStack: Array<any>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the Frame annotations below only add debugElementStack in dev mode which causd a lot of errors in the logging utility functions. Just declaring it as always present (which it is in dev, and I think appropriately defended against in prod) solved a lot of the flow errors.

@iamdustan iamdustan force-pushed the flow-react-partial-renderer branch 2 times, most recently from d8de171 to 6484d95 Compare October 17, 2017 14:28
@@ -320,6 +321,7 @@ function validateRenderResult(child, type) {

function resolve(child, context) {
while (React.isValidElement(child)) {
child = (child: React.Element);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not really sure why Flow isn’t understanding that child is a React.Element at this point without this extra hint. I thought that by typing the callsites to resolve this would just work.

// Assume all trees start in the HTML namespace (not totally true, but
// this is what we did historically)
domNamespace: Namespaces.html,
children,
childIndex: 0,
context: emptyObject,
footer: '',
};
}: any);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these are because of #11251 (comment)

var frame = {
  ...everythingExcept_debugElementStack
};
if (process.env.NODE_ENV) {
  frame.debugElementStack = [];
}

So the frame: Frame = ({}: any) annotation is so that flow knows that this object can have the debugElementStack property.

var frame: Frame = ({
  ...everythingExcept_debugElementStack
// the any is to prevent Flow from considered this a
// sealed object without the debugElementStack
}: any);

without this then the following chunks of code have a lot of errors due to debugElementStack being undefined

https://github.com/iamdustan/react/blob/bacc1e72f58e1ec3eab16c52d6db28c7d0c21014/src/renderers/shared/server/ReactPartialRenderer.js#L65-L98

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typing something as any is generally as bad as not typing in the first place. Or possibly worse because now you have an illusion of type safety.

Could we perhaps use approach like

if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);

and

const parentNamespace = ((parentHostContext: any): HostContextProd);

?

Or just treat that field as always optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is typed similarly, just var binding: Type = ({}: any); instead of var binding = (({}: any): Type);

Though I’ll look to into make the making separate *Prod and *Dev variants yet.

domNamespace: parentNamespace,
children,
childIndex: 0,
context: context,
footer: '',
};
}: Object);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

domNamespace: getChildNamespace(parentNamespace, element.type),
tag,
children,
childIndex: 0,
context: context,
footer: footer,
};
}: any);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to get rid of any's.

@@ -319,6 +321,7 @@ function validateRenderResult(child, type) {

function resolve(child, context) {
while (React.isValidElement(child)) {
child = (child: React.Element);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast seemed suspicious to me. Because if it "worked" then Flow would've inferred it anyway.

I checked, and it seems like React.Element is any 😞 Flow can be so confusing. This is not the thing we're supposed to be using, apparently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, one of my few wishes for flow is that it would be easier to see what it thought something was at any point in time and why.

constructor(element, makeStaticMarkup) {
stack: Array<Frame>;
exhausted: boolean;
currentSelectValue: null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a very helpful annotation. It's not always null. So by encoding it like this we actually make it harder to fix the Flow coverage because now it looks like fixing this is a Flow violation.

@gaearon gaearon force-pushed the flow-react-partial-renderer branch 2 times, most recently from 720e328 to d9bab39 Compare October 26, 2017 16:48
@gaearon gaearon changed the title flow ReactPartialRenderer Flow-ify ReactPartialRenderer Oct 26, 2017
Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a few changes.

while (React.isValidElement(child)) {
// Safe because we just checked it's an element.
var element: ReactElement = ((child: any): ReactElement);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how to safely "cast" things to elements. (Ideally isValidElement() should work as a refinement but Flow folks haven't implemented that yet.)

I introduced a separate variable because you can't change the type of the existing one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the : ReactElement on both sides of the assignment? I’ve only ever done one or the other and it always seemed to work as expected.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary. Can remove.

child: mixed,
context: Object,
): {|
child: mixed,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed is always better than any.

childIndex: 0,
context: emptyObject,
footer: '',
};
if (__DEV__) {
topFrame.debugElementStack = [];
((topFrame: any): FrameDev).debugElementStack = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the trick I meant to suggest. Keep both PROD and DEV paths typechecked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying (and unable) to find a way to get Flow to know that the only variant between Frame and FrameDev that could ever make it to the *CurrentDebugStack methods above were FrameDev types.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it's possible. I think using PROD type most things and then unsafely casting to DEV type for DEV things is the way to go.

@gaearon gaearon merged commit 80849fd into facebook:master Oct 26, 2017
@iamdustan iamdustan deleted the flow-react-partial-renderer branch October 27, 2017 13:28
@@ -66,7 +66,8 @@ if (__DEV__) {
var currentDebugStack = null;
var currentDebugElementStack = null;
var setCurrentDebugStack = function(stack) {
currentDebugElementStack = stack[stack.length - 1].debugElementStack;
var frame: Frame = stack[stack.length - 1];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this entire chunk of code is inside a if (__DEV__) block I think we could safely say that var frame: FrameDev = stack[stack.length - 1];

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess yea.

Ethan-Arrowood pushed a commit to Ethan-Arrowood/react that referenced this pull request Dec 8, 2017
* flow ReactPartialRenderer

* prettier

* moving flow type around;

* Move anys to DEV-only code path and keep it typechecked

* Increase Flow coverage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants