Skip to content

Widen inference candidates for error reporting #15221

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

Closed
wants to merge 12 commits into from
Closed
38 changes: 18 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9596,12 +9596,8 @@ namespace ts {
if (!strictNullChecks) {
return getSupertypeOrUnion(types);
}
const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable));
if (!primaryTypes.length) {
return getUnionType(types, /*subtypeReduction*/ true);
}
const supertype = getSupertypeOrUnion(primaryTypes);
return supertype && includeFalsyTypes(supertype, getFalsyFlagsOfTypes(types) & TypeFlags.Nullable);
const supertype = getSupertypeOrUnion(map(types, getNonNullableType));
return supertype && includeFalsyTypes(supertype, getFalsyFlagsOfTypes(types) & (TypeFlags.Nullable | TypeFlags.Void));
}

function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void {
Expand All @@ -9624,6 +9620,7 @@ namespace ts {
}
}

Debug.assert(score < types.length, "types.length - 1 is the maximum score, given that getCommonSuperType returned false");
Debug.assert(!!downfallType, "If there is no common supertype, each type should have a downfallType");

if (score > bestSupertypeScore) {
Expand All @@ -9632,7 +9629,6 @@ namespace ts {
bestSupertypeScore = score;
}

// types.length - 1 is the maximum score, given that getCommonSupertype returned false
if (bestSupertypeScore === types.length - 1) {
break;
}
Expand Down Expand Up @@ -10322,9 +10318,20 @@ namespace ts {
return type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes);
}

function getInferenceCandidates(context: InferenceContext, index: number): Type[] {
const inferences = context.inferences[index];
return inferences.primary || inferences.secondary || emptyArray;
/**
* We widen inferred literal types if
* all inferences were made to top-level ocurrences of the type parameter, and
* the type parameter has no constraint or its constraint includes no primitive or literal types, and
* the type parameter was fixed during inference or does not occur at top-level in the return type.
*/
function getInferenceCandidates(context: InferenceContext, index: number) {
const typeInferences = context.inferences[index];
const inferences: Type[] = typeInferences.primary || typeInferences.secondary || emptyArray;
const signature = context.signature;
const widenLiteralTypes = typeInferences.topLevel &&
!hasPrimitiveConstraint(signature.typeParameters[index]) &&
(typeInferences.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), signature.typeParameters[index]));
return widenLiteralTypes ? sameMap(inferences, getWidenedLiteralType) : inferences;
}

function hasPrimitiveConstraint(type: TypeParameter): boolean {
Expand All @@ -10338,17 +10345,8 @@ namespace ts {
if (!inferredType) {
const inferences = getInferenceCandidates(context, index);
if (inferences.length) {
// We widen inferred literal types if
Copy link
Member Author

Choose a reason for hiding this comment

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

This code moves into getInferenceCandidates so that both type inference (this section) and type inference error reporting (the other call to getInferenceCandidates) widen the same way.

// all inferences were made to top-level ocurrences of the type parameter, and
// the type parameter has no constraint or its constraint includes no primitive or literal types, and
// the type parameter was fixed during inference or does not occur at top-level in the return type.
const signature = context.signature;
const widenLiteralTypes = context.inferences[index].topLevel &&
!hasPrimitiveConstraint(signature.typeParameters[index]) &&
(context.inferences[index].isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), signature.typeParameters[index]));
const baseInferences = widenLiteralTypes ? sameMap(inferences, getWidenedLiteralType) : inferences;
// Infer widened union or supertype, or the unknown type for no common supertype
const unionOrSuperType = context.inferUnionTypes ? getUnionType(baseInferences, /*subtypeReduction*/ true) : getCommonSupertype(baseInferences);
const unionOrSuperType = context.inferUnionTypes ? getUnionType(inferences, /*subtypeReduction*/ true) : getCommonSupertype(inferences);
inferredType = unionOrSuperType ? getWidenedType(unionOrSuperType) : unknownType;
inferenceSucceeded = !!unionOrSuperType;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
tests/cases/compiler/fixTypeParameterInSignatureWithRestParameters.ts(2,1): error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate '1' is not a valid type argument because it is not a supertype of candidate '""'.
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'.


==== tests/cases/compiler/fixTypeParameterInSignatureWithRestParameters.ts (1 errors) ====
function bar<T>(item1: T, item2: T) { }
bar(1, ""); // Should be ok
~~~
!!! error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
!!! error TS2453: Type argument candidate '1' is not a valid type argument because it is not a supertype of candidate '""'.
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'.
8 changes: 4 additions & 4 deletions tests/baselines/reference/genericRestArgs.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
tests/cases/compiler/genericRestArgs.ts(2,12): error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate '1' is not a valid type argument because it is not a supertype of candidate '""'.
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'.
tests/cases/compiler/genericRestArgs.ts(5,34): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'.
tests/cases/compiler/genericRestArgs.ts(10,12): error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate '1' is not a valid type argument because it is not a supertype of candidate '""'.
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'.
tests/cases/compiler/genericRestArgs.ts(12,30): error TS2345: Argument of type '1' is not assignable to parameter of type 'any[]'.


Expand All @@ -11,7 +11,7 @@ tests/cases/compiler/genericRestArgs.ts(12,30): error TS2345: Argument of type '
var a1Ga = makeArrayG(1, ""); // no error
~~~~~~~~~~
!!! error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
!!! error TS2453: Type argument candidate '1' is not a valid type argument because it is not a supertype of candidate '""'.
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'.
var a1Gb = makeArrayG<any>(1, "");
var a1Gc = makeArrayG<Object>(1, "");
var a1Gd = makeArrayG<number>(1, ""); // error
Expand All @@ -24,7 +24,7 @@ tests/cases/compiler/genericRestArgs.ts(12,30): error TS2345: Argument of type '
var a2Ga = makeArrayGOpt(1, "");
~~~~~~~~~~~~~
!!! error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
!!! error TS2453: Type argument candidate '1' is not a valid type argument because it is not a supertype of candidate '""'.
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'string'.
var a2Gb = makeArrayG<any>(1, "");
var a2Gc = makeArrayG<any[]>(1, ""); // error
~
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//// [noCommonSupertypeTypeInferenceMatchesReporting.ts]
// Fixes #15116, which asserted on line 5 but not 6
declare function f<T>(a: T, b: T): boolean;
function g(gut: { n: 12 | undefined }) {
f(gut.n, 12); // ok, T = number | undefined
f(12, gut.n); // ok, T = number | undefined
}


//// [noCommonSupertypeTypeInferenceMatchesReporting.js]
function g(gut) {
f(gut.n, 12); // ok, T = number | undefined
f(12, gut.n); // ok, T = number | undefined
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== tests/cases/compiler/noCommonSupertypeTypeInferenceMatchesReporting.ts ===
// Fixes #15116, which asserted on line 5 but not 6
declare function f<T>(a: T, b: T): boolean;
>f : Symbol(f, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 0, 0))
>T : Symbol(T, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 1, 19))
>a : Symbol(a, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 1, 22))
>T : Symbol(T, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 1, 19))
>b : Symbol(b, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 1, 27))
>T : Symbol(T, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 1, 19))

