Skip to content

Commit ea74958

Browse files
committed
Defer simplification of conditionals on deferred type references
1 parent 2458c8a commit ea74958

File tree

6 files changed

+450
-36
lines changed

6 files changed

+450
-36
lines changed

src/compiler/checker.ts

Lines changed: 99 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9853,7 +9853,7 @@ namespace ts {
98539853
}
98549854

98559855
function getPropertiesOfType(type: Type): Symbol[] {
9856-
type = getApparentType(getReducedType(type));
9856+
type = getApparentType(type);
98579857
return type.flags & TypeFlags.UnionOrIntersection ?
98589858
getPropertiesOfUnionOrIntersectionType(<UnionType>type) :
98599859
getPropertiesOfObjectType(type);
@@ -10195,6 +10195,7 @@ namespace ts {
1019510195
* type itself.
1019610196
*/
1019710197
function getApparentType(type: Type): Type {
10198+
type = getReducedType(type);
1019810199
const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type;
1019910200
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(<MappedType>t) :
1020010201
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
@@ -10352,15 +10353,21 @@ namespace ts {
1035210353
* no constituent property has type 'never', but the intersection of the constituent property types is 'never'.
1035310354
*/
1035410355
function getReducedType(type: Type): Type {
10355-
if (type.flags & TypeFlags.Union && (<UnionType>type).objectFlags & ObjectFlags.ContainsIntersections) {
10356+
if (type.flags & TypeFlags.Union && (<UnionType>type).objectFlags & ObjectFlags.ContainsReducibles) {
1035610357
return (<UnionType>type).resolvedReducedType || ((<UnionType>type).resolvedReducedType = getReducedUnionType(<UnionType>type));
1035710358
}
1035810359
else if (type.flags & TypeFlags.Intersection) {
1035910360
if (!((<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) {
1036010361
(<IntersectionType>type).objectFlags |= ObjectFlags.IsNeverIntersectionComputed |
1036110362
(some(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isDiscriminantWithNeverType) ? ObjectFlags.IsNeverIntersection : 0);
1036210363
}
10363-
return (<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type;
10364+
if ((<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersection) {
10365+
return neverType;
10366+
}
10367+
return (<IntersectionType>type).resolvedReducedType || ((<IntersectionType>type).resolvedReducedType = getReducedIntersectionType(<IntersectionType>type));
10368+
}
10369+
else if (type.flags & TypeFlags.Conditional) {
10370+
return getReducedConditionalType(type as ConditionalType);
1036410371
}
1036510372
return type;
1036610373
}
@@ -10377,6 +10384,18 @@ namespace ts {
1037710384
return reduced;
1037810385
}
1037910386

10387+
function getReducedIntersectionType(type: IntersectionType) {
10388+
const reducedTypes = sameMap(type.types, getReducedType);
10389+
if (reducedTypes === type.types) {
10390+
return type;
10391+
}
10392+
const reduced = getIntersectionType(reducedTypes);
10393+
if (reduced.flags & TypeFlags.Intersection) {
10394+
(<IntersectionType>reduced).resolvedReducedType = reduced;
10395+
}
10396+
return reduced;
10397+
}
10398+
1038010399
function isDiscriminantWithNeverType(prop: Symbol) {
1038110400
return !(prop.flags & SymbolFlags.Optional) &&
1038210401
(getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant &&
@@ -10430,7 +10449,7 @@ namespace ts {
1043010449
* maps primitive types and type parameters are to their apparent types.
1043110450
*/
1043210451
function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] {
10433-
return getSignaturesOfStructuredType(getApparentType(getReducedType(type)), kind);
10452+
return getSignaturesOfStructuredType(getApparentType(type), kind);
1043410453
}
1043510454

1043610455
function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo | undefined {
@@ -10448,13 +10467,13 @@ namespace ts {
1044810467
// Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and
1044910468
// maps primitive types and type parameters are to their apparent types.
1045010469
function getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined {
10451-
return getIndexInfoOfStructuredType(getApparentType(getReducedType(type)), kind);
10470+
return getIndexInfoOfStructuredType(getApparentType(type), kind);
1045210471
}
1045310472

1045410473
// Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and
1045510474
// maps primitive types and type parameters are to their apparent types.
1045610475
function getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined {
10457-
return getIndexTypeOfStructuredType(getApparentType(getReducedType(type)), kind);
10476+
return getIndexTypeOfStructuredType(getApparentType(type), kind);
1045810477
}
1045910478

1046010479
function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined {
@@ -12035,7 +12054,7 @@ namespace ts {
1203512054
}
1203612055
}
1203712056
const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) |
12038-
(includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0);
12057+
(includes & TypeFlags.ReducibleNotUnion ? ObjectFlags.ContainsReducibles : 0);
1203912058
return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments);
1204012059
}
1204112060

@@ -12750,6 +12769,25 @@ namespace ts {
1275012769
return type[cache] = type;
1275112770
}
1275212771

12772+
function getReducedConditionalType(type: ConditionalType): Type {
12773+
const checkType = type.checkType;
12774+
const extendsType = getInferredExtendsTypeOfConditional(type);
12775+
12776+
if (!isGenericObjectType(checkType) && !isGenericIndexType(checkType) && !isGenericObjectType(extendsType) && !isGenericIndexType(extendsType)) {
12777+
const result = getConditionalSimplificationState(checkType, extendsType);
12778+
switch (result) {
12779+
case ConditionalSimplificationState.True:
12780+
return getReducedType(getInferredTrueTypeFromConditionalType(type));
12781+
case ConditionalSimplificationState.False:
12782+
return getReducedType(getFalseTypeFromConditionalType(type));
12783+
case ConditionalSimplificationState.Both:
12784+
return getUnionType([getReducedType(getInferredTrueTypeFromConditionalType(type)), getReducedType(getFalseTypeFromConditionalType(type))]);
12785+
// None: Fall out and return `type`
12786+
}
12787+
}
12788+
return type;
12789+
}
12790+
1275312791
function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
1275412792
const checkType = type.checkType;
1275512793
const extendsType = type.extendsType;
@@ -12821,7 +12859,7 @@ namespace ts {
1282112859
// In the following we resolve T[K] to the type of the property in T selected by K.
1282212860
// We treat boolean as different from other unions to improve errors;
1282312861
// skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'.
12824-
const apparentObjectType = getApparentType(getReducedType(objectType));
12862+
const apparentObjectType = getApparentType(objectType);
1282512863
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) {
1282612864
const propTypes: Type[] = [];
1282712865
let wasMissingProp = false;
@@ -12888,6 +12926,14 @@ namespace ts {
1288812926
return type;
1288912927
}
1289012928

12929+
function isTypeDeferredTypeReference(type: Type) {
12930+
return !!(getObjectFlags(type) & ObjectFlags.Reference) && !!(type as TypeReference).node;
12931+
}
12932+
12933+
function getInferredExtendsTypeOfConditional(type: ConditionalType) {
12934+
return instantiateType(type.root.extendsType, type.combinedMapper || type.mapper);
12935+
}
12936+
1289112937
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type {
1289212938
const checkType = instantiateType(root.checkType, mapper);
1289312939
const extendsType = instantiateType(root.extendsType, mapper);
@@ -12913,28 +12959,17 @@ namespace ts {
1291312959
// Instantiate the extends type including inferences for 'infer T' type parameters
1291412960
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
1291512961
// We attempt to resolve the conditional type only when the check and extends types are non-generic
12916-
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) {
12917-
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
12918-
return instantiateType(root.trueType, combinedMapper || mapper);
12919-
}
12920-
// Return union of trueType and falseType for 'any' since it matches anything
12921-
if (checkType.flags & TypeFlags.Any) {
12922-
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
12923-
}
12924-
// Return falseType for a definitely false extends check. We check an instantiations of the two
12925-
// types with type parameters mapped to the wildcard type, the most permissive instantiations
12926-
// possible (the wildcard type is assignable to and from all types). If those are not related,
12927-
// then no instantiations will be and we can just return the false branch type.
12928-
if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
12929-
return instantiateType(root.falseType, mapper);
12930-
}
12931-
// Return trueType for a definitely true extends check. We check instantiations of the two
12932-
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
12933-
// that has no constraint. This ensures that, for example, the type
12934-
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
12935-
// doesn't immediately resolve to 'string' instead of being deferred.
12936-
if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
12937-
return instantiateType(root.trueType, combinedMapper || mapper);
12962+
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)
12963+
&& !isTypeDeferredTypeReference(checkType) && !isTypeDeferredTypeReference(inferredExtendsType)) {
12964+
const result = getConditionalSimplificationState(checkType, inferredExtendsType);
12965+
switch (result) {
12966+
case ConditionalSimplificationState.True:
12967+
return instantiateType(root.trueType, combinedMapper || mapper);
12968+
case ConditionalSimplificationState.Both:
12969+
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
12970+
case ConditionalSimplificationState.False:
12971+
return instantiateType(root.falseType, mapper);
12972+
// None: Fall out and defer
1293812973
}
1293912974
}
1294012975
// Return a deferred type for a check that is neither definitely true nor definitely false
@@ -12950,6 +12985,39 @@ namespace ts {
1295012985
return result;
1295112986
}
1295212987

12988+
const enum ConditionalSimplificationState {
12989+
None = 0,
12990+
True = 1,
12991+
False = 2,
12992+
Both = True | False,
12993+
}
12994+
12995+
function getConditionalSimplificationState(checkType: Type, inferredExtendsType: Type): ConditionalSimplificationState {
12996+
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
12997+
return ConditionalSimplificationState.True;
12998+
}
12999+
// Return union of trueType and falseType for 'any' since it matches anything
13000+
if (checkType.flags & TypeFlags.Any) {
13001+
return ConditionalSimplificationState.Both;
13002+
}
13003+
// Return falseType for a definitely false extends check. We check an instantiations of the two
13004+
// types with type parameters mapped to the wildcard type, the most permissive instantiations
13005+
// possible (the wildcard type is assignable to and from all types). If those are not related,
13006+
// then no instantiations will be and we can just return the false branch type.
13007+
if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
13008+
return ConditionalSimplificationState.False;
13009+
}
13010+
// Return trueType for a definitely true extends check. We check instantiations of the two
13011+
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
13012+
// that has no constraint. This ensures that, for example, the type
13013+
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
13014+
// doesn't immediately resolve to 'string' instead of being deferred.
13015+
if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
13016+
return ConditionalSimplificationState.True;
13017+
}
13018+
return ConditionalSimplificationState.None;
13019+
}
13020+
1295313021
function getTrueTypeFromConditionalType(type: ConditionalType) {
1295413022
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper));
1295513023
}
@@ -14986,7 +15054,7 @@ namespace ts {
1498615054
while (true) {
1498715055
const t = isFreshLiteralType(type) ? (<FreshableType>type).regularType :
1498815056
getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).node ? createTypeReference((<TypeReference>type).target, getTypeArguments(<TypeReference>type)) :
14989-
type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) :
15057+
type.flags & TypeFlags.Reducible ? getSimplifiedType(getReducedType(type), writing) :
1499015058
type.flags & TypeFlags.Substitution ? writing ? (<SubstitutionType>type).baseType : (<SubstitutionType>type).substitute :
1499115059
type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) :
1499215060
type;

src/compiler/types.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4376,6 +4376,10 @@ namespace ts {
43764376
/* @internal */
43774377
Simplifiable = IndexedAccess | Conditional,
43784378
/* @internal */
4379+
ReducibleNotUnion = Intersection | Conditional,
4380+
/* @internal */
4381+
Reducible = ReducibleNotUnion | Union,
4382+
/* @internal */
43794383
Substructure = Object | Union | Intersection | Index | IndexedAccess | Conditional | Substitution,
43804384
// 'Narrowable' types are types where narrowing actually narrows.
43814385
// This *should* be every type other than null, undefined, void, and never
@@ -4385,7 +4389,7 @@ namespace ts {
43854389
NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | StructuredOrInstantiable,
43864390
// The following flags are aggregated during union and intersection type construction
43874391
/* @internal */
4388-
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive,
4392+
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive | Conditional,
43894393
// The following flags are used for different purposes during union and intersection type construction
43904394
/* @internal */
43914395
IncludesStructuredOrInstantiable = TypeParameter,
@@ -4394,7 +4398,7 @@ namespace ts {
43944398
/* @internal */
43954399
IncludesWildcard = IndexedAccess,
43964400
/* @internal */
4397-
IncludesEmptyObject = Conditional,
4401+
IncludesEmptyObject = 1 << 27,
43984402
}
43994403

44004404
export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;
@@ -4511,7 +4515,7 @@ namespace ts {
45114515
/* @internal */
45124516
CouldContainTypeVariables = 1 << 27, // Type could contain a type variable
45134517
/* @internal */
4514-
ContainsIntersections = 1 << 28, // Union contains intersections
4518+
ContainsReducibles = 1 << 28, // Union contains intersections
45154519
/* @internal */
45164520
IsNeverIntersectionComputed = 1 << 28, // IsNeverLike flag has been computed
45174521
/* @internal */
@@ -4634,11 +4638,11 @@ namespace ts {
46344638
resolvedStringIndexType: IndexType;
46354639
/* @internal */
46364640
resolvedBaseConstraint: Type;
4641+
/* @internal */
4642+
resolvedReducedType: Type;
46374643
}
46384644

46394645
export interface UnionType extends UnionOrIntersectionType {
4640-
/* @internal */
4641-
resolvedReducedType: Type;
46424646
}
46434647

46444648
export interface IntersectionType extends UnionOrIntersectionType {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//// [recursiveArrayNotCircular.ts]
2+
type Action<T, P> = P extends void ? { type : T } : { type: T, payload: P }
3+
4+
enum ActionType {
5+
Foo,
6+
Bar,
7+
Baz,
8+
Batch
9+
}
10+
11+
type ReducerAction =
12+
| Action<ActionType.Bar, number>
13+
| Action<ActionType.Baz, boolean>
14+
| Action<ActionType.Foo, string>
15+
| Action<ActionType.Batch, ReducerAction[]>
16+
17+
function assertNever(a: never): never {
18+
throw new Error("Unreachable!");
19+
}
20+
21+
function reducer(action: ReducerAction): void {
22+
switch(action.type) {
23+
case ActionType.Bar:
24+
const x: number = action.payload;
25+
break;
26+
case ActionType.Baz:
27+
const y: boolean = action.payload;
28+
break;
29+
case ActionType.Foo:
30+
const z: string = action.payload;
31+
break;
32+
case ActionType.Batch:
33+
action.payload.map(reducer);
34+
break;
35+
default: return assertNever(action);
36+
}
37+
}
38+
39+
//// [recursiveArrayNotCircular.js]
40+
var ActionType;
41+
(function (ActionType) {
42+
ActionType[ActionType["Foo"] = 0] = "Foo";
43+
ActionType[ActionType["Bar"] = 1] = "Bar";
44+
ActionType[ActionType["Baz"] = 2] = "Baz";
45+
ActionType[ActionType["Batch"] = 3] = "Batch";
46+
})(ActionType || (ActionType = {}));
47+
function assertNever(a) {
48+
throw new Error("Unreachable!");
49+
}
50+
function reducer(action) {
51+
switch (action.type) {
52+
case ActionType.Bar:
53+
var x = action.payload;
54+
break;
55+
case ActionType.Baz:
56+
var y = action.payload;
57+
break;
58+
case ActionType.Foo:
59+
var z = action.payload;
60+
break;
61+
case ActionType.Batch:
62+
action.payload.map(reducer);
63+
break;
64+
default: return assertNever(action);
65+
}
66+
}

0 commit comments

Comments
 (0)