diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index faab9a6dfe5..5a8dcb5af89 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -218,6 +218,9 @@ namespace ts { // or if compiler options contain alwaysStrict. let inStrictMode: boolean; + // If we are binding an assignment pattern, we will bind certain expressions differently. + let inAssignmentPattern = false; + let symbolCount = 0; let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; @@ -275,6 +278,7 @@ namespace ts { currentExceptionTarget = undefined; activeLabelList = undefined; hasExplicitReturn = false; + inAssignmentPattern = false; emitFlags = NodeFlags.None; } @@ -733,9 +737,14 @@ namespace ts { } function bindChildren(node: Node): void { + const saveInAssignmentPattern = inAssignmentPattern; + // Most nodes aren't valid in an assignment pattern, so we clear the value here + // and set it before we descend into nodes that could actually be part of an assignment pattern. + inAssignmentPattern = false; if (checkUnreachable(node)) { bindEachChild(node); bindJSDoc(node); + inAssignmentPattern = saveInAssignmentPattern; return; } if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { @@ -791,6 +800,13 @@ namespace ts { bindPostfixUnaryExpressionFlow(node); break; case SyntaxKind.BinaryExpression: + if (isDestructuringAssignment(node)) { + // Carry over whether we are in an assignment pattern to + // binary expressions that could actually be an initializer + inAssignmentPattern = saveInAssignmentPattern; + bindDestructuringAssignmentFlow(node); + return; + } bindBinaryExpressionFlow(node); break; case SyntaxKind.DeleteExpression: @@ -827,11 +843,23 @@ namespace ts { case SyntaxKind.ModuleBlock: bindEachFunctionsFirst((node as Block).statements); break; + case SyntaxKind.BindingElement: + bindBindingElementFlow(node); + break; + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.SpreadElement: + // Carry over whether we are in an assignment pattern of Object and Array literals + // as well as their children that are valid assignment targets. + inAssignmentPattern = saveInAssignmentPattern; + // falls through default: bindEachChild(node); break; } bindJSDoc(node); + inAssignmentPattern = saveInAssignmentPattern; } function isNarrowingExpression(expr: Expression): boolean { @@ -1449,6 +1477,24 @@ namespace ts { } } + function bindDestructuringAssignmentFlow(node: DestructuringAssignment) { + if (inAssignmentPattern) { + inAssignmentPattern = false; + bind(node.operatorToken); + bind(node.right); + inAssignmentPattern = true; + bind(node.left); + } + else { + inAssignmentPattern = true; + bind(node.left); + inAssignmentPattern = false; + bind(node.operatorToken); + bind(node.right); + } + bindAssignmentTargetFlow(node.left); + } + const enum BindBinaryExpressionFlowState { BindThenBindChildren, MaybeBindLeft, @@ -1617,6 +1663,25 @@ namespace ts { } } + function bindBindingElementFlow(node: BindingElement) { + if (isBindingPattern(node.name)) { + // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: + // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + bindEach(node.decorators); + bindEach(node.modifiers); + bind(node.dotDotDotToken); + bind(node.propertyName); + bind(node.initializer); + bind(node.name); + } + else { + bindEachChild(node); + } + } + function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { setParent(node.tagName, node); if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { diff --git a/tests/baselines/reference/controlFlowAssignmentPatternOrder.symbols b/tests/baselines/reference/controlFlowAssignmentPatternOrder.symbols new file mode 100644 index 00000000000..b3b7d3ba4c4 --- /dev/null +++ b/tests/baselines/reference/controlFlowAssignmentPatternOrder.symbols @@ -0,0 +1,70 @@ +=== tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts === +// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 +{ + let a: 0 | 1 = 0; +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 2, 7)) + + let b: 0 | 1 | 9; +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 3, 7)) + + [{ [(a = 1)]: b } = [9, a] as const] = []; +>[(a = 1)] : Symbol([(a = 1)], Decl(controlFlowAssignmentPatternOrder.ts, 4, 6)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 2, 7)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 3, 7)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 2, 7)) + + const bb: 0 = b; +>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 5, 9)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 3, 7)) +} +{ + let a: 0 | 1 = 1; +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 8, 7)) + + let b: 0 | 1 | 9; +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 9, 7)) + + [{ [a]: b } = [9, a = 0] as const] = []; +>[a] : Symbol([a], Decl(controlFlowAssignmentPatternOrder.ts, 10, 6)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 8, 7)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 9, 7)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 8, 7)) + + const bb: 9 = b; +>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 11, 9)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 9, 7)) +} +{ + let a: 0 | 1 = 0; +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 14, 7)) + + let b: 0 | 1 | 8 | 9; +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 15, 7)) + + [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; +>[(a = 1)] : Symbol([(a = 1)], Decl(controlFlowAssignmentPatternOrder.ts, 16, 6)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 14, 7)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 15, 7)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 14, 7)) + + const bb: 0 | 8 = b; +>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 17, 9)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 15, 7)) +} +{ + let a: 0 | 1 = 1; +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 20, 7)) + + let b: 0 | 1 | 8 | 9; +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 21, 7)) + + [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; +>[a] : Symbol([a], Decl(controlFlowAssignmentPatternOrder.ts, 22, 6)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 20, 7)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 21, 7)) +>a : Symbol(a, Decl(controlFlowAssignmentPatternOrder.ts, 20, 7)) + + const bb: 0 | 8 = b; +>bb : Symbol(bb, Decl(controlFlowAssignmentPatternOrder.ts, 23, 9)) +>b : Symbol(b, Decl(controlFlowAssignmentPatternOrder.ts, 21, 7)) +} diff --git a/tests/baselines/reference/controlFlowAssignmentPatternOrder.types b/tests/baselines/reference/controlFlowAssignmentPatternOrder.types new file mode 100644 index 00000000000..0df2eda9106 --- /dev/null +++ b/tests/baselines/reference/controlFlowAssignmentPatternOrder.types @@ -0,0 +1,124 @@ +=== tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts === +// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 +{ + let a: 0 | 1 = 0; +>a : 0 | 1 +>0 : 0 + + let b: 0 | 1 | 9; +>b : 0 | 1 | 9 + + [{ [(a = 1)]: b } = [9, a] as const] = []; +>[{ [(a = 1)]: b } = [9, a] as const] = [] : [] +>[{ [(a = 1)]: b } = [9, a] as const] : [readonly [9, 0]] +>{ [(a = 1)]: b } = [9, a] as const : readonly [9, 0] +>{ [(a = 1)]: b } : { 1: 0 | 1 | 9; } +>[(a = 1)] : 0 | 1 | 9 +>(a = 1) : 1 +>a = 1 : 1 +>a : 0 | 1 +>1 : 1 +>b : 0 | 1 | 9 +>[9, a] as const : readonly [9, 0] +>[9, a] : readonly [9, 0] +>9 : 9 +>a : 0 +>[] : [] + + const bb: 0 = b; +>bb : 0 +>b : 0 +} +{ + let a: 0 | 1 = 1; +>a : 0 | 1 +>1 : 1 + + let b: 0 | 1 | 9; +>b : 0 | 1 | 9 + + [{ [a]: b } = [9, a = 0] as const] = []; +>[{ [a]: b } = [9, a = 0] as const] = [] : [] +>[{ [a]: b } = [9, a = 0] as const] : [readonly [9, 0]] +>{ [a]: b } = [9, a = 0] as const : readonly [9, 0] +>{ [a]: b } : { 0: 0 | 1 | 9; } +>[a] : 0 | 1 | 9 +>a : 0 +>b : 0 | 1 | 9 +>[9, a = 0] as const : readonly [9, 0] +>[9, a = 0] : readonly [9, 0] +>9 : 9 +>a = 0 : 0 +>a : 0 | 1 +>0 : 0 +>[] : [] + + const bb: 9 = b; +>bb : 9 +>b : 9 +} +{ + let a: 0 | 1 = 0; +>a : 0 | 1 +>0 : 0 + + let b: 0 | 1 | 8 | 9; +>b : 0 | 1 | 9 | 8 + + [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; +>[{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const] : [readonly [9, 8]] +>[{ [(a = 1)]: b } = [9, a] as const] : [readonly [9, 0]] +>{ [(a = 1)]: b } = [9, a] as const : readonly [9, 0] +>{ [(a = 1)]: b } : { 1: 0 | 1 | 9 | 8; } +>[(a = 1)] : 0 | 1 | 9 | 8 +>(a = 1) : 1 +>a = 1 : 1 +>a : 0 | 1 +>1 : 1 +>b : 0 | 1 | 9 | 8 +>[9, a] as const : readonly [9, 0] +>[9, a] : readonly [9, 0] +>9 : 9 +>a : 0 +>[[9, 8] as const] : [readonly [9, 8]] +>[9, 8] as const : readonly [9, 8] +>[9, 8] : readonly [9, 8] +>9 : 9 +>8 : 8 + + const bb: 0 | 8 = b; +>bb : 0 | 8 +>b : 0 | 8 +} +{ + let a: 0 | 1 = 1; +>a : 0 | 1 +>1 : 1 + + let b: 0 | 1 | 8 | 9; +>b : 0 | 1 | 9 | 8 + + [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; +>[{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const] : [readonly [8, 9]] +>[{ [a]: b } = [a = 0, 9] as const] : [readonly [0, 9]] +>{ [a]: b } = [a = 0, 9] as const : readonly [0, 9] +>{ [a]: b } : { 0: 0 | 1 | 9 | 8; } +>[a] : 0 | 1 | 9 | 8 +>a : 0 +>b : 0 | 1 | 9 | 8 +>[a = 0, 9] as const : readonly [0, 9] +>[a = 0, 9] : readonly [0, 9] +>a = 0 : 0 +>a : 0 | 1 +>0 : 0 +>9 : 9 +>[[8, 9] as const] : [readonly [8, 9]] +>[8, 9] as const : readonly [8, 9] +>[8, 9] : readonly [8, 9] +>8 : 8 +>9 : 9 + + const bb: 0 | 8 = b; +>bb : 0 | 8 +>b : 0 | 8 +} diff --git a/tests/baselines/reference/controlFlowBindingPatternOrder.symbols b/tests/baselines/reference/controlFlowBindingPatternOrder.symbols new file mode 100644 index 00000000000..359b15cd79b --- /dev/null +++ b/tests/baselines/reference/controlFlowBindingPatternOrder.symbols @@ -0,0 +1,54 @@ +=== tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts === +// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 +{ + let a: 0 | 1 = 0; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 2, 7)) + + const [{ [(a = 1)]: b } = [9, a] as const] = []; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 2, 7)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 3, 12)) +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 2, 7)) + + const bb: 0 = b; +>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 4, 9)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 3, 12)) +} +{ + let a: 0 | 1 = 1; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 7, 7)) + + const [{ [a]: b } = [9, a = 0] as const] = []; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 7, 7)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 8, 12)) +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 7, 7)) + + const bb: 9 = b; +>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 9, 9)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 8, 12)) +} +{ + let a: 0 | 1 = 0; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7)) + + const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 13, 12)) +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7)) + + const bb: 0 | 8 = b; +>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 14, 9)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 13, 12)) +} +{ + let a: 0 | 1 = 1; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7)) + + const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 18, 12)) +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7)) + + const bb: 0 | 8 = b; +>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 19, 9)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 18, 12)) +} diff --git a/tests/baselines/reference/controlFlowBindingPatternOrder.types b/tests/baselines/reference/controlFlowBindingPatternOrder.types new file mode 100644 index 00000000000..5c9d426c23b --- /dev/null +++ b/tests/baselines/reference/controlFlowBindingPatternOrder.types @@ -0,0 +1,92 @@ +=== tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts === +// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 +{ + let a: 0 | 1 = 0; +>a : 0 | 1 +>0 : 0 + + const [{ [(a = 1)]: b } = [9, a] as const] = []; +>(a = 1) : 1 +>a = 1 : 1 +>a : 0 | 1 +>1 : 1 +>b : 0 +>[9, a] as const : readonly [9, 0] +>[9, a] : readonly [9, 0] +>9 : 9 +>a : 0 +>[] : [] + + const bb: 0 = b; +>bb : 0 +>b : 0 +} +{ + let a: 0 | 1 = 1; +>a : 0 | 1 +>1 : 1 + + const [{ [a]: b } = [9, a = 0] as const] = []; +>a : 0 +>b : 9 +>[9, a = 0] as const : readonly [9, 0] +>[9, a = 0] : readonly [9, 0] +>9 : 9 +>a = 0 : 0 +>a : 0 | 1 +>0 : 0 +>[] : [] + + const bb: 9 = b; +>bb : 9 +>b : 9 +} +{ + let a: 0 | 1 = 0; +>a : 0 | 1 +>0 : 0 + + const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; +>(a = 1) : 1 +>a = 1 : 1 +>a : 0 | 1 +>1 : 1 +>b : 0 | 8 +>[9, a] as const : readonly [9, 0] +>[9, a] : readonly [9, 0] +>9 : 9 +>a : 0 +>[[9, 8] as const] : [readonly [9, 8]] +>[9, 8] as const : readonly [9, 8] +>[9, 8] : readonly [9, 8] +>9 : 9 +>8 : 8 + + const bb: 0 | 8 = b; +>bb : 0 | 8 +>b : 0 | 8 +} +{ + let a: 0 | 1 = 1; +>a : 0 | 1 +>1 : 1 + + const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; +>a : 0 +>b : 0 | 8 +>[a = 0, 9] as const : readonly [0, 9] +>[a = 0, 9] : readonly [0, 9] +>a = 0 : 0 +>a : 0 | 1 +>0 : 0 +>9 : 9 +>[[8, 9] as const] : [readonly [8, 9]] +>[8, 9] as const : readonly [8, 9] +>[8, 9] : readonly [8, 9] +>8 : 8 +>9 : 9 + + const bb: 0 | 8 = b; +>bb : 0 | 8 +>b : 0 | 8 +} diff --git a/tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts b/tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts new file mode 100644 index 00000000000..2c8fcaf16c3 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts @@ -0,0 +1,28 @@ +// @target: esnext +// @noEmit: true + +// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 +{ + let a: 0 | 1 = 0; + let b: 0 | 1 | 9; + [{ [(a = 1)]: b } = [9, a] as const] = []; + const bb: 0 = b; +} +{ + let a: 0 | 1 = 1; + let b: 0 | 1 | 9; + [{ [a]: b } = [9, a = 0] as const] = []; + const bb: 9 = b; +} +{ + let a: 0 | 1 = 0; + let b: 0 | 1 | 8 | 9; + [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; + const bb: 0 | 8 = b; +} +{ + let a: 0 | 1 = 1; + let b: 0 | 1 | 8 | 9; + [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; + const bb: 0 | 8 = b; +} \ No newline at end of file diff --git a/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts b/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts new file mode 100644 index 00000000000..804947c9b70 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts @@ -0,0 +1,24 @@ +// @target: esnext +// @noEmit: true + +// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 +{ + let a: 0 | 1 = 0; + const [{ [(a = 1)]: b } = [9, a] as const] = []; + const bb: 0 = b; +} +{ + let a: 0 | 1 = 1; + const [{ [a]: b } = [9, a = 0] as const] = []; + const bb: 9 = b; +} +{ + let a: 0 | 1 = 0; + const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; + const bb: 0 | 8 = b; +} +{ + let a: 0 | 1 = 1; + const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; + const bb: 0 | 8 = b; +} \ No newline at end of file