function g(gut: { n: 12 | undefined }) {
>g : Symbol(g, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 1, 43))
>gut : Symbol(gut, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 11))
>n : Symbol(n, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 17))

f(gut.n, 12); // ok, T = number | undefined
>f : Symbol(f, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 0, 0))
>gut.n : Symbol(n, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 17))
>gut : Symbol(gut, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 11))
>n : Symbol(n, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 17))

f(12, gut.n); // ok, T = number | undefined
>f : Symbol(f, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 0, 0))
>gut.n : Symbol(n, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 17))
>gut : Symbol(gut, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 11))
>n : Symbol(n, Decl(noCommonSupertypeTypeInferenceMatchesReporting.ts, 2, 17))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/compiler/noCommonSupertypeTypeInferenceMatchesReporting.ts ===
// Fixes #15116, which asserted on line 5 but not 6
declare function f<T>(a: T, b: T): boolean;
>f : <T>(a: T, b: T) => boolean
>T : T
>a : T
>T : T
>b : T
>T : T

function g(gut: { n: 12 | undefined }) {
>g : (gut: { n: 12 | undefined; }) => void
>gut : { n: 12 | undefined; }
>n : 12 | undefined

f(gut.n, 12); // ok, T = number | undefined
>f(gut.n, 12) : boolean
>f : <T>(a: T, b: T) => boolean
>gut.n : 12 | undefined
>gut : { n: 12 | undefined; }
>n : 12 | undefined
>12 : 12

f(12, gut.n); // ok, T = number | undefined
>f(12, gut.n) : boolean
>f : <T>(a: T, b: T) => boolean
>12 : 12
>gut.n : 12 | undefined
>gut : { n: 12 | undefined; }
>n : 12 | undefined
}

4 changes: 2 additions & 2 deletions tests/baselines/reference/parser15.4.4.14-9-2.errors.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
tests/cases/conformance/parser/ecmascript5/parser15.4.4.14-9-2.ts(16,15): error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'false'.
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'boolean'.
tests/cases/conformance/parser/ecmascript5/parser15.4.4.14-9-2.ts(25,1): error TS2304: Cannot find name 'runTestCase'.


Expand All @@ -22,7 +22,7 @@ tests/cases/conformance/parser/ecmascript5/parser15.4.4.14-9-2.ts(25,1): error T
var a = new Array(false,undefined,null,"0",obj,-1.3333333333333, "str",-0,true,+0, one, 1,0, false, _float, -(4/3));
~~~~~
!!! error TS2453: The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'false'.
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate 'boolean'.
if (a.indexOf(-(4/3)) === 14 && // a[14]=_float===-(4/3)
a.indexOf(0) === 7 && // a[7] = +0, 0===+0
a.indexOf(-0) === 7 && // a[7] = +0, -0===+0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @strictNullChecks: true
// Fixes #15116, which asserted on line 5 but not 6
declare function f<T>(a: T, b: T): boolean;
function g(gut: { n: 12 | undefined }) {
f(gut.n, 12); // ok, T = number | undefined
f(12, gut.n); // ok, T = number | undefined
}