diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 920dbb775d2a1..a0ca4a9be1b49 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9491,8 +9491,33 @@ namespace ts { // The type implied by a binding pattern is independent of context, so we check the initializer with no // contextual type or, if the element itself is a binding pattern, with the type implied by that binding // pattern. - const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; - return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); + if (isBindingPattern(element.name)) { + const contextualType = getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false); + return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); + } + + // (#49989) + // In cases where the intitializer is an identifier referencing a sibling symbol (i.e., one that is + // defined in the same declaration) a false circular relationship will be concluded. For example, take + // the declarations below: + // + // const [a, b = a] = [1]; + // const {a, b = a} = {a: 1}; + // + // Here, when the `element` is the second binding element (i.e., `b = a`) the initializer is `a` which + // itself is defined within the same binding pattern. + // + // So, we check the initializer expression for any references to sibling symbols and if any, then we'd + // conclude the binding element type as `unknownType` and thus skip further circulations in type + // checking. + const siblings = mapDefined(element.parent.elements, x => x !== element && isBindingElement(x) && isIdentifier(x.name) ? x : undefined); + const checkIsSiblingInvolved = (node: Node) => { + const declaration = isIdentifier(node) && getReferencedValueDeclaration(node); + return declaration && isBindingElement(declaration) && siblings.includes(declaration); + }; + const isSiblingElementInvolded = checkIsSiblingInvolved(element.initializer) || forEachChildRecursively(element.initializer, checkIsSiblingInvolved); + return isSiblingElementInvolded ? anyType + : addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, unknownType))); } if (isBindingPattern(element.name)) { return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); diff --git a/tests/baselines/reference/dependentDestructuredVariables.errors.txt b/tests/baselines/reference/dependentDestructuredVariables.errors.txt index e3704b567ac66..7de02531e7486 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.errors.txt +++ b/tests/baselines/reference/dependentDestructuredVariables.errors.txt @@ -1,8 +1,7 @@ -tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts(314,5): error TS7022: 'value1' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts(314,5): error TS7031: Binding element 'value1' implicitly has an 'any' type. -==== tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts (2 errors) ==== +==== tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts (1 errors) ==== type Action = | { kind: 'A', payload: number } | { kind: 'B', payload: string }; @@ -318,8 +317,6 @@ tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts(314,5): er function foo({ value1, ~~~~~~ -!!! error TS7022: 'value1' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - ~~~~~~ !!! error TS7031: Binding element 'value1' implicitly has an 'any' type. test1 = value1.test1, test2 = value1.test2, diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types index 5a0e03dda6e56..a1e95eade9dce 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types @@ -1,28 +1,28 @@ === tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts === const [a, b = a] = [1]; // ok ->a : any ->b : any ->a : any +>a : number +>b : number +>a : number >[1] : [number] >1 : 1 const [c, d = c, e = e] = [1]; // error for e = e ->c : any ->d : any ->c : any +>c : number +>d : number +>c : number >e : any >e : any >[1] : [number] >1 : 1 const [f, g = f, h = i, i = f] = [1]; // error for h = i ->f : any ->g : any ->f : any ->h : any ->i : any ->i : any ->f : any +>f : number +>g : number +>f : number +>h : number +>i : number +>i : number +>f : number >[1] : [number] >1 : 1 diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js new file mode 100644 index 0000000000000..70ba454c96b5b --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js @@ -0,0 +1,48 @@ +//// [destructuringArrayBindingPatternAndAssignment5.ts] +// To be inferred as `number` +function f1() { + const [a1, b1 = a1] = [1]; + const [a2, b2 = 1 + a2] = [1]; + const [a3, b3 = (() => 1 + a3)()] = [1]; + const [a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]; +} + +// To be inferred as `string` +function f2() { + const [a1, b1 = a1] = ['hi']; + const [a2, b2 = [a2, '!'].join()] = ['hi']; + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi']; + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi']; +} + +// To be inferred as `string | number` +function f3() { + const [a1, b1 = a1] = ['hi', 1]; + const [a2, b2 = [a2, '!'].join()] = ['hi', 1]; + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi', 1]; + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi', 1]; +} + + +//// [destructuringArrayBindingPatternAndAssignment5.js] +// To be inferred as `number` +function f1() { + var _a = [1], a1 = _a[0], _b = _a[1], b1 = _b === void 0 ? a1 : _b; + var _c = [1], a2 = _c[0], _d = _c[1], b2 = _d === void 0 ? 1 + a2 : _d; + var _e = [1], a3 = _e[0], _f = _e[1], b3 = _f === void 0 ? (function () { return 1 + a3; })() : _f; + var _g = [1], a4 = _g[0], _h = _g[1], b4 = _h === void 0 ? (function () { return (function () { return 1 + a4; })() + 1; })() : _h; +} +// To be inferred as `string` +function f2() { + var _a = ['hi'], a1 = _a[0], _b = _a[1], b1 = _b === void 0 ? a1 : _b; + var _c = ['hi'], a2 = _c[0], _d = _c[1], b2 = _d === void 0 ? [a2, '!'].join() : _d; + var _e = ['hi'], a3 = _e[0], _f = _e[1], b3 = _f === void 0 ? (function () { return [a3, '!'].join(); })() : _f; + var _g = ['hi'], a4 = _g[0], _h = _g[1], b4 = _h === void 0 ? (function () { return (function () { return [a4, '!'].join(); })() + '!'; })() : _h; +} +// To be inferred as `string | number` +function f3() { + var _a = ['hi', 1], a1 = _a[0], _b = _a[1], b1 = _b === void 0 ? a1 : _b; + var _c = ['hi', 1], a2 = _c[0], _d = _c[1], b2 = _d === void 0 ? [a2, '!'].join() : _d; + var _e = ['hi', 1], a3 = _e[0], _f = _e[1], b3 = _f === void 0 ? (function () { return [a3, '!'].join(); })() : _f; + var _g = ['hi', 1], a4 = _g[0], _h = _g[1], b4 = _h === void 0 ? (function () { return (function () { return [a4, '!'].join(); })() + '!'; })() : _h; +} diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols new file mode 100644 index 0000000000000..1bdbecfad3db0 --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols @@ -0,0 +1,88 @@ +=== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts === +// To be inferred as `number` +function f1() { +>f1 : Symbol(f1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 0, 0)) + + const [a1, b1 = a1] = [1]; +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 2, 11)) +>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 2, 14)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 2, 11)) + + const [a2, b2 = 1 + a2] = [1]; +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 3, 11)) +>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 3, 14)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 3, 11)) + + const [a3, b3 = (() => 1 + a3)()] = [1]; +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 4, 11)) +>b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 4, 14)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 4, 11)) + + const [a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]; +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 11)) +>b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 14)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 11)) +} + +// To be inferred as `string` +function f2() { +>f2 : Symbol(f2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 6, 1)) + + const [a1, b1 = a1] = ['hi']; +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 11)) +>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 14)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 11)) + + const [a2, b2 = [a2, '!'].join()] = ['hi']; +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 11, 11)) +>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 11, 14)) +>[a2, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 11, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi']; +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 12, 11)) +>b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 12, 14)) +>[a3, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 12, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi']; +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 13, 11)) +>b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 13, 14)) +>[a4, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 13, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +} + +// To be inferred as `string | number` +function f3() { +>f3 : Symbol(f3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 14, 1)) + + const [a1, b1 = a1] = ['hi', 1]; +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 11)) +>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 14)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 11)) + + const [a2, b2 = [a2, '!'].join()] = ['hi', 1]; +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 19, 11)) +>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 19, 14)) +>[a2, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 19, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi', 1]; +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 20, 11)) +>b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 20, 14)) +>[a3, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 20, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi', 1]; +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 21, 11)) +>b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 21, 14)) +>[a4, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 21, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.types b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.types new file mode 100644 index 0000000000000..dd7092eac3181 --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.types @@ -0,0 +1,173 @@ +=== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts === +// To be inferred as `number` +function f1() { +>f1 : () => void + + const [a1, b1 = a1] = [1]; +>a1 : number +>b1 : number +>a1 : number +>[1] : [number] +>1 : 1 + + const [a2, b2 = 1 + a2] = [1]; +>a2 : number +>b2 : number +>1 + a2 : number +>1 : 1 +>a2 : number +>[1] : [number] +>1 : 1 + + const [a3, b3 = (() => 1 + a3)()] = [1]; +>a3 : number +>b3 : number +>(() => 1 + a3)() : number +>(() => 1 + a3) : () => number +>() => 1 + a3 : () => number +>1 + a3 : number +>1 : 1 +>a3 : number +>[1] : [number] +>1 : 1 + + const [a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]; +>a4 : number +>b4 : number +>(() => (() => 1 + a4)() + 1)() : number +>(() => (() => 1 + a4)() + 1) : () => number +>() => (() => 1 + a4)() + 1 : () => number +>(() => 1 + a4)() + 1 : number +>(() => 1 + a4)() : number +>(() => 1 + a4) : () => number +>() => 1 + a4 : () => number +>1 + a4 : number +>1 : 1 +>a4 : number +>1 : 1 +>[1] : [number] +>1 : 1 +} + +// To be inferred as `string` +function f2() { +>f2 : () => void + + const [a1, b1 = a1] = ['hi']; +>a1 : string +>b1 : string +>a1 : string +>['hi'] : [string] +>'hi' : "hi" + + const [a2, b2 = [a2, '!'].join()] = ['hi']; +>a2 : string +>b2 : string +>[a2, '!'].join() : string +>[a2, '!'].join : (separator?: string) => string +>[a2, '!'] : string[] +>a2 : string +>'!' : "!" +>join : (separator?: string) => string +>['hi'] : [string] +>'hi' : "hi" + + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi']; +>a3 : string +>b3 : string +>(() => [a3, '!'].join())() : string +>(() => [a3, '!'].join()) : () => string +>() => [a3, '!'].join() : () => string +>[a3, '!'].join() : string +>[a3, '!'].join : (separator?: string) => string +>[a3, '!'] : string[] +>a3 : string +>'!' : "!" +>join : (separator?: string) => string +>['hi'] : [string] +>'hi' : "hi" + + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi']; +>a4 : string +>b4 : string +>(() => (() => [a4, '!'].join())() + '!')() : string +>(() => (() => [a4, '!'].join())() + '!') : () => string +>() => (() => [a4, '!'].join())() + '!' : () => string +>(() => [a4, '!'].join())() + '!' : string +>(() => [a4, '!'].join())() : string +>(() => [a4, '!'].join()) : () => string +>() => [a4, '!'].join() : () => string +>[a4, '!'].join() : string +>[a4, '!'].join : (separator?: string) => string +>[a4, '!'] : string[] +>a4 : string +>'!' : "!" +>join : (separator?: string) => string +>'!' : "!" +>['hi'] : [string] +>'hi' : "hi" +} + +// To be inferred as `string | number` +function f3() { +>f3 : () => void + + const [a1, b1 = a1] = ['hi', 1]; +>a1 : string +>b1 : string | number +>a1 : string +>['hi', 1] : [string, number] +>'hi' : "hi" +>1 : 1 + + const [a2, b2 = [a2, '!'].join()] = ['hi', 1]; +>a2 : string +>b2 : string | number +>[a2, '!'].join() : string +>[a2, '!'].join : (separator?: string) => string +>[a2, '!'] : string[] +>a2 : string +>'!' : "!" +>join : (separator?: string) => string +>['hi', 1] : [string, number] +>'hi' : "hi" +>1 : 1 + + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi', 1]; +>a3 : string +>b3 : string | number +>(() => [a3, '!'].join())() : string +>(() => [a3, '!'].join()) : () => string +>() => [a3, '!'].join() : () => string +>[a3, '!'].join() : string +>[a3, '!'].join : (separator?: string) => string +>[a3, '!'] : string[] +>a3 : string +>'!' : "!" +>join : (separator?: string) => string +>['hi', 1] : [string, number] +>'hi' : "hi" +>1 : 1 + + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi', 1]; +>a4 : string +>b4 : string | number +>(() => (() => [a4, '!'].join())() + '!')() : string +>(() => (() => [a4, '!'].join())() + '!') : () => string +>() => (() => [a4, '!'].join())() + '!' : () => string +>(() => [a4, '!'].join())() + '!' : string +>(() => [a4, '!'].join())() : string +>(() => [a4, '!'].join()) : () => string +>() => [a4, '!'].join() : () => string +>[a4, '!'].join() : string +>[a4, '!'].join : (separator?: string) => string +>[a4, '!'] : string[] +>a4 : string +>'!' : "!" +>join : (separator?: string) => string +>'!' : "!" +>['hi', 1] : [string, number] +>'hi' : "hi" +>1 : 1 +} + diff --git a/tests/baselines/reference/useBeforeDeclaration_destructuring.types b/tests/baselines/reference/useBeforeDeclaration_destructuring.types index 935d320353243..d1ce6d7c4c114 100644 --- a/tests/baselines/reference/useBeforeDeclaration_destructuring.types +++ b/tests/baselines/reference/useBeforeDeclaration_destructuring.types @@ -1,11 +1,11 @@ === tests/cases/compiler/useBeforeDeclaration_destructuring.ts === a; ->a : any +>a : string let {a, b = a} = {a: '', b: 1}; ->a : any ->b : any ->a : any +>a : string +>b : string | number +>a : string >{a: '', b: 1} : { a: string; b?: number; } >a : string >'' : "" @@ -13,7 +13,7 @@ let {a, b = a} = {a: '', b: 1}; >1 : 1 b; ->b : any +>b : string | number function test({c, d = c}: Record) {} >test : ({ c, d }: Record) => void diff --git a/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts new file mode 100644 index 0000000000000..dc748a61ad68d --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts @@ -0,0 +1,25 @@ +// @noImplicitAny: true + +// To be inferred as `number` +function f1() { + const [a1, b1 = a1] = [1]; + const [a2, b2 = 1 + a2] = [1]; + const [a3, b3 = (() => 1 + a3)()] = [1]; + const [a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]; +} + +// To be inferred as `string` +function f2() { + const [a1, b1 = a1] = ['hi']; + const [a2, b2 = [a2, '!'].join()] = ['hi']; + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi']; + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi']; +} + +// To be inferred as `string | number` +function f3() { + const [a1, b1 = a1] = ['hi', 1]; + const [a2, b2 = [a2, '!'].join()] = ['hi', 1]; + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi', 1]; + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi', 1]; +}