Skip to content

Commit e19d934

Browse files
committed
Initial implementation of return type inference
1 parent 24a25fd commit e19d934

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7937,6 +7937,10 @@ namespace ts {
79377937
return context.mapper;
79387938
}
79397939

7940+
function cloneTypeMapper(mapper: TypeMapper): TypeMapper {
7941+
return mapper && mapper.context ? getInferenceMapper(cloneInferenceContext(mapper.context)) : mapper;
7942+
}
7943+
79407944
function identityMapper(type: Type): Type {
79417945
return type;
79427946
}
@@ -10130,12 +10134,13 @@ namespace ts {
1013010134
}
1013110135
}
1013210136

10133-
function createInferenceContext(signature: Signature, inferUnionTypes: boolean, useAnyForNoInferences: boolean): InferenceContext {
10137+
function createInferenceContext(callNode: CallLikeExpression, signature: Signature, inferUnionTypes: boolean, noInferenceType: Type): InferenceContext {
1013410138
return {
10139+
callNode,
1013510140
signature,
1013610141
inferences: map(signature.typeParameters, createInferenceInfo),
1013710142
inferUnionTypes,
10138-
useAnyForNoInferences
10143+
noInferenceType
1013910144
};
1014010145
}
1014110146

@@ -10150,6 +10155,27 @@ namespace ts {
1015010155
};
1015110156
}
1015210157

10158+
function cloneInferenceContext(context: InferenceContext): InferenceContext {
10159+
return {
10160+
callNode: context.callNode,
10161+
signature: context.signature,
10162+
inferences: map(context.inferences, cloneInferenceInfo),
10163+
inferUnionTypes: context.inferUnionTypes,
10164+
noInferenceType: silentNeverType
10165+
}
10166+
}
10167+
10168+
function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo {
10169+
return {
10170+
typeParameter: inference.typeParameter,
10171+
candidates: inference.candidates && inference.candidates.slice(),
10172+
inferredType: inference.inferredType,
10173+
priority: inference.priority,
10174+
topLevel: inference.topLevel,
10175+
isFixed: inference.isFixed
10176+
};
10177+
}
10178+
1015310179
// Return true if the given type could possibly reference a type parameter for which
1015410180
// we perform type inference (i.e. a type parameter of a generic function). We cache
1015510181
// results for union and intersection types for performance reasons.
@@ -10221,10 +10247,9 @@ namespace ts {
1022110247
inferTypes(context.inferences, originalSource, originalTarget);
1022210248
}
1022310249

