diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d54f2e9db5d5..7ca14b6ec7dc9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8466,12 +8466,16 @@ namespace ts { /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type | undefined { - const pattern = declaration.parent; - let parentType = getTypeForBindingElementParent(pattern.parent); - // If no type or an any type was inferred for parent, infer that for the binding element - if (!parentType || isTypeAny(parentType)) { + const parentType = getTypeForBindingElementParent(declaration.parent.parent); + return parentType && getBindingElementTypeFromParentType(declaration, parentType); + } + + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type { + // If an any type was inferred for parent, infer that for the binding element + if (isTypeAny(parentType)) { return parentType; } + const pattern = declaration.parent; // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { parentType = getNonNullableType(parentType); @@ -22534,30 +22538,6 @@ namespace ts { return false; } - function getPropertyAccess(expr: Expression) { - if (isAccessExpression(expr)) { - return expr; - } - if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration!; - // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' - if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer)) { - return declaration.initializer; - } - // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' - if (isBindingElement(declaration) && !declaration.initializer) { - const parent = declaration.parent.parent; - if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer))) { - return declaration; - } - } - } - } - return undefined; - } - function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined { let propertyName; return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : @@ -23526,19 +23506,19 @@ namespace ts { return false; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { let key: string | undefined; let isKeySet = false; let flowDepth = 0; if (flowAnalysisDisabled) { return errorType; } - if (!reference.flowNode) { + if (!flowNode) { return declaredType; } flowInvocationCount++; const sharedFlowStart = sharedFlowCount; - const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); + const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); sharedFlowCount = sharedFlowStart; // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations @@ -23982,13 +23962,58 @@ namespace ts { return result; } + function getCandidateDiscriminantPropertyAccess(expr: Expression) { + if (isBindingPattern(reference)) { + // When the reference is a binding pattern, we are narrowing a pesudo-reference in getNarrowedTypeOfSymbol. + // An identifier for a destructuring variable declared in the same binding pattern is a candidate. + if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + const declaration = symbol.valueDeclaration; + if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && reference === declaration.parent) { + return declaration; + } + } + } + else if (isAccessExpression(expr)) { + // An access expression is a candidate if the reference matches the left hand expression. + if (isMatchingReference(reference, expr.expression)) { + return expr; + } + } + else if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration!; + // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' + if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && + isMatchingReference(reference, declaration.initializer.expression)) { + return declaration.initializer; + } + // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' + if (isBindingElement(declaration) && !declaration.initializer) { + const parent = declaration.parent.parent; + if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && + isMatchingReference(reference, parent.initializer)) { + return declaration; + } + } + } + } + return undefined; + } + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { - let access, name; const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; - return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) && - isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) && - isDiscriminantProperty(type, name) ? - access : undefined; + if (type.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name && isDiscriminantProperty(type, name)) { + return access; + } + } + } + return undefined; } function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type { @@ -24801,6 +24826,50 @@ namespace ts { } } + function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { + // If we have a non-rest binding element with no initializer declared as a const variable or a const-like + // parameter (a parameter for which there are no assignments in the function body), and if the parent type + // for the destructuring is a union type, one or more of the binding elements may represent discriminant + // properties, and we want the effects of conditional checks on such discriminants to affect the types of + // other binding elements from the same destructuring. Consider: + // + // type Action = + // | { kind: 'A', payload: number } + // | { kind: 'B', payload: string }; + // + // function f1({ kind, payload }: Action) { + // if (kind === 'A') { + // payload.toFixed(); + // } + // if (kind === 'B') { + // payload.toUpperCase(); + // } + // } + // + // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use + // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference + // as if it occurred in the specified location. We then recompute the narrowed binding element type by + // destructuring from the narrowed parent type. + const declaration = symbol.valueDeclaration; + if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { + const parent = declaration.parent.parent; + if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) { + const links = getNodeLinks(location); + if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { + links.flags |= NodeCheckFlags.InCheckIdentifier; + const parentType = getTypeForBindingElementParent(parent); + links.flags &= ~NodeCheckFlags.InCheckIdentifier; + 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); + return getBindingElementTypeFromParentType(declaration, narrowedType); + } + } + } + } + return getTypeOfSymbol(symbol); + } + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { const symbol = getResolvedSymbol(node); if (symbol === unknownSymbol) { @@ -24884,7 +24953,7 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); - let type = getTypeOfSymbol(localOrExportSymbol); + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index beff69baaa11b..5477f1caf0434 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5073,6 +5073,7 @@ namespace ts { ConstructorReferenceInClass = 0x02000000, // Binding to a class constructor inside of the class's body. ContainsClassWithPrivateIdentifiers = 0x04000000, // Marked on all block-scoped containers containing a class with private identifiers. ContainsSuperPropertyInStaticInitializer = 0x08000000, // Marked on all block-scoped containers containing a static initializer with 'super.x' or 'super[x]'. + InCheckIdentifier = 0x10000000, } /* @internal */ diff --git a/tests/baselines/reference/controlFlowAliasing.errors.txt b/tests/baselines/reference/controlFlowAliasing.errors.txt index 04c2aacd71207..3bdd7b9a7eec4 100644 --- a/tests/baselines/reference/controlFlowAliasing.errors.txt +++ b/tests/baselines/reference/controlFlowAliasing.errors.txt @@ -28,15 +28,11 @@ tests/cases/conformance/controlFlow/controlFlowAliasing.ts(232,13): error TS2322 Type 'number' is not assignable to type 'string'. tests/cases/conformance/controlFlow/controlFlowAliasing.ts(233,13): error TS2322: Type 'string | number' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'. -tests/cases/conformance/controlFlow/controlFlowAliasing.ts(267,13): error TS2322: Type 'string | number' is not assignable to type 'string'. - Type 'number' is not assignable to type 'string'. -tests/cases/conformance/controlFlow/controlFlowAliasing.ts(270,13): error TS2322: Type 'string | number' is not assignable to type 'number'. - Type 'string' is not assignable to type 'number'. tests/cases/conformance/controlFlow/controlFlowAliasing.ts(280,5): error TS2448: Block-scoped variable 'a' used before its declaration. tests/cases/conformance/controlFlow/controlFlowAliasing.ts(280,5): error TS2454: Variable 'a' is used before being assigned. -==== tests/cases/conformance/controlFlow/controlFlowAliasing.ts (19 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowAliasing.ts (17 errors) ==== // Narrowing by aliased conditional expressions function f10(x: string | number) { @@ -349,15 +345,9 @@ tests/cases/conformance/controlFlow/controlFlowAliasing.ts(280,5): error TS2454: function foo({ kind, payload }: Data) { if (kind === 'str') { let t: string = payload; - ~ -!!! error TS2322: Type 'string | number' is not assignable to type 'string'. -!!! error TS2322: Type 'number' is not assignable to type 'string'. } else { let t: number = payload; - ~ -!!! error TS2322: Type 'string | number' is not assignable to type 'number'. -!!! error TS2322: Type 'string' is not assignable to type 'number'. } } diff --git a/tests/baselines/reference/controlFlowAliasing.types b/tests/baselines/reference/controlFlowAliasing.types index bbca40eb168b8..a3c7f9f7f4f19 100644 --- a/tests/baselines/reference/controlFlowAliasing.types +++ b/tests/baselines/reference/controlFlowAliasing.types @@ -888,12 +888,12 @@ function foo({ kind, payload }: Data) { let t: string = payload; >t : string ->payload : string | number +>payload : string } else { let t: number = payload; >t : number ->payload : string | number +>payload : number } } diff --git a/tests/baselines/reference/dependentDestructuredVariables.js b/tests/baselines/reference/dependentDestructuredVariables.js new file mode 100644 index 0000000000000..82d41eb8b8d8d --- /dev/null +++ b/tests/baselines/reference/dependentDestructuredVariables.js @@ -0,0 +1,336 @@ +//// [dependentDestructuredVariables.ts] +type Action = + | { kind: 'A', payload: number } + | { kind: 'B', payload: string }; + +function f10({ kind, payload }: Action) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +} + +function f11(action: Action) { + const { kind, payload } = action; + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +} + +function f12({ kind, payload }: Action) { + switch (kind) { + case 'A': + payload.toFixed(); + break; + case 'B': + payload.toUpperCase(); + break; + default: + payload; // never + } +} + +type Action2 = + | { kind: 'A', payload: number | undefined } + | { kind: 'B', payload: string | undefined }; + +function f20({ kind, payload }: Action2) { + if (payload) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} + +function f21(action: Action2) { + const { kind, payload } = action; + if (payload) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} + +function f22(action: Action2) { + if (action.payload) { + const { kind, payload } = action; + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} + +function f23({ kind, payload }: Action2) { + if (payload) { + switch (kind) { + case 'A': + payload.toFixed(); + break; + case 'B': + payload.toUpperCase(); + break; + default: + payload; // never + } + } +} + +type Foo = + | { kind: 'A', isA: true } + | { kind: 'B', isA: false } + | { kind: 'C', isA: false }; + +function f30({ kind, isA }: Foo) { + if (kind === 'A') { + isA; // true + } + if (kind === 'B') { + isA; // false + } + if (kind === 'C') { + isA; // false + } + if (isA) { + kind; // 'A' + } + else { + kind; // 'B' | 'C' + } +} + +// Repro from #35283 + +interface A { variant: 'a', value: T } + +interface B { variant: 'b', value: Array } + +type AB = A | B; + +declare function printValue(t: T): void; + +declare function printValueList(t: Array): void; + +function unrefined1(ab: AB): void { + const { variant, value } = ab; + if (variant === 'a') { + printValue(value); + } + else { + printValueList(value); + } +} + +// Repro from #38020 + +type Action3 = + | {type: 'add', payload: { toAdd: number } } + | {type: 'remove', payload: { toRemove: number } }; + +const reducerBroken = (state: number, { type, payload }: Action3) => { + switch (type) { + case 'add': + return state + payload.toAdd; + case 'remove': + return state - payload.toRemove; + } +} + +// Repro from #46143 + +declare var it: Iterator; +const { value, done } = it.next(); +if (!done) { + value; // number +} + + +//// [dependentDestructuredVariables.js] +"use strict"; +function f10({ kind, payload }) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +} +function f11(action) { + const { kind, payload } = action; + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +} +function f12({ kind, payload }) { + switch (kind) { + case 'A': + payload.toFixed(); + break; + case 'B': + payload.toUpperCase(); + break; + default: + payload; // never + } +} +function f20({ kind, payload }) { + if (payload) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} +function f21(action) { + const { kind, payload } = action; + if (payload) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} +function f22(action) { + if (action.payload) { + const { kind, payload } = action; + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} +function f23({ kind, payload }) { + if (payload) { + switch (kind) { + case 'A': + payload.toFixed(); + break; + case 'B': + payload.toUpperCase(); + break; + default: + payload; // never + } + } +} +function f30({ kind, isA }) { + if (kind === 'A') { + isA; // true + } + if (kind === 'B') { + isA; // false + } + if (kind === 'C') { + isA; // false + } + if (isA) { + kind; // 'A' + } + else { + kind; // 'B' | 'C' + } +} +function unrefined1(ab) { + const { variant, value } = ab; + if (variant === 'a') { + printValue(value); + } + else { + printValueList(value); + } +} +const reducerBroken = (state, { type, payload }) => { + switch (type) { + case 'add': + return state + payload.toAdd; + case 'remove': + return state - payload.toRemove; + } +}; +const { value, done } = it.next(); +if (!done) { + value; // number +} + + +//// [dependentDestructuredVariables.d.ts] +declare type Action = { + kind: 'A'; + payload: number; +} | { + kind: 'B'; + payload: string; +}; +declare function f10({ kind, payload }: Action): void; +declare function f11(action: Action): void; +declare function f12({ kind, payload }: Action): void; +declare type Action2 = { + kind: 'A'; + payload: number | undefined; +} | { + kind: 'B'; + payload: string | undefined; +}; +declare function f20({ kind, payload }: Action2): void; +declare function f21(action: Action2): void; +declare function f22(action: Action2): void; +declare function f23({ kind, payload }: Action2): void; +declare type Foo = { + kind: 'A'; + isA: true; +} | { + kind: 'B'; + isA: false; +} | { + kind: 'C'; + isA: false; +}; +declare function f30({ kind, isA }: Foo): void; +interface A { + variant: 'a'; + value: T; +} +interface B { + variant: 'b'; + value: Array; +} +declare type AB = A | B; +declare function printValue(t: T): void; +declare function printValueList(t: Array): void; +declare function unrefined1(ab: AB): void; +declare type Action3 = { + type: 'add'; + payload: { + toAdd: number; + }; +} | { + type: 'remove'; + payload: { + toRemove: number; + }; +}; +declare const reducerBroken: (state: number, { type, payload }: Action3) => number; +declare var it: Iterator; +declare const value: any, done: boolean | undefined; diff --git a/tests/baselines/reference/dependentDestructuredVariables.symbols b/tests/baselines/reference/dependentDestructuredVariables.symbols new file mode 100644 index 0000000000000..2700f368585fe --- /dev/null +++ b/tests/baselines/reference/dependentDestructuredVariables.symbols @@ -0,0 +1,409 @@ +=== tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts === +type Action = +>Action : Symbol(Action, Decl(dependentDestructuredVariables.ts, 0, 0)) + + | { kind: 'A', payload: number } +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 1, 7)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 1, 18)) + + | { kind: 'B', payload: string }; +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 2, 7)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 2, 18)) + +function f10({ kind, payload }: Action) { +>f10 : Symbol(f10, Decl(dependentDestructuredVariables.ts, 2, 37)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 4, 14)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 4, 20)) +>Action : Symbol(Action, Decl(dependentDestructuredVariables.ts, 0, 0)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 4, 14)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 4, 20)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 4, 14)) + + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 4, 20)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +} + +function f11(action: Action) { +>f11 : Symbol(f11, Decl(dependentDestructuredVariables.ts, 11, 1)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 13, 13)) +>Action : Symbol(Action, Decl(dependentDestructuredVariables.ts, 0, 0)) + + const { kind, payload } = action; +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 14, 11)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 14, 17)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 13, 13)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 14, 11)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 14, 17)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 14, 11)) + + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 14, 17)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +} + +function f12({ kind, payload }: Action) { +>f12 : Symbol(f12, Decl(dependentDestructuredVariables.ts, 21, 1)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 23, 14)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 23, 20)) +>Action : Symbol(Action, Decl(dependentDestructuredVariables.ts, 0, 0)) + + switch (kind) { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 23, 14)) + + case 'A': + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 23, 20)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + + break; + case 'B': + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 23, 20)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + break; + default: + payload; // never +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 23, 20)) + } +} + +type Action2 = +>Action2 : Symbol(Action2, Decl(dependentDestructuredVariables.ts, 34, 1)) + + | { kind: 'A', payload: number | undefined } +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 37, 7)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 37, 18)) + + | { kind: 'B', payload: string | undefined }; +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 38, 7)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 38, 18)) + +function f20({ kind, payload }: Action2) { +>f20 : Symbol(f20, Decl(dependentDestructuredVariables.ts, 38, 49)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 40, 14)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 40, 20)) +>Action2 : Symbol(Action2, Decl(dependentDestructuredVariables.ts, 34, 1)) + + if (payload) { +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 40, 20)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 40, 14)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 40, 20)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 40, 14)) + + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 40, 20)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + } +} + +function f21(action: Action2) { +>f21 : Symbol(f21, Decl(dependentDestructuredVariables.ts, 49, 1)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 51, 13)) +>Action2 : Symbol(Action2, Decl(dependentDestructuredVariables.ts, 34, 1)) + + const { kind, payload } = action; +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 52, 11)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 52, 17)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 51, 13)) + + if (payload) { +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 52, 17)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 52, 11)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 52, 17)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 52, 11)) + + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 52, 17)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + } +} + +function f22(action: Action2) { +>f22 : Symbol(f22, Decl(dependentDestructuredVariables.ts, 61, 1)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 63, 13)) +>Action2 : Symbol(Action2, Decl(dependentDestructuredVariables.ts, 34, 1)) + + if (action.payload) { +>action.payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 37, 18), Decl(dependentDestructuredVariables.ts, 38, 18)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 63, 13)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 37, 18), Decl(dependentDestructuredVariables.ts, 38, 18)) + + const { kind, payload } = action; +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 65, 15)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 65, 21)) +>action : Symbol(action, Decl(dependentDestructuredVariables.ts, 63, 13)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 65, 15)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 65, 21)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 65, 15)) + + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 65, 21)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + } +} + +function f23({ kind, payload }: Action2) { +>f23 : Symbol(f23, Decl(dependentDestructuredVariables.ts, 73, 1)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 75, 14)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 75, 20)) +>Action2 : Symbol(Action2, Decl(dependentDestructuredVariables.ts, 34, 1)) + + if (payload) { +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 75, 20)) + + switch (kind) { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 75, 14)) + + case 'A': + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 75, 20)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + + break; + case 'B': + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 75, 20)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + break; + default: + payload; // never +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 75, 20)) + } + } +} + +type Foo = +>Foo : Symbol(Foo, Decl(dependentDestructuredVariables.ts, 88, 1)) + + | { kind: 'A', isA: true } +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 91, 7)) +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 91, 18)) + + | { kind: 'B', isA: false } +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 92, 7)) +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 92, 18)) + + | { kind: 'C', isA: false }; +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 93, 7)) +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 93, 18)) + +function f30({ kind, isA }: Foo) { +>f30 : Symbol(f30, Decl(dependentDestructuredVariables.ts, 93, 32)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 95, 14)) +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 95, 20)) +>Foo : Symbol(Foo, Decl(dependentDestructuredVariables.ts, 88, 1)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 95, 14)) + + isA; // true +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 95, 20)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 95, 14)) + + isA; // false +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 95, 20)) + } + if (kind === 'C') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 95, 14)) + + isA; // false +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 95, 20)) + } + if (isA) { +>isA : Symbol(isA, Decl(dependentDestructuredVariables.ts, 95, 20)) + + kind; // 'A' +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 95, 14)) + } + else { + kind; // 'B' | 'C' +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 95, 14)) + } +} + +// Repro from #35283 + +interface A { variant: 'a', value: T } +>A : Symbol(A, Decl(dependentDestructuredVariables.ts, 111, 1)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 115, 12)) +>variant : Symbol(A.variant, Decl(dependentDestructuredVariables.ts, 115, 16)) +>value : Symbol(A.value, Decl(dependentDestructuredVariables.ts, 115, 30)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 115, 12)) + +interface B { variant: 'b', value: Array } +>B : Symbol(B, Decl(dependentDestructuredVariables.ts, 115, 41)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 117, 12)) +>variant : Symbol(B.variant, Decl(dependentDestructuredVariables.ts, 117, 16)) +>value : Symbol(B.value, Decl(dependentDestructuredVariables.ts, 117, 30)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 117, 12)) + +type AB = A | B; +>AB : Symbol(AB, Decl(dependentDestructuredVariables.ts, 117, 48)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 119, 8)) +>A : Symbol(A, Decl(dependentDestructuredVariables.ts, 111, 1)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 119, 8)) +>B : Symbol(B, Decl(dependentDestructuredVariables.ts, 115, 41)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 119, 8)) + +declare function printValue(t: T): void; +>printValue : Symbol(printValue, Decl(dependentDestructuredVariables.ts, 119, 25)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 121, 28)) +>t : Symbol(t, Decl(dependentDestructuredVariables.ts, 121, 31)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 121, 28)) + +declare function printValueList(t: Array): void; +>printValueList : Symbol(printValueList, Decl(dependentDestructuredVariables.ts, 121, 43)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 123, 32)) +>t : Symbol(t, Decl(dependentDestructuredVariables.ts, 123, 35)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 123, 32)) + +function unrefined1(ab: AB): void { +>unrefined1 : Symbol(unrefined1, Decl(dependentDestructuredVariables.ts, 123, 54)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 125, 20)) +>ab : Symbol(ab, Decl(dependentDestructuredVariables.ts, 125, 23)) +>AB : Symbol(AB, Decl(dependentDestructuredVariables.ts, 117, 48)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 125, 20)) + + const { variant, value } = ab; +>variant : Symbol(variant, Decl(dependentDestructuredVariables.ts, 126, 11)) +>value : Symbol(value, Decl(dependentDestructuredVariables.ts, 126, 20)) +>ab : Symbol(ab, Decl(dependentDestructuredVariables.ts, 125, 23)) + + if (variant === 'a') { +>variant : Symbol(variant, Decl(dependentDestructuredVariables.ts, 126, 11)) + + printValue(value); +>printValue : Symbol(printValue, Decl(dependentDestructuredVariables.ts, 119, 25)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 125, 20)) +>value : Symbol(value, Decl(dependentDestructuredVariables.ts, 126, 20)) + } + else { + printValueList(value); +>printValueList : Symbol(printValueList, Decl(dependentDestructuredVariables.ts, 121, 43)) +>T : Symbol(T, Decl(dependentDestructuredVariables.ts, 125, 20)) +>value : Symbol(value, Decl(dependentDestructuredVariables.ts, 126, 20)) + } +} + +// Repro from #38020 + +type Action3 = +>Action3 : Symbol(Action3, Decl(dependentDestructuredVariables.ts, 133, 1)) + + | {type: 'add', payload: { toAdd: number } } +>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 138, 7)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 138, 19)) +>toAdd : Symbol(toAdd, Decl(dependentDestructuredVariables.ts, 138, 30)) + + | {type: 'remove', payload: { toRemove: number } }; +>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 139, 7)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 139, 22)) +>toRemove : Symbol(toRemove, Decl(dependentDestructuredVariables.ts, 139, 33)) + +const reducerBroken = (state: number, { type, payload }: Action3) => { +>reducerBroken : Symbol(reducerBroken, Decl(dependentDestructuredVariables.ts, 141, 5)) +>state : Symbol(state, Decl(dependentDestructuredVariables.ts, 141, 23)) +>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 141, 39)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 141, 45)) +>Action3 : Symbol(Action3, Decl(dependentDestructuredVariables.ts, 133, 1)) + + switch (type) { +>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 141, 39)) + + case 'add': + return state + payload.toAdd; +>state : Symbol(state, Decl(dependentDestructuredVariables.ts, 141, 23)) +>payload.toAdd : Symbol(toAdd, Decl(dependentDestructuredVariables.ts, 138, 30)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 141, 45)) +>toAdd : Symbol(toAdd, Decl(dependentDestructuredVariables.ts, 138, 30)) + + case 'remove': + return state - payload.toRemove; +>state : Symbol(state, Decl(dependentDestructuredVariables.ts, 141, 23)) +>payload.toRemove : Symbol(toRemove, Decl(dependentDestructuredVariables.ts, 139, 33)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 141, 45)) +>toRemove : Symbol(toRemove, Decl(dependentDestructuredVariables.ts, 139, 33)) + } +} + +// Repro from #46143 + +declare var it: Iterator; +>it : Symbol(it, Decl(dependentDestructuredVariables.ts, 152, 11)) +>Iterator : Symbol(Iterator, Decl(lib.es2015.iterable.d.ts, --, --)) + +const { value, done } = it.next(); +>value : Symbol(value, Decl(dependentDestructuredVariables.ts, 153, 7)) +>done : Symbol(done, Decl(dependentDestructuredVariables.ts, 153, 14)) +>it.next : Symbol(Iterator.next, Decl(lib.es2015.iterable.d.ts, --, --)) +>it : Symbol(it, Decl(dependentDestructuredVariables.ts, 152, 11)) +>next : Symbol(Iterator.next, Decl(lib.es2015.iterable.d.ts, --, --)) + +if (!done) { +>done : Symbol(done, Decl(dependentDestructuredVariables.ts, 153, 14)) + + value; // number +>value : Symbol(value, Decl(dependentDestructuredVariables.ts, 153, 7)) +} + diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types new file mode 100644 index 0000000000000..1c8df34341cef --- /dev/null +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -0,0 +1,441 @@ +=== tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts === +type Action = +>Action : Action + + | { kind: 'A', payload: number } +>kind : "A" +>payload : number + + | { kind: 'B', payload: string }; +>kind : "B" +>payload : string + +function f10({ kind, payload }: Action) { +>f10 : ({ kind, payload }: Action) => void +>kind : "A" | "B" +>payload : string | number + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + } +} + +function f11(action: Action) { +>f11 : (action: Action) => void +>action : Action + + const { kind, payload } = action; +>kind : "A" | "B" +>payload : string | number +>action : Action + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + } +} + +function f12({ kind, payload }: Action) { +>f12 : ({ kind, payload }: Action) => void +>kind : "A" | "B" +>payload : string | number + + switch (kind) { +>kind : "A" | "B" + + case 'A': +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + + break; + case 'B': +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + + break; + default: + payload; // never +>payload : never + } +} + +type Action2 = +>Action2 : Action2 + + | { kind: 'A', payload: number | undefined } +>kind : "A" +>payload : number | undefined + + | { kind: 'B', payload: string | undefined }; +>kind : "B" +>payload : string | undefined + +function f20({ kind, payload }: Action2) { +>f20 : ({ kind, payload }: Action2) => void +>kind : "A" | "B" +>payload : string | number | undefined + + if (payload) { +>payload : string | number | undefined + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + } + } +} + +function f21(action: Action2) { +>f21 : (action: Action2) => void +>action : Action2 + + const { kind, payload } = action; +>kind : "A" | "B" +>payload : string | number | undefined +>action : Action2 + + if (payload) { +>payload : string | number | undefined + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + } + } +} + +function f22(action: Action2) { +>f22 : (action: Action2) => void +>action : Action2 + + if (action.payload) { +>action.payload : string | number | undefined +>action : Action2 +>payload : string | number | undefined + + const { kind, payload } = action; +>kind : "A" | "B" +>payload : string | number +>action : Action2 + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + } + } +} + +function f23({ kind, payload }: Action2) { +>f23 : ({ kind, payload }: Action2) => void +>kind : "A" | "B" +>payload : string | number | undefined + + if (payload) { +>payload : string | number | undefined + + switch (kind) { +>kind : "A" | "B" + + case 'A': +>'A' : "A" + + payload.toFixed(); +>payload.toFixed() : string +>payload.toFixed : (fractionDigits?: number | undefined) => string +>payload : number +>toFixed : (fractionDigits?: number | undefined) => string + + break; + case 'B': +>'B' : "B" + + payload.toUpperCase(); +>payload.toUpperCase() : string +>payload.toUpperCase : () => string +>payload : string +>toUpperCase : () => string + + break; + default: + payload; // never +>payload : never + } + } +} + +type Foo = +>Foo : Foo + + | { kind: 'A', isA: true } +>kind : "A" +>isA : true +>true : true + + | { kind: 'B', isA: false } +>kind : "B" +>isA : false +>false : false + + | { kind: 'C', isA: false }; +>kind : "C" +>isA : false +>false : false + +function f30({ kind, isA }: Foo) { +>f30 : ({ kind, isA }: Foo) => void +>kind : "A" | "B" | "C" +>isA : boolean + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" | "C" +>'A' : "A" + + isA; // true +>isA : true + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" | "C" +>'B' : "B" + + isA; // false +>isA : false + } + if (kind === 'C') { +>kind === 'C' : boolean +>kind : "A" | "B" | "C" +>'C' : "C" + + isA; // false +>isA : false + } + if (isA) { +>isA : boolean + + kind; // 'A' +>kind : "A" + } + else { + kind; // 'B' | 'C' +>kind : "B" | "C" + } +} + +// Repro from #35283 + +interface A { variant: 'a', value: T } +>variant : "a" +>value : T + +interface B { variant: 'b', value: Array } +>variant : "b" +>value : T[] + +type AB = A | B; +>AB : AB + +declare function printValue(t: T): void; +>printValue : (t: T) => void +>t : T + +declare function printValueList(t: Array): void; +>printValueList : (t: Array) => void +>t : T[] + +function unrefined1(ab: AB): void { +>unrefined1 : (ab: AB) => void +>ab : AB + + const { variant, value } = ab; +>variant : "a" | "b" +>value : T | T[] +>ab : AB + + if (variant === 'a') { +>variant === 'a' : boolean +>variant : "a" | "b" +>'a' : "a" + + printValue(value); +>printValue(value) : void +>printValue : (t: T) => void +>value : T + } + else { + printValueList(value); +>printValueList(value) : void +>printValueList : (t: T[]) => void +>value : T[] + } +} + +// Repro from #38020 + +type Action3 = +>Action3 : Action3 + + | {type: 'add', payload: { toAdd: number } } +>type : "add" +>payload : { toAdd: number; } +>toAdd : number + + | {type: 'remove', payload: { toRemove: number } }; +>type : "remove" +>payload : { toRemove: number; } +>toRemove : number + +const reducerBroken = (state: number, { type, payload }: Action3) => { +>reducerBroken : (state: number, { type, payload }: Action3) => number +>(state: number, { type, payload }: Action3) => { switch (type) { case 'add': return state + payload.toAdd; case 'remove': return state - payload.toRemove; }} : (state: number, { type, payload }: Action3) => number +>state : number +>type : "add" | "remove" +>payload : { toAdd: number; } | { toRemove: number; } + + switch (type) { +>type : "add" | "remove" + + case 'add': +>'add' : "add" + + return state + payload.toAdd; +>state + payload.toAdd : number +>state : number +>payload.toAdd : number +>payload : { toAdd: number; } +>toAdd : number + + case 'remove': +>'remove' : "remove" + + return state - payload.toRemove; +>state - payload.toRemove : number +>state : number +>payload.toRemove : number +>payload : { toRemove: number; } +>toRemove : number + } +} + +// Repro from #46143 + +declare var it: Iterator; +>it : Iterator + +const { value, done } = it.next(); +>value : any +>done : boolean | undefined +>it.next() : IteratorResult +>it.next : (...args: [] | [undefined]) => IteratorResult +>it : Iterator +>next : (...args: [] | [undefined]) => IteratorResult + +if (!done) { +>!done : boolean +>done : boolean | undefined + + value; // number +>value : number +} + diff --git a/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts b/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts new file mode 100644 index 0000000000000..55e95ddf09163 --- /dev/null +++ b/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts @@ -0,0 +1,161 @@ +// @strict: true +// @declaration: true +// @target: es2015 + +type Action = + | { kind: 'A', payload: number } + | { kind: 'B', payload: string }; + +function f10({ kind, payload }: Action) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +} + +function f11(action: Action) { + const { kind, payload } = action; + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +} + +function f12({ kind, payload }: Action) { + switch (kind) { + case 'A': + payload.toFixed(); + break; + case 'B': + payload.toUpperCase(); + break; + default: + payload; // never + } +} + +type Action2 = + | { kind: 'A', payload: number | undefined } + | { kind: 'B', payload: string | undefined }; + +function f20({ kind, payload }: Action2) { + if (payload) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} + +function f21(action: Action2) { + const { kind, payload } = action; + if (payload) { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} + +function f22(action: Action2) { + if (action.payload) { + const { kind, payload } = action; + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } + } +} + +function f23({ kind, payload }: Action2) { + if (payload) { + switch (kind) { + case 'A': + payload.toFixed(); + break; + case 'B': + payload.toUpperCase(); + break; + default: + payload; // never + } + } +} + +type Foo = + | { kind: 'A', isA: true } + | { kind: 'B', isA: false } + | { kind: 'C', isA: false }; + +function f30({ kind, isA }: Foo) { + if (kind === 'A') { + isA; // true + } + if (kind === 'B') { + isA; // false + } + if (kind === 'C') { + isA; // false + } + if (isA) { + kind; // 'A' + } + else { + kind; // 'B' | 'C' + } +} + +// Repro from #35283 + +interface A { variant: 'a', value: T } + +interface B { variant: 'b', value: Array } + +type AB = A | B; + +declare function printValue(t: T): void; + +declare function printValueList(t: Array): void; + +function unrefined1(ab: AB): void { + const { variant, value } = ab; + if (variant === 'a') { + printValue(value); + } + else { + printValueList(value); + } +} + +// Repro from #38020 + +type Action3 = + | {type: 'add', payload: { toAdd: number } } + | {type: 'remove', payload: { toRemove: number } }; + +const reducerBroken = (state: number, { type, payload }: Action3) => { + switch (type) { + case 'add': + return state + payload.toAdd; + case 'remove': + return state - payload.toRemove; + } +} + +// Repro from #46143 + +declare var it: Iterator; +const { value, done } = it.next(); +if (!done) { + value; // number +}