-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Fix discriminant checking in contextual types #23794
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15036,6 +15036,26 @@ namespace ts { | |
} | ||
} | ||
|
||
// Return true if the given expression is possibly a discriminant value. We limit the kinds of | ||
// expressions we check to those that don't depend on their contextual type in order not to cause | ||
// recursive (and possibly infinite) invocations of getContextualType. | ||
function isPossiblyDiscriminantValue(node: Expression): boolean { | ||
switch (node.kind) { | ||
case SyntaxKind.StringLiteral: | ||
case SyntaxKind.NumericLiteral: | ||
case SyntaxKind.NoSubstitutionTemplateLiteral: | ||
case SyntaxKind.TrueKeyword: | ||
case SyntaxKind.FalseKeyword: | ||
case SyntaxKind.NullKeyword: | ||
case SyntaxKind.Identifier: | ||
return true; | ||
case SyntaxKind.PropertyAccessExpression: | ||
case SyntaxKind.ParenthesizedExpression: | ||
return isPossiblyDiscriminantValue((<PropertyAccessExpression | ParenthesizedExpression>node).expression); | ||
} | ||
return false; | ||
} | ||
|
||
// Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily | ||
// be "pushed" onto a node using the contextualType property. | ||
function getApparentTypeOfContextualType(node: Expression): Type { | ||
|
@@ -15049,8 +15069,8 @@ namespace ts { | |
propLoop: for (const prop of node.properties) { | ||
if (!prop.symbol) continue; | ||
if (prop.kind !== SyntaxKind.PropertyAssignment) continue; | ||
if (isDiscriminantProperty(contextualType, prop.symbol.escapedName)) { | ||
const discriminatingType = getTypeOfNode(prop.initializer); | ||
if (isPossiblyDiscriminantValue(prop.initializer) && isDiscriminantProperty(contextualType, prop.symbol.escapedName)) { | ||
const discriminatingType = checkExpression(prop.initializer); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd work much more uniformly to just check the initializer with the thus-far-known context, eg: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so. There's nothing kludgy about this fix and it definitely reduces the amount of work more than what you suggest. When obtaining a contextual type we should never reach back down to the same type again unless we're absolutely sure that it won't again ask for a contextual type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We use |
||
for (const type of (contextualType as UnionType).types) { | ||
const targetType = getTypeOfPropertyOfType(type, prop.symbol.escapedName); | ||
if (targetType && checkTypeAssignableTo(discriminatingType, targetType, /*errorNode*/ undefined)) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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.
An identifier may end up being any type and cause a circularity still (likely indirected through control flow analysis, like the issue in #23661 is) - while this change may happen to break the exact loop in the issue, there's probably still a problem if you add an indirection.
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.
I'm not sure what you mean. The key here is that
checkIdentifier
will never ask for the contextual type of the identifier and therefore it can't cause a circularity issue.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.
AFAIK if the
Identifier
in question is a parameter name it can still be contextually typed - though I suppose that case should be handled outside ofcheckExpression
, and insidegetContextualType
.Uh oh!
There was an error while loading. Please reload this page.
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.
Also, literally any expression can be a discriminant here (because discrimination is type-based and not syntactic), see this, which works today and this PR would break: