Skip to content

Commit 81adb5d

Browse files
committed
Allow self refential type aliases
1 parent f6155f8 commit 81adb5d

14 files changed

+444
-91
lines changed

src/compiler/checker.ts

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5028,6 +5028,17 @@ namespace ts {
50285028
return true;
50295029
}
50305030

5031+
/**
5032+
* Checks the circularity stack like `pushTypeResolution` without making any edits
5033+
*/
5034+
function peekTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName) {
5035+
const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName);
5036+
if (resolutionCycleStartIndex >= 0) {
5037+
return false;
5038+
}
5039+
return true;
5040+
}
5041+
50315042
function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number {
50325043
for (let i = resolutionTargets.length - 1; i >= 0; i--) {
50335044
if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) {
@@ -6531,6 +6542,12 @@ namespace ts {
65316542
// If typeNode is missing, we will error in checkJSDocTypedefTag.
65326543
let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType;
65336544

6545+
if (type.flags & TypeFlags.Substitution) {
6546+
// Trigger resolution of any deferred substiutes that are directly assigned to the alias so they
6547+
// are marked as circular and the alias becomes `any`
6548+
void (type as SubstitutionType).substitute;
6549+
}
6550+
65346551
if (popTypeResolution()) {
65356552
const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
65366553
if (typeParameters) {
@@ -9145,6 +9162,18 @@ namespace ts {
91459162
* declared type. Instantiations are cached using the type identities of the type arguments as the key.
91469163
*/
91479164
function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type {
9165+
if (!peekTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) {
9166+
const links = getNodeLinks(node);
9167+
let cb = links.substituteCallback;
9168+
if (!cb) {
9169+
cb = links.substituteCallback = () => getTypeFromTypeAliasReferenceWorker(node, symbol, typeArguments);
9170+
}
9171+
return getDeferredSubstitutionType(cb, symbol, typeArguments);
9172+
}
9173+
return getTypeFromTypeAliasReferenceWorker(node, symbol, typeArguments);
9174+
}
9175+
9176+
function getTypeFromTypeAliasReferenceWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined) {
91489177
const type = getDeclaredTypeOfSymbol(symbol);
91499178
const typeParameters = getSymbolLinks(symbol).typeParameters;
91509179
if (typeParameters) {
@@ -9284,6 +9313,33 @@ namespace ts {
92849313
return result;
92859314
}
92869315

9316+
/**
9317+
* Manufactures a substitute whose typeVariable/substitute are the same type,
9318+
* and whose values are not generated until accessed.
9319+
*/
9320+
function getDeferredSubstitutionType(cb: DeferredSubsCallback, aliasSymbol: Symbol, aliasTypeArguments: readonly Type[] | undefined) {
9321+
const id = cb.deferredSubsId;
9322+
if (id) {
9323+
return substitutionTypes.get(id)!;
9324+
}
9325+
const result = <SubstitutionType>createType(TypeFlags.Substitution);
9326+
result.aliasSymbol = aliasSymbol; // Yep - a substitute with a type alias. Without this, a bunch of anonymous self referential types would probably blow up in printback
9327+
result.aliasTypeArguments = aliasTypeArguments;
9328+
let cached: Type;
9329+
Object.defineProperty(result, "typeVariable", {
9330+
get() {
9331+
return cached || (cached = cb());
9332+
}
9333+
});
9334+
Object.defineProperty(result, "substitute", {
9335+
get() {
9336+
return cached || (cached = cb());
9337+
}
9338+
});
9339+
substitutionTypes.set(cb.deferredSubsId = "" + getTypeId(result), result);
9340+
return result;
9341+
}
9342+
92879343
function isUnaryTupleTypeNode(node: TypeNode) {
92889344
return node.kind === SyntaxKind.TupleType && (<TupleTypeNode>node).elementTypes.length === 1;
92899345
}
@@ -11643,6 +11699,11 @@ namespace ts {
1164311699
return result;
1164411700
}
1164511701

11702+
var mapperIdCount: number;
11703+
function getTypeMapperId(mapper: TypeMapper) {
11704+
return mapper.id || (mapper.id = (mapperIdCount = (mapperIdCount || 0) + 1));
11705+
}
11706+
1164611707
function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type {
1164711708
const flags = type.flags;
1164811709
if (flags & TypeFlags.TypeParameter) {
@@ -11687,16 +11748,28 @@ namespace ts {
1168711748
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
1168811749
}
1168911750
if (flags & TypeFlags.Substitution) {
11690-
const maybeVariable = instantiateType((<SubstitutionType>type).typeVariable, mapper);
11751+
const sub = (type as SubstitutionType);
11752+
if (substitutionTypes.has("" + getTypeId(sub))) {
11753+
// substitution is deferred - wrap it
11754+
const mapperId = "" + getTypeMapperId(mapper);
11755+
let cb = sub.deferredInstantiationCallbacks && sub.deferredInstantiationCallbacks.get(mapperId);
11756+
if (!cb) {
11757+
cb = () => instantiateType(sub.substitute, mapper);
11758+
(sub.deferredInstantiationCallbacks || (sub.deferredInstantiationCallbacks = createMap())).set(mapperId, cb);
11759+
}
11760+
11761+
return getDeferredSubstitutionType(cb, sub.aliasSymbol!, instantiateTypes(sub.aliasTypeArguments, mapper));
11762+
}
11763+
const maybeVariable = instantiateType(sub.typeVariable, mapper);
1169111764
if (maybeVariable.flags & TypeFlags.TypeVariable) {
11692-
return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((<SubstitutionType>type).substitute, mapper));
11765+
return getSubstitutionType(maybeVariable as TypeVariable, instantiateType(sub.substitute, mapper));
1169311766
}
1169411767
else {
11695-
const sub = instantiateType((<SubstitutionType>type).substitute, mapper);
11696-
if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) {
11768+
const newSub = instantiateType(sub.substitute, mapper);
11769+
if (newSub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(newSub))) {
1169711770
return maybeVariable;
1169811771
}
11699-
return sub;
11772+
return newSub;
1170011773
}
1170111774
}
1170211775
return type;

src/compiler/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3953,6 +3953,13 @@ namespace ts {
39533953
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
39543954
deferredNodes?: Map<Node>; // Set of nodes whose checking has been deferred
39553955
capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement
3956+
substituteCallback?: DeferredSubsCallback; // For a type alias reference, the callback manufactured to create the reference to the alias in a deferred way
3957+
}
3958+
3959+
/* @internal */
3960+
export interface DeferredSubsCallback {
3961+
(): Type;
3962+
deferredSubsId?: string;
39563963
}
39573964

39583965
export const enum TypeFlags {
@@ -4428,6 +4435,8 @@ namespace ts {
44284435
export interface SubstitutionType extends InstantiableType {
44294436
typeVariable: TypeVariable; // Target type variable
44304437
substitute: Type; // Type to substitute for type parameter
4438+
/* @internal */
4439+
deferredInstantiationCallbacks?: Map<DeferredSubsCallback>; // list of callbacks used to instantiate in a deferred way - stored to cache
44314440
}
44324441

44334442
/* @internal */
@@ -4490,7 +4499,10 @@ namespace ts {
44904499
}
44914500

44924501
/* @internal */
4493-
export type TypeMapper = (t: TypeParameter) => Type;
4502+
export interface TypeMapper {
4503+
(t: TypeParameter): Type;
4504+
id?: number; // lazily assigned
4505+
}
44944506

44954507
export const enum InferencePriority {
44964508
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type

tests/baselines/reference/circularTypeofWithVarOrFunc.errors.txt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(1,6): error TS2456: Type alias 'typeAlias1' circularly references itself.
22
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(2,5): error TS2502: 'varOfAliasedType1' is referenced directly or indirectly in its own type annotation.
3-
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(4,5): error TS2502: 'varOfAliasedType2' is referenced directly or indirectly in its own type annotation.
43
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(5,6): error TS2456: Type alias 'typeAlias2' circularly references itself.
5-
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(7,18): error TS2577: Return type annotation circularly references itself.
64
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(9,6): error TS2456: Type alias 'typeAlias3' circularly references itself.
75
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(18,6): error TS2456: Type alias 'R' circularly references itself.
86
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(19,29): error TS2577: Return type annotation circularly references itself.
97
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(25,6): error TS2456: Type alias 'R2' circularly references itself.
108
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(26,15): error TS2577: Return type annotation circularly references itself.
119

1210

13-
==== tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts (10 errors) ====
11+
==== tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts (8 errors) ====
1412
type typeAlias1 = typeof varOfAliasedType1;
1513
~~~~~~~~~~
1614
!!! error TS2456: Type alias 'typeAlias1' circularly references itself.
@@ -19,15 +17,11 @@ tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarO
1917
!!! error TS2502: 'varOfAliasedType1' is referenced directly or indirectly in its own type annotation.
2018

2119
var varOfAliasedType2: typeAlias2;
22-
~~~~~~~~~~~~~~~~~
23-
!!! error TS2502: 'varOfAliasedType2' is referenced directly or indirectly in its own type annotation.
2420
type typeAlias2 = typeof varOfAliasedType2;
2521
~~~~~~~~~~
2622
!!! error TS2456: Type alias 'typeAlias2' circularly references itself.
2723

2824
function func(): typeAlias3 { return null; }
29-
~~~~~~~~~~
30-
!!! error TS2577: Return type annotation circularly references itself.
3125
var varOfAliasedType3 = func();
3226
type typeAlias3 = typeof varOfAliasedType3;
3327
~~~~~~~~~~

tests/baselines/reference/circularTypeofWithVarOrFunc.types

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@ var varOfAliasedType1: typeAlias1;
77
>varOfAliasedType1 : any
88

99
var varOfAliasedType2: typeAlias2;
10-
>varOfAliasedType2 : any
10+
>varOfAliasedType2 : typeAlias2
1111

1212
type typeAlias2 = typeof varOfAliasedType2;
1313
>typeAlias2 : any
14-
>varOfAliasedType2 : any
14+
>varOfAliasedType2 : typeAlias2
1515

1616
function func(): typeAlias3 { return null; }
17-
>func : () => any
17+
>func : () => typeAlias3
1818
>null : null
1919

2020
var varOfAliasedType3 = func();
21-
>varOfAliasedType3 : any
22-
>func() : any
23-
>func : () => any
21+
>varOfAliasedType3 : typeAlias3
22+
>func() : typeAlias3
23+
>func : () => typeAlias3
2424

2525
type typeAlias3 = typeof varOfAliasedType3;
2626
>typeAlias3 : any
27-
>varOfAliasedType3 : any
27+
>varOfAliasedType3 : typeAlias3
2828

2929
// Repro from #26104
3030

tests/baselines/reference/directDependenceBetweenTypeAliases.errors.txt

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,9 @@ tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(
22
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(5,6): error TS2456: Type alias 'T0_1' circularly references itself.
33
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(6,6): error TS2456: Type alias 'T0_2' circularly references itself.
44
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(7,6): error TS2456: Type alias 'T0_3' circularly references itself.
5-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(11,6): error TS2456: Type alias 'T1' circularly references itself.
6-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(14,6): error TS2456: Type alias 'T2' circularly references itself.
7-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(16,6): error TS2456: Type alias 'T2_1' circularly references itself.
8-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(19,6): error TS2456: Type alias 'T3' circularly references itself.
9-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(22,6): error TS2456: Type alias 'T4' circularly references itself.
10-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(25,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
11-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(26,6): error TS2456: Type alias 'T5' circularly references itself.
12-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(29,6): error TS2456: Type alias 'T6' circularly references itself.
13-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(30,6): error TS2456: Type alias 'T7' circularly references itself.
14-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(31,5): error TS2502: 'yy' is referenced directly or indirectly in its own type annotation.
15-
tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(32,6): error TS2456: Type alias 'T8' circularly references itself.
165

176

18-
==== tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts (15 errors) ====
7+
==== tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts (4 errors) ====
198
// It is an error for the type specified in a type alias to depend on that type alias
209

2110
// A type alias directly depends on the type it aliases.
@@ -35,49 +24,27 @@ tests/cases/conformance/types/typeAliases/directDependenceBetweenTypeAliases.ts(
3524
// A type reference directly depends on the referenced type and each of the type arguments, if any.
3625
interface I<T> {}
3726
type T1 = I<T1>
38-
~~
39-
!!! error TS2456: Type alias 'T1' circularly references itself.
4027

4128
// A union type directly depends on each of the constituent types.
4229
type T2 = T2 | string
43-
~~
44-
!!! error TS2456: Type alias 'T2' circularly references itself.
4530
class C<T> {}
4631
type T2_1 = T2_1[] | number
47-
~~~~
48-
!!! error TS2456: Type alias 'T2_1' circularly references itself.
4932

5033
// An array type directly depends on its element type.
5134
type T3 = T3[]
52-
~~
53-
!!! error TS2456: Type alias 'T3' circularly references itself.
5435

5536
// A tuple type directly depends on each of its element types.
5637
type T4 = [number, T4]
57-
~~
58-
!!! error TS2456: Type alias 'T4' circularly references itself.
5938

6039
// A type query directly depends on the type of the referenced entity.
6140
var x: T5[] = []
62-
~
63-
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
6441
type T5 = typeof x
65-
~~
66-
!!! error TS2456: Type alias 'T5' circularly references itself.
6742

6843
class C1<T> {}
6944
type T6 = T7 | number
70-
~~
71-
!!! error TS2456: Type alias 'T6' circularly references itself.
7245
type T7 = typeof yy
73-
~~
74-
!!! error TS2456: Type alias 'T7' circularly references itself.
7546
var yy: [string, T8[]];
76-
~~
77-
!!! error TS2502: 'yy' is referenced directly or indirectly in its own type annotation.
7847
type T8 = C<T6>
79-
~~
80-
!!! error TS2456: Type alias 'T8' circularly references itself.
8148

8249
// legal cases
8350
type T9 = () => T9

tests/baselines/reference/directDependenceBetweenTypeAliases.types

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,50 +17,50 @@ type T0_3 = T0_1
1717
// A type reference directly depends on the referenced type and each of the type arguments, if any.
1818
interface I<T> {}
1919
type T1 = I<T1>
20-
>T1 : any
20+
>T1 : I<T1>
2121

2222
// A union type directly depends on each of the constituent types.
2323
type T2 = T2 | string
24-
>T2 : any
24+
>T2 : T2
2525

2626
class C<T> {}
2727
>C : C<T>
2828

2929
type T2_1 = T2_1[] | number
30-
>T2_1 : any
30+
>T2_1 : T2_1
3131

3232
// An array type directly depends on its element type.
3333
type T3 = T3[]
34-
>T3 : any
34+
>T3 : T3[]
3535

3636
// A tuple type directly depends on each of its element types.
3737
type T4 = [number, T4]
38-
>T4 : any
38+
>T4 : [number, T4]
3939

4040
// A type query directly depends on the type of the referenced entity.
4141
var x: T5[] = []
42-
>x : any
42+
>x : T5[]
4343
>[] : undefined[]
4444

4545
type T5 = typeof x
46-
>T5 : any
47-
>x : any
46+
>T5 : T5[]
47+
>x : T5[]
4848

4949
class C1<T> {}
5050
>C1 : C1<T>
5151

5252
type T6 = T7 | number
53-
>T6 : any
53+
>T6 : T6
5454

5555
type T7 = typeof yy
56-
>T7 : any
57-
>yy : any
56+
>T7 : [string, C<T6>[]]
57+
>yy : [string, C<T6>[]]
5858

5959
var yy: [string, T8[]];
60-
>yy : any
60+
>yy : [string, C<T6>[]]
6161

6262
type T8 = C<T6>
63-
>T8 : any
63+
>T8 : C<T6>
6464

6565
// legal cases
6666
type T9 = () => T9

0 commit comments

Comments
 (0)