Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13326,6 +13326,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
t := c.getTypeOfSymbol(symbol)
declaration := symbol.ValueDeclaration
if declaration != nil {
switch {
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
// parameter (a parameter for which there are no assignments in the function body), and if the parent type
// for the destructuring is a union type, one or more of the binding elements may represent discriminant
Expand All @@ -13349,7 +13350,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
// the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
// as if it occurred in the specified location. We then recompute the narrowed binding element type by
// destructuring from the narrowed parent type.
if ast.IsBindingElement(declaration) && declaration.Initializer() == nil && !hasDotDotDotToken(declaration) && len(declaration.Parent.Elements()) >= 2 {
case ast.IsBindingElement(declaration) && declaration.Initializer() == nil && !hasDotDotDotToken(declaration) && len(declaration.Parent.Elements()) >= 2:
parent := declaration.Parent.Parent
rootDeclaration := ast.GetRootDeclaration(parent)
if ast.IsVariableDeclaration(rootDeclaration) && c.getCombinedNodeFlagsCached(rootDeclaration)&ast.NodeFlagsConstant != 0 || ast.IsParameter(rootDeclaration) {
Expand All @@ -13361,21 +13362,21 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
if parentType != nil {
parentTypeConstraint = c.mapType(parentType, c.getBaseConstraintOrType)
}
links.flags &^= NodeCheckFlagsInCheckIdentifier
if parentTypeConstraint != nil && parentTypeConstraint.flags&TypeFlagsUnion != 0 && !(ast.IsParameter(rootDeclaration) && c.isSomeSymbolAssigned(rootDeclaration)) {
pattern := declaration.Parent
narrowedType := c.getFlowTypeOfReferenceEx(pattern, parentTypeConstraint, parentTypeConstraint, nil /*flowContainer*/, getFlowNodeOfNode(location))
if narrowedType.flags&TypeFlagsNever != 0 {
return c.neverType
t = c.neverType
} else {
// Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds
// checks because the narrowed type may have lower arity than the full parent type. For example,
// for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3].
t = c.getBindingElementTypeFromParentType(declaration, narrowedType, true /*noTupleBoundsCheck*/)
}
// Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds
// checks because the narrowed type may have lower arity than the full parent type. For example,
// for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3].
return c.getBindingElementTypeFromParentType(declaration, narrowedType, true /*noTupleBoundsCheck*/)
}
links.flags &^= NodeCheckFlagsInCheckIdentifier
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason you didn't unset the flag before the if like in the original code?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it was simply that it wasn't clear we might recurse through getBindingElementTypeFromParentType.

}
}
}
// If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually
// typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may
// represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to
Expand All @@ -13396,7 +13397,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
// the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as
// if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the
// narrowed tuple type.
if ast.IsParameter(declaration) && declaration.Type() == nil && declaration.Initializer() == nil && !hasDotDotDotToken(declaration) {
case ast.IsParameter(declaration) && declaration.Type() == nil && declaration.Initializer() == nil && !hasDotDotDotToken(declaration):
fn := declaration.Parent
if len(fn.Parameters()) >= 2 && c.isContextSensitiveFunctionOrObjectLiteralMethod(fn) {
contextualSignature := c.getContextualSignature(fn)
Expand All @@ -13410,7 +13411,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node
if restType.flags&TypeFlagsUnion != 0 && everyType(restType, isTupleType) && !core.Some(fn.Parameters(), c.isSomeSymbolAssigned) {
narrowedType := c.getFlowTypeOfReferenceEx(fn, restType, restType, nil /*flowContainer*/, getFlowNodeOfNode(location))
index := slices.Index(fn.Parameters(), declaration) - (core.IfElse(ast.GetThisParameter(fn) != nil, 1, 0))
return c.getIndexedAccessType(narrowedType, c.getNumberLiteralType(jsnum.Number(index)))
t = c.getIndexedAccessType(narrowedType, c.getNumberLiteralType(jsnum.Number(index)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
circularDestructuring.ts(1,7): error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number'.
circularDestructuring.ts(1,9): error TS2339: Property 'c' does not exist on type 'string | number'.
circularDestructuring.ts(1,12): error TS2339: Property 'f' does not exist on type 'string | number'.
circularDestructuring.ts(1,12): error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
circularDestructuring.ts(1,43): error TS2448: Block-scoped variable 'f' used before its declaration.


==== circularDestructuring.ts (5 errors) ====
const { c, f }: string | number = { c: 0, f };
~~~~~~~~
!!! error TS2322: Type '{ c: number; f: any; }' is not assignable to type 'string | number'.
~
!!! error TS2339: Property 'c' does not exist on type 'string | number'.
~
!!! error TS2339: Property 'f' does not exist on type 'string | number'.
~
!!! error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
~
!!! error TS2448: Block-scoped variable 'f' used before its declaration.
!!! related TS2728 circularDestructuring.ts:1:12: 'f' is declared here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//// [tests/cases/compiler/circularDestructuring.ts] ////

=== circularDestructuring.ts ===
const { c, f }: string | number = { c: 0, f };
>c : Symbol(c, Decl(circularDestructuring.ts, 0, 7))
>f : Symbol(f, Decl(circularDestructuring.ts, 0, 10))
>c : Symbol(c, Decl(circularDestructuring.ts, 0, 35))
>f : Symbol(f, Decl(circularDestructuring.ts, 0, 41))

11 changes: 11 additions & 0 deletions testdata/baselines/reference/compiler/circularDestructuring.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//// [tests/cases/compiler/circularDestructuring.ts] ////

=== circularDestructuring.ts ===
const { c, f }: string | number = { c: 0, f };
>c : any
>f : any
>{ c: 0, f } : { c: number; f: any; }
>c : number
>0 : 0
>f : any

3 changes: 3 additions & 0 deletions testdata/tests/cases/compiler/circularDestructuring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @strict: true
// @noEmit: true
const { c, f }: string | number = { c: 0, f };
Loading