-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Fix destructuring control flow analysis #29053
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
Conversation
@@ -41,18 +41,18 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: | |||
function f1(t1: T1, t2: T2, t3: T3, t4: T4, x: number) { | |||
let [d10, d11, d12] = t1; // string, number | |||
~~~ | |||
!!! error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. |
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.
This error message change is a regression, I believe. An explicit tuple length error here is quite desirable.
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 think the new error is just as good, if not better, and certainly more consistent. I always found this particular message strange as there isn't actually a tuple being assigned to. If we think it is important, we could instead improve the message we give when accessing an out-of-range tuple element in getIndexedAccessType
.
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.
We explicitly added the tuple length error message to override the message it regressed to. Changing getIndexedAccessType
to use it seems kinda appropriate.
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 think it would be nice to give a more specific error message if it can still be rewired into getIndexedAccessType
:
Tuple type '{0}' of length '{1}' has no element at index '{2}'.
Seems pretty reasonable.
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 think, like Daniel, that will be better message to avoid issues like #29058.
function getAccessedPropertyName(access: PropertyAccessExpression | ElementAccessExpression): __String | undefined { | ||
return isPropertyAccessExpression(access) ? access.name.escapedText : | ||
function getAccessedPropertyName(access: AccessExpression): __String | undefined { | ||
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : | ||
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : |
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.
While we're here, we should probably change this to isStringOrNumericLiteralLike
so NoSubstitutionTemplateLiteral
s can act like string literals.
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.
The results of getAccessedPropertyName
need to be more strongly inspected - it seems like the results are normally checked with a truthiness check, which will be incorrect for properties named by the empty string. It also looks like some explicit tuple length errors have been replaced with some worse errors - we probably shouldn't regress there.
@@ -3350,6 +3351,7 @@ declare namespace ts { | |||
function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; | |||
function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; | |||
function isElementAccessExpression(node: Node): node is ElementAccessExpression; | |||
function isAccessExpression(node: Node): node is AccessExpression; |
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.
Do we really need to export this from the public API?
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.
While I think the error message could still usage a change, this looks OK.
@DanielRosenwasser @weswigham Now with new and improved error message. |
@DanielRosenwasser And now with no public API changes. |
This PR fixes multiple issues related to control flow analysis of destructuring declarations and assignments. Intuitively, for type checking purposes the following should always be true:
obj.x
is equivalent toobj["x"]
(property access vs. element access)let { x } = obj
is equivalent tolet x = obj.x
(destructuring declaration){ x } = obj
is equivalent tox = obj.x
(destructuring assignment)However, there were several situations where this was not the case:
This PR fixes all of the above. The PR changes type checking for destructuring declarations and assignments to consistently use
getIndexedAccessType
to compute the destructured type andgetFlowTypeOfReference
on a synthetic element access expression to compute the control flow type. Furthermore, the PR properly unifies all handing of property access expressions (obj.x
) and element access expressions (obj["x"]
) in control flow analysis, and removes the increasingly convoluted logic we needed to handle binding elements from destructuring declarations and object and array literals from destructuring assignments (they now become synthetic element access expressions).Fixes #26379.
Fixes #28792.