From 78ee4cacafc20491fca5557da7908580df18db0e Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Wed, 23 Feb 2022 00:56:14 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Pick=20PR=20#47927=20(Don't=20er?= =?UTF-8?q?ror=20if=20binding=20pattern=20type...)=20into=20release-4.6=20?= =?UTF-8?q?(#48005)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cherry-pick PR #47927 into release-4.6 Component commits: c4aa31b67f early return if pattern type is never * Update LKG Co-authored-by: Gabriela Araujo Britto Co-authored-by: typescript-bot --- lib/tsc.js | 3 + lib/tsserver.js | 3 + lib/tsserverlibrary.js | 3 + lib/typescript.js | 3 + lib/typescriptServices.js | 3 + lib/typingsInstaller.js | 3 + src/compiler/checker.ts | 3 + .../reference/arrayDestructuringInSwitch1.js | 47 +++++++++++++ .../arrayDestructuringInSwitch1.symbols | 55 ++++++++++++++++ .../arrayDestructuringInSwitch1.types | 66 +++++++++++++++++++ .../arrayDestructuringInSwitch2.errors.txt | 20 ++++++ .../reference/arrayDestructuringInSwitch2.js | 29 ++++++++ .../arrayDestructuringInSwitch2.symbols | 37 +++++++++++ .../arrayDestructuringInSwitch2.types | 43 ++++++++++++ .../compiler/arrayDestructuringInSwitch1.ts | 21 ++++++ .../compiler/arrayDestructuringInSwitch2.ts | 14 ++++ 16 files changed, 353 insertions(+) create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch1.js create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch1.symbols create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch1.types create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch2.errors.txt create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch2.js create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch2.symbols create mode 100644 tests/baselines/reference/arrayDestructuringInSwitch2.types create mode 100644 tests/cases/compiler/arrayDestructuringInSwitch1.ts create mode 100644 tests/cases/compiler/arrayDestructuringInSwitch2.ts diff --git a/lib/tsc.js b/lib/tsc.js index 49ef5010f3fe1..7704b1a2be538 100644 --- a/lib/tsc.js +++ b/lib/tsc.js @@ -58514,6 +58514,9 @@ var ts; if (parentType && parentType.flags & 1048576 && !(parent.kind === 163 && isSymbolAssigned(symbol))) { var pattern = declaration.parent; var narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, undefined, location.flowNode); + if (narrowedType.flags & 131072) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/lib/tsserver.js b/lib/tsserver.js index 026f89d462f6d..ff74f351d1bd6 100644 --- a/lib/tsserver.js +++ b/lib/tsserver.js @@ -70031,6 +70031,9 @@ var ts; if (parentType && parentType.flags & 1048576 /* Union */ && !(parent.kind === 163 /* Parameter */ && isSymbolAssigned(symbol))) { var pattern = declaration.parent; var narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & 131072 /* Never */) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/lib/tsserverlibrary.js b/lib/tsserverlibrary.js index b020575e40900..9290a3661080e 100644 --- a/lib/tsserverlibrary.js +++ b/lib/tsserverlibrary.js @@ -70225,6 +70225,9 @@ var ts; if (parentType && parentType.flags & 1048576 /* Union */ && !(parent.kind === 163 /* Parameter */ && isSymbolAssigned(symbol))) { var pattern = declaration.parent; var narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & 131072 /* Never */) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/lib/typescript.js b/lib/typescript.js index 4c53194abdf6d..89cc0ba3ffb18 100644 --- a/lib/typescript.js +++ b/lib/typescript.js @@ -70225,6 +70225,9 @@ var ts; if (parentType && parentType.flags & 1048576 /* Union */ && !(parent.kind === 163 /* Parameter */ && isSymbolAssigned(symbol))) { var pattern = declaration.parent; var narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & 131072 /* Never */) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/lib/typescriptServices.js b/lib/typescriptServices.js index 3f3862aee4202..d1d97fe03d60c 100644 --- a/lib/typescriptServices.js +++ b/lib/typescriptServices.js @@ -70225,6 +70225,9 @@ var ts; if (parentType && parentType.flags & 1048576 /* Union */ && !(parent.kind === 163 /* Parameter */ && isSymbolAssigned(symbol))) { var pattern = declaration.parent; var narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & 131072 /* Never */) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/lib/typingsInstaller.js b/lib/typingsInstaller.js index 67dc478c9c1f3..67aa6f7336aa4 100644 --- a/lib/typingsInstaller.js +++ b/lib/typingsInstaller.js @@ -70020,6 +70020,9 @@ var ts; if (parentType && parentType.flags & 1048576 /* Union */ && !(parent.kind === 163 /* Parameter */ && isSymbolAssigned(symbol))) { var pattern = declaration.parent; var narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & 131072 /* Never */) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 20d88c8209a46..cbff12a26e269 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25127,6 +25127,9 @@ namespace ts { if (parentType && parentType.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { const pattern = declaration.parent; const narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & TypeFlags.Never) { + return neverType; + } return getBindingElementTypeFromParentType(declaration, narrowedType); } } diff --git a/tests/baselines/reference/arrayDestructuringInSwitch1.js b/tests/baselines/reference/arrayDestructuringInSwitch1.js new file mode 100644 index 0000000000000..1947d7583e346 --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch1.js @@ -0,0 +1,47 @@ +//// [arrayDestructuringInSwitch1.ts] +export type Expression = BooleanLogicExpression | 'true' | 'false'; +export type BooleanLogicExpression = ['and', ...Expression[]] | ['not', Expression]; + +export function evaluate(expression: Expression): boolean { + if (Array.isArray(expression)) { + const [operator, ...operands] = expression; + switch (operator) { + case 'and': { + return operands.every((child) => evaluate(child)); + } + case 'not': { + return !evaluate(operands[0]); + } + default: { + throw new Error(`${operator} is not a supported operator`); + } + } + } else { + return expression === 'true'; + } +} + +//// [arrayDestructuringInSwitch1.js] +"use strict"; +exports.__esModule = true; +exports.evaluate = void 0; +function evaluate(expression) { + if (Array.isArray(expression)) { + var operator = expression[0], operands = expression.slice(1); + switch (operator) { + case 'and': { + return operands.every(function (child) { return evaluate(child); }); + } + case 'not': { + return !evaluate(operands[0]); + } + default: { + throw new Error("".concat(operator, " is not a supported operator")); + } + } + } + else { + return expression === 'true'; + } +} +exports.evaluate = evaluate; diff --git a/tests/baselines/reference/arrayDestructuringInSwitch1.symbols b/tests/baselines/reference/arrayDestructuringInSwitch1.symbols new file mode 100644 index 0000000000000..f1a9bea3fa35f --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch1.symbols @@ -0,0 +1,55 @@ +=== tests/cases/compiler/arrayDestructuringInSwitch1.ts === +export type Expression = BooleanLogicExpression | 'true' | 'false'; +>Expression : Symbol(Expression, Decl(arrayDestructuringInSwitch1.ts, 0, 0)) +>BooleanLogicExpression : Symbol(BooleanLogicExpression, Decl(arrayDestructuringInSwitch1.ts, 0, 67)) + +export type BooleanLogicExpression = ['and', ...Expression[]] | ['not', Expression]; +>BooleanLogicExpression : Symbol(BooleanLogicExpression, Decl(arrayDestructuringInSwitch1.ts, 0, 67)) +>Expression : Symbol(Expression, Decl(arrayDestructuringInSwitch1.ts, 0, 0)) +>Expression : Symbol(Expression, Decl(arrayDestructuringInSwitch1.ts, 0, 0)) + +export function evaluate(expression: Expression): boolean { +>evaluate : Symbol(evaluate, Decl(arrayDestructuringInSwitch1.ts, 1, 84)) +>expression : Symbol(expression, Decl(arrayDestructuringInSwitch1.ts, 3, 25)) +>Expression : Symbol(Expression, Decl(arrayDestructuringInSwitch1.ts, 0, 0)) + + if (Array.isArray(expression)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>expression : Symbol(expression, Decl(arrayDestructuringInSwitch1.ts, 3, 25)) + + const [operator, ...operands] = expression; +>operator : Symbol(operator, Decl(arrayDestructuringInSwitch1.ts, 5, 11)) +>operands : Symbol(operands, Decl(arrayDestructuringInSwitch1.ts, 5, 20)) +>expression : Symbol(expression, Decl(arrayDestructuringInSwitch1.ts, 3, 25)) + + switch (operator) { +>operator : Symbol(operator, Decl(arrayDestructuringInSwitch1.ts, 5, 11)) + + case 'and': { + return operands.every((child) => evaluate(child)); +>operands.every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>operands : Symbol(operands, Decl(arrayDestructuringInSwitch1.ts, 5, 20)) +>every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>child : Symbol(child, Decl(arrayDestructuringInSwitch1.ts, 8, 31)) +>evaluate : Symbol(evaluate, Decl(arrayDestructuringInSwitch1.ts, 1, 84)) +>child : Symbol(child, Decl(arrayDestructuringInSwitch1.ts, 8, 31)) + } + case 'not': { + return !evaluate(operands[0]); +>evaluate : Symbol(evaluate, Decl(arrayDestructuringInSwitch1.ts, 1, 84)) +>operands : Symbol(operands, Decl(arrayDestructuringInSwitch1.ts, 5, 20)) +>0 : Symbol(0) + } + default: { + throw new Error(`${operator} is not a supported operator`); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>operator : Symbol(operator, Decl(arrayDestructuringInSwitch1.ts, 5, 11)) + } + } + } else { + return expression === 'true'; +>expression : Symbol(expression, Decl(arrayDestructuringInSwitch1.ts, 3, 25)) + } +} diff --git a/tests/baselines/reference/arrayDestructuringInSwitch1.types b/tests/baselines/reference/arrayDestructuringInSwitch1.types new file mode 100644 index 0000000000000..dfb06774cec16 --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch1.types @@ -0,0 +1,66 @@ +=== tests/cases/compiler/arrayDestructuringInSwitch1.ts === +export type Expression = BooleanLogicExpression | 'true' | 'false'; +>Expression : Expression + +export type BooleanLogicExpression = ['and', ...Expression[]] | ['not', Expression]; +>BooleanLogicExpression : BooleanLogicExpression + +export function evaluate(expression: Expression): boolean { +>evaluate : (expression: Expression) => boolean +>expression : Expression + + if (Array.isArray(expression)) { +>Array.isArray(expression) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>expression : Expression + + const [operator, ...operands] = expression; +>operator : "and" | "not" +>operands : Expression[] | [Expression] +>expression : BooleanLogicExpression + + switch (operator) { +>operator : "and" | "not" + + case 'and': { +>'and' : "and" + + return operands.every((child) => evaluate(child)); +>operands.every((child) => evaluate(child)) : boolean +>operands.every : { (predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } | { (predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } +>operands : Expression[] | [Expression] +>every : { (predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } | { (predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } +>(child) => evaluate(child) : (child: Expression) => boolean +>child : Expression +>evaluate(child) : boolean +>evaluate : (expression: Expression) => boolean +>child : Expression + } + case 'not': { +>'not' : "not" + + return !evaluate(operands[0]); +>!evaluate(operands[0]) : boolean +>evaluate(operands[0]) : boolean +>evaluate : (expression: Expression) => boolean +>operands[0] : Expression +>operands : Expression[] | [Expression] +>0 : 0 + } + default: { + throw new Error(`${operator} is not a supported operator`); +>new Error(`${operator} is not a supported operator`) : Error +>Error : ErrorConstructor +>`${operator} is not a supported operator` : string +>operator : never + } + } + } else { + return expression === 'true'; +>expression === 'true' : boolean +>expression : "true" | "false" +>'true' : "true" + } +} diff --git a/tests/baselines/reference/arrayDestructuringInSwitch2.errors.txt b/tests/baselines/reference/arrayDestructuringInSwitch2.errors.txt new file mode 100644 index 0000000000000..48fb17f0c6edc --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch2.errors.txt @@ -0,0 +1,20 @@ +tests/cases/compiler/arrayDestructuringInSwitch2.ts(11,13): error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator. + + +==== tests/cases/compiler/arrayDestructuringInSwitch2.ts (1 errors) ==== + type X = { kind: "a", a: [1] } | { kind: "b", a: [] } + + function foo(x: X): 1 { + const { kind, a } = x; + switch (kind) { + case "a": + return a[0]; + case "b": + return 1; + default: + const [n] = a; + ~~~ +!!! error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator. + return a; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/arrayDestructuringInSwitch2.js b/tests/baselines/reference/arrayDestructuringInSwitch2.js new file mode 100644 index 0000000000000..be6f00c08416a --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch2.js @@ -0,0 +1,29 @@ +//// [arrayDestructuringInSwitch2.ts] +type X = { kind: "a", a: [1] } | { kind: "b", a: [] } + +function foo(x: X): 1 { + const { kind, a } = x; + switch (kind) { + case "a": + return a[0]; + case "b": + return 1; + default: + const [n] = a; + return a; + } +} + +//// [arrayDestructuringInSwitch2.js] +function foo(x) { + var kind = x.kind, a = x.a; + switch (kind) { + case "a": + return a[0]; + case "b": + return 1; + default: + var n = a[0]; + return a; + } +} diff --git a/tests/baselines/reference/arrayDestructuringInSwitch2.symbols b/tests/baselines/reference/arrayDestructuringInSwitch2.symbols new file mode 100644 index 0000000000000..7d64ebdb8a432 --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch2.symbols @@ -0,0 +1,37 @@ +=== tests/cases/compiler/arrayDestructuringInSwitch2.ts === +type X = { kind: "a", a: [1] } | { kind: "b", a: [] } +>X : Symbol(X, Decl(arrayDestructuringInSwitch2.ts, 0, 0)) +>kind : Symbol(kind, Decl(arrayDestructuringInSwitch2.ts, 0, 10)) +>a : Symbol(a, Decl(arrayDestructuringInSwitch2.ts, 0, 21)) +>kind : Symbol(kind, Decl(arrayDestructuringInSwitch2.ts, 0, 34)) +>a : Symbol(a, Decl(arrayDestructuringInSwitch2.ts, 0, 45)) + +function foo(x: X): 1 { +>foo : Symbol(foo, Decl(arrayDestructuringInSwitch2.ts, 0, 53)) +>x : Symbol(x, Decl(arrayDestructuringInSwitch2.ts, 2, 13)) +>X : Symbol(X, Decl(arrayDestructuringInSwitch2.ts, 0, 0)) + + const { kind, a } = x; +>kind : Symbol(kind, Decl(arrayDestructuringInSwitch2.ts, 3, 9)) +>a : Symbol(a, Decl(arrayDestructuringInSwitch2.ts, 3, 15)) +>x : Symbol(x, Decl(arrayDestructuringInSwitch2.ts, 2, 13)) + + switch (kind) { +>kind : Symbol(kind, Decl(arrayDestructuringInSwitch2.ts, 3, 9)) + + case "a": + return a[0]; +>a : Symbol(a, Decl(arrayDestructuringInSwitch2.ts, 3, 15)) +>0 : Symbol(0) + + case "b": + return 1; + default: + const [n] = a; +>n : Symbol(n, Decl(arrayDestructuringInSwitch2.ts, 10, 13)) +>a : Symbol(a, Decl(arrayDestructuringInSwitch2.ts, 3, 15)) + + return a; +>a : Symbol(a, Decl(arrayDestructuringInSwitch2.ts, 3, 15)) + } +} diff --git a/tests/baselines/reference/arrayDestructuringInSwitch2.types b/tests/baselines/reference/arrayDestructuringInSwitch2.types new file mode 100644 index 0000000000000..727a2fc68ebc3 --- /dev/null +++ b/tests/baselines/reference/arrayDestructuringInSwitch2.types @@ -0,0 +1,43 @@ +=== tests/cases/compiler/arrayDestructuringInSwitch2.ts === +type X = { kind: "a", a: [1] } | { kind: "b", a: [] } +>X : X +>kind : "a" +>a : [1] +>kind : "b" +>a : [] + +function foo(x: X): 1 { +>foo : (x: X) => 1 +>x : X + + const { kind, a } = x; +>kind : "a" | "b" +>a : [1] | [] +>x : X + + switch (kind) { +>kind : "a" | "b" + + case "a": +>"a" : "a" + + return a[0]; +>a[0] : 1 +>a : [1] +>0 : 0 + + case "b": +>"b" : "b" + + return 1; +>1 : 1 + + default: + const [n] = a; +>n : never +>a : never + + return a; +>a : never + } +} diff --git a/tests/cases/compiler/arrayDestructuringInSwitch1.ts b/tests/cases/compiler/arrayDestructuringInSwitch1.ts new file mode 100644 index 0000000000000..47d77f97d513e --- /dev/null +++ b/tests/cases/compiler/arrayDestructuringInSwitch1.ts @@ -0,0 +1,21 @@ +export type Expression = BooleanLogicExpression | 'true' | 'false'; +export type BooleanLogicExpression = ['and', ...Expression[]] | ['not', Expression]; + +export function evaluate(expression: Expression): boolean { + if (Array.isArray(expression)) { + const [operator, ...operands] = expression; + switch (operator) { + case 'and': { + return operands.every((child) => evaluate(child)); + } + case 'not': { + return !evaluate(operands[0]); + } + default: { + throw new Error(`${operator} is not a supported operator`); + } + } + } else { + return expression === 'true'; + } +} \ No newline at end of file diff --git a/tests/cases/compiler/arrayDestructuringInSwitch2.ts b/tests/cases/compiler/arrayDestructuringInSwitch2.ts new file mode 100644 index 0000000000000..ec084295d0907 --- /dev/null +++ b/tests/cases/compiler/arrayDestructuringInSwitch2.ts @@ -0,0 +1,14 @@ +type X = { kind: "a", a: [1] } | { kind: "b", a: [] } + +function foo(x: X): 1 { + const { kind, a } = x; + switch (kind) { + case "a": + return a[0]; + case "b": + return 1; + default: + const [n] = a; + return a; + } +} \ No newline at end of file