Control flow analysis for dependent parameters (#47190)

* Support control flow analysis for dependent parameters

* Add tests
This commit is contained in:
Anders Hejlsberg 2022-01-04 09:22:11 -10:00 committed by GitHub
parent 2f058b72d6
commit f4e1efbc29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 620 additions and 43 deletions

View File

@ -22694,11 +22694,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;
}
@ -24120,13 +24121,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;
}
}
@ -24173,7 +24175,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;
@ -24191,7 +24193,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)) {
@ -24206,7 +24208,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));
@ -24984,42 +24986,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));
}
}
}
}

View File

@ -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<number>;
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;

View File

@ -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))

View File

@ -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>[]): any[]; (...items: any[]): any[]; }
>args.firstArr : any[]
>args : { firstArr: any[]; secondArr: any[]; }
>firstArr : any[]
>concat : { (...items: ConcatArray<any>[]): 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

View File

@ -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] });