-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Improve binding element type inference #50241
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
Improve binding element type inference #50241
Conversation
… sibling definitions Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index abf3e9d..793ffb3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9445,8 +9445,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))); + } + + // (microsoft#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);
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js new file mode 100644 index 0000000000..003e92b --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js @@ -0,0 +1,69 @@ +//// [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]; + + function fn1([a1, b1 = a1] = [1]) { }; + function fn2([a2, b2 = 1 + a2] = [1]) { }; + function fn3([a3, b3 = (() => 1 + a3)()] = [1]) { }; + function fn4([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; + function fn1(_a) { + var _b = _a === void 0 ? [1] : _a, a1 = _b[0], _c = _b[1], b1 = _c === void 0 ? a1 : _c; + } + ; + function fn2(_a) { + var _b = _a === void 0 ? [1] : _a, a2 = _b[0], _c = _b[1], b2 = _c === void 0 ? 1 + a2 : _c; + } + ; + function fn3(_a) { + var _b = _a === void 0 ? [1] : _a, a3 = _b[0], _c = _b[1], b3 = _c === void 0 ? (function () { return 1 + a3; })() : _c; + } + ; + function fn4(_a) { + var _b = _a === void 0 ? [1] : _a, a4 = _b[0], _c = _b[1], b4 = _c === void 0 ? (function () { return (function () { return 1 + a4; })() + 1; })() : _c; + } + ; +} +// 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 0000000000..298e9d5 --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols @@ -0,0 +1,112 @@ +=== 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)) + + function fn1([a1, b1 = a1] = [1]) { }; +>fn1 : Symbol(fn1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 58)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 18)) +>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 21)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 18)) + + function fn2([a2, b2 = 1 + a2] = [1]) { }; +>fn2 : Symbol(fn2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 42)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 18)) +>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 21)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 18)) + + function fn3([a3, b3 = (() => 1 + a3)()] = [1]) { }; +>fn3 : Symbol(fn3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 46)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 18)) +>b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 21)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 18)) + + function fn4([a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]) { }; +>fn4 : Symbol(fn4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 56)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 18)) +>b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 21)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 18)) +} + +// To be inferred as `string` +function f2() { +>f2 : Symbol(f2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 11, 1)) + + const [a1, b1 = a1] = ['hi']; +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 15, 11)) +>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 15, 14)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 15, 11)) + + const [a2, b2 = [a2, '!'].join()] = ['hi']; +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 16, 11)) +>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 16, 14)) +>[a2, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 16, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi']; +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 17, 11)) +>b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 17, 14)) +>[a3, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 17, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi']; +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 11)) +>b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 14)) +>[a4, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +} + +// To be inferred as `string | number` +function f3() { +>f3 : Symbol(f3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 19, 1)) + + const [a1, b1 = a1] = ['hi', 1]; +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 23, 11)) +>b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 23, 14)) +>a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 23, 11)) + + const [a2, b2 = [a2, '!'].join()] = ['hi', 1]; +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 24, 11)) +>b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 24, 14)) +>[a2, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 24, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a3, b3 = (() => [a3, '!'].join())()] = ['hi', 1]; +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 25, 11)) +>b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 25, 14)) +>[a3, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 25, 11)) +>join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) + + const [a4, b4 = (() => (() => [a4, '!'].join())() + '!')()] = ['hi', 1]; +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 26, 11)) +>b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 26, 14)) +>[a4, '!'].join : Symbol(Array.join, Decl(lib.es5.d.ts, --, --)) +>a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 26, 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 0000000000..34bad2f --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.types @@ -0,0 +1,222 @@ +=== 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 + + function fn1([a1, b1 = a1] = [1]) { }; +>fn1 : ([a1, b1]?: [number, any?]) => void +>a1 : number +>b1 : any +>a1 : number +>[1] : [number] +>1 : 1 + + function fn2([a2, b2 = 1 + a2] = [1]) { }; +>fn2 : ([a2, b2]?: [number, any?]) => void +>a2 : number +>b2 : any +>1 + a2 : number +>1 : 1 +>a2 : number +>[1] : [number] +>1 : 1 + + function fn3([a3, b3 = (() => 1 + a3)()] = [1]) { }; +>fn3 : ([a3, b3]?: [number, any?]) => void +>a3 : number +>b3 : any +>(() => 1 + a3)() : number +>(() => 1 + a3) : () => number +>() => 1 + a3 : () => number +>1 + a3 : number +>1 : 1 +>a3 : number +>[1] : [number] +>1 : 1 + + function fn4([a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]) { }; +>fn4 : ([a4, b4]?: [number, any?]) => void +>a4 : number +>b4 : any +>(() => (() => 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/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts new file mode 100644 index 0000000000..b14b985 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts @@ -0,0 +1,23 @@ +// 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]; +}
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> diff --git a/tests/baselines/reference/dependentDestructuredVariables.errors.txt b/tests/baselines/reference/dependentDestructuredVariables.errors.txt index e3704b5..7de0253 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 5a0e03d..a1e95ea 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/useBeforeDeclaration_destructuring.types b/tests/baselines/reference/useBeforeDeclaration_destructuring.types index 935d320..d1ce6d7 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<string, number>) {} >test : ({ c, d }: Record<string, number>) => void
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js index 003e92b..70ba454 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.js @@ -5,11 +5,6 @@ function f1() { const [a2, b2 = 1 + a2] = [1]; const [a3, b3 = (() => 1 + a3)()] = [1]; const [a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]; - - function fn1([a1, b1 = a1] = [1]) { }; - function fn2([a2, b2 = 1 + a2] = [1]) { }; - function fn3([a3, b3 = (() => 1 + a3)()] = [1]) { }; - function fn4([a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]) { }; } // To be inferred as `string` @@ -36,22 +31,6 @@ function f1() { 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; - function fn1(_a) { - var _b = _a === void 0 ? [1] : _a, a1 = _b[0], _c = _b[1], b1 = _c === void 0 ? a1 : _c; - } - ; - function fn2(_a) { - var _b = _a === void 0 ? [1] : _a, a2 = _b[0], _c = _b[1], b2 = _c === void 0 ? 1 + a2 : _c; - } - ; - function fn3(_a) { - var _b = _a === void 0 ? [1] : _a, a3 = _b[0], _c = _b[1], b3 = _c === void 0 ? (function () { return 1 + a3; })() : _c; - } - ; - function fn4(_a) { - var _b = _a === void 0 ? [1] : _a, a4 = _b[0], _c = _b[1], b4 = _c === void 0 ? (function () { return (function () { return 1 + a4; })() + 1; })() : _c; - } - ; } // To be inferred as `string` function f2() { diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols index 298e9d5..1bdbecf 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.symbols @@ -22,91 +22,67 @@ function f1() { >a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 11)) >b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 14)) >a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 11)) - - function fn1([a1, b1 = a1] = [1]) { }; ->fn1 : Symbol(fn1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 5, 58)) ->a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 18)) ->b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 21)) ->a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 18)) - - function fn2([a2, b2 = 1 + a2] = [1]) { }; ->fn2 : Symbol(fn2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 7, 42)) ->a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 18)) ->b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 21)) ->a2 : Symbol(a2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 18)) - - function fn3([a3, b3 = (() => 1 + a3)()] = [1]) { }; ->fn3 : Symbol(fn3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 8, 46)) ->a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 18)) ->b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 21)) ->a3 : Symbol(a3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 18)) - - function fn4([a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]) { }; ->fn4 : Symbol(fn4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 9, 56)) ->a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 18)) ->b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 21)) ->a4 : Symbol(a4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 10, 18)) } // To be inferred as `string` function f2() { ->f2 : Symbol(f2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 11, 1)) +>f2 : Symbol(f2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 6, 1)) const [a1, b1 = a1] = ['hi']; ->a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 15, 11)) ->b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 15, 14)) ->a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 15, 11)) +>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, 16, 11)) ->b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 16, 14)) +>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, 16, 11)) +>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, 17, 11)) ->b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 17, 14)) +>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, 17, 11)) +>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, 18, 11)) ->b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 18, 14)) +>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, 18, 11)) +>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, 19, 1)) +>f3 : Symbol(f3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 14, 1)) const [a1, b1 = a1] = ['hi', 1]; ->a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 23, 11)) ->b1 : Symbol(b1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 23, 14)) ->a1 : Symbol(a1, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 23, 11)) +>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, 24, 11)) ->b2 : Symbol(b2, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 24, 14)) +>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, 24, 11)) +>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, 25, 11)) ->b3 : Symbol(b3, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 25, 14)) +>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, 25, 11)) +>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, 26, 11)) ->b4 : Symbol(b4, Decl(destructuringArrayBindingPatternAndAssignment5.ts, 26, 14)) +>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, 26, 11)) +>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 index 34bad2f..dd7092e 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.types +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment5.types @@ -46,55 +46,6 @@ function f1() { >a4 : number >1 : 1 >[1] : [number] ->1 : 1 - - function fn1([a1, b1 = a1] = [1]) { }; ->fn1 : ([a1, b1]?: [number, any?]) => void ->a1 : number ->b1 : any ->a1 : number ->[1] : [number] ->1 : 1 - - function fn2([a2, b2 = 1 + a2] = [1]) { }; ->fn2 : ([a2, b2]?: [number, any?]) => void ->a2 : number ->b2 : any ->1 + a2 : number ->1 : 1 ->a2 : number ->[1] : [number] ->1 : 1 - - function fn3([a3, b3 = (() => 1 + a3)()] = [1]) { }; ->fn3 : ([a3, b3]?: [number, any?]) => void ->a3 : number ->b3 : any ->(() => 1 + a3)() : number ->(() => 1 + a3) : () => number ->() => 1 + a3 : () => number ->1 + a3 : number ->1 : 1 ->a3 : number ->[1] : [number] ->1 : 1 - - function fn4([a4, b4 = (() => (() => 1 + a4)() + 1)()] = [1]) { }; ->fn4 : ([a4, b4]?: [number, any?]) => void ->a4 : number ->b4 : any ->(() => (() => 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 } diff --git a/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts index b14b985..dc748a6 100644 --- a/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts +++ b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment5.ts @@ -1,3 +1,5 @@ +// @noImplicitAny: true + // To be inferred as `number` function f1() { const [a1, b1 = a1] = [1];
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
>b : any | ||
>a : any | ||
>a : string | ||
>b : string | number |
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 feel like this probably could be improved as it's impossible for b
to be of type string
here. Perhaps this doesn't have to be improved in this PR though and a followup issue could be created to track 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.
Totally agree.
@typescript-bot perf test this |
Heya @andrewbranch, I've started to run the perf test suite on this PR at 84b0110. You can monitor the build here. Update: The results are in! |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at 84b0110. You can monitor the build here. |
I haven’t checked yet, but I believe this might break this case: const [a, b = () => a] = [0] I doubt that eagerly checking for circularities by syntax/symbol is going to be a workable approach because of cases like this. I think this example works today only because checking function bodies is a deferred operation, which may provide a hint for an alternative approach, but I’m really not sure. You can also imagine cases where const [a, b = (a, 0)] = [0]; // errors today
const [c, d = () => { console.log(c); return 0; }] = [0]; // ok Are you sure that this code path doesn’t just need to pass |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
It looks like the examples I mentioned still work in your PR—but that really still makes me think it should just be possible to squash the error without doing the eager circularity check. |
@andrewbranch Here they are:
CompilerComparison Report - main..50241
System
Hosts
Scenarios
TSServerComparison Report - main..50241
System
Hosts
Scenarios
Developer Information: |
@andrewbranch Thanks for the hints. Actually, this wasn't my first approach. I was looking for some way to suppress type-checking errors by propagating Below is the stack trace from the const [a, b = a] = [1]; // ok
... As in the stack trace, there are 3 important points (marked with asterisks):
With this PR, just the first call happens, and then the type checking returns with Stack trace
|
@andrewbranch Could defining a new |
@babakks @weswigham speculated that probably only |
I gotta say - I love your extensive descriptions/notes here, @babakks ❤️ |
@andrewbranch I've submitted another PR with the |
Looks like this was abandoned in favor of #56753 |
Fixes #49989
Problem
There were two problems:
Type inference for variables declared by binding elements was not accurate. For example, for the declaration below the type of both variables were inferred to be
any
(instead ofnumber
).With
noImplicitAny
option enabled, a false circular relationship error would be emitted:Cause
The problems were hidden in the
getTypeFromBindingElement
function whose responsibility is to infer the type of variables defined as binding elements without looking onto the declaration initializer (the array literal,[1]
, in the example above).When a binding element itself had a default value (as in
b = a
) and that default value involved any sibling variable (i.e., variables defined in the same binding pattern, herea
) then the inquiry for the type of the default value would begin and eventually result in a circular relationship error down the path. This error made the type checker to infer the types asany
.Solution
Since the
getTypeFromBindingElement
function just needs to narrowly look inside the binding elements/patterns, we can skip further type checkings (that is falling back toany
) when any other sibling variables are somehow involved in the default value expressions.Although this seems to be an ad-hoc/one-off kind of solution, type checkings for binding elements are deeply integrated with other parts of the type checker, and solving the problem in a more generic way, I believe, requires greater knowledge of the code base (compared to mine, of course).
Effect on the existing behavior
Three existing test cases were affected by this PR, fortunately in a good way. They're now correctly inferring the type
number
. Also, the circular relationship error was erased from one similar test case.New test case
A new test case,
destructuringArrayBindingPatternAndAssignment5.ts
, was added that contains cases where default values somehow reference other sibling variables.