Skip to content
Closed
68 changes: 39 additions & 29 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2854,7 +2854,7 @@ namespace ts {

let parameterType = getTypeOfSymbol(parameterSymbol);
if (isRequiredInitializedParameter(parameterDeclaration)) {
parameterType = getNullableType(parameterType, TypeFlags.Undefined);
parameterType = getOptionalType(parameterType);
}
const parameterTypeNode = typeToTypeNodeHelper(parameterType, context);

Expand Down Expand Up @@ -3619,7 +3619,7 @@ namespace ts {

let type = getTypeOfSymbol(p);
if (parameterNode && isRequiredInitializedParameter(parameterNode)) {
type = getNullableType(type, TypeFlags.Undefined);
type = getOptionalType(type);
}
buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack);
}
Expand Down Expand Up @@ -4195,7 +4195,7 @@ namespace ts {
}

function addOptionality(type: Type, optional: boolean): Type {
return strictNullChecks && optional ? getNullableType(type, TypeFlags.Undefined) : type;
return strictNullChecks && optional ? getOptionalType(type) : type;
}

// Return the inferred type for a variable, parameter, or property declaration
Expand Down Expand Up @@ -4622,7 +4622,7 @@ namespace ts {
links.type = baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type;
}
else {
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? getNullableType(type, TypeFlags.Undefined) : type;
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type;
}
}
}
Expand Down Expand Up @@ -9884,12 +9884,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 && getNullableType(supertype, getFalsyFlagsOfTypes(types) & TypeFlags.Nullable);
const supertype = getSupertypeOrUnion(map(types, getNonNullableType));
return supertype && getNullableType(supertype, getFalsyFlagsOfTypes(types) & (TypeFlags.Nullable | TypeFlags.Void));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Here's what caused the addition TypeFlags.Void to getFalsyFlagsOfTypes. When types contains void, the old filter(types ... code did not remove it because void only has TypeFlags.Void. But map(types, getNonNullableType) relies on getTypeFacts, which treats void and undefined exactly the same. So it removes void and undefined both. This causes the following bug:

declare function f<T>(cb: () => T | undefined): T;
f(() => { });
  ~~~~~~~~~
  Argument of type '() => void' is not assignable to parameter of type '() => undefined'.

I would like getNonNullableType not to remove void, but I'm not sure how to do that without changing getTypeFacts, which I am not at all comfortable doing.

}

function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void {
Expand Down Expand Up @@ -9920,7 +9916,7 @@ namespace ts {
bestSupertypeScore = score;
}

// types.length - 1 is the maximum score, given that getCommonSupertype returned false
Debug.assert(bestSupertypeScore < types.length, "types.length - 1 is the maximum score, given that getCommonSuperType returned false");
if (bestSupertypeScore === types.length - 1) {
break;
}
Expand Down Expand Up @@ -10023,12 +10019,21 @@ namespace ts {
neverType;
}

/** Add undefined to a type */
function getOptionalType(type: Type): Type {
return type.flags & TypeFlags.Undefined ? type : getUnionType([type, undefinedType]);
}

/** Add undefined, null or void to a type as requested by flags */
function getNullableType(type: Type, flags: TypeFlags): Type {
const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null);
return missing === 0 ? type :
missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) :
missing === TypeFlags.Null ? getUnionType([type, nullType]) :
getUnionType([type, undefinedType, nullType]);
if ((getFalsyFlags(type) & flags) === flags) {
return type;
}
const types = [type];
if (flags & TypeFlags.Void) types.push(voidType);
if (flags & TypeFlags.Undefined) types.push(undefinedType);
if (flags & TypeFlags.Null) types.push(nullType);
return getUnionType(types);
}

function getNonNullableType(type: Type): Type {
Expand Down Expand Up @@ -10631,6 +10636,19 @@ namespace ts {
return type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes);
}

/**
* 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 widenInferenceCandidates(inference: InferenceInfo, signature: Signature) {
const widenLiteralTypes = inference.topLevel &&
!hasPrimitiveConstraint(inference.typeParameter) &&
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
return widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates;
}

function hasPrimitiveConstraint(type: TypeParameter): boolean {
const constraint = getConstraintOfTypeParameter(type);
return constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive | TypeFlags.Index);
Expand All @@ -10642,15 +10660,7 @@ namespace ts {
let inferenceSucceeded: boolean;
if (!inferredType) {
if (inference.candidates) {
// 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.
const signature = context.signature;
const widenLiteralTypes = inference.topLevel &&
!hasPrimitiveConstraint(inference.typeParameter) &&
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
const baseCandidates = widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates;
const baseCandidates = widenInferenceCandidates(inference, context.signature);
// Infer widened union or supertype, or the unknown type for no common supertype. We infer union types
// for inferences coming from return types in order to avoid common supertype failures.
const unionOrSuperType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ?
Expand Down Expand Up @@ -12152,7 +12162,7 @@ namespace ts {
isInAmbientContext(declaration);
const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, getRootDeclaration(declaration) as VariableLikeDeclaration) : type) :
type === autoType || type === autoArrayType ? undefinedType :
getNullableType(type, TypeFlags.Undefined);
getOptionalType(type);
const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized);
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
// from declaration to use, and when the variable's declared type doesn't include undefined but the
Expand Down Expand Up @@ -15649,7 +15659,7 @@ namespace ts {
else {
Debug.assert(resultOfFailedInference.failedTypeParameterIndex >= 0);
const failedTypeParameter = candidateForTypeArgumentError.typeParameters[resultOfFailedInference.failedTypeParameterIndex];
const inferenceCandidates = resultOfFailedInference.inferences[resultOfFailedInference.failedTypeParameterIndex].candidates;
const inferenceCandidates = widenInferenceCandidates(resultOfFailedInference.inferences[resultOfFailedInference.failedTypeParameterIndex], resultOfFailedInference.signature);

let diagnosticChainHead = chainDiagnosticMessages(/*details*/ undefined, // details will be provided by call to reportNoCommonSupertypeError
Diagnostics.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly,
Expand Down Expand Up @@ -16341,7 +16351,7 @@ namespace ts {
if (strictNullChecks) {
const declaration = symbol.valueDeclaration;
if (declaration && (<VariableLikeDeclaration>declaration).initializer) {
return getNullableType(type, TypeFlags.Undefined);
return getOptionalType(type);
}
}
return type;
Expand Down Expand Up @@ -23248,7 +23258,7 @@ namespace ts {
? getWidenedLiteralType(getTypeOfSymbol(symbol))
: unknownType;
if (flags & TypeFormatFlags.AddUndefined) {
type = getNullableType(type, TypeFlags.Undefined);
type = getOptionalType(type);
}
getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags);
}
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
}