Skip to content

Commit fda479e

Browse files
committed
Mark conditional extends as Unmeasurable and use a conditional-opaque wildcard for type erasure
1 parent 4ecb563 commit fda479e

20 files changed

+674
-27
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -735,9 +735,12 @@ namespace ts {
735735
const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String);
736736
const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving);
737737

738+
// note: wildcards must be first types constructed due to an assumption made during union construction
739+
// if you reorder these, you must adjust that, too!
740+
const wildcardType = createIntrinsicType(TypeFlags.Any, "any");
741+
const opaqueWildcardType = createIntrinsicType(TypeFlags.Any, "any");
738742
const anyType = createIntrinsicType(TypeFlags.Any, "any");
739743
const autoType = createIntrinsicType(TypeFlags.Any, "any");
740-
const wildcardType = createIntrinsicType(TypeFlags.Any, "any");
741744
const errorType = createIntrinsicType(TypeFlags.Any, "error");
742745
const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType);
743746
const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic");
@@ -13629,7 +13632,7 @@ namespace ts {
1362913632
if (!(flags & TypeFlags.Never)) {
1363013633
includes |= flags & TypeFlags.IncludesMask;
1363113634
if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable;
13632-
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
13635+
if (type === wildcardType || type === opaqueWildcardType) includes |= TypeFlags.IncludesWildcard;
1363313636
if (!strictNullChecks && flags & TypeFlags.Nullable) {
1363413637
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
1363513638
}
@@ -13788,7 +13791,10 @@ namespace ts {
1378813791
const includes = addTypesToUnion(typeSet, 0, types);
1378913792
if (unionReduction !== UnionReduction.None) {
1379013793
if (includes & TypeFlags.AnyOrUnknown) {
13791-
return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType;
13794+
// A wildcard has the _lowest_ type id, as it's first types created, and it's made in priority order
13795+
// - a normal `wildcardType` should take priority over an `opaqueWildcardType` if both somehoe end up
13796+
// in the same construct. (And, likewise, both should have priority over a normal `any`)
13797+
return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? typeSet[0] : anyType : unknownType;
1379213798
}
1379313799
if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) {
1379413800
removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype));
@@ -13927,7 +13933,8 @@ namespace ts {
1392713933
}
1392813934
else {
1392913935
if (flags & TypeFlags.AnyOrUnknown) {
13930-
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
13936+
if (type === wildcardType || type === opaqueWildcardType) includes |= TypeFlags.IncludesWildcard;
13937+
if (type === opaqueWildcardType) includes |= TypeFlags.IncludesOpaqueWildcard;
1393113938
}
1393213939
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) {
1393313940
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
@@ -14111,7 +14118,7 @@ namespace ts {
1411114118
return neverType;
1411214119
}
1411314120
if (includes & TypeFlags.Any) {
14114-
return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType;
14121+
return includes & TypeFlags.IncludesWildcard ? includes & TypeFlags.IncludesOpaqueWildcard ? opaqueWildcardType : wildcardType : anyType;
1411514122
}
1411614123
if (!strictNullChecks && includes & TypeFlags.Nullable) {
1411714124
return includes & TypeFlags.Undefined ? undefinedType : nullType;
@@ -14313,6 +14320,7 @@ namespace ts {
1431314320
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && maybeNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
1431414321
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(<MappedType>type, noIndexSignatures) :
1431514322
type === wildcardType ? wildcardType :
14323+
type === opaqueWildcardType ? opaqueWildcardType :
1431614324
type.flags & TypeFlags.Unknown ? neverType :
1431714325
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
1431814326
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral, includeOrigin) :
@@ -14373,6 +14381,9 @@ namespace ts {
1437314381
mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) :
1437414382
errorType;
1437514383
}
14384+
if (contains(types, opaqueWildcardType)) {
14385+
return opaqueWildcardType;
14386+
}
1437614387
if (contains(types, wildcardType)) {
1437714388
return wildcardType;
1437814389
}
@@ -14890,6 +14901,9 @@ namespace ts {
1489014901
}
1489114902

1489214903
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, noUncheckedIndexedAccessCandidate?: boolean, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined {
14904+
if (objectType === opaqueWildcardType || indexType === opaqueWildcardType) {
14905+
return wildcardType;
14906+
}
1489314907
if (objectType === wildcardType || indexType === wildcardType) {
1489414908
return wildcardType;
1489514909
}
@@ -15023,7 +15037,7 @@ namespace ts {
1502315037
while (true) {
1502415038
const isUnwrapped = isTypicalNondistributiveConditional(root);
1502515039
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.checkType), mapper);
15026-
const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType);
15040+
const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType) || checkType === opaqueWildcardType;
1502715041
const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper);
1502815042
if (checkType === wildcardType || extendsType === wildcardType) {
1502915043
return wildcardType;
@@ -15045,7 +15059,7 @@ namespace ts {
1504515059
// Instantiate the extends type including inferences for 'infer T' type parameters
1504615060
const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType;
1504715061
// We attempt to resolve the conditional type only when the check and extends types are non-generic
15048-
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) {
15062+
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType) && inferredExtendsType !== opaqueWildcardType) {
1504915063
// Return falseType for a definitely false extends check. We check an instantiations of the two
1505015064
// types with type parameters mapped to the wildcard type, the most permissive instantiations
1505115065
// possible (the wildcard type is assignable to and from all types). If those are not related,
@@ -15782,7 +15796,7 @@ namespace ts {
1578215796
}
1578315797

1578415798
function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper {
15785-
return createTypeMapper(sources, /*targets*/ undefined);
15799+
return createTypeMapper(sources, map(sources, () => opaqueWildcardType));
1578615800
}
1578715801

1578815802
/**
@@ -15997,7 +16011,7 @@ namespace ts {
1599716011
const mappedTypeVariable = instantiateType(typeVariable, mapper);
1599816012
if (typeVariable !== mappedTypeVariable) {
1599916013
return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => {
16000-
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) {
16014+
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== opaqueWildcardType && t !== errorType) {
1600116015
if (!type.declaration.nameType) {
1600216016
if (isArrayType(t)) {
1600316017
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
@@ -16016,7 +16030,8 @@ namespace ts {
1601616030
}
1601716031
}
1601816032
// If the constraint type of the instantiation is the wildcard type, return the wildcard type.
16019-
return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments);
16033+
const instantiatedConstraint = instantiateType(getConstraintTypeFromMappedType(type), mapper);
16034+
return instantiatedConstraint === opaqueWildcardType ? opaqueWildcardType : instantiatedConstraint === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments);
1602016035
}
1602116036

1602216037
function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {
@@ -17161,7 +17176,7 @@ namespace ts {
1716117176
function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap<string, RelationComparisonResult>, errorReporter?: ErrorReporter) {
1716217177
const s = source.flags;
1716317178
const t = target.flags;
17164-
if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true;
17179+
if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType || source === opaqueWildcardType) return true;
1716517180
if (t & TypeFlags.Never) return false;
1716617181
if (s & TypeFlags.StringLike && t & TypeFlags.String) return true;
1716717182
if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral &&
@@ -18546,7 +18561,7 @@ namespace ts {
1854618561
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
1854718562
// and Y1 is related to Y2.
1854818563
const sourceParams = (source as ConditionalType).root.inferTypeParameters;
18549-
let sourceExtends = (<ConditionalType>source).extendsType;
18564+
let sourceExtends = instantiateType((<ConditionalType>source).extendsType, makeFunctionTypeMapper(reportUnmeasurableMarkers));
1855018565
let mapper: TypeMapper | undefined;
1855118566
if (sourceParams) {
1855218567
// If the source has infer type parameters, we instantiate them in the context of the target
@@ -21035,7 +21050,7 @@ namespace ts {
2103521050
if (!couldContainTypeVariables(target)) {
2103621051
return;
2103721052
}
21038-
if (source === wildcardType) {
21053+
if (source === wildcardType || source === opaqueWildcardType) {
2103921054
// We are inferring from an 'any' type. We want to infer this type for every type parameter
2104021055
// referenced in the target type, so we record it as the propagation type and infer from the
2104121056
// target to itself. Then, as we find candidates we substitute the propagation type.

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5074,6 +5074,8 @@ namespace ts {
50745074
IncludesWildcard = IndexedAccess,
50755075
/* @internal */
50765076
IncludesEmptyObject = Conditional,
5077+
/* @internal */
5078+
IncludesOpaqueWildcard = StringMapping,
50775079
}
50785080

50795081
export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;

tests/baselines/reference/complexRecursiveCollections.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ declare module Immutable {
11371137
>Seq : typeof Seq
11381138

11391139
function isSeq(maybeSeq: any): maybeSeq is Seq.Indexed<any> | Seq.Keyed<any, any>;
1140-
>isSeq : (maybeSeq: any) => maybeSeq is Indexed<any> | Keyed<any, any>
1140+
>isSeq : (maybeSeq: any) => maybeSeq is Keyed<any, any> | Indexed<any>
11411141
>maybeSeq : any
11421142
>Seq : any
11431143
>Seq : any
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
tests/cases/compiler/emptyClassSomehowNeverChecksConditionals.ts(5,7): error TS2322: Type 'EqualsTest<string>' is not assignable to type 'EqualsTest<number>'.
2+
Type 'any extends string ? 1 : 0' is not assignable to type 'any extends number ? 1 : 0'.
3+
Type '0 | 1' is not assignable to type 'any extends number ? 1 : 0'.
4+
Type '0' is not assignable to type 'any extends number ? 1 : 0'.
5+
tests/cases/compiler/emptyClassSomehowNeverChecksConditionals.ts(6,7): error TS2322: Type 'EqualsTest1<string>' is not assignable to type 'EqualsTest<number>'.
6+
Type 'A extends string ? 1 : 0' is not assignable to type 'A extends number ? 1 : 0'.
7+
Type '0 | 1' is not assignable to type 'A extends number ? 1 : 0'.
8+
Type '0' is not assignable to type 'A extends number ? 1 : 0'.
9+
tests/cases/compiler/emptyClassSomehowNeverChecksConditionals.ts(12,27): error TS2344: Type 'this' does not satisfy the constraint 'Model<typeof Model>'.
10+
Type 'Model<MClass>' is not assignable to type 'Model<typeof Model>'.
11+
Types of property 'set' are incompatible.
12+
Type '<K>(value: K extends MClass ? number : string) => void' is not assignable to type '<K>(value: K extends typeof Model ? number : string) => void'.
13+
Types of parameters 'value' and 'value' are incompatible.
14+
Type 'K extends typeof Model ? number : string' is not assignable to type 'K extends MClass ? number : string'.
15+
Type 'string | number' is not assignable to type 'K extends MClass ? number : string'.
16+
Type 'string' is not assignable to type 'K extends MClass ? number : string'.
17+
tests/cases/compiler/emptyClassSomehowNeverChecksConditionals.ts(20,28): error TS2344: Type 'this' does not satisfy the constraint 'ModelSub'.
18+
Type 'Model2<MClass>' is not assignable to type 'Model2<typeof ModelSub>'.
19+
Types of property 'set' are incompatible.
20+
Type '<K>(value: K extends MClass ? number : string) => void' is not assignable to type '<K>(value: K extends typeof ModelSub ? number : string) => void'.
21+
Types of parameters 'value' and 'value' are incompatible.
22+
Type 'K extends typeof ModelSub ? number : string' is not assignable to type 'K extends MClass ? number : string'.
23+
Type 'string | number' is not assignable to type 'K extends MClass ? number : string'.
24+
Type 'string' is not assignable to type 'K extends MClass ? number : string'.
25+
26+
27+
==== tests/cases/compiler/emptyClassSomehowNeverChecksConditionals.ts (4 errors) ====
28+
// quick distillation of conditionals which were previously erased by signature relating
29+
type EqualsTest<T> = <A>() => A extends T ? 1 : 0;
30+
type EqualsTest1<T> = <A>() => A extends T ? 1 : 0;
31+
32+
const x: EqualsTest<number> = undefined as any as EqualsTest<string>; // should error, obviously wrong
33+
~
34+
!!! error TS2322: Type 'EqualsTest<string>' is not assignable to type 'EqualsTest<number>'.
35+
!!! error TS2322: Type 'any extends string ? 1 : 0' is not assignable to type 'any extends number ? 1 : 0'.
36+
!!! error TS2322: Type '0 | 1' is not assignable to type 'any extends number ? 1 : 0'.
37+
!!! error TS2322: Type '0' is not assignable to type 'any extends number ? 1 : 0'.
38+
const y: EqualsTest<number> = undefined as any as EqualsTest1<string>; // same as the above, but seperate type aliases
39+
~
40+
!!! error TS2322: Type 'EqualsTest1<string>' is not assignable to type 'EqualsTest<number>'.
41+
!!! error TS2322: Type 'A extends string ? 1 : 0' is not assignable to type 'A extends number ? 1 : 0'.
42+
!!! error TS2322: Type '0 | 1' is not assignable to type 'A extends number ? 1 : 0'.
43+
!!! error TS2322: Type '0' is not assignable to type 'A extends number ? 1 : 0'.
44+
45+
// Slightly extended example using class inheritance
46+
type ModelId<M extends Model> = M; // just validates the input matches the `Model` type to issue an error
47+
export declare class Model<MClass extends typeof Model = typeof Model> {
48+
class: MClass;
49+
readonly ref: ModelId<this>;
50+
~~~~
51+
!!! error TS2344: Type 'this' does not satisfy the constraint 'Model<typeof Model>'.
52+
!!! error TS2344: Type 'Model<MClass>' is not assignable to type 'Model<typeof Model>'.
53+
!!! error TS2344: Types of property 'set' are incompatible.
54+
!!! error TS2344: Type '<K>(value: K extends MClass ? number : string) => void' is not assignable to type '<K>(value: K extends typeof Model ? number : string) => void'.
55+
!!! error TS2344: Types of parameters 'value' and 'value' are incompatible.
56+
!!! error TS2344: Type 'K extends typeof Model ? number : string' is not assignable to type 'K extends MClass ? number : string'.
57+
!!! error TS2344: Type 'string | number' is not assignable to type 'K extends MClass ? number : string'.
58+
!!! error TS2344: Type 'string' is not assignable to type 'K extends MClass ? number : string'.
59+
set<K>(value: K extends MClass ? number : string): void;
60+
}
61+
62+
// identical to the above, but with a no-op subclass
63+
type ModelId2<M extends ModelSub> = M;
64+
export declare class Model2<MClass extends typeof ModelSub = typeof ModelSub> {
65+
class: MClass;
66+
readonly ref: ModelId2<this>;
67+
~~~~
68+
!!! error TS2344: Type 'this' does not satisfy the constraint 'ModelSub'.
69+
!!! error TS2344: Type 'Model2<MClass>' is not assignable to type 'Model2<typeof ModelSub>'.
70+
!!! error TS2344: Types of property 'set' are incompatible.
71+
!!! error TS2344: Type '<K>(value: K extends MClass ? number : string) => void' is not assignable to type '<K>(value: K extends typeof ModelSub ? number : string) => void'.
72+
!!! error TS2344: Types of parameters 'value' and 'value' are incompatible.
73+
!!! error TS2344: Type 'K extends typeof ModelSub ? number : string' is not assignable to type 'K extends MClass ? number : string'.
74+
!!! error TS2344: Type 'string | number' is not assignable to type 'K extends MClass ? number : string'.
75+
!!! error TS2344: Type 'string' is not assignable to type 'K extends MClass ? number : string'.
76+
set<K>(value: K extends MClass ? number : string): void;
77+
}
78+
export declare class ModelSub extends Model2 {}

0 commit comments

Comments
 (0)