Skip to content

Improve signature assignability for argument lists of different lengths #49218

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 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1819397
Fixed an issue with shorter param list not being assignable to rest p…
Andarist May 23, 2022
67892c3
Merge branch 'main' into fix/rest-tuple-union-shorter-contextual-params
Andarist Sep 25, 2022
bd712ab
Merge remote-tracking branch 'origin/main' into fix/rest-tuple-union-…
Andarist Dec 28, 2022
88027d5
Fixed the contextual params assignability with target params declared…
Andarist Dec 28, 2022
6c5383d
Fill the shorter target tuple with undefined when comparing signatures
Andarist Dec 28, 2022
46f2e57
Add an additional test case for mixed length tuples in the target's rest
Andarist Dec 28, 2022
0de8923
Add tests for mixed-length tuples used as rest
Andarist Dec 28, 2022
beb241d
add tests from #45972
Andarist Dec 28, 2022
388ec89
Allow rest in source
Andarist Dec 29, 2022
8753248
Merge remote-tracking branch 'origin/main' into fix/rest-tuple-union-…
Andarist Jan 15, 2023
e357450
Fixed cases involving generics and add comments
Andarist Jan 16, 2023
29979e2
Merge remote-tracking branch 'origin/main' into fix/rest-tuple-union-…
Andarist Jun 13, 2023
fd52884
Merge remote-tracking branch 'origin/main' into fix/rest-tuple-union-…
Andarist Jul 10, 2023
336288a
fix extra cases
Andarist Jul 10, 2023
92d0267
Fixed tupel structure matching in the signature-related codepath
Andarist Jul 10, 2023
1c24035
use conditional undefined instead of any
Andarist Jul 10, 2023
608f7c3
Merge remote-tracking branch 'origin/main' into fix/rest-tuple-union-…
Andarist Jan 6, 2024
61d2612
Merge remote-tracking branch 'origin/main' into fix/rest-tuple-union-…
Andarist Feb 19, 2024
919fc0e
add extra test case
Andarist Feb 19, 2024
b920e9a
Merge branch 'main' into pr-49218
jakebailey Apr 19, 2024
a8ddfc3
Update baselines
jakebailey Apr 19, 2024
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
71 changes: 67 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,12 @@ export const enum SignatureCheckMode {
Callback = BivariantCallback | StrictCallback,
}

const enum TupleStructureComparisonKind {
None = 0,
MatchFixed = 1 << 0,
MatchVariable = 1 << 1,
}

const enum IntersectionState {
None = 0,
Source = 1 << 0, // Source type is a constituent of an outer intersection
Expand Down Expand Up @@ -20732,7 +20738,59 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

for (let i = 0; i < paramCount; i++) {
const sourceType = i === restIndex ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i);
const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i);
let targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i);
if (i === restIndex && targetType && sourceType && isTupleType(sourceType)) {
targetType = mapType(targetType, t => {
if (
!isTupleType(t) ||
// When both sides are tuples of the same structure, we don't want to "propagate" types from elements of variable positions
// to the following positions as that would disallow signatures of the exact same structures when trailing fixed elements are involved:
//
// let fn: (...rest: [...string[], number]) => void = (...rest: [...string[], number]) => {}; // ok
//
// Since we want to allow contextual types to flow into paremeters, we don't need to differentiate between rest and variadic elements
// as that doesn't affect the contextual type of the parameter
isTupleTypeStructureMatching(sourceType, t, TupleStructureComparisonKind.MatchVariable)
) {
return t;
}

// We create a tuple type based on the target elements and the source's length here.
// When the source signature accepts less parameters than the target signature
// we only need to check the *used* elements of the target tuple, the rest is ignored by the source anyway
// and thus it can be safely ignored here.
//
// let fn: (a: number, b: string) => void = (a: number) => {}; // ok
//
// In addition to that we also want to "propagate" element types of variable positions
// to all following positions, as that represents possible argument types.
//
// function fn(...[a, b]: [...number[], string]) {
// a; // number | string
// b; // number | string
// }
// fn('str'); // valid
const elementTypes: Type[] = [];
const elementFlags: ElementFlags[] = [];
Comment on lines +20773 to +20774
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically we use append rather than pushing onto an empty list directly; this is typically more efficient due to how the runtime constructs arrays.


const sourceArity = getTypeReferenceArity(sourceType);
const targetArity = getTypeReferenceArity(t);

for (let i = 0; i < sourceArity; i++) {
if (i >= targetArity) {
if (sourceType.target.elementFlags[i] & ElementFlags.Fixed) {
elementTypes.push(undefinedType);
elementFlags.push(sourceType.target.elementFlags[i]);
}
continue;
}
elementTypes.push(getTupleElementType(t, i)!);
elementFlags.push(sourceType.target.elementFlags[i]);
}

return createTupleType(elementTypes, elementFlags);
});
}
if (sourceType && targetType) {
// In order to ensure that any generic type Foo<T> is at least co-variant with respect to T no matter
// how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions,
Expand Down Expand Up @@ -24440,9 +24498,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return undefined;
}

