Skip to content

Commit b7881a2

Browse files
weswighamRyanCavanaugh
authored andcommitted
Unify substitution type any handling into costruction and instantiation (#30592)
* Unify substitution type `any` handling into costruction and instantiation * Strengthen supertype reduction check to reduce breakage * Rename conditional type fields per convention * Explicitly handle anyish signatures in compareSignaturesRelated so strict variance doesnt kill subtyping * Allow tuple expansions to an `any` rest to be considered an `any` signature as well
1 parent 307bf39 commit b7881a2

17 files changed

+1280
-66
lines changed

src/compiler/checker.ts

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3492,8 +3492,8 @@ namespace ts {
34923492
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
34933493
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
34943494
context.inferTypeParameters = saveInferTypeParameters;
3495-
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
3496-
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
3495+
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
3496+
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
34973497
context.approximateLength += 15;
34983498
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
34993499
}
@@ -7477,6 +7477,10 @@ namespace ts {
74777477
}
74787478

74797479
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
7480+
return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined;
7481+
}
7482+
7483+
function getConstraintFromIndexedAccess(type: IndexedAccessType) {
74807484
const objectType = getConstraintOfType(type.objectType) || type.objectType;
74817485
if (objectType !== type.objectType) {
74827486
const constraint = getIndexedAccessType(objectType, type.indexType, /*accessNode*/ undefined, errorType);
@@ -7488,24 +7492,14 @@ namespace ts {
74887492
return baseConstraint && baseConstraint !== type ? baseConstraint : undefined;
74897493
}
74907494

7491-
function getDefaultConstraintOfTrueBranchOfConditionalType(root: ConditionalRoot, combinedMapper: TypeMapper | undefined, mapper: TypeMapper | undefined) {
7492-
const rootTrueType = root.trueType;
7493-
const rootTrueConstraint = !(rootTrueType.flags & TypeFlags.Substitution)
7494-
? rootTrueType
7495-
: instantiateType(((<SubstitutionType>rootTrueType).substitute), combinedMapper || mapper).flags & TypeFlags.AnyOrUnknown
7496-
? (<SubstitutionType>rootTrueType).typeVariable
7497-
: getIntersectionType([(<SubstitutionType>rootTrueType).substitute, (<SubstitutionType>rootTrueType).typeVariable]);
7498-
return instantiateType(rootTrueConstraint, combinedMapper || mapper);
7499-
}
7500-
75017495
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
75027496
if (!type.resolvedDefaultConstraint) {
75037497
// An `any` branch of a conditional type would normally be viral - specifically, without special handling here,
75047498
// a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to
75057499
// just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type,
75067500
// in effect treating `any` like `never` rather than `unknown` in this location.
7507-
const trueConstraint = getDefaultConstraintOfTrueBranchOfConditionalType(type.root, type.combinedMapper, type.mapper);
7508-
const falseConstraint = getFalseTypeFromConditionalType(type);
7501+
const trueConstraint = getInferredTrueTypeFromConditionalType(type);
7502+
const falseConstraint = type.falseType;
75097503
type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]);
75107504
}
75117505
return type.resolvedDefaultConstraint;
@@ -7537,10 +7531,14 @@ namespace ts {
75377531
return undefined;
75387532
}
75397533

7540-
function getConstraintOfConditionalType(type: ConditionalType) {
7534+
function getConstraintFromConditionalType(type: ConditionalType) {
75417535
return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type);
75427536
}
75437537

7538+
function getConstraintOfConditionalType(type: ConditionalType) {
7539+
return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined;
7540+
}
7541+
75447542
function getUnionConstraintOfIntersection(type: IntersectionType, targetIsUnion: boolean) {
75457543
let constraints: Type[] | undefined;
75467544
let hasDisjointDomainType = false;
@@ -7617,7 +7615,7 @@ namespace ts {
76177615
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
76187616
return circularConstraintType;
76197617
}
7620-
if (constraintDepth === 50) {
7618+
if (constraintDepth >= 50) {
76217619
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
76227620
// very high likelyhood we're dealing with an infinite generic type that perpetually generates
76237621
// new type identities as we descend into it. We stop the recursion here and mark this type
@@ -7684,8 +7682,11 @@ namespace ts {
76847682
return baseIndexedAccess && baseIndexedAccess !== errorType ? getBaseConstraint(baseIndexedAccess) : undefined;
76857683
}
76867684
if (t.flags & TypeFlags.Conditional) {
7687-
const constraint = getConstraintOfConditionalType(<ConditionalType>t);
7688-
return constraint && getBaseConstraint(constraint);
7685+
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
7686+
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
7687+
const result = constraint && getBaseConstraint(constraint);
7688+
constraintDepth--;
7689+
return result;
76897690
}
76907691
if (t.flags & TypeFlags.Substitution) {
76917692
return getBaseConstraint((<SubstitutionType>t).substitute);
@@ -8866,6 +8867,9 @@ namespace ts {
88668867
}
88678868

88688869
function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
8870+
if (substitute.flags & TypeFlags.AnyOrUnknown) {
8871+
return typeVariable;
8872+
}
88698873
const result = <SubstitutionType>createType(TypeFlags.Substitution);
88708874
result.typeVariable = typeVariable;
88718875
result.substitute = substitute;
@@ -10161,7 +10165,7 @@ namespace ts {
1016110165
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
1016210166
if (falseType.flags & TypeFlags.Never && isTypeIdenticalTo(getActualTypeVariable(trueType), getActualTypeVariable(checkType))) {
1016310167
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
10164-
return getDefaultConstraintOfTrueBranchOfConditionalType(root, /*combinedMapper*/ undefined, mapper);
10168+
return trueType;
1016510169
}
1016610170
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
1016710171
return neverType;
@@ -10172,7 +10176,7 @@ namespace ts {
1017210176
return neverType;
1017310177
}
1017410178
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
10175-
return falseType; // TODO: Intersect negated `extends` type here
10179+
return falseType;
1017610180
}
1017710181
}
1017810182

@@ -10227,21 +10231,15 @@ namespace ts {
1022710231
result.extendsType = extendsType;
1022810232
result.mapper = mapper;
1022910233
result.combinedMapper = combinedMapper;
10230-
if (!combinedMapper) {
10231-
result.resolvedTrueType = trueType;
10232-
result.resolvedFalseType = falseType;
10233-
}
10234+
result.trueType = trueType;
10235+
result.falseType = falseType;
1023410236
result.aliasSymbol = root.aliasSymbol;
1023510237
result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
1023610238
return result;
1023710239
}
1023810240

10239-
function getTrueTypeFromConditionalType(type: ConditionalType) {
10240-
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper));
10241-
}
10242-
10243-
function getFalseTypeFromConditionalType(type: ConditionalType) {
10244-
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
10241+
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
10242+
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = instantiateType(type.root.trueType, type.combinedMapper || type.mapper));
1024510243
}
1024610244

1024710245
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined {
@@ -11182,7 +11180,11 @@ namespace ts {
1118211180
return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((<SubstitutionType>type).substitute, mapper));
1118311181
}
1118411182
else {
11185-
return maybeVariable;
11183+
const sub = instantiateType((<SubstitutionType>type).substitute, mapper);
11184+
if (sub.flags & TypeFlags.AnyOrUnknown || isTypeSubtypeOf(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) {
11185+
return maybeVariable;
11186+
}
11187+
return sub;
1118611188
}
1118711189
}
1118811190
return type;
@@ -11727,6 +11729,15 @@ namespace ts {
1172711729

1172811730
type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;
1172911731

11732+
/**
11733+
* Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any`
11734+
*/
11735+
function isAnySignature(s: Signature) {
11736+
return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 &&
11737+
s.hasRestParameter && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) &&
11738+
isTypeAny(getReturnTypeOfSignature(s));
11739+
}
11740+
1173011741
/**
1173111742
* See signatureRelatedTo, compareSignaturesIdentical
1173211743
*/
@@ -11742,6 +11753,10 @@ namespace ts {
1174211753
return Ternary.True;
1174311754
}
1174411755

