Skip to content

Commit 37fce9f

Browse files
committed
Optimize relating types which are subtypes of a type alias/reference already beign related
1 parent ad4d457 commit 37fce9f

File tree

4 files changed

+1076
-4
lines changed

4 files changed

+1076
-4
lines changed

src/compiler/checker.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ namespace ts {
77
let nextMergeId = 1;
88
let nextFlowId = 1;
99

10-
const maxSymbolRecusionDepth = 10;
11-
1210
export function getNodeId(node: Node): number {
1311
if (!node.id) {
1412
node.id = nextNodeId;
@@ -3724,7 +3722,7 @@ namespace ts {
37243722
}
37253723

37263724
const depth = context.symbolDepth.get(id) || 0;
3727-
if (depth > maxSymbolRecusionDepth) {
3725+
if (depth > 10) {
37283726
return createElidedInformationPlaceholder(context);
37293727
}
37303728
context.symbolDepth.set(id, depth + 1);
@@ -13050,6 +13048,13 @@ namespace ts {
1305013048
return result;
1305113049
}
1305213050

13051+
function getInstanceOfAliasOrReferenceWithMarker(input: Type, typeArguments: readonly Type[]) {
13052+
const s = input.aliasSymbol ? getTypeAliasInstantiation(input.aliasSymbol, typeArguments) : createTypeReference((<TypeReference>input).target, typeArguments);
13053+
if (s.aliasSymbol) s.aliasTypeArgumentsContainsMarker = true;
13054+
else (<TypeReference>s).objectFlags |= ObjectFlags.MarkerType;
13055+
return s;
13056+
}
13057+
1305313058
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary {
1305413059
const flags = source.flags & target.flags;
1305513060
if (relation === identityRelation && !(flags & TypeFlags.Object)) {
@@ -13101,6 +13106,40 @@ namespace ts {
1310113106
}
1310213107
}
1310313108

13109+
// If a more _general_ version of the source and target are being compared, consider them related with assumptions
13110+
// eg, if { x: Q } and { x: Q, y: A } are being compared and we're about to look at { x: Q' } and { x: Q', y: A } where Q'
13111+
// is some specialization or subtype of Q
13112+
// This is difficult to detect generally, so we scan for prior comparisons of the same instantiated type, and match up matching
13113+
// type arguments into sets to create a canonicalization based on those matches
13114+
if (relation !== identityRelation && ((source.aliasSymbol && !source.aliasTypeArgumentsContainsMarker && source.aliasTypeArguments) || (getObjectFlags(source) & ObjectFlags.Reference && (<TypeReference>source).typeArguments && !(getObjectFlags(source) & ObjectFlags.MarkerType))) &&
13115+
((target.aliasSymbol && !target.aliasTypeArgumentsContainsMarker && target.aliasTypeArguments) || (getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>target).typeArguments && !(getObjectFlags(target) & ObjectFlags.MarkerType)))) {
13116+
const originalKey = getRelationKey(source, target, relation);
13117+
const sourceTypeArguments = source.aliasTypeArguments || (<TypeReference>source).typeArguments!;
13118+
const targetTypeArguments = target.aliasTypeArguments || (<TypeReference>target).typeArguments!;
13119+
for (let i = 0; i < sourceTypeArguments.length; i++) {
13120+
for (let j = 0; j < targetTypeArguments.length; j++) {
13121+
if (!(sourceTypeArguments[i].flags & TypeFlags.TypeParameter) && isTypeIdenticalTo(sourceTypeArguments[i], targetTypeArguments[j])) {
13122+
const sourceClone = sourceTypeArguments.slice();
13123+
sourceClone[i] = markerOtherType;
13124+
const s = getInstanceOfAliasOrReferenceWithMarker(source, sourceClone);
13125+
const targetClone = targetTypeArguments.slice();
13126+
targetClone[j] = markerOtherType;
13127+
const t = getInstanceOfAliasOrReferenceWithMarker(target, targetClone);
13128+
// If the marker-instantiated form looks "the same" as the type we already have (eg,
13129+
// because we replace unconstrained generics with unconstrained generics), skip the check
13130+
// since we'll otherwise deliver a spurious `Maybe` result from the key _just_ set upon
13131+
// entry into `recursiveTypeRelatedTo`
13132+
if (getRelationKey(s, t, relation) !== originalKey) {
13133+
const result = isRelatedTo(s, t, /*reportErrors*/ false);
13134+
if (result) {
13135+
return result;
13136+
}
13137+
}
13138+
}
13139+
}
13140+
}
13141+
}
13142+
1310413143
if (target.flags & TypeFlags.TypeParameter) {
1310513144
// A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q].
1310613145
if (getObjectFlags(source) & ObjectFlags.Mapped && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(<MappedType>source))) {
@@ -15387,7 +15426,7 @@ namespace ts {
1538715426
if (symbol) {
1538815427
const id = "" + getSymbolId(symbol);
1538915428
const depth = symbolDepth && symbolDepth.get(id) || 0;
15390-
if (depth > maxSymbolRecusionDepth) {
15429+
if (depth > 5) {
1539115430
return;
1539215431
}
1539315432
(symbolDepth || (symbolDepth = createMap())).set(id, depth + 1);
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
//// [registryMonoidalPatternNoOOM.ts]
2+
interface Plugins<TConfig> {}
3+
4+
type PluginNames = keyof Plugins<any>;
5+
6+
interface MiddlewarePlugin<TKind extends PluginNames, TPreviousConfig> {
7+
readonly _group: TKind;
8+
configure<TConfig>(cb: (conf: TPreviousConfig) => TConfig): Plugins<TConfig>[TKind];
9+
// combine<TConfig>(cb: Plugins<(conf: TPreviousConfig) => TConfig>[TKind]): Plugins<TConfig>[TKind];
10+
// The above "works", but the below _should_ result in less to check and is simpler (and is needed to trigger the OOM bug)
11+
combine<TConfig>(cb: MiddlewarePlugin<TKind, (conf: TPreviousConfig) => TConfig>): Plugins<TConfig>[TKind];
12+
}
13+
14+
const GroupOneName = "GroupOne";
15+
type GroupOneName = typeof GroupOneName;
16+
type GroupOne<T> = PluginA<T> | PluginB<T>;
17+
interface Plugins<TConfig> {
18+
[GroupOneName]: GroupOne<TConfig>;
19+
}
20+
class PluginA<TConf> implements MiddlewarePlugin<GroupOneName, TConf> {
21+
readonly _kind = "PluginA";
22+
readonly _group = GroupOneName;
23+
constructor(public value: TConf) {}
24+
configure<T>(cb: (conf: TConf) => T): GroupOne<T> {
25+
return new PluginA(cb(this.value));
26+
}
27+
combine<T>(plug: GroupOne<(conf: TConf) => T>): GroupOne<T> {
28+
return plug.configure(f => f(this.value));
29+
}
30+
}
31+
32+
class PluginB<TConf> implements MiddlewarePlugin<GroupOneName, TConf> {
33+
readonly _kind = "PluginB";
34+
readonly _group = GroupOneName;
35+
constructor(public value: TConf) {}
36+
configure<T>(cb: (conf: TConf) => T): GroupOne<T> {
37+
return new PluginB(cb(this.value));
38+
}
39+
combine<T>(plug: GroupOne<(conf: TConf) => T>): GroupOne<T> {
40+
return plug.configure(f => f(this.value));
41+
}
42+
}
43+
44+
// One plugin group isn't quite enough to run OOM - we need a few more
45+
46+
const GroupTwoName = "GroupTwo";
47+
type GroupTwoName = typeof GroupTwoName;
48+
type GroupTwo<T> = PluginC<T> | PluginD<T>;
49+
interface Plugins<TConfig> {
50+
[GroupTwoName]: GroupTwo<TConfig>;
51+
}
52+
class PluginC<TConf> implements MiddlewarePlugin<GroupTwoName, TConf> {
53+
readonly _kind = "PluginC";
54+
readonly _group = GroupTwoName;
55+
constructor(public value: TConf) {}
56+
configure<T>(cb: (conf: TConf) => T): GroupTwo<T> {
57+
return new PluginC(cb(this.value));
58+
}
59+
combine<T>(plug: GroupTwo<(conf: TConf) => T>): GroupTwo<T> {
60+
return plug.configure(f => f(this.value));
61+
}
62+
}
63+
64+
class PluginD<TConf> implements MiddlewarePlugin<GroupTwoName, TConf> {
65+
readonly _kind = "PluginD";
66+
readonly _group = GroupTwoName;
67+
constructor(public value: TConf) {}
68+
configure<T>(cb: (conf: TConf) => T): GroupTwo<T> {
69+
return new PluginD(cb(this.value));
70+
}
71+
combine<T>(plug: GroupTwo<(conf: TConf) => T>): GroupTwo<T> {
72+
return plug.configure(f => f(this.value));
73+
}
74+
}
75+
76+
const GroupThreeName = "GroupThree";
77+
type GroupThreeName = typeof GroupThreeName;
78+
type GroupThree<T> = PluginE<T> | PluginF<T>;
79+
interface Plugins<TConfig> {
80+
[GroupThreeName]: GroupThree<TConfig>;
81+
}
82+
class PluginE<TConf> implements MiddlewarePlugin<GroupThreeName, TConf> {
83+
readonly _kind = "PluginC";
84+
readonly _group = GroupThreeName;
85+
constructor(public value: TConf) {}
86+
configure<T>(cb: (conf: TConf) => T): GroupThree<T> {
87+
return new PluginE(cb(this.value));
88+
}
89+
combine<T>(plug: GroupThree<(conf: TConf) => T>): GroupThree<T> {
90+
return plug.configure(f => f(this.value));
91+
}
92+
}
93+
94+
class PluginF<TConf> implements MiddlewarePlugin<GroupThreeName, TConf> {
95+
readonly _kind = "PluginD";
96+
readonly _group = GroupThreeName;
97+
constructor(public value: TConf) {}
98+
configure<T>(cb: (conf: TConf) => T): GroupThree<T> {
99+
return new PluginF(cb(this.value));
100+
}
101+
combine<T>(plug: GroupThree<(conf: TConf) => T>): GroupThree<T> {
102+
return plug.configure(f => f(this.value));
103+
}
104+
}
105+
106+
107+
//// [registryMonoidalPatternNoOOM.js]
108+
var GroupOneName = "GroupOne";
109+
var PluginA = /** @class */ (function () {
110+
function PluginA(value) {
111+
this.value = value;
112+
this._kind = "PluginA";
113+
this._group = GroupOneName;
114+
}
115+
PluginA.prototype.configure = function (cb) {
116+
return new PluginA(cb(this.value));
117+
};
118+
PluginA.prototype.combine = function (plug) {
119+
var _this = this;
120+
return plug.configure(function (f) { return f(_this.value); });
121+
};
122+
return PluginA;
123+
}());
124+
var PluginB = /** @class */ (function () {
125+
function PluginB(value) {
126+
this.value = value;
127+
this._kind = "PluginB";
128+
this._group = GroupOneName;
129+
}
130+
PluginB.prototype.configure = function (cb) {
131+
return new PluginB(cb(this.value));
132+
};
133+
PluginB.prototype.combine = function (plug) {
134+
var _this = this;
135+
return plug.configure(function (f) { return f(_this.value); });
136+
};
137+
return PluginB;
138+
}());
139+
// One plugin group isn't quite enough to run OOM - we need a few more
140+
var GroupTwoName = "GroupTwo";
141+
var PluginC = /** @class */ (function () {
142+
function PluginC(value) {
143+
this.value = value;
144+
this._kind = "PluginC";
145+
this._group = GroupTwoName;
146+
}
147+
PluginC.prototype.configure = function (cb) {
148+
return new PluginC(cb(this.value));
149+
};
150+
PluginC.prototype.combine = function (plug) {
151+
var _this = this;
152+
return plug.configure(function (f) { return f(_this.value); });
153+
};
154+
return PluginC;
155+
}());
156+
var PluginD = /** @class */ (function () {
157+
function PluginD(value) {
158+
this.value = value;
159+
this._kind = "PluginD";
160+
this._group = GroupTwoName;
161+
}
162+
PluginD.prototype.configure = function (cb) {
163+
return new PluginD(cb(this.value));
164+
};
165+
PluginD.prototype.combine = function (plug) {
166+
var _this = this;
167+
return plug.configure(function (f) { return f(_this.value); });
168+
};
169+
return PluginD;
170+
}());
171+
var GroupThreeName = "GroupThree";
172+
var PluginE = /** @class */ (function () {
173+
function PluginE(value) {
174+
this.value = value;
175+
this._kind = "PluginC";
176+
this._group = GroupThreeName;
177+
}
178+
PluginE.prototype.configure = function (cb) {
179+
return new PluginE(cb(this.value));
180+
};
181+
PluginE.prototype.combine = function (plug) {
182+
var _this = this;
183+
return plug.configure(function (f) { return f(_this.value); });
184+
};
185+
return PluginE;
186+
}());
187+
var PluginF = /** @class */ (function () {
188+
function PluginF(value) {
189+
this.value = value;
190+
this._kind = "PluginD";
191+
this._group = GroupThreeName;
192+
}
193+
PluginF.prototype.configure = function (cb) {
194+
return new PluginF(cb(this.value));
195+
};
196+
PluginF.prototype.combine = function (plug) {
197+
var _this = this;
198+
return plug.configure(function (f) { return f(_this.value); });
199+
};
200+
return PluginF;
201+
}());

0 commit comments

Comments
 (0)