Skip to content

Make getContextualTypeOfApparentType mapType over unions #17668

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

Merged

Conversation

weswigham
Copy link
Member

@weswigham weswigham commented Aug 7, 2017

This allows us to actually find member types for union-typed object members in the same way we do for non-unions.

Fixes #16817.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Had a couple of questions on my first pass:

*/
function getInstantiatedContextualType(node: Expression, checkMode?: CheckMode): Type {
if (checkMode === CheckMode.Inferential && node.contextualType && node.contextualMapper) {
return instantiateType(node.contextualType, node.contextualMapper);
Copy link
Member

Choose a reason for hiding this comment

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

don't you still need to get an apparent type in case the result is still a type parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Absolutely. I used to be doing it at use-site, but forgot to add it back when I decided not to. Good catch; that's actually why there were baseline changes, and why the build was broken. It's a much neater fix now. 🐱

@@ -16844,7 +16855,7 @@ namespace ts {

// Check if function expression is contextually typed and assign parameter types if so.
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
const contextualSignature = getContextualSignature(node);
const contextualSignature = getContextualSignature(node); // Intentionally do not pass check mode; resolve without instantiation
Copy link
Member

Choose a reason for hiding this comment

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

why? Because it's not NodeCheckFlags.ContextChecked?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's what I would assume; experimentally, the real issue is that we fail to make a ton of inferences when this code path does propagate the check mode.

@weswigham weswigham force-pushed the contextual-type-of-optional-members branch from 33fe6f9 to 9ccdf04 Compare August 8, 2017 19:45
Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Looks good to me, although it would be nice to have @ahejlsberg take a look at a structural change like this.

Copy link
Contributor

@rbuckton rbuckton left a comment

Choose a reason for hiding this comment

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

@ahejlsberg, should we be caching the types for any CheckMode other than Normal? It seems we cache the type in checkExpressionCached as well regardless of the provided CheckMode value.

@@ -4515,7 +4515,7 @@ namespace ts {
}
// Handle export default expressions
if (declaration.kind === SyntaxKind.ExportAssignment) {
return links.type = checkExpression((<ExportAssignment>declaration).expression);
return links.type = checkExpression((<ExportAssignment>declaration).expression, checkMode);
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't seem like we should be holding on to the type here if this is not a normal check mode.

Copy link
Member

Choose a reason for hiding this comment

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

Will this ever be anything apart from normal?

Copy link
Member Author

Choose a reason for hiding this comment

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

Inferential. The reason its even being threaded through here is to catch when it is set to inferential.

return type && getApparentType(type);
}

/**
* This must be called when a contextual type function is attempting to dig into a type to contextually type a subexpression
* Otherwise, contextual typing stops when a type argument is encountered, and `any` is ued instead.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: used

@DanielRosenwasser
Copy link
Member

Do you happen to know if this fixes #16136?

@DanielRosenwasser
Copy link
Member

Can you add the following simple test case since, as you mentioned, this isn't necessarily tied to optionality?

interface Bar {
    bar: (a: number) => void;
}

declare function foo<T extends Bar>(x: string | T): T

let x = foo({
    bar(x) {
    }
})

@weswigham
Copy link
Member Author

@DanielRosenwasser Synced with master and updated tests. Also I've checked; this does not fix #16136 (though it may not be possible to fix that without this change done in conjunction with it).

@weswigham
Copy link
Member Author

I've modified the solution to work for array literals in addition to object literals. Also theoretically JSX, but JSX apparently never does an inferential pass because it's "overload resolution" is not real overload resolution (but I've included a test showcasing the issue).

@weswigham weswigham changed the title Sometimes instantiate the contextual type of optional members Sometimes instantiate the contextual type of members during inferential typing Oct 19, 2017
@ahejlsberg
Copy link
Member

I'm concerned with the cost of instantiating contextual types. We don't cache contextual types, yet obtaining a contextual type is assumed to be cheap and we do it at will in a number of places. Furthermore, if we're going to instantiate contextual types, it seems like we should always do it and not just when obtaining a member of a contextual object type. Indeed, with the approach in this PR I think we potentially end up instantiating types that were already instantiated (e.g. if we have nested object literals).

@ahejlsberg
Copy link
Member

I should add also that instantiation of contextual types is deferred by design such that we don't prematurely "pull" on type parameters for which we are currently performing inference. For example, when assigning contextual types to function expression or arrow function arguments, we do instantiate the contextual type, but only once we have drilled down to the corresponding part of the contextual type. Thus, we only fix type parameters if they are actually referenced in the (part of the) contextual type for the function.

@weswigham weswigham changed the title Sometimes instantiate the contextual type of members during inferential typing Make getContextualTypeOfApparentType mapType over unions Oct 21, 2017
@weswigham weswigham force-pushed the contextual-type-of-optional-members branch from 09c2693 to 0864f7d Compare October 21, 2017 00:23
After much deliberation and inspection, much simpler fix

Undo

Redo
@weswigham weswigham force-pushed the contextual-type-of-optional-members branch from 0864f7d to 0d3a4ae Compare October 21, 2017 00:24
@weswigham
Copy link
Member Author

@ahejlsberg Implemented the waaaaay simpler fix we looked at today; which interestingly, still doesn't fix the JSX case.

@weswigham weswigham merged commit 0593ba2 into microsoft:master Nov 6, 2017
@weswigham weswigham deleted the contextual-type-of-optional-members branch November 6, 2017 20:52
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants