Skip to content

Fixed an issue with spreading generic types with tuple constraints into calls #53615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32862,6 +32862,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const arg = args[i];
// We can call checkExpressionCached because spread expressions never have a contextual type.
const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression));
const constraint = spreadType && getConstraintOfType(spreadType);
if (spreadType && isTupleType(spreadType)) {
forEach(getElementTypes(spreadType), (t, i) => {
const flags = spreadType.target.elementFlags[i];
Expand All @@ -32870,6 +32871,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
effectiveArgs.push(syntheticArg);
});
}
else if (constraint && isTupleType(constraint)) {
forEach(getTypeArguments(constraint), (_, i) => {
const isVariable = !!(constraint.target.elementFlags[i] & ElementFlags.Variable);
const syntheticArg = createSyntheticExpression(arg, !isVariable ? getIndexedAccessType(spreadType, getStringLiteralType("" + i))! : spreadType,
/*isSpread*/ isVariable, constraint.target.labeledElementDeclarations?.[i]);
effectiveArgs.push(syntheticArg);
});
}
else {
effectiveArgs.push(arg);
}
Expand Down
32 changes: 32 additions & 0 deletions tests/baselines/reference/arraySpreadInCall.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,36 @@ arraySpreadInCall.ts(21,12): error TS2554: Expected 0-1 arguments, but got 2.
~~~~~~~~~~~~~~~
!!! error TS2554: Expected 0-1 arguments, but got 2.

// repro from #53541
class T {
fn(name?: string, sex?: number): void {}
}

class M<X extends T> {
constructor(public m: X) {}

fn(...args: Parameters<X["fn"]>) {
this.m.fn(...args);
}
}

// repro from #53541#issuecomment-1487859044
interface HasMethod {
method(first?: string, second?: number): void;
method2(...args: [name: string, sex?: number] | [other: number]): void;
}

function fn21<HasMethodLike extends HasMethod>(
instance: HasMethodLike,
...args: Parameters<HasMethodLike["method"]>
) {
instance.method(...args);
}

function fn22<HasMethodLike extends HasMethod>(
instance: HasMethodLike,
...args: Parameters<HasMethodLike["method2"]>
) {
instance.method2(...args);
}

92 changes: 92 additions & 0 deletions tests/baselines/reference/arraySpreadInCall.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,96 @@ action.run(...[100, 'foo']) // error
>action : Symbol(action, Decl(arraySpreadInCall.ts, 19, 13))
>run : Symbol(IAction.run, Decl(arraySpreadInCall.ts, 16, 19))

// repro from #53541
class T {
>T : Symbol(T, Decl(arraySpreadInCall.ts, 20, 27))

fn(name?: string, sex?: number): void {}
>fn : Symbol(T.fn, Decl(arraySpreadInCall.ts, 23, 9))
>name : Symbol(name, Decl(arraySpreadInCall.ts, 24, 5))
>sex : Symbol(sex, Decl(arraySpreadInCall.ts, 24, 19))
}

class M<X extends T> {
>M : Symbol(M, Decl(arraySpreadInCall.ts, 25, 1))
>X : Symbol(X, Decl(arraySpreadInCall.ts, 27, 8))
>T : Symbol(T, Decl(arraySpreadInCall.ts, 20, 27))

constructor(public m: X) {}
>m : Symbol(M.m, Decl(arraySpreadInCall.ts, 28, 14))
>X : Symbol(X, Decl(arraySpreadInCall.ts, 27, 8))

fn(...args: Parameters<X["fn"]>) {
>fn : Symbol(M.fn, Decl(arraySpreadInCall.ts, 28, 29))
>args : Symbol(args, Decl(arraySpreadInCall.ts, 30, 5))
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
>X : Symbol(X, Decl(arraySpreadInCall.ts, 27, 8))

this.m.fn(...args);
>this.m.fn : Symbol(T.fn, Decl(arraySpreadInCall.ts, 23, 9))
>this.m : Symbol(M.m, Decl(arraySpreadInCall.ts, 28, 14))
>this : Symbol(M, Decl(arraySpreadInCall.ts, 25, 1))
>m : Symbol(M.m, Decl(arraySpreadInCall.ts, 28, 14))
>fn : Symbol(T.fn, Decl(arraySpreadInCall.ts, 23, 9))
>args : Symbol(args, Decl(arraySpreadInCall.ts, 30, 5))
}
}

// repro from #53541#issuecomment-1487859044
interface HasMethod {
>HasMethod : Symbol(HasMethod, Decl(arraySpreadInCall.ts, 33, 1))

method(first?: string, second?: number): void;
>method : Symbol(HasMethod.method, Decl(arraySpreadInCall.ts, 36, 21))
>first : Symbol(first, Decl(arraySpreadInCall.ts, 37, 9))
>second : Symbol(second, Decl(arraySpreadInCall.ts, 37, 24))

method2(...args: [name: string, sex?: number] | [other: number]): void;
>method2 : Symbol(HasMethod.method2, Decl(arraySpreadInCall.ts, 37, 48))
>args : Symbol(args, Decl(arraySpreadInCall.ts, 38, 10))
}

function fn21<HasMethodLike extends HasMethod>(
>fn21 : Symbol(fn21, Decl(arraySpreadInCall.ts, 39, 1))
>HasMethodLike : Symbol(HasMethodLike, Decl(arraySpreadInCall.ts, 41, 14))
>HasMethod : Symbol(HasMethod, Decl(arraySpreadInCall.ts, 33, 1))

instance: HasMethodLike,
>instance : Symbol(instance, Decl(arraySpreadInCall.ts, 41, 47))
>HasMethodLike : Symbol(HasMethodLike, Decl(arraySpreadInCall.ts, 41, 14))

...args: Parameters<HasMethodLike["method"]>
>args : Symbol(args, Decl(arraySpreadInCall.ts, 42, 26))
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
>HasMethodLike : Symbol(HasMethodLike, Decl(arraySpreadInCall.ts, 41, 14))

) {
instance.method(...args);
>instance.method : Symbol(HasMethod.method, Decl(arraySpreadInCall.ts, 36, 21))
>instance : Symbol(instance, Decl(arraySpreadInCall.ts, 41, 47))
>method : Symbol(HasMethod.method, Decl(arraySpreadInCall.ts, 36, 21))
>args : Symbol(args, Decl(arraySpreadInCall.ts, 42, 26))
}

function fn22<HasMethodLike extends HasMethod>(
>fn22 : Symbol(fn22, Decl(arraySpreadInCall.ts, 46, 1))
>HasMethodLike : Symbol(HasMethodLike, Decl(arraySpreadInCall.ts, 48, 14))
>HasMethod : Symbol(HasMethod, Decl(arraySpreadInCall.ts, 33, 1))

instance: HasMethodLike,
>instance : Symbol(instance, Decl(arraySpreadInCall.ts, 48, 47))
>HasMethodLike : Symbol(HasMethodLike, Decl(arraySpreadInCall.ts, 48, 14))

...args: Parameters<HasMethodLike["method2"]>
>args : Symbol(args, Decl(arraySpreadInCall.ts, 49, 26))
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
>HasMethodLike : Symbol(HasMethodLike, Decl(arraySpreadInCall.ts, 48, 14))

) {
instance.method2(...args);
>instance.method2 : Symbol(HasMethod.method2, Decl(arraySpreadInCall.ts, 37, 48))
>instance : Symbol(instance, Decl(arraySpreadInCall.ts, 48, 47))
>method2 : Symbol(HasMethod.method2, Decl(arraySpreadInCall.ts, 37, 48))
>args : Symbol(args, Decl(arraySpreadInCall.ts, 49, 26))
}

81 changes: 81 additions & 0 deletions tests/baselines/reference/arraySpreadInCall.types
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,85 @@ action.run(...[100, 'foo']) // error
>100 : 100
>'foo' : "foo"

// repro from #53541
class T {
>T : T

fn(name?: string, sex?: number): void {}
>fn : (name?: string, sex?: number) => void
>name : string | undefined
>sex : number | undefined
}

class M<X extends T> {
>M : M<X>

constructor(public m: X) {}
>m : X

fn(...args: Parameters<X["fn"]>) {
>fn : (...args: Parameters<X["fn"]>) => void
>args : Parameters<X["fn"]>

this.m.fn(...args);
>this.m.fn(...args) : void
>this.m.fn : (name?: string | undefined, sex?: number | undefined) => void
>this.m : X
>this : this
>m : X
>fn : (name?: string | undefined, sex?: number | undefined) => void
>...args : string | number | undefined
>args : Parameters<X["fn"]>
}
}

// repro from #53541#issuecomment-1487859044
interface HasMethod {
method(first?: string, second?: number): void;
>method : (first?: string, second?: number) => void
>first : string | undefined
>second : number | undefined

method2(...args: [name: string, sex?: number] | [other: number]): void;
>method2 : (...args: [name: string, sex?: number] | [other: number]) => void
>args : [name: string, sex?: number | undefined] | [other: number]
}

function fn21<HasMethodLike extends HasMethod>(
>fn21 : <HasMethodLike extends HasMethod>(instance: HasMethodLike, ...args: Parameters<HasMethodLike["method"]>) => void

instance: HasMethodLike,
>instance : HasMethodLike

...args: Parameters<HasMethodLike["method"]>
>args : Parameters<HasMethodLike["method"]>

) {
instance.method(...args);
>instance.method(...args) : void
>instance.method : (first?: string | undefined, second?: number | undefined) => void
>instance : HasMethodLike
>method : (first?: string | undefined, second?: number | undefined) => void
>...args : string | number | undefined
>args : Parameters<HasMethodLike["method"]>
}