11756+
if (isAnySignature(target)) {
11757+
return Ternary.True;
11758+
}
11759+
1174511760
const targetCount = getParameterCount(target);
1174611761
if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) {
1174711762
return Ternary.False;
@@ -12706,8 +12721,8 @@ namespace ts {
1270612721
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive) {
1270712722
if (result = isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType, /*reportErrors*/ false)) {
1270812723
if (result &= isRelatedTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType, /*reportErrors*/ false)) {
12709-
if (result &= isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
12710-
if (result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
12724+
if (result &= isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, /*reportErrors*/ false)) {
12725+
if (result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, /*reportErrors*/ false)) {
1271112726
return result;
1271212727
}
1271312728
}
@@ -12828,7 +12843,7 @@ namespace ts {
1282812843
return result;
1282912844
}
1283012845
}
12831-
const constraint = getConstraintOfType(<TypeParameter>source);
12846+
const constraint = getConstraintOfType(<TypeVariable>source);
1283212847
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
1283312848
// A type variable with no constraint is not related to the non-primitive object type.
1283412849
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
@@ -12860,8 +12875,8 @@ namespace ts {
1286012875
// and Y1 is related to Y2.
1286112876
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
1286212877
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
12863-
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
12864-
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
12878+
if (result = isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, reportErrors)) {
12879+
result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, reportErrors);
1286512880
}
1286612881
if (result) {
1286712882
errorInfo = saveErrorInfo;
@@ -14696,12 +14711,12 @@ namespace ts {
1469614711
else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) {
1469714712
inferFromTypes((<ConditionalType>source).checkType, (<ConditionalType>target).checkType);
1469814713
inferFromTypes((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType);
14699-
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target));
14700-
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
14714+
inferFromTypes((<ConditionalType>source).trueType, (<ConditionalType>target).trueType);
14715+
inferFromTypes((<ConditionalType>source).falseType, (<ConditionalType>target).falseType);
1470114716
}
1470214717
else if (target.flags & TypeFlags.Conditional && !contravariant) {
14703-
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
14704-
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
14718+
inferFromTypes(source, (<ConditionalType>target).trueType);
14719+
inferFromTypes(source, (<ConditionalType>target).falseType);
1470514720
}
1470614721
else if (target.flags & TypeFlags.UnionOrIntersection) {
1470714722
for (const t of (<UnionOrIntersectionType>target).types) {

src/compiler/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4310,8 +4310,10 @@ namespace ts {
43104310
root: ConditionalRoot;
43114311
checkType: Type;
43124312
extendsType: Type;
4313-
resolvedTrueType?: Type;
4314-
resolvedFalseType?: Type;
4313+
trueType: Type;
4314+
falseType: Type;
4315+
/* @internal */
4316+
resolvedInferredTrueType?: Type; // The `trueType` instantiated with the `combinedMapper`, if present
43154317
/* @internal */
43164318
resolvedDefaultConstraint?: Type;
43174319
/* @internal */

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,8 +2382,8 @@ declare namespace ts {
23822382
root: ConditionalRoot;
23832383
checkType: Type;
23842384
extendsType: Type;
2385-
resolvedTrueType?: Type;
2386-
resolvedFalseType?: Type;
2385+
trueType: Type;
2386+
falseType: Type;
23872387
}
23882388
interface SubstitutionType extends InstantiableType {
23892389
typeVariable: TypeVariable;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,8 +2382,8 @@ declare namespace ts {
23822382
root: ConditionalRoot;
23832383
checkType: Type;
23842384
extendsType: Type;
2385-
resolvedTrueType?: Type;
2386-
resolvedFalseType?: Type;
2385+
trueType: Type;
2386+
falseType: Type;
23872387
}
23882388
interface SubstitutionType extends InstantiableType {
23892389
typeVariable: TypeVariable;

tests/baselines/reference/conditionalTypes1.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,6 @@ declare function f5<T extends Options, K extends string>(p: K): Extract<T, {
509509
}>;
510510
declare let x0: {
511511
k: "a";
512-
} & {
513-
k: "a";
514512
a: number;
515513
};
516514
declare type OptionsOfKind<K extends Options["k"]> = Extract<Options, {

tests/baselines/reference/conditionalTypes1.types

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type T02 = Exclude<string | number | (() => void), Function>; // string | numbe
99
>T02 : string | number
1010

1111
type T03 = Extract<string | number | (() => void), Function>; // () => void
12-
>T03 : Function & (() => void)
12+
>T03 : () => void
1313

1414
type T04 = NonNullable<string | number | undefined>; // string | number
1515
>T04 : string | number
@@ -113,7 +113,7 @@ type T10 = Exclude<Options, { k: "a" | "b" }>; // { k: "c", c: boolean }
113113
>k : "a" | "b"
114114

115115
type T11 = Extract<Options, { k: "a" | "b" }>; // { k: "a", a: number } | { k: "b", b: string }
116-
>T11 : ({ k: "a" | "b"; } & { k: "a"; a: number; }) | ({ k: "a" | "b"; } & { k: "b"; b: string; })
116+
>T11 : { k: "a"; a: number; } | { k: "b"; b: string; }
117117
>k : "a" | "b"
118118

119119
type T12 = Exclude<Options, { k: "a" } | { k: "b" }>; // { k: "c", c: boolean }
@@ -122,7 +122,7 @@ type T12 = Exclude<Options, { k: "a" } | { k: "b" }>; // { k: "c", c: boolean }
122122
>k : "b"
123123

124124
type T13 = Extract<Options, { k: "a" } | { k: "b" }>; // { k: "a", a: number } | { k: "b", b: string }
125-
>T13 : ({ k: "a"; } & { k: "a"; a: number; }) | ({ k: "b"; } & { k: "a"; a: number; }) | ({ k: "a"; } & { k: "b"; b: string; }) | ({ k: "b"; } & { k: "b"; b: string; })
125+
>T13 : { k: "a"; a: number; } | { k: "b"; b: string; }
126126
>k : "a"
127127
>k : "b"
128128

@@ -140,8 +140,8 @@ declare function f5<T extends Options, K extends string>(p: K): Extract<T, { k:
140140
>k : K
141141

142142
let x0 = f5("a"); // { k: "a", a: number }
143-
>x0 : { k: "a"; } & { k: "a"; a: number; }
144-
>f5("a") : { k: "a"; } & { k: "a"; a: number; }
143+
>x0 : { k: "a"; a: number; }
144+
>f5("a") : { k: "a"; a: number; }
145145
>f5 : <T extends Options, K extends string>(p: K) => Extract<T, { k: K; }>
146146
>"a" : "a"
147147

@@ -150,13 +150,13 @@ type OptionsOfKind<K extends Options["k"]> = Extract<Options, { k: K }>;
150150
>k : K
151151

152152
type T16 = OptionsOfKind<"a" | "b">; // { k: "a", a: number } | { k: "b", b: string }
153-
>T16 : ({ k: "a" | "b"; } & { k: "a"; a: number; }) | ({ k: "a" | "b"; } & { k: "b"; b: string; })
153+
>T16 : { k: "a"; a: number; } | { k: "b"; b: string; }
154154

155155
type Select<T, K extends keyof T, V extends T[K]> = Extract<T, { [P in K]: V }>;
156156
>Select : Extract<T, { [P in K]: V; }>
157157

158158
type T17 = Select<Options, "k", "a" | "b">; // // { k: "a", a: number } | { k: "b", b: string }
159-
>T17 : ({ k: "a" | "b"; } & { k: "a"; a: number; }) | ({ k: "a" | "b"; } & { k: "b"; b: string; })
159+
>T17 : { k: "a"; a: number; } | { k: "b"; b: string; }
160160

161161
type TypeName<T> =
162162
>TypeName : TypeName<T>

0 commit comments

Comments
 (0)