function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) {
function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference, tupleStructureComparisonKind: TupleStructureComparisonKind) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if we could just accept ElementFlags as a mask here instead of coming up with a new enum.

return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) &&
every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable));
every(t1.target.elementFlags, (f1, i) => {
const f2 = t2.target.elementFlags[i];
return f1 === f2 ||
!!(tupleStructureComparisonKind & TupleStructureComparisonKind.MatchFixed && f1 & ElementFlags.Fixed && f2 & ElementFlags.Fixed) ||
!!(tupleStructureComparisonKind & TupleStructureComparisonKind.MatchVariable && f1 & ElementFlags.Variable && f2 & ElementFlags.Variable);
Comment on lines +24506 to +24507
Copy link
Member

@jakebailey jakebailey Apr 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't these something like (f1 & ElementFlags.Fixed) === (f2 & ElementFlags.Fixed), as the original code did?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I guess that breaks things.

});
}

function isZeroBigInt({ value }: BigIntLiteralType) {
Expand Down Expand Up @@ -26050,7 +26113,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const elementFlags = target.target.elementFlags;
// When source and target are tuple types with the same structure (fixed, variadic, and rest are matched
// to the same kind in each position), simply infer between the element types.
if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) {
if (isTupleType(source) && isTupleTypeStructureMatching(source, target, TupleStructureComparisonKind.MatchFixed)) {
for (let i = 0; i < targetArity; i++) {
inferFromTypes(getTypeArguments(source)[i], elementTypes[i]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ dependentDestructuredVariables.ts(431,15): error TS2322: Type 'number' is not as
warn: [message: string];
shardDisconnect: [closeEvent: CloseEvent, shardId: number];
}

declare class Client {
public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): void;
}
Expand Down Expand Up @@ -474,4 +474,12 @@ dependentDestructuredVariables.ts(431,15): error TS2322: Type 'number' is not as
x; // 1 | 3
}
}

// repros from #47190#issuecomment-1339753554
const f70: (...args: [type: "one"] | [type: "two", x: string]) => void = (type, x) => {
if (type !== "one") x.toUpperCase();
}
const f71: (...args: [type: "one", x?: number] | [type: "two", x: string]) => void = (type, x) => {
if (type !== "one") x.toUpperCase();
}

21 changes: 20 additions & 1 deletion tests/baselines/reference/dependentDestructuredVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ interface ClientEvents {
warn: [message: string];
shardDisconnect: [closeEvent: CloseEvent, shardId: number];
}

declare class Client {
public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): void;
}
Expand Down Expand Up @@ -465,6 +465,14 @@ const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (
x; // 1 | 3
}
}

// repros from #47190#issuecomment-1339753554
const f70: (...args: [type: "one"] | [type: "two", x: string]) => void = (type, x) => {
if (type !== "one") x.toUpperCase();
}
const f71: (...args: [type: "one", x?: number] | [type: "two", x: string]) => void = (type, x) => {
if (type !== "one") x.toUpperCase();
}


//// [dependentDestructuredVariables.js]
Expand Down Expand Up @@ -823,6 +831,15 @@ const parameterReassignedContextualRest1 = (x, y) => {
x; // 1 | 3
}
};
// repros from #47190#issuecomment-1339753554
const f70 = (type, x) => {
if (type !== "one")
x.toUpperCase();
};
const f71 = (type, x) => {
if (type !== "one")
x.toUpperCase();
};


