Skip to content

Commit 2bcf84c

Browse files
committed
Merge pull request #2356 from Microsoft/typeParameterFixing
Make sure type parameters stay fixed throughout the inference process
2 parents 1227210 + 3879d0a commit 2bcf84c

28 files changed

+524
-212
lines changed

src/compiler/checker.ts

+80-40
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ module ts {
7979
let emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
8080
let anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
8181
let noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
82-
let inferenceFailureType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
83-
82+
8483
let anySignature = createSignature(undefined, undefined, emptyArray, anyType, 0, false, false);
8584
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, 0, false, false);
8685

@@ -3514,6 +3513,7 @@ module ts {
35143513
return t => {
35153514
for (let i = 0; i < context.typeParameters.length; i++) {
35163515
if (t === context.typeParameters[i]) {
3516+
context.inferences[i].isFixed = true;
35173517
return getInferredType(context, i);
35183518
}
35193519
}
@@ -4372,8 +4372,11 @@ module ts {
43724372
}
43734373

43744374
function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void {
4375+
// The downfallType/bestSupertypeDownfallType is the first type that caused a particular candidate
4376+
// to not be the common supertype. So if it weren't for this one downfallType (and possibly others),
4377+
// the type in question could have been the common supertype.
43754378
let bestSupertype: Type;
4376-
let bestSupertypeDownfallType: Type; // The type that caused bestSupertype not to be the common supertype
4379+
let bestSupertypeDownfallType: Type;
43774380
let bestSupertypeScore = 0;
43784381

43794382
for (let i = 0; i < types.length; i++) {
@@ -4388,6 +4391,8 @@ module ts {
43884391
}
43894392
}
43904393

4394+
Debug.assert(!!downfallType, "If there is no common supertype, each type should have a downfallType");
4395+
43914396
if (score > bestSupertypeScore) {
43924397
bestSupertype = types[i];
43934398
bestSupertypeDownfallType = downfallType;
@@ -4570,13 +4575,12 @@ module ts {
45704575
function createInferenceContext(typeParameters: TypeParameter[], inferUnionTypes: boolean): InferenceContext {
45714576
let inferences: TypeInferences[] = [];
45724577
for (let unused of typeParameters) {
4573-
inferences.push({ primary: undefined, secondary: undefined });
4578+
inferences.push({ primary: undefined, secondary: undefined, isFixed: false });
45744579
}
45754580
return {
4576-
typeParameters: typeParameters,
4577-
inferUnionTypes: inferUnionTypes,
4578-
inferenceCount: 0,
4579-
inferences: inferences,
4581+
typeParameters,
4582+
inferUnionTypes,
4583+
inferences,
45804584
inferredTypes: new Array(typeParameters.length),
45814585
};
45824586
}
@@ -4622,11 +4626,21 @@ module ts {
46224626
for (let i = 0; i < typeParameters.length; i++) {
46234627
if (target === typeParameters[i]) {
46244628
let inferences = context.inferences[i];
4625-
let candidates = inferiority ?
4626-
inferences.secondary || (inferences.secondary = []) :
4627-
inferences.primary || (inferences.primary = []);
4628-
if (!contains(candidates, source)) candidates.push(source);
4629-
break;
4629+
if (!inferences.isFixed) {
4630+
// Any inferences that are made to a type parameter in a union type are inferior
4631+
// to inferences made to a flat (non-union) type. This is because if we infer to
4632+
// T | string[], we really don't know if we should be inferring to T or not (because
4633+
// the correct constituent on the target side could be string[]). Therefore, we put
4634+
// such inferior inferences into a secondary bucket, and only use them if the primary
4635+
// bucket is empty.
4636+
let candidates = inferiority ?
4637+
inferences.secondary || (inferences.secondary = []) :
4638+
inferences.primary || (inferences.primary = []);
4639+
if (!contains(candidates, source)) {
4640+
candidates.push(source);
4641+
}
4642+
}
4643+
return;
46304644
}
46314645
}
46324646
}
@@ -4732,21 +4746,35 @@ module ts {
47324746

47334747
function getInferredType(context: InferenceContext, index: number): Type {
47344748
let inferredType = context.inferredTypes[index];
4749+
let inferenceSucceeded: boolean;
47354750
if (!inferredType) {
47364751
let inferences = getInferenceCandidates(context, index);
47374752
if (inferences.length) {
4738-
// Infer widened union or supertype, or the undefined type for no common supertype
4753+
// Infer widened union or supertype, or the unknown type for no common supertype
47394754
let unionOrSuperType = context.inferUnionTypes ? getUnionType(inferences) : getCommonSupertype(inferences);
4740-
inferredType = unionOrSuperType ? getWidenedType(unionOrSuperType) : inferenceFailureType;
4755+
inferredType = unionOrSuperType ? getWidenedType(unionOrSuperType) : unknownType;
4756+
inferenceSucceeded = !!unionOrSuperType;
47414757
}
47424758
else {
4743-
// Infer the empty object type when no inferences were made
4759+
// Infer the empty object type when no inferences were made. It is important to remember that
4760+
// in this case, inference still succeeds, meaning there is no error for not having inference
4761+
// candidates. An inference error only occurs when there are *conflicting* candidates, i.e.
4762+
// candidates with no common supertype.
47444763
inferredType = emptyObjectType;
4764+
inferenceSucceeded = true;
47454765
}
4746-
if (inferredType !== inferenceFailureType) {
4766+
4767+
// Only do the constraint check if inference succeeded (to prevent cascading errors)
4768+
if (inferenceSucceeded) {
47474769
let constraint = getConstraintOfTypeParameter(context.typeParameters[index]);
47484770
inferredType = constraint && !isTypeAssignableTo(inferredType, constraint) ? constraint : inferredType;
47494771
}
4772+
else if (context.failedTypeParameterIndex === undefined || context.failedTypeParameterIndex > index) {
4773+
// If inference failed, it is necessary to record the index of the failed type parameter (the one we are on).
4774+
// It might be that inference has already failed on a later type parameter on a previous call to inferTypeArguments.
4775+
// So if this failure is on preceding type parameter, this type parameter is the new failure index.
4776+
context.failedTypeParameterIndex = index;
4777+
}
47504778
context.inferredTypes[index] = inferredType;
47514779
}
47524780
return inferredType;
@@ -6343,11 +6371,32 @@ module ts {
63436371
return getSignatureInstantiation(signature, getInferredTypes(context));
63446372
}
63456373

6346-
function inferTypeArguments(signature: Signature, args: Expression[], excludeArgument: boolean[]): InferenceContext {
6374+
function inferTypeArguments(signature: Signature, args: Expression[], excludeArgument: boolean[], context: InferenceContext): void {
63476375
let typeParameters = signature.typeParameters;
6348-
let context = createInferenceContext(typeParameters, /*inferUnionTypes*/ false);
63496376
let inferenceMapper = createInferenceMapper(context);
63506377

6378+
// Clear out all the inference results from the last time inferTypeArguments was called on this context
6379+
for (let i = 0; i < typeParameters.length; i++) {
6380+
// As an optimization, we don't have to clear (and later recompute) inferred types
6381+
// for type parameters that have already been fixed on the previous call to inferTypeArguments.
6382+
// It would be just as correct to reset all of them. But then we'd be repeating the same work
6383+
// for the type parameters that were fixed, namely the work done by getInferredType.
6384+
if (!context.inferences[i].isFixed) {
6385+
context.inferredTypes[i] = undefined;
6386+
}
6387+
}
6388+
6389+
// On this call to inferTypeArguments, we may get more inferences for certain type parameters that were not
6390+
// fixed last time. This means that a type parameter that failed inference last time may succeed this time,
6391+
// or vice versa. Therefore, the failedTypeParameterIndex is useless if it points to an unfixed type parameter,
6392+
// because it may change. So here we reset it. However, getInferredType will not revisit any type parameters
6393+
// that were previously fixed. So if a fixed type parameter failed previously, it will fail again because
6394+
// it will contain the exact same set of inferences. So if we reset the index from a fixed type parameter,
6395+
// we will lose information that we won't recover this time around.
6396+
if (context.failedTypeParameterIndex !== undefined && !context.inferences[context.failedTypeParameterIndex].isFixed) {
6397+
context.failedTypeParameterIndex = undefined;
6398+
}
6399+
63516400
// We perform two passes over the arguments. In the first pass we infer from all arguments, but use
63526401
// wildcards for all context sensitive function expressions.
63536402
for (let i = 0; i < args.length; i++) {
@@ -6382,18 +6431,7 @@ module ts {
63826431
}
63836432
}
63846433

6385-
let inferredTypes = getInferredTypes(context);
6386-
// Inference has failed if the inferenceFailureType type is in list of inferences
6387-
context.failedTypeParameterIndex = indexOf(inferredTypes, inferenceFailureType);
6388-
6389-
// Wipe out the inferenceFailureType from the array so that error recovery can work properly
6390-
for (let i = 0; i < inferredTypes.length; i++) {
6391-
if (inferredTypes[i] === inferenceFailureType) {
6392-
inferredTypes[i] = unknownType;
6393-
}
6394-
}
6395-
6396-
return context;
6434+
getInferredTypes(context);
63976435
}
63986436

63996437
function checkTypeArguments(signature: Signature, typeArguments: TypeNode[], typeArgumentResultTypes: Type[], reportErrors: boolean): boolean {
@@ -6627,15 +6665,17 @@ module ts {
66276665
return resolveErrorCall(node);
66286666

66296667
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>) {
6630-
for (let current of candidates) {
6631-
if (!hasCorrectArity(node, args, current)) {
6668+
for (let originalCandidate of candidates) {
6669+
if (!hasCorrectArity(node, args, originalCandidate)) {
66326670
continue;
66336671
}
6634-
6635-
let originalCandidate = current;
6636-
let inferenceResult: InferenceContext;
6672+
66376673
let candidate: Signature;
66386674
let typeArgumentsAreValid: boolean;
6675+
let inferenceContext = originalCandidate.typeParameters
6676+
? createInferenceContext(originalCandidate.typeParameters, /*inferUnionTypes*/ false)
6677+
: undefined;
6678+
66396679
while (true) {
66406680
candidate = originalCandidate;
66416681
if (candidate.typeParameters) {
@@ -6645,9 +6685,9 @@ module ts {
66456685
typeArgumentsAreValid = checkTypeArguments(candidate, typeArguments, typeArgumentTypes, /*reportErrors*/ false)
66466686
}
66476687
else {
6648-
inferenceResult = inferTypeArguments(candidate, args, excludeArgument);
6649-
typeArgumentsAreValid = inferenceResult.failedTypeParameterIndex < 0;
6650-
typeArgumentTypes = inferenceResult.inferredTypes;
6688+
inferTypeArguments(candidate, args, excludeArgument, inferenceContext);
6689+
typeArgumentsAreValid = inferenceContext.failedTypeParameterIndex === undefined;
6690+
typeArgumentTypes = inferenceContext.inferredTypes;
66516691
}
66526692
if (!typeArgumentsAreValid) {
66536693
break;
@@ -6677,7 +6717,7 @@ module ts {
66776717
else {
66786718
candidateForTypeArgumentError = originalCandidate;
66796719
if (!typeArguments) {
6680-
resultOfFailedInference = inferenceResult;
6720+
resultOfFailedInference = inferenceContext;
66816721
}
66826722
}
66836723
}

src/compiler/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1487,11 +1487,15 @@ module ts {
14871487
(t: Type): Type;
14881488
}
14891489

1490+
// @internal
14901491
export interface TypeInferences {
14911492
primary: Type[]; // Inferences made directly to a type parameter
14921493
secondary: Type[]; // Inferences made to a type parameter in a union type
1494+
isFixed: boolean; // Whether the type parameter is fixed, as defined in section 4.12.2 of the TypeScript spec
1495+
// If a type parameter is fixed, no more inferences can be made for the type parameter
14931496
}
14941497

1498+
// @internal
14951499
export interface InferenceContext {
14961500
typeParameters: TypeParameter[]; // Type parameters for which inferences are made
14971501
inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType)

tests/baselines/reference/APISample_compile.js

-11
Original file line numberDiff line numberDiff line change
@@ -1176,17 +1176,6 @@ declare module "typescript" {
11761176
interface TypeMapper {
11771177
(t: Type): Type;
11781178
}
1179-
interface TypeInferences {
1180-
primary: Type[];
1181-
secondary: Type[];
1182-
}
1183-
interface InferenceContext {
1184-
typeParameters: TypeParameter[];
1185-
inferUnionTypes: boolean;
1186-
inferences: TypeInferences[];
1187-
inferredTypes: Type[];
1188-
failedTypeParameterIndex?: number;
1189-
}
11901179
interface DiagnosticMessage {
11911180
key: string;
11921181
category: DiagnosticCategory;

tests/baselines/reference/APISample_compile.types

-32
Original file line numberDiff line numberDiff line change
@@ -3769,38 +3769,6 @@ declare module "typescript" {
37693769
>t : Type
37703770
>Type : Type
37713771
>Type : Type
3772-
}
3773-
interface TypeInferences {
3774-
>TypeInferences : TypeInferences
3775-
3776-
primary: Type[];
3777-
>primary : Type[]
3778-
>Type : Type
3779-
3780-
secondary: Type[];
3781-
>secondary : Type[]
3782-
>Type : Type
3783-
}
3784-
interface InferenceContext {
3785-
>InferenceContext : InferenceContext
3786-
3787-
typeParameters: TypeParameter[];
3788-
>typeParameters : TypeParameter[]
3789-
>TypeParameter : TypeParameter
3790-
3791-
inferUnionTypes: boolean;
3792-
>inferUnionTypes : boolean
3793-
3794-
inferences: TypeInferences[];
3795-
>inferences : TypeInferences[]
3796-
>TypeInferences : TypeInferences
3797-
3798-
inferredTypes: Type[];
3799-
>inferredTypes : Type[]
3800-
>Type : Type
3801-
3802-
failedTypeParameterIndex?: number;
3803-
>failedTypeParameterIndex : number
38043772
}
38053773
interface DiagnosticMessage {
38063774
>DiagnosticMessage : DiagnosticMessage

tests/baselines/reference/APISample_linter.js

-11
Original file line numberDiff line numberDiff line change
@@ -1207,17 +1207,6 @@ declare module "typescript" {
12071207
interface TypeMapper {
12081208
(t: Type): Type;
12091209
}
1210-
interface TypeInferences {
1211-
primary: Type[];
1212-
secondary: Type[];
1213-
}
1214-
interface InferenceContext {
1215-
typeParameters: TypeParameter[];
1216-
inferUnionTypes: boolean;
1217-
inferences: TypeInferences[];
1218-
inferredTypes: Type[];
1219-
failedTypeParameterIndex?: number;
1220-
}
12211210
interface DiagnosticMessage {
12221211
key: string;
12231212
category: DiagnosticCategory;

tests/baselines/reference/APISample_linter.types

-32
Original file line numberDiff line numberDiff line change
@@ -3915,38 +3915,6 @@ declare module "typescript" {
39153915
>t : Type
39163916
>Type : Type
39173917
>Type : Type
3918-
}
3919-
interface TypeInferences {
3920-
>TypeInferences : TypeInferences
3921-
3922-
primary: Type[];
3923-
>primary : Type[]
3924-
>Type : Type
3925-
3926-
secondary: Type[];
3927-
>secondary : Type[]
3928-
>Type : Type
3929-
}
3930-
interface InferenceContext {
3931-
>InferenceContext : InferenceContext
3932-
3933-
typeParameters: TypeParameter[];
3934-
>typeParameters : TypeParameter[]
3935-
>TypeParameter : TypeParameter
3936-
3937-
inferUnionTypes: boolean;
3938-
>inferUnionTypes : boolean
3939-
3940-
inferences: TypeInferences[];
3941-
>inferences : TypeInferences[]
3942-
>TypeInferences : TypeInferences
3943-
3944-
inferredTypes: Type[];
3945-
>inferredTypes : Type[]
3946-
>Type : Type
3947-
3948-
failedTypeParameterIndex?: number;
3949-
>failedTypeParameterIndex : number
39503918
}
39513919
interface DiagnosticMessage {
39523920
>DiagnosticMessage : DiagnosticMessage

tests/baselines/reference/APISample_transform.js

-11
Original file line numberDiff line numberDiff line change
@@ -1208,17 +1208,6 @@ declare module "typescript" {
12081208
interface TypeMapper {
12091209
(t: Type): Type;
12101210
}
1211-
interface TypeInferences {
1212-
primary: Type[];
1213-
secondary: Type[];
1214-
}
1215-
interface InferenceContext {
1216-
typeParameters: TypeParameter[];
1217-
inferUnionTypes: boolean;
1218-
inferences: TypeInferences[];
1219-
inferredTypes: Type[];
1220-
failedTypeParameterIndex?: number;
1221-
}
12221211
interface DiagnosticMessage {
12231212
key: string;
12241213
category: DiagnosticCategory;

0 commit comments

Comments
 (0)