diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e9fa90a6a8ddf..bd340016123a2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16156,36 +16156,38 @@ namespace ts { const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; for (let i = 0; i < paramCount; i++) { - const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : getTypeAtPosition(source, i); - const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : getTypeAtPosition(target, i); - // In order to ensure that any generic type Foo 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, - // they naturally relate only contra-variantly). However, if the source and target parameters both have - // function types with a single call signature, we know we are relating two callback parameters. In - // that case it is sufficient to only relate the parameters of the signatures co-variantly because, - // similar to return values, callback parameters are output positions. This means that a Promise, - // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) - // with respect to T. - const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); - const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); - const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && - (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); - let related = callbacks ? - compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : - !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); - // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void - if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { - related = Ternary.False; - } - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, - unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), - unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); + const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + if (sourceType && targetType) { + // In order to ensure that any generic type Foo 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, + // they naturally relate only contra-variantly). However, if the source and target parameters both have + // function types with a single call signature, we know we are relating two callback parameters. In + // that case it is sufficient to only relate the parameters of the signatures co-variantly because, + // similar to return values, callback parameters are output positions. This means that a Promise, + // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) + // with respect to T. + const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); + const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && + (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); + let related = callbacks ? + compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : + !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void + if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { + related = Ternary.False; } - return Ternary.False; + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, + unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), + unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); + } + return Ternary.False; + } + result &= related; } - result &= related; } if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { diff --git a/tests/baselines/reference/unmatchedParameterPositions.js b/tests/baselines/reference/unmatchedParameterPositions.js new file mode 100644 index 0000000000000..5859091fb0e94 --- /dev/null +++ b/tests/baselines/reference/unmatchedParameterPositions.js @@ -0,0 +1,13 @@ +//// [unmatchedParameterPositions.ts] +// Repros from #40251 + +declare let s: (...items: never[]) => never[]; +let t1: () => unknown[] = s; +let t2: (...args: []) => unknown[] = s; + + +//// [unmatchedParameterPositions.js] +"use strict"; +// Repros from #40251 +var t1 = s; +var t2 = s; diff --git a/tests/baselines/reference/unmatchedParameterPositions.symbols b/tests/baselines/reference/unmatchedParameterPositions.symbols new file mode 100644 index 0000000000000..cf7a7c19df6eb --- /dev/null +++ b/tests/baselines/reference/unmatchedParameterPositions.symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/unmatchedParameterPositions.ts === +// Repros from #40251 + +declare let s: (...items: never[]) => never[]; +>s : Symbol(s, Decl(unmatchedParameterPositions.ts, 2, 11)) +>items : Symbol(items, Decl(unmatchedParameterPositions.ts, 2, 16)) + +let t1: () => unknown[] = s; +>t1 : Symbol(t1, Decl(unmatchedParameterPositions.ts, 3, 3)) +>s : Symbol(s, Decl(unmatchedParameterPositions.ts, 2, 11)) + +let t2: (...args: []) => unknown[] = s; +>t2 : Symbol(t2, Decl(unmatchedParameterPositions.ts, 4, 3)) +>args : Symbol(args, Decl(unmatchedParameterPositions.ts, 4, 9)) +>s : Symbol(s, Decl(unmatchedParameterPositions.ts, 2, 11)) + diff --git a/tests/baselines/reference/unmatchedParameterPositions.types b/tests/baselines/reference/unmatchedParameterPositions.types new file mode 100644 index 0000000000000..522ac7e0170c5 --- /dev/null +++ b/tests/baselines/reference/unmatchedParameterPositions.types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/unmatchedParameterPositions.ts === +// Repros from #40251 + +declare let s: (...items: never[]) => never[]; +>s : (...items: never[]) => never[] +>items : never[] + +let t1: () => unknown[] = s; +>t1 : () => unknown[] +>s : (...items: never[]) => never[] + +let t2: (...args: []) => unknown[] = s; +>t2 : () => unknown[] +>args : [] +>s : (...items: never[]) => never[] + diff --git a/tests/cases/compiler/unmatchedParameterPositions.ts b/tests/cases/compiler/unmatchedParameterPositions.ts new file mode 100644 index 0000000000000..91202eb49da8e --- /dev/null +++ b/tests/cases/compiler/unmatchedParameterPositions.ts @@ -0,0 +1,7 @@ +// @strict: true + +// Repros from #40251 + +declare let s: (...items: never[]) => never[]; +let t1: () => unknown[] = s; +let t2: (...args: []) => unknown[] = s;