//// [dependentDestructuredVariables.d.ts]
Expand Down Expand Up @@ -976,3 +993,5 @@ declare function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]): void;
declare function parameterReassigned1([x, y]: [1, 2] | [3, 4]): void;
declare function parameterReassigned2([x, y]: [1, 2] | [3, 4]): void;
declare const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void;
declare const f70: (...args: [type: "one"] | [type: "two", x: string]) => void;
declare const f71: (...args: [type: "one", x?: number] | [type: "two", x: string]) => void;
28 changes: 27 additions & 1 deletion tests/baselines/reference/dependentDestructuredVariables.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ interface ClientEvents {
>shardDisconnect : Symbol(ClientEvents.shardDisconnect, Decl(dependentDestructuredVariables.ts, 391, 28))
>CloseEvent : Symbol(CloseEvent, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
}

declare class Client {
>Client : Symbol(Client, Decl(dependentDestructuredVariables.ts, 393, 1))

Expand Down Expand Up @@ -1168,3 +1168,29 @@ const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (
}
}

// repros from #47190#issuecomment-1339753554
const f70: (...args: [type: "one"] | [type: "two", x: string]) => void = (type, x) => {
>f70 : Symbol(f70, Decl(dependentDestructuredVariables.ts, 466, 5))
>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 466, 12))
>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 466, 74))
>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 466, 79))

if (type !== "one") x.toUpperCase();
>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 466, 74))
>x.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 466, 79))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
const f71: (...args: [type: "one", x?: number] | [type: "two", x: string]) => void = (type, x) => {
>f71 : Symbol(f71, Decl(dependentDestructuredVariables.ts, 469, 5))
>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 469, 12))
>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 469, 86))
>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 469, 91))

if (type !== "one") x.toUpperCase();
>type : Symbol(type, Decl(dependentDestructuredVariables.ts, 469, 86))
>x.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 469, 91))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}

62 changes: 60 additions & 2 deletions tests/baselines/reference/dependentDestructuredVariables.types
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

=== Performance Stats ===
Type Count: 2,500
Instantiation count: 1,000
Instantiation count: 1,000 -> 2,500