10224-
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type) {
10250+
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
1022510251
let symbolStack: Symbol[];
1022610252
let visited: Map<boolean>;
10227-
let priority = 0;
1022810253
inferFromTypes(originalSource, originalTarget);
1022910254

1023010255
function inferFromTypes(source: Type, target: Type) {
@@ -10285,7 +10310,7 @@ namespace ts {
1028510310
// it as an inference candidate. Hopefully, a better candidate will come along that does
1028610311
// not contain anyFunctionType when we come back to this argument for its second round
1028710312
// of inference.
10288-
if (source.flags & TypeFlags.ContainsAnyFunctionType) {
10313+
if (source.flags & TypeFlags.ContainsAnyFunctionType || source === silentNeverType) {
1028910314
return;
1029010315
}
1029110316
for (const inference of inferences) {
@@ -10517,8 +10542,19 @@ namespace ts {
1051710542
let inferredType = inference.inferredType;
1051810543
let inferenceSucceeded: boolean;
1051910544
if (!inferredType) {
10520-
const candidates = inference.candidates;
10521-
if (candidates) {
10545+
if (!inference.candidates && context.callNode && isExpression(context.callNode)) {
10546+
const contextualType = getContextualType(context.callNode);
10547+
if (contextualType) {
10548+
const mapper = cloneTypeMapper(getContextualMapper(context.callNode));
10549+
const instantiatedType = instantiateType(contextualType, mapper);
10550+
const returnType = getReturnTypeOfSignature(context.signature);
10551+
const saveFixed = inference.isFixed;
10552+
inference.isFixed = false;
10553+
inferTypes([inference], instantiatedType, returnType, InferencePriority.ReturnType);
10554+
inference.isFixed = saveFixed;
10555+
}
10556+
}
10557+
if (inference.candidates) {
1052210558
// We widen inferred literal types if
1052310559
// all inferences were made to top-level ocurrences of the type parameter, and
1052410560
// the type parameter has no constraint or its constraint includes no primitive or literal types, and
@@ -10527,7 +10563,7 @@ namespace ts {
1052710563
const widenLiteralTypes = inference.topLevel &&
1052810564
!hasPrimitiveConstraint(inference.typeParameter) &&
1052910565
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
10530-
const baseCandidates = widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : candidates;
10566+
const baseCandidates = widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates;
1053110567
// Infer widened union or supertype, or the unknown type for no common supertype
1053210568
const unionOrSuperType = context.inferUnionTypes ? getUnionType(baseCandidates, /*subtypeReduction*/ true) : getCommonSupertype(baseCandidates);
1053310569
inferredType = unionOrSuperType ? getWidenedType(unionOrSuperType) : unknownType;
@@ -10539,7 +10575,7 @@ namespace ts {
1053910575
// succeeds, meaning there is no error for not having inference candidates. An
1054010576
// inference error only occurs when there are *conflicting* candidates, i.e.
1054110577
// candidates with no common supertype.
10542-
const defaultType = getDefaultFromTypeParameter(inference.typeParameter);
10578+
const defaultType = context.noInferenceType === silentNeverType ? undefined : getDefaultFromTypeParameter(inference.typeParameter);
1054310579
if (defaultType) {
1054410580
// Instantiate the default type. Any forward reference to a type
1054510581
// parameter should be instantiated to the empty object type.
@@ -10549,7 +10585,7 @@ namespace ts {
1054910585
getInferenceMapper(context)));
1055010586
}
1055110587
else {
10552-
inferredType = context.useAnyForNoInferences ? anyType : emptyObjectType;
10588+
inferredType = context.noInferenceType;
1055310589
}
1055410590

1055510591
inferenceSucceeded = true;
@@ -14836,7 +14872,7 @@ namespace ts {
1483614872

1483714873
// Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec)
1483814874
function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper: TypeMapper): Signature {
14839-
const context = createInferenceContext(signature, /*inferUnionTypes*/ true, /*useAnyForNoInferences*/ false);
14875+
const context = createInferenceContext(/*callNode*/ undefined, signature, /*inferUnionTypes*/ true, /*noInferenceType*/ emptyObjectType);
1484014876
forEachMatchingParameterType(contextualSignature, signature, (source, target) => {
1484114877
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
1484214878
inferTypesWithContext(context, instantiateType(source, contextualMapper), target);
@@ -15570,7 +15606,7 @@ namespace ts {
1557015606
let candidate: Signature;
1557115607
let typeArgumentsAreValid: boolean;
1557215608
const inferenceContext = originalCandidate.typeParameters
15573-
? createInferenceContext(originalCandidate, /*inferUnionTypes*/ false, /*useAnyForNoInferences*/ isInJavaScriptFile(node))
15609+
? createInferenceContext(node, originalCandidate, /*inferUnionTypes*/ false, /*noInferenceType*/ isInJavaScriptFile(node) ? anyType : emptyObjectType)
1557415610
: undefined;
1557515611

1557615612
while (true) {

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3344,6 +3344,7 @@ namespace ts {
33443344
export const enum InferencePriority {
33453345
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
33463346
MappedType = 1 << 1, // Reverse inference for mapped type
3347+
ReturnType = 1 << 2, // Inference made from return type of generic function
33473348
}
33483349

33493350
export interface InferenceInfo {
@@ -3357,11 +3358,12 @@ namespace ts {
33573358

33583359
/* @internal */
33593360
export interface InferenceContext {
3361+
callNode: CallLikeExpression; // Call expression node for which inferences are made
33603362
signature: Signature; // Generic signature for which inferences are made
33613363
inferences: InferenceInfo[]; // Inferences made for each type parameter
33623364
mapper?: TypeMapper; // Type mapper for this inference context
33633365
inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType)
3364-
useAnyForNoInferences: boolean; // Use any instead of {} for no inferences
3366+
noInferenceType: Type; // Type to use for no inferences
33653367
failedTypeParameterIndex?: number; // Index of type parameter for which inference failed
33663368
// It is optional because in contextual signature instantiation, nothing fails
33673369
}

0 commit comments

Comments
 (0)