diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dfd479127202c..8295a3d1ba707 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12576,8 +12576,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); } else if ( - isParameter(declaration) - || isPropertyDeclaration(declaration) + isPropertyDeclaration(declaration) || isPropertySignature(declaration) || isVariableDeclaration(declaration) || isBindingElement(declaration) @@ -12585,6 +12584,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ) { type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); } + else if (isParameter(declaration)) { + type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); + if (declaration.dotDotDotToken) { + type = normalizeNoInferSpread(type); + } + } // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. // Re-dispatch based on valueDeclaration.kind instead. else if (isEnumDeclaration(declaration)) { @@ -12808,7 +12813,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { const links = getSymbolLinks(symbol); - return links.type || (links.type = instantiateType(getTypeOfSymbol(links.target!), links.mapper)); + if (!links.type) { + const declaration = links.target!.valueDeclaration; + let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper); + if (declaration && isParameter(declaration) && declaration.dotDotDotToken) { + type = normalizeNoInferSpread(type); + } + links.type = type; + } + return links.type; } function getWriteTypeOfInstantiatedSymbol(symbol: Symbol): Type { @@ -17866,6 +17879,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createTypeReference(target, elementTypes); } if (target.combinedFlags & ElementFlags.Variadic) { + elementTypes = sameMap(elementTypes, (t, i) => { + return target.elementFlags[i] & ElementFlags.Variadic ? normalizeNoInferSpread(t) : t; + }); // Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z] const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union))); if (unionIndex >= 0) { @@ -17952,6 +17968,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function normalizeNoInferSpread(type: Type): Type { + if (!isNoInferType(type)) { + return type; + } + return mapType((type as SubstitutionType).baseType, t => { + if (!isTupleType(t)) { + return getNoInferType(type); + } + return createTupleType( + map(getElementTypes(t), getNoInferType), + t.target.elementFlags, + t.target.readonly, + t.target.labeledElementDeclarations, + ); + }); + } + function sliceTupleType(type: TupleTypeReference, index: number, endSkipCount = 0) { const target = type.target; const endIndex = getTypeReferenceArity(type) - endSkipCount; diff --git a/tests/baselines/reference/noInferRestSpread1.errors.txt b/tests/baselines/reference/noInferRestSpread1.errors.txt new file mode 100644 index 0000000000000..21f67ac0e0a60 --- /dev/null +++ b/tests/baselines/reference/noInferRestSpread1.errors.txt @@ -0,0 +1,41 @@ +noInferRestSpread1.ts(7,22): error TS2345: Argument of type '(a: number, b: number) => void' is not assignable to parameter of type '(args_0: number) => void'. + Target signature provides too few arguments. Expected 2 or more, but got 1. + + +==== noInferRestSpread1.ts (1 errors) ==== + declare function call( + arg: (...args: NoInfer) => void, + ...args: A + ): A; + + const result1 = call((a: number) => {}, 1, 2); + const result2 = call((a: number, b: number) => {}, 1); // error + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2345: Argument of type '(a: number, b: number) => void' is not assignable to parameter of type '(args_0: number) => void'. +!!! error TS2345: Target signature provides too few arguments. Expected 2 or more, but got 1. + const result3 = call((a) => {}, 1, ''); // test contextual parameters + const result4 = call((a, b) => {}, 1, ''); // test contextual parameters + const result5 = call((...args) => {}, 1, ''); // test contextual parameters + const result6 = call((a, ...rest) => {}, 1, ''); // test contextual parameters + + declare function fn1( + cb: (a: [number, ...NoInfer]) => void, + args: A, + ): A; + + declare const singleStr: [string]; + + const result7 = fn1((arg) => { + arg.length; + }, singleStr); + + declare const tupleUnion: [string] | [number, boolean]; + + const result8 = fn1((arg) => { + arg.length; + }, tupleUnion); + + declare function fn2(arg: (...args: NoInfer<[string, number]>) => void): void; + + fn2((a, ...rest) => {}); + \ No newline at end of file diff --git a/tests/baselines/reference/noInferRestSpread1.symbols b/tests/baselines/reference/noInferRestSpread1.symbols new file mode 100644 index 0000000000000..18dc968f1abc5 --- /dev/null +++ b/tests/baselines/reference/noInferRestSpread1.symbols @@ -0,0 +1,113 @@ +//// [tests/cases/conformance/types/typeRelationships/typeInference/noInferRestSpread1.ts] //// + +=== noInferRestSpread1.ts === +declare function call( +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>A : Symbol(A, Decl(noInferRestSpread1.ts, 0, 22)) + + arg: (...args: NoInfer) => void, +>arg : Symbol(arg, Decl(noInferRestSpread1.ts, 0, 52)) +>args : Symbol(args, Decl(noInferRestSpread1.ts, 1, 8)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>A : Symbol(A, Decl(noInferRestSpread1.ts, 0, 22)) + + ...args: A +>args : Symbol(args, Decl(noInferRestSpread1.ts, 1, 37)) +>A : Symbol(A, Decl(noInferRestSpread1.ts, 0, 22)) + +): A; +>A : Symbol(A, Decl(noInferRestSpread1.ts, 0, 22)) + +const result1 = call((a: number) => {}, 1, 2); +>result1 : Symbol(result1, Decl(noInferRestSpread1.ts, 5, 5)) +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 5, 22)) + +const result2 = call((a: number, b: number) => {}, 1); // error +>result2 : Symbol(result2, Decl(noInferRestSpread1.ts, 6, 5)) +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 6, 22)) +>b : Symbol(b, Decl(noInferRestSpread1.ts, 6, 32)) + +const result3 = call((a) => {}, 1, ''); // test contextual parameters +>result3 : Symbol(result3, Decl(noInferRestSpread1.ts, 7, 5)) +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 7, 22)) + +const result4 = call((a, b) => {}, 1, ''); // test contextual parameters +>result4 : Symbol(result4, Decl(noInferRestSpread1.ts, 8, 5)) +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 8, 22)) +>b : Symbol(b, Decl(noInferRestSpread1.ts, 8, 24)) + +const result5 = call((...args) => {}, 1, ''); // test contextual parameters +>result5 : Symbol(result5, Decl(noInferRestSpread1.ts, 9, 5)) +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>args : Symbol(args, Decl(noInferRestSpread1.ts, 9, 22)) + +const result6 = call((a, ...rest) => {}, 1, ''); // test contextual parameters +>result6 : Symbol(result6, Decl(noInferRestSpread1.ts, 10, 5)) +>call : Symbol(call, Decl(noInferRestSpread1.ts, 0, 0)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 10, 22)) +>rest : Symbol(rest, Decl(noInferRestSpread1.ts, 10, 24)) + +declare function fn1( +>fn1 : Symbol(fn1, Decl(noInferRestSpread1.ts, 10, 48)) +>A : Symbol(A, Decl(noInferRestSpread1.ts, 12, 21)) + + cb: (a: [number, ...NoInfer]) => void, +>cb : Symbol(cb, Decl(noInferRestSpread1.ts, 12, 42)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 13, 7)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>A : Symbol(A, Decl(noInferRestSpread1.ts, 12, 21)) + + args: A, +>args : Symbol(args, Decl(noInferRestSpread1.ts, 13, 43)) +>A : Symbol(A, Decl(noInferRestSpread1.ts, 12, 21)) + +): A; +>A : Symbol(A, Decl(noInferRestSpread1.ts, 12, 21)) + +declare const singleStr: [string]; +>singleStr : Symbol(singleStr, Decl(noInferRestSpread1.ts, 17, 13)) + +const result7 = fn1((arg) => { +>result7 : Symbol(result7, Decl(noInferRestSpread1.ts, 19, 5)) +>fn1 : Symbol(fn1, Decl(noInferRestSpread1.ts, 10, 48)) +>arg : Symbol(arg, Decl(noInferRestSpread1.ts, 19, 21)) + + arg.length; +>arg.length : Symbol(length) +>arg : Symbol(arg, Decl(noInferRestSpread1.ts, 19, 21)) +>length : Symbol(length) + +}, singleStr); +>singleStr : Symbol(singleStr, Decl(noInferRestSpread1.ts, 17, 13)) + +declare const tupleUnion: [string] | [number, boolean]; +>tupleUnion : Symbol(tupleUnion, Decl(noInferRestSpread1.ts, 23, 13)) + +const result8 = fn1((arg) => { +>result8 : Symbol(result8, Decl(noInferRestSpread1.ts, 25, 5)) +>fn1 : Symbol(fn1, Decl(noInferRestSpread1.ts, 10, 48)) +>arg : Symbol(arg, Decl(noInferRestSpread1.ts, 25, 21)) + + arg.length; +>arg.length : Symbol(length) +>arg : Symbol(arg, Decl(noInferRestSpread1.ts, 25, 21)) +>length : Symbol(length) + +}, tupleUnion); +>tupleUnion : Symbol(tupleUnion, Decl(noInferRestSpread1.ts, 23, 13)) + +declare function fn2(arg: (...args: NoInfer<[string, number]>) => void): void; +>fn2 : Symbol(fn2, Decl(noInferRestSpread1.ts, 27, 15)) +>arg : Symbol(arg, Decl(noInferRestSpread1.ts, 29, 21)) +>args : Symbol(args, Decl(noInferRestSpread1.ts, 29, 27)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) + +fn2((a, ...rest) => {}); +>fn2 : Symbol(fn2, Decl(noInferRestSpread1.ts, 27, 15)) +>a : Symbol(a, Decl(noInferRestSpread1.ts, 31, 5)) +>rest : Symbol(rest, Decl(noInferRestSpread1.ts, 31, 7)) + diff --git a/tests/baselines/reference/noInferRestSpread1.types b/tests/baselines/reference/noInferRestSpread1.types new file mode 100644 index 0000000000000..6ff3b0dbaf7ef --- /dev/null +++ b/tests/baselines/reference/noInferRestSpread1.types @@ -0,0 +1,211 @@ +//// [tests/cases/conformance/types/typeRelationships/typeInference/noInferRestSpread1.ts] //// + +=== noInferRestSpread1.ts === +declare function call( +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ + + arg: (...args: NoInfer) => void, +>arg : (...args: NoInfer) => void +> : ^^^^ ^^ ^^^^^ +>args : NoInfer +> : ^^^^^^^^^^ + + ...args: A +>args : A +> : ^ + +): A; + +const result1 = call((a: number) => {}, 1, 2); +>result1 : [number, number] +> : ^^^^^^^^^^^^^^^^ +>call((a: number) => {}, 1, 2) : [number, number] +> : ^^^^^^^^^^^^^^^^ +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>(a: number) => {} : (a: number) => void +> : ^ ^^ ^^^^^^^^^ +>a : number +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ + +const result2 = call((a: number, b: number) => {}, 1); // error +>result2 : [number] +> : ^^^^^^^^ +>call((a: number, b: number) => {}, 1) : [number] +> : ^^^^^^^^ +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>(a: number, b: number) => {} : (a: number, b: number) => void +> : ^ ^^ ^^ ^^ ^^^^^^^^^ +>a : number +> : ^^^^^^ +>b : number +> : ^^^^^^ +>1 : 1 +> : ^ + +const result3 = call((a) => {}, 1, ''); // test contextual parameters +>result3 : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call((a) => {}, 1, '') : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>(a) => {} : (a: number) => void +> : ^ ^^^^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>1 : 1 +> : ^ +>'' : "" +> : ^^ + +const result4 = call((a, b) => {}, 1, ''); // test contextual parameters +>result4 : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call((a, b) => {}, 1, '') : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>(a, b) => {} : (a: number, b: string) => void +> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>b : string +> : ^^^^^^ +>1 : 1 +> : ^ +>'' : "" +> : ^^ + +const result5 = call((...args) => {}, 1, ''); // test contextual parameters +>result5 : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call((...args) => {}, 1, '') : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>(...args) => {} : (args_0: number, args_1: string) => void +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>args : [number, string] +> : ^^^^^^^^^^^^^^^^ +>1 : 1 +> : ^ +>'' : "" +> : ^^ + +const result6 = call((a, ...rest) => {}, 1, ''); // test contextual parameters +>result6 : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call((a, ...rest) => {}, 1, '') : [number, string] +> : ^^^^^^^^^^^^^^^^ +>call : (arg: (...args: NoInfer) => void, ...args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>(a, ...rest) => {} : (a: number, rest_0: string) => void +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>rest : [string] +> : ^^^^^^^^ +>1 : 1 +> : ^ +>'' : "" +> : ^^ + +declare function fn1( +>fn1 : (cb: (a: [number, ...NoInfer]) => void, args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ + + cb: (a: [number, ...NoInfer]) => void, +>cb : (a: [number, ...NoInfer]) => void +> : ^ ^^ ^^^^^ +>a : [number, ...NoInfer] +> : ^^^^^^^^^^^^^^^^^^^^^^^ + + args: A, +>args : A +> : ^ + +): A; + +declare const singleStr: [string]; +>singleStr : [string] +> : ^^^^^^^^ + +const result7 = fn1((arg) => { +>result7 : [string] +> : ^^^^^^^^ +>fn1((arg) => { arg.length;}, singleStr) : [string] +> : ^^^^^^^^ +>fn1 : (cb: (a: [number, ...NoInfer]) => void, args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>(arg) => { arg.length;} : (arg: [number, string]) => void +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>arg : [number, string] +> : ^^^^^^^^^^^^^^^^ + + arg.length; +>arg.length : 2 +> : ^ +>arg : [number, string] +> : ^^^^^^^^^^^^^^^^ +>length : 2 +> : ^ + +}, singleStr); +>singleStr : [string] +> : ^^^^^^^^ + +declare const tupleUnion: [string] | [number, boolean]; +>tupleUnion : [string] | [number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +const result8 = fn1((arg) => { +>result8 : [string] | [number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fn1((arg) => { arg.length;}, tupleUnion) : [string] | [number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fn1 : (cb: (a: [number, ...NoInfer]) => void, args: A) => A +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>(arg) => { arg.length;} : (arg: [number, string] | [number, number, boolean]) => void +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>arg : [number, string] | [number, number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + arg.length; +>arg.length : 2 | 3 +> : ^^^^^ +>arg : [number, string] | [number, number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>length : 2 | 3 +> : ^^^^^ + +}, tupleUnion); +>tupleUnion : [string] | [number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +declare function fn2(arg: (...args: NoInfer<[string, number]>) => void): void; +>fn2 : (arg: (...args: NoInfer<[string, number]>) => void) => void +> : ^ ^^ ^^^^^ +>arg : (args_0: string, args_1: number) => void +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>args : [string, number] +> : ^^^^^^^^^^^^^^^^ + +fn2((a, ...rest) => {}); +>fn2((a, ...rest) => {}) : void +> : ^^^^ +>fn2 : (arg: (...args: NoInfer<[string, number]>) => void) => void +> : ^ ^^ ^^^^^ +>(a, ...rest) => {} : (a: string, rest_0: number) => void +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : string +> : ^^^^^^ +>rest : [number] +> : ^^^^^^^^ + diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/noInferRestSpread1.ts b/tests/cases/conformance/types/typeRelationships/typeInference/noInferRestSpread1.ts new file mode 100644 index 0000000000000..c86f2ec235a8d --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/noInferRestSpread1.ts @@ -0,0 +1,35 @@ +// @strict: true +// @noEmit: true + +declare function call( + arg: (...args: NoInfer) => void, + ...args: A +): A; + +const result1 = call((a: number) => {}, 1, 2); +const result2 = call((a: number, b: number) => {}, 1); // error +const result3 = call((a) => {}, 1, ''); // test contextual parameters +const result4 = call((a, b) => {}, 1, ''); // test contextual parameters +const result5 = call((...args) => {}, 1, ''); // test contextual parameters +const result6 = call((a, ...rest) => {}, 1, ''); // test contextual parameters + +declare function fn1( + cb: (a: [number, ...NoInfer]) => void, + args: A, +): A; + +declare const singleStr: [string]; + +const result7 = fn1((arg) => { + arg.length; +}, singleStr); + +declare const tupleUnion: [string] | [number, boolean]; + +const result8 = fn1((arg) => { + arg.length; +}, tupleUnion); + +declare function fn2(arg: (...args: NoInfer<[string, number]>) => void): void; + +fn2((a, ...rest) => {});