function fn22<HasMethodLike extends HasMethod>(
>fn22 : <HasMethodLike extends HasMethod>(instance: HasMethodLike, ...args: Parameters<HasMethodLike["method2"]>) => void

instance: HasMethodLike,
>instance : HasMethodLike

...args: Parameters<HasMethodLike["method2"]>
>args : Parameters<HasMethodLike["method2"]>

) {
instance.method2(...args);
>instance.method2(...args) : void
>instance.method2 : (...args: [name: string, sex?: number | undefined] | [other: number]) => void
>instance : HasMethodLike
>method2 : (...args: [name: string, sex?: number | undefined] | [other: number]) => void
>...args : string | number | undefined
>args : Parameters<HasMethodLike["method2"]>
}

26 changes: 13 additions & 13 deletions tests/baselines/reference/genericRestParameters1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ genericRestParameters1.ts(164,1): error TS2322: Type '(a: never) => void' is not
f1(42, "hello", ...t1);
f1(42, "hello", true, ...t0);
f1(ns[0], ns[1], true);
f1(...ns, true); // FIXME: Error, since ...ns is considered as string|number here
f1(...ns, true);

f2(42, "hello", true);
f2(t3[0], t3[1], t3[2]);
Expand All @@ -36,7 +36,7 @@ genericRestParameters1.ts(164,1): error TS2322: Type '(a: never) => void' is not
f2(42, "hello", ...t1);
f2(42, "hello", true, ...t0);
f2(ns[0], ns[1], true);
f2(...ns, true); // FIXME: Error, since ...ns is considered as string|number here
f2(...ns, true);

declare function f10<T extends unknown[]>(...args: T): T;

Expand All @@ -48,13 +48,13 @@ genericRestParameters1.ts(164,1): error TS2322: Type '(a: never) => void' is not
const x15 = f10(42, ...t2); // [number, string, boolean]
const x16 = f10(42, "hello", ...t1); // [number, string, boolean]
const x17 = f10(42, "hello", true, ...t0); // [number, string, boolean]
const x18 = f10(...ns, true); // (string | number | boolean)[]
const x18 = f10(...ns, true); // [number, string, boolean]

function g10<U extends string[], V extends [number, number]>(u: U, v: V) {
let x1 = f10(...u); // U
let x2 = f10(...v); // V
let x3 = f10(1, ...u); // [number, ...string[]]
let x4 = f10(...u, ...v); // (string | number)[]
let x2 = f10(...v); // [V["0"], V["1"]]
let x3 = f10(1, ...u); // [number, ...U]
let x4 = f10(...u, ...v); // [...U, V["0"], V["1"]]
}

declare function f11<T extends (string | number | boolean)[]>(...args: T): T;
Expand All @@ -67,13 +67,13 @@ genericRestParameters1.ts(164,1): error TS2322: Type '(a: never) => void' is not
const z15 = f11(42, ...t2); // [42, string, boolean]
const z16 = f11(42, "hello", ...t1); // [42, "hello", boolean]
const z17 = f11(42, "hello", true, ...t0); // [42, "hello", true]
const z18 = f11(...ns, true); // (string | number | true)[]
const z18 = f11(...ns, true); // [number, string, true]

function g11<U extends string[], V extends [number, number]>(u: U, v: V) {
let x1 = f11(...u); // U
let x2 = f11(...v); // V
let x3 = f11(1, ...u); // [1, ...string[]]
let x4 = f11(...u, ...v); // (string | number)[]
let x2 = f11(...v); // [V["0"], V["1"]]
let x3 = f11(1, ...u); // [1, ...U]
let x4 = f11(...u, ...v); // [...U, V["0"], V["1"]]
}

function call<T extends unknown[], U>(f: (...args: T) => U, ...args: T) {
Expand All @@ -90,7 +90,7 @@ genericRestParameters1.ts(164,1): error TS2322: Type '(a: never) => void' is not
let x20 = call((x, y) => x + y, 10, 20); // number
let x21 = call((x, y) => x + y, 10, "hello"); // string
let x22 = call(f15, "hello", 42); // string | number
let x23 = call(f16, "hello", 42); // unknown
let x23 = call(f16, "hello", 42); // string | number
let x24 = call<[string, number], string | number>(f16, "hello", 42); // string | number

let x30 = callr(sn, (x, y) => x + y); // string
Expand All @@ -114,8 +114,8 @@ genericRestParameters1.ts(164,1): error TS2322: Type '(a: never) => void' is not

declare const g20: (x: number, y?: string, z?: boolean) => string[];

const g21 = bind(g20, 42); // (y: string, z: boolean) => string[]
const g22 = bind(g21, "hello"); // (z: boolean) => string[]
const g21 = bind(g20, 42); // (y?: string, z?: boolean) => string[]
const g22 = bind(g21, "hello"); // (z?: boolean) => string[]
const g23 = bind(g22, true); // () => string[]

g20(42, "hello", true);
Expand Down
Loading