=== dependentDestructuredVariables.ts ===
type Action =
Expand Down Expand Up @@ -1713,7 +1713,7 @@ interface ClientEvents {
>shardDisconnect : [closeEvent: CloseEvent, shardId: number]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

declare class Client {
>Client : Client
> : ^^^^^^
Expand Down Expand Up @@ -2067,3 +2067,61 @@ const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (
}
}

// repros from #47190#issuecomment-1339753554
const f70: (...args: [type: "one"] | [type: "two", x: string]) => void = (type, x) => {
>f70 : (...args: [type: "one"] | [type: "two", x: string]) => void
> : ^^^^ ^^ ^^^^^
>args : [type: "one"] | [type: "two", x: string]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>(type, x) => { if (type !== "one") x.toUpperCase();} : (type: "one" | "two", x: string | undefined) => void
> : ^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>type : "one" | "two"
> : ^^^^^^^^^^^^^
>x : string | undefined
> : ^^^^^^^^^^^^^^^^^^

if (type !== "one") x.toUpperCase();
>type !== "one" : boolean
> : ^^^^^^^
>type : "one" | "two"
> : ^^^^^^^^^^^^^
>"one" : "one"
> : ^^^^^
>x.toUpperCase() : string
> : ^^^^^^
>x.toUpperCase : () => string
> : ^^^^^^^^^^^^
>x : string
> : ^^^^^^
>toUpperCase : () => string
> : ^^^^^^^^^^^^
}
const f71: (...args: [type: "one", x?: number] | [type: "two", x: string]) => void = (type, x) => {
>f71 : (...args: [type: "one", x?: number] | [type: "two", x: string]) => void
> : ^^^^ ^^ ^^^^^
>args : [type: "one", x?: number | undefined] | [type: "two", x: string]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>(type, x) => { if (type !== "one") x.toUpperCase();} : (type: "one" | "two", x: string | number | undefined) => void
> : ^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>type : "one" | "two"
> : ^^^^^^^^^^^^^
>x : string | number | undefined
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^

if (type !== "one") x.toUpperCase();
>type !== "one" : boolean
> : ^^^^^^^
>type : "one" | "two"
> : ^^^^^^^^^^^^^
>"one" : "one"
> : ^^^^^
>x.toUpperCase() : string
> : ^^^^^^
>x.toUpperCase : () => string
> : ^^^^^^^^^^^^
>x : string
> : ^^^^^^
>toUpperCase : () => string
> : ^^^^^^^^^^^^
}

26 changes: 14 additions & 12 deletions tests/baselines/reference/genericRestParameters3.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ genericRestParameters3.ts(18,1): error TS2345: Argument of type '[]' is not assi
Source has 0 element(s) but target requires 2.
genericRestParameters3.ts(23,1): error TS2322: Type '(x: string, y: string) => void' is not assignable to type '(x: string, ...args: [string] | [number, boolean]) => void'.
Types of parameters 'y' and 'args' are incompatible.
Type '[string] | [number, boolean]' is not assignable to type '[y: string]'.
Type '[number, boolean]' is not assignable to type '[y: string]'.
Source has 2 element(s) but target allows only 1.
Type '[string] | [number]' is not assignable to type '[y: string]'.
Type '[number]' is not assignable to type '[y: string]'.
Type 'number' is not assignable to type 'string'.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To the best of my understanding, this change is correct and welcome under the implemented changes.

genericRestParameters3.ts(24,1): error TS2322: Type '(x: string, y: number, z: boolean) => void' is not assignable to type '(x: string, ...args: [string] | [number, boolean]) => void'.
Types of parameters 'y' and 'args' are incompatible.
Type '[string] | [number, boolean]' is not assignable to type '[y: number, z: boolean]'.
Type '[string]' is not assignable to type '[y: number, z: boolean]'.
Source has 1 element(s) but target requires 2.
Type '[number, boolean] | [string, undefined]' is not assignable to type '[y: number, z: boolean]'.
Type '[string, undefined]' is not assignable to type '[y: number, z: boolean]'.
Type at position 0 in source is not compatible with type at position 0 in target.
Type 'string' is not assignable to type 'number'.
genericRestParameters3.ts(35,1): error TS2554: Expected 1 arguments, but got 0.
genericRestParameters3.ts(36,21): error TS2345: Argument of type 'number' is not assignable to parameter of type '(...args: CoolArray<any>) => void'.
genericRestParameters3.ts(37,21): error TS2345: Argument of type '<T extends any[]>(cb: (...args: T) => void) => void' is not assignable to parameter of type '(...args: CoolArray<any>) => void'.
Expand Down Expand Up @@ -69,16 +70,17 @@ genericRestParameters3.ts(59,5): error TS2345: Argument of type '["what"]' is no
~~
!!! error TS2322: Type '(x: string, y: string) => void' is not assignable to type '(x: string, ...args: [string] | [number, boolean]) => void'.
!!! error TS2322: Types of parameters 'y' and 'args' are incompatible.
!!! error TS2322: Type '[string] | [number, boolean]' is not assignable to type '[y: string]'.
!!! error TS2322: Type '[number, boolean]' is not assignable to type '[y: string]'.
!!! error TS2322: Source has 2 element(s) but target allows only 1.
!!! error TS2322: Type '[string] | [number]' is not assignable to type '[y: string]'.
!!! error TS2322: Type '[number]' is not assignable to type '[y: string]'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
Comment on lines +73 to +75
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change of the error for this case:

declare let f1: (x: string, ...args: [string] | [number, boolean]) => void;
declare let f2: (x: string, y: string) => void;

f1 = f2

With the current changes in this PR, I create a slice from the target tuple and I cap it to the source's length~. I do that for similar reasons that I've outlined in the other comment. The source signature can freely ignore the "extra" arguments so we don't even need to check them here (we'd have to ignore them through some other mechanism anyway)

