-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Make getContextualTypeOfApparentType mapType over unions #17668
Conversation
There was a problem hiding this 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:
src/compiler/checker.ts
Outdated
*/ | ||
function getInstantiatedContextualType(node: Expression, checkMode?: CheckMode): Type { | ||
if (checkMode === CheckMode.Inferential && node.contextualType && node.contextualMapper) { | ||
return instantiateType(node.contextualType, node.contextualMapper); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. 🐱
src/compiler/checker.ts
Outdated
@@ -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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
33fe6f9
to
9ccdf04
Compare
There was a problem hiding this 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.
There was a problem hiding this 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.
src/compiler/checker.ts
Outdated
@@ -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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/compiler/checker.ts
Outdated
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: used
Do you happen to know if this fixes #16136? |
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) {
}
}) |
@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). |
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). |
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). |
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. |
09c2693
to
0864f7d
Compare
After much deliberation and inspection, much simpler fix Undo Redo
0864f7d
to
0d3a4ae
Compare
@ahejlsberg Implemented the waaaaay simpler fix we looked at today; which interestingly, still doesn't fix the JSX case. |
This allows us to actually find member types for union-typed object members in the same way we do for non-unions.
Fixes #16817.