diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 590cebef919fb..05203d714d3ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22679,11 +22679,12 @@ namespace ts { return false; } - function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined { + function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined { let propertyName; return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) : + access.kind === SyntaxKind.Parameter ? ("" + access.parent.parameters.indexOf(access)) as __String : undefined; } @@ -24105,13 +24106,14 @@ namespace ts { } 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 (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference)) { + // When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in + // getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or + // parameter declared in the same parameter list 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) { + if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) { return declaration; } } @@ -24158,7 +24160,7 @@ namespace ts { return undefined; } - function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type { + function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, narrowType: (t: Type) => Type): Type { const propName = getAccessedPropertyName(access); if (propName === undefined) { return type; @@ -24176,7 +24178,7 @@ namespace ts { }); } - function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { + function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { const keyPropertyName = getKeyPropertyName(type as UnionType); if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { @@ -24191,7 +24193,7 @@ namespace ts { return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } - function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); @@ -24969,42 +24971,78 @@ 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); + if (declaration) { + // 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 f({ 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. + if (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); + } + } + } + } + // If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually + // typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may + // represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to + // affect the types of other parameters in the same parameter list. Consider: + // + // type Action = [kind: 'A', payload: number] | [kind: 'B', payload: string]; + // + // const f: (...args: Action) => void = (kind, payload) => { + // 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 arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as + // if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the + // narrowed tuple type. + if (isParameter(declaration) && !declaration.type && !declaration.initializer && !declaration.dotDotDotToken) { + const func = declaration.parent; + if (func.parameters.length >= 2 && isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + const contextualSignature = getContextualSignature(func); + if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { + const restType = getTypeOfSymbol(contextualSignature.parameters[0]); + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) { + const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); + const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); + return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); + } } } } diff --git a/tests/baselines/reference/dependentDestructuredVariables.js b/tests/baselines/reference/dependentDestructuredVariables.js index ac99a33df54bd..2eb7be3d946a4 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.js +++ b/tests/baselines/reference/dependentDestructuredVariables.js @@ -167,6 +167,64 @@ const { value, done } = it.next(); if (!done) { value; // number } + +// Repro from #46658 + +declare function f50(cb: (...args: Args) => void): void + +f50((kind, data) => { + if (kind === 'A') { + data.toFixed(); + } + if (kind === 'B') { + data.toUpperCase(); + } +}); + +const f51: (...args: ['A', number] | ['B', string]) => void = (kind, payload) => { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +}; + +const f52: (...args: ['A', number] | ['B']) => void = (kind, payload?) => { + if (kind === 'A') { + payload.toFixed(); + } + else { + payload; // undefined + } +}; + +declare function readFile(path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void): void; + +readFile('hello', (err, data) => { + if (err === null) { + data.length; + } + else { + err.message; + } +}); + +type ReducerArgs = ["add", { a: number, b: number }] | ["concat", { firstArr: any[], secondArr: any[] }]; + +const reducer: (...args: ReducerArgs) => void = (op, args) => { + switch (op) { + case "add": + console.log(args.a + args.b); + break; + case "concat": + console.log(args.firstArr.concat(args.secondArr)); + break; + } +} + +reducer("add", { a: 1, b: 3 }); +reducer("concat", { firstArr: [1, 2], secondArr: [3, 4] }); //// [dependentDestructuredVariables.js] @@ -292,6 +350,50 @@ const { value, done } = it.next(); if (!done) { value; // number } +f50((kind, data) => { + if (kind === 'A') { + data.toFixed(); + } + if (kind === 'B') { + data.toUpperCase(); + } +}); +const f51 = (kind, payload) => { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +}; +const f52 = (kind, payload) => { + if (kind === 'A') { + payload.toFixed(); + } + else { + payload; // undefined + } +}; +readFile('hello', (err, data) => { + if (err === null) { + data.length; + } + else { + err.message; + } +}); +const reducer = (op, args) => { + switch (op) { + case "add": + console.log(args.a + args.b); + break; + case "concat": + console.log(args.firstArr.concat(args.secondArr)); + break; + } +}; +reducer("add", { a: 1, b: 3 }); +reducer("concat", { firstArr: [1, 2], secondArr: [3, 4] }); //// [dependentDestructuredVariables.d.ts] @@ -355,3 +457,15 @@ declare type Action3 = { declare const reducerBroken: (state: number, { type, payload }: Action3) => number; declare var it: Iterator; declare const value: any, done: boolean | undefined; +declare function f50(cb: (...args: Args) => void): void; +declare const f51: (...args: ['A', number] | ['B', string]) => void; +declare const f52: (...args: ['A', number] | ['B']) => void; +declare function readFile(path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void): void; +declare type ReducerArgs = ["add", { + a: number; + b: number; +}] | ["concat", { + firstArr: any[]; + secondArr: any[]; +}]; +declare const reducer: (...args: ReducerArgs) => void; diff --git a/tests/baselines/reference/dependentDestructuredVariables.symbols b/tests/baselines/reference/dependentDestructuredVariables.symbols index 7c8d0649bf018..bad7fbf5b690e 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.symbols +++ b/tests/baselines/reference/dependentDestructuredVariables.symbols @@ -434,3 +434,164 @@ if (!done) { >value : Symbol(value, Decl(dependentDestructuredVariables.ts, 164, 7)) } +// Repro from #46658 + +declare function f50(cb: (...args: Args) => void): void +>f50 : Symbol(f50, Decl(dependentDestructuredVariables.ts, 167, 1)) +>cb : Symbol(cb, Decl(dependentDestructuredVariables.ts, 171, 21)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 171, 26)) +>Args : Symbol(Args, Decl(dependentDestructuredVariables.ts, 111, 1)) + +f50((kind, data) => { +>f50 : Symbol(f50, Decl(dependentDestructuredVariables.ts, 167, 1)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 173, 5)) +>data : Symbol(data, Decl(dependentDestructuredVariables.ts, 173, 10)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 173, 5)) + + data.toFixed(); +>data.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>data : Symbol(data, Decl(dependentDestructuredVariables.ts, 173, 10)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 173, 5)) + + data.toUpperCase(); +>data.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>data : Symbol(data, Decl(dependentDestructuredVariables.ts, 173, 10)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +}); + +const f51: (...args: ['A', number] | ['B', string]) => void = (kind, payload) => { +>f51 : Symbol(f51, Decl(dependentDestructuredVariables.ts, 182, 5)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 182, 12)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 182, 63)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 182, 68)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 182, 63)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 182, 68)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + if (kind === 'B') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 182, 63)) + + payload.toUpperCase(); +>payload.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 182, 68)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +}; + +const f52: (...args: ['A', number] | ['B']) => void = (kind, payload?) => { +>f52 : Symbol(f52, Decl(dependentDestructuredVariables.ts, 191, 5)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 191, 12)) +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 191, 55)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 191, 60)) + + if (kind === 'A') { +>kind : Symbol(kind, Decl(dependentDestructuredVariables.ts, 191, 55)) + + payload.toFixed(); +>payload.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 191, 60)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + else { + payload; // undefined +>payload : Symbol(payload, Decl(dependentDestructuredVariables.ts, 191, 60)) + } +}; + +declare function readFile(path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void): void; +>readFile : Symbol(readFile, Decl(dependentDestructuredVariables.ts, 198, 2)) +>path : Symbol(path, Decl(dependentDestructuredVariables.ts, 200, 26)) +>callback : Symbol(callback, Decl(dependentDestructuredVariables.ts, 200, 39)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 200, 51)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +readFile('hello', (err, data) => { +>readFile : Symbol(readFile, Decl(dependentDestructuredVariables.ts, 198, 2)) +>err : Symbol(err, Decl(dependentDestructuredVariables.ts, 202, 19)) +>data : Symbol(data, Decl(dependentDestructuredVariables.ts, 202, 23)) + + if (err === null) { +>err : Symbol(err, Decl(dependentDestructuredVariables.ts, 202, 19)) + + data.length; +>data.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>data : Symbol(data, Decl(dependentDestructuredVariables.ts, 202, 23)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) + } + else { + err.message; +>err.message : Symbol(Error.message, Decl(lib.es5.d.ts, --, --)) +>err : Symbol(err, Decl(dependentDestructuredVariables.ts, 202, 19)) +>message : Symbol(Error.message, Decl(lib.es5.d.ts, --, --)) + } +}); + +type ReducerArgs = ["add", { a: number, b: number }] | ["concat", { firstArr: any[], secondArr: any[] }]; +>ReducerArgs : Symbol(ReducerArgs, Decl(dependentDestructuredVariables.ts, 209, 3)) +>a : Symbol(a, Decl(dependentDestructuredVariables.ts, 211, 28)) +>b : Symbol(b, Decl(dependentDestructuredVariables.ts, 211, 39)) +>firstArr : Symbol(firstArr, Decl(dependentDestructuredVariables.ts, 211, 67)) +>secondArr : Symbol(secondArr, Decl(dependentDestructuredVariables.ts, 211, 84)) + +const reducer: (...args: ReducerArgs) => void = (op, args) => { +>reducer : Symbol(reducer, Decl(dependentDestructuredVariables.ts, 213, 5)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 213, 16)) +>ReducerArgs : Symbol(ReducerArgs, Decl(dependentDestructuredVariables.ts, 209, 3)) +>op : Symbol(op, Decl(dependentDestructuredVariables.ts, 213, 49)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 213, 52)) + + switch (op) { +>op : Symbol(op, Decl(dependentDestructuredVariables.ts, 213, 49)) + + case "add": + console.log(args.a + args.b); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>args.a : Symbol(a, Decl(dependentDestructuredVariables.ts, 211, 28)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 213, 52)) +>a : Symbol(a, Decl(dependentDestructuredVariables.ts, 211, 28)) +>args.b : Symbol(b, Decl(dependentDestructuredVariables.ts, 211, 39)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 213, 52)) +>b : Symbol(b, Decl(dependentDestructuredVariables.ts, 211, 39)) + + break; + case "concat": + console.log(args.firstArr.concat(args.secondArr)); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>args.firstArr.concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>args.firstArr : Symbol(firstArr, Decl(dependentDestructuredVariables.ts, 211, 67)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 213, 52)) +>firstArr : Symbol(firstArr, Decl(dependentDestructuredVariables.ts, 211, 67)) +>concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>args.secondArr : Symbol(secondArr, Decl(dependentDestructuredVariables.ts, 211, 84)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 213, 52)) +>secondArr : Symbol(secondArr, Decl(dependentDestructuredVariables.ts, 211, 84)) + + break; + } +} + +reducer("add", { a: 1, b: 3 }); +>reducer : Symbol(reducer, Decl(dependentDestructuredVariables.ts, 213, 5)) +>a : Symbol(a, Decl(dependentDestructuredVariables.ts, 224, 16)) +>b : Symbol(b, Decl(dependentDestructuredVariables.ts, 224, 22)) + +reducer("concat", { firstArr: [1, 2], secondArr: [3, 4] }); +>reducer : Symbol(reducer, Decl(dependentDestructuredVariables.ts, 213, 5)) +>firstArr : Symbol(firstArr, Decl(dependentDestructuredVariables.ts, 225, 19)) +>secondArr : Symbol(secondArr, Decl(dependentDestructuredVariables.ts, 225, 37)) + diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types index de8851a3661db..a1b16d186ee08 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.types +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -471,3 +471,209 @@ if (!done) { >value : number } +// Repro from #46658 + +declare function f50(cb: (...args: Args) => void): void +>f50 : (cb: (...args: Args) => void) => void +>cb : (...args: Args) => void +>args : Args + +f50((kind, data) => { +>f50((kind, data) => { if (kind === 'A') { data.toFixed(); } if (kind === 'B') { data.toUpperCase(); }}) : void +>f50 : (cb: (...args: Args) => void) => void +>(kind, data) => { if (kind === 'A') { data.toFixed(); } if (kind === 'B') { data.toUpperCase(); }} : (kind: "A" | "B", data: string | number) => void +>kind : "A" | "B" +>data : string | number + + if (kind === 'A') { +>kind === 'A' : boolean +>kind : "A" | "B" +>'A' : "A" + + data.toFixed(); +>data.toFixed() : string +>data.toFixed : (fractionDigits?: number | undefined) => string +>data : number +>toFixed : (fractionDigits?: number | undefined) => string + } + if (kind === 'B') { +>kind === 'B' : boolean +>kind : "A" | "B" +>'B' : "B" + + data.toUpperCase(); +>data.toUpperCase() : string +>data.toUpperCase : () => string +>data : string +>toUpperCase : () => string + } +}); + +const f51: (...args: ['A', number] | ['B', string]) => void = (kind, payload) => { +>f51 : (...args: ['A', number] | ['B', string]) => void +>args : ["A", number] | ["B", string] +>(kind, payload) => { if (kind === 'A') { payload.toFixed(); } if (kind === 'B') { payload.toUpperCase(); }} : (kind: "A" | "B", payload: string | number) => 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 + } +}; + +const f52: (...args: ['A', number] | ['B']) => void = (kind, payload?) => { +>f52 : (...args: ['A', number] | ['B']) => void +>args : ["A", number] | ["B"] +>(kind, payload?) => { if (kind === 'A') { payload.toFixed(); } else { payload; // undefined }} : (kind: "A" | "B", payload?: number | undefined) => void +>kind : "A" | "B" +>payload : 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 + } + else { + payload; // undefined +>payload : undefined + } +}; + +declare function readFile(path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void): void; +>readFile : (path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void) => void +>path : string +>callback : (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void +>args : [err: null, data: unknown[]] | [err: Error, data: undefined] +>null : null + +readFile('hello', (err, data) => { +>readFile('hello', (err, data) => { if (err === null) { data.length; } else { err.message; }}) : void +>readFile : (path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void) => void +>'hello' : "hello" +>(err, data) => { if (err === null) { data.length; } else { err.message; }} : (err: Error | null, data: unknown[] | undefined) => void +>err : Error | null +>data : unknown[] | undefined + + if (err === null) { +>err === null : boolean +>err : Error | null +>null : null + + data.length; +>data.length : number +>data : unknown[] +>length : number + } + else { + err.message; +>err.message : string +>err : Error +>message : string + } +}); + +type ReducerArgs = ["add", { a: number, b: number }] | ["concat", { firstArr: any[], secondArr: any[] }]; +>ReducerArgs : ReducerArgs +>a : number +>b : number +>firstArr : any[] +>secondArr : any[] + +const reducer: (...args: ReducerArgs) => void = (op, args) => { +>reducer : (...args: ReducerArgs) => void +>args : ReducerArgs +>(op, args) => { switch (op) { case "add": console.log(args.a + args.b); break; case "concat": console.log(args.firstArr.concat(args.secondArr)); break; }} : (op: "add" | "concat", args: { a: number; b: number; } | { firstArr: any[]; secondArr: any[]; }) => void +>op : "add" | "concat" +>args : { a: number; b: number; } | { firstArr: any[]; secondArr: any[]; } + + switch (op) { +>op : "add" | "concat" + + case "add": +>"add" : "add" + + console.log(args.a + args.b); +>console.log(args.a + args.b) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>args.a + args.b : number +>args.a : number +>args : { a: number; b: number; } +>a : number +>args.b : number +>args : { a: number; b: number; } +>b : number + + break; + case "concat": +>"concat" : "concat" + + console.log(args.firstArr.concat(args.secondArr)); +>console.log(args.firstArr.concat(args.secondArr)) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>args.firstArr.concat(args.secondArr) : any[] +>args.firstArr.concat : { (...items: ConcatArray[]): any[]; (...items: any[]): any[]; } +>args.firstArr : any[] +>args : { firstArr: any[]; secondArr: any[]; } +>firstArr : any[] +>concat : { (...items: ConcatArray[]): any[]; (...items: any[]): any[]; } +>args.secondArr : any[] +>args : { firstArr: any[]; secondArr: any[]; } +>secondArr : any[] + + break; + } +} + +reducer("add", { a: 1, b: 3 }); +>reducer("add", { a: 1, b: 3 }) : void +>reducer : (...args: ReducerArgs) => void +>"add" : "add" +>{ a: 1, b: 3 } : { a: number; b: number; } +>a : number +>1 : 1 +>b : number +>3 : 3 + +reducer("concat", { firstArr: [1, 2], secondArr: [3, 4] }); +>reducer("concat", { firstArr: [1, 2], secondArr: [3, 4] }) : void +>reducer : (...args: ReducerArgs) => void +>"concat" : "concat" +>{ firstArr: [1, 2], secondArr: [3, 4] } : { firstArr: number[]; secondArr: number[]; } +>firstArr : number[] +>[1, 2] : number[] +>1 : 1 +>2 : 2 +>secondArr : number[] +>[3, 4] : number[] +>3 : 3 +>4 : 4 + diff --git a/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts b/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts index c7a3f911acbfc..38bcb68d048e8 100644 --- a/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts +++ b/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts @@ -170,3 +170,61 @@ const { value, done } = it.next(); if (!done) { value; // number } + +// Repro from #46658 + +declare function f50(cb: (...args: Args) => void): void + +f50((kind, data) => { + if (kind === 'A') { + data.toFixed(); + } + if (kind === 'B') { + data.toUpperCase(); + } +}); + +const f51: (...args: ['A', number] | ['B', string]) => void = (kind, payload) => { + if (kind === 'A') { + payload.toFixed(); + } + if (kind === 'B') { + payload.toUpperCase(); + } +}; + +const f52: (...args: ['A', number] | ['B']) => void = (kind, payload?) => { + if (kind === 'A') { + payload.toFixed(); + } + else { + payload; // undefined + } +}; + +declare function readFile(path: string, callback: (...args: [err: null, data: unknown[]] | [err: Error, data: undefined]) => void): void; + +readFile('hello', (err, data) => { + if (err === null) { + data.length; + } + else { + err.message; + } +}); + +type ReducerArgs = ["add", { a: number, b: number }] | ["concat", { firstArr: any[], secondArr: any[] }]; + +const reducer: (...args: ReducerArgs) => void = (op, args) => { + switch (op) { + case "add": + console.log(args.a + args.b); + break; + case "concat": + console.log(args.firstArr.concat(args.secondArr)); + break; + } +} + +reducer("add", { a: 1, b: 3 }); +reducer("concat", { firstArr: [1, 2], secondArr: [3, 4] });