f1 = f3; // Error
~~
!!! error TS2322: Type '(x: string, y: number, z: boolean) => void' is not assignable to type '(x: string, ...args: [string] | [number, boolean]) => void'.
!!! error TS2322: Types of parameters 'y' and 'args' are incompatible.
!!! error TS2322: Type '[string] | [number, boolean]' is not assignable to type '[y: number, z: boolean]'.
!!! error TS2322: Type '[string]' is not assignable to type '[y: number, z: boolean]'.
!!! error TS2322: Source has 1 element(s) but target requires 2.
!!! error TS2322: Type '[number, boolean] | [string, undefined]' is not assignable to type '[y: number, z: boolean]'.
!!! error TS2322: Type '[string, undefined]' is not assignable to type '[y: number, z: boolean]'.
!!! error TS2322: Type at position 0 in source is not compatible with type at position 0 in target.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
Comment on lines +80 to +83
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error change comes from the fact that I fill the slice of the target type with undefineds here (when it's shorter than the source's slice):
https://github.dev/microsoft/TypeScript/blob/6c5383d701417e8a98398b4171af0f201ee593c5/src/compiler/checker.ts#L19394-L19402

I think this is a good change. This will allow more patterns to be assignable, especially with target signatures using unions of tuples for their rest parameter. After all, the implementation is free to ignore any of the provided arguments etc. This is also already just fine in TS:

declare let f1: (a: string) => void
declare let f2: (a: string, b: number) => void

f2 = f1

And since this is OK I think that this one should be too:

declare let f1: (x: string, ...args: [string] | [number, boolean]) => void;

// Type '(a: string, b: string | number, c: boolean | undefined) => void' is not assignable to type '(x: string, ...args: [string] | [number, boolean]) => void'.
//   Types of parameters 'b' and 'args' are incompatible.
//     Type '[string] | [number, boolean]' is not assignable to type '[b: string | number, c: boolean | undefined]'.
//       Type '[string]' is not assignable to type '[b: string | number, c: boolean | undefined]'.
//         Source has 1 element(s) but target requires 2.
f1 = (a, b, c) => {}

So with this change that caused this particular error here to be reported differently we now allow the example above and I added a test case for it:

const f2: (x: string, ...args: [string] | [number, boolean]) => void = (a, b, c) => {};

f1 = f4;

// Repro from #26110
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//// [tests/cases/compiler/restTupleUnionShorterContextualParams.ts] ////

=== restTupleUnionShorterContextualParams.ts ===
// repro #48663

// showcase how those transitive assignments are OK
const f1: (x: string | number) => void = x => {};
>f1 : Symbol(f1, Decl(restTupleUnionShorterContextualParams.ts, 3, 5))
>x : Symbol(x, Decl(restTupleUnionShorterContextualParams.ts, 3, 11))
>x : Symbol(x, Decl(restTupleUnionShorterContextualParams.ts, 3, 40))

const f2: (x: string | number, y: string | number) => void = f1;
>f2 : Symbol(f2, Decl(restTupleUnionShorterContextualParams.ts, 4, 5))
>x : Symbol(x, Decl(restTupleUnionShorterContextualParams.ts, 4, 11))
>y : Symbol(y, Decl(restTupleUnionShorterContextualParams.ts, 4, 30))
>f1 : Symbol(f1, Decl(restTupleUnionShorterContextualParams.ts, 3, 5))

const f3: (...args: [number, string] | [string, number]) => void = f2;
>f3 : Symbol(f3, Decl(restTupleUnionShorterContextualParams.ts, 5, 5))
>args : Symbol(args, Decl(restTupleUnionShorterContextualParams.ts, 5, 11))
>f2 : Symbol(f2, Decl(restTupleUnionShorterContextualParams.ts, 4, 5))

// by extension those should be OK too
const f4: (...args: [number, string] | [string, number]) => void = (item) => {}
>f4 : Symbol(f4, Decl(restTupleUnionShorterContextualParams.ts, 8, 5))
>args : Symbol(args, Decl(restTupleUnionShorterContextualParams.ts, 8, 11))
>item : Symbol(item, Decl(restTupleUnionShorterContextualParams.ts, 8, 68))

const f5: (...args: [number, string] | [string, number]) => void = (item: number | string) => {}
>f5 : Symbol(f5, Decl(restTupleUnionShorterContextualParams.ts, 9, 5))
>args : Symbol(args, Decl(restTupleUnionShorterContextualParams.ts, 9, 11))
>item : Symbol(item, Decl(restTupleUnionShorterContextualParams.ts, 9, 68))

Loading