Skip to content

Commit e70f2af

Browse files
authored
Defer union or intersection property type normalization (#31486)
* Defer union or intersection property type normalization * Accept moved span
1 parent 38f3b05 commit e70f2af

15 files changed

+474
-11
lines changed

src/compiler/checker.ts

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5921,7 +5921,20 @@ namespace ts {
59215921
return anyType;
59225922
}
59235923

5924+
function getTypeOfSymbolWithDeferredType(symbol: Symbol) {
5925+
const links = getSymbolLinks(symbol);
5926+
if (!links.type) {
5927+
Debug.assertDefined(links.deferralParent);
5928+
Debug.assertDefined(links.deferralConstituents);
5929+
links.type = links.deferralParent!.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents!) : getIntersectionType(links.deferralConstituents!);
5930+
}
5931+
return links.type;
5932+
}
5933+
59245934
function getTypeOfSymbol(symbol: Symbol): Type {
5935+
if (getCheckFlags(symbol) & CheckFlags.DeferredType) {
5936+
return getTypeOfSymbolWithDeferredType(symbol);
5937+
}
59255938
if (getCheckFlags(symbol) & CheckFlags.Instantiated) {
59265939
return getTypeOfInstantiatedSymbol(symbol);
59275940
}
@@ -8061,7 +8074,15 @@ namespace ts {
80618074

80628075
result.declarations = declarations!;
80638076
result.nameType = nameType;
8064-
result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
8077+
if (propTypes.length > 2) {
8078+
// When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed
8079+
result.checkFlags |= CheckFlags.DeferredType;
8080+
result.deferralParent = containingType;
8081+
result.deferralConstituents = propTypes;
8082+
}
8083+
else {
8084+
result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
8085+
}
80658086
return result;
80668087
}
80678088

@@ -12313,8 +12334,8 @@ namespace ts {
1231312334
return false;
1231412335
}
1231512336

12316-
function isIgnoredJsxProperty(source: Type, sourceProp: Symbol, targetMemberType: Type | undefined) {
12317-
return getObjectFlags(source) & ObjectFlags.JsxAttributes && !(isUnhyphenatedJsxName(sourceProp.escapedName) || targetMemberType);
12337+
function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) {
12338+
return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName);
1231812339
}
1231912340

1232012341
/**
@@ -13495,6 +13516,49 @@ namespace ts {
1349513516
return result || properties;
1349613517
}
1349713518

13519+
function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
13520+
const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial);
13521+
const source = getTypeOfSourceProperty(sourceProp);
13522+
if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) {
13523+
// Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes
13524+
const links = getSymbolLinks(targetProp);
13525+
Debug.assertDefined(links.deferralParent);
13526+
Debug.assertDefined(links.deferralConstituents);
13527+
const unionParent = !!(links.deferralParent!.flags & TypeFlags.Union);
13528+
let result = unionParent ? Ternary.False : Ternary.True;
13529+
const targetTypes = links.deferralConstituents!;
13530+
for (const targetType of targetTypes) {
13531+
const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, /*isIntersectionConstituent*/ !unionParent);
13532+
if (!unionParent) {
13533+
if (!related) {
13534+
// Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization)
13535+
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
13536+
}
13537+
result &= related;
13538+
}
13539+
else {
13540+
if (related) {
13541+
return related;
13542+
}
13543+
}
13544+
}
13545+
if (unionParent && !result && targetIsOptional) {
13546+
result = isRelatedTo(source, undefinedType);
13547+
}
13548+
if (unionParent && !result && reportErrors) {
13549+
// The easiest way to get the right errors here is to un-defer (which may be costly)
13550+
// If it turns out this is too costly too often, we can replicate the error handling logic within
13551+
// typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union
13552+
// type on which to hand discriminable properties, which we are expressly trying to avoid here)
13553+
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
13554+
}
13555+
return result;
13556+
}
13557+
else {
13558+
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
13559+
}
13560+
}
13561+
1349813562
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
1349913563
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
1350013564
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
@@ -13537,7 +13601,7 @@ namespace ts {
1353713601
return Ternary.False;
1353813602
}
1353913603
// If the target comes from a partial union prop, allow `undefined` in the target type
13540-
const related = isRelatedTo(getTypeOfSourceProperty(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors);
13604+
const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors);
1354113605
if (!related) {
1354213606
if (reportErrors) {
1354313607
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
@@ -13649,9 +13713,6 @@ namespace ts {
1364913713
if (!(targetProp.flags & SymbolFlags.Prototype)) {
1365013714
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
1365113715
if (sourceProp && sourceProp !== targetProp) {
13652-
if (isIgnoredJsxProperty(source, sourceProp, getTypeOfSymbol(targetProp))) {
13653-
continue;
13654-
}
1365513716
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors);
1365613717
if (!related) {
1365713718
return Ternary.False;
@@ -13797,7 +13858,7 @@ namespace ts {
1379713858
function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary {
1379813859
let result = Ternary.True;
1379913860
for (const prop of getPropertiesOfObjectType(source)) {
13800-
if (isIgnoredJsxProperty(source, prop, /*targetMemberType*/ undefined)) {
13861+
if (isIgnoredJsxProperty(source, prop)) {
1380113862
continue;
1380213863
}
1380313864
// Skip over symbol-named members

src/compiler/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3752,6 +3752,8 @@ namespace ts {
37523752
extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in
37533753
extendedContainersByFile?: Map<Symbol[]>; // Containers (other than the parent) which this symbol is aliased in
37543754
variances?: VarianceFlags[]; // Alias symbol type argument variance cache
3755+
deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type
3756+
deferralParent?: Type; // Source union/intersection of a deferred type
37553757
}
37563758

37573759
/* @internal */
@@ -3778,6 +3780,7 @@ namespace ts {
37783780
ReverseMapped = 1 << 13, // Property of reverse-inferred homomorphic mapped type
37793781
OptionalParameter = 1 << 14, // Optional parameter
37803782
RestParameter = 1 << 15, // Rest parameter
3783+
DeferredType = 1 << 16, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType`
37813784
Synthetic = SyntheticProperty | SyntheticMethod,
37823785
Discriminant = HasNonUniformType | HasLiteralType,
37833786
Partial = ReadPartial | WritePartial

tests/baselines/reference/intersectionTypeMembers.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@ const de: D & E = {
4343
other: { g: 101 }
4444
}
4545
}
46+
47+
// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props
48+
interface F {
49+
nested: { doublyNested: { g: string; } }
50+
}
51+
52+
interface G {
53+
nested: { doublyNested: { h: string; } }
54+
}
55+
56+
const defg: D & E & F & G = {
57+
nested: {
58+
doublyNested: {
59+
d: 'yes',
60+
f: 'no',
61+
g: 'ok',
62+
h: 'affirmative'
63+
},
64+
different: { e: 12 },
65+
other: { g: 101 }
66+
}
67+
}
4668

4769

4870
//// [intersectionTypeMembers.js]
@@ -69,3 +91,15 @@ var de = {
6991
other: { g: 101 }
7092
}
7193
};
94+
var defg = {
95+
nested: {
96+
doublyNested: {
97+
d: 'yes',
98+
f: 'no',
99+
g: 'ok',
100+
h: 'affirmative'
101+
},
102+
different: { e: 12 },
103+
other: { g: 101 }
104+
}
105+
};

tests/baselines/reference/intersectionTypeMembers.symbols

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,58 @@ const de: D & E = {
146146
}
147147
}
148148

149+
// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props
150+
interface F {
151+
>F : Symbol(F, Decl(intersectionTypeMembers.ts, 43, 1))
152+
153+
nested: { doublyNested: { g: string; } }
154+
>nested : Symbol(F.nested, Decl(intersectionTypeMembers.ts, 46, 13))
155+
>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 47, 13))
156+
>g : Symbol(g, Decl(intersectionTypeMembers.ts, 47, 29))
157+
}
158+
159+
interface G {
160+
>G : Symbol(G, Decl(intersectionTypeMembers.ts, 48, 1))
161+
162+
nested: { doublyNested: { h: string; } }
163+
>nested : Symbol(G.nested, Decl(intersectionTypeMembers.ts, 50, 13))
164+
>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 51, 13))
165+
>h : Symbol(h, Decl(intersectionTypeMembers.ts, 51, 29))
166+
}
167+
168+
const defg: D & E & F & G = {
169+
>defg : Symbol(defg, Decl(intersectionTypeMembers.ts, 54, 5))
170+
>D : Symbol(D, Decl(intersectionTypeMembers.ts, 26, 14))
171+
>E : Symbol(E, Decl(intersectionTypeMembers.ts, 30, 1))
172+
>F : Symbol(F, Decl(intersectionTypeMembers.ts, 43, 1))
173+
>G : Symbol(G, Decl(intersectionTypeMembers.ts, 48, 1))
174+
175+
nested: {
176+
>nested : Symbol(nested, Decl(intersectionTypeMembers.ts, 54, 29))
177+
178+
doublyNested: {
179+
>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 55, 13))
180+
181+
d: 'yes',
182+
>d : Symbol(d, Decl(intersectionTypeMembers.ts, 56, 23))
183+
184+
f: 'no',
185+
>f : Symbol(f, Decl(intersectionTypeMembers.ts, 57, 21))
186+
187+
g: 'ok',
188+
>g : Symbol(g, Decl(intersectionTypeMembers.ts, 58, 20))
189+
190+
h: 'affirmative'
191+
>h : Symbol(h, Decl(intersectionTypeMembers.ts, 59, 20))
192+
193+
},
194+
different: { e: 12 },
195+
>different : Symbol(different, Decl(intersectionTypeMembers.ts, 61, 10))
196+
>e : Symbol(e, Decl(intersectionTypeMembers.ts, 62, 20))
197+
198+
other: { g: 101 }
199+
>other : Symbol(other, Decl(intersectionTypeMembers.ts, 62, 29))
200+
>g : Symbol(g, Decl(intersectionTypeMembers.ts, 63, 16))
201+
}
202+
}
203+

tests/baselines/reference/intersectionTypeMembers.types

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,61 @@ const de: D & E = {
148148
}
149149
}
150150

151+
// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props
152+
interface F {
153+
nested: { doublyNested: { g: string; } }
154+
>nested : { doublyNested: { g: string; }; }
155+
>doublyNested : { g: string; }
156+
>g : string
157+
}
158+
159+
interface G {
160+
nested: { doublyNested: { h: string; } }
161+
>nested : { doublyNested: { h: string; }; }
162+
>doublyNested : { h: string; }
163+
>h : string
164+
}
165+
166+
const defg: D & E & F & G = {
167+
>defg : D & E & F & G
168+
>{ nested: { doublyNested: { d: 'yes', f: 'no', g: 'ok', h: 'affirmative' }, different: { e: 12 }, other: { g: 101 } }} : { nested: { doublyNested: { d: string; f: string; g: string; h: string; }; different: { e: number; }; other: { g: number; }; }; }
169+
170+
nested: {
171+
>nested : { doublyNested: { d: string; f: string; g: string; h: string; }; different: { e: number; }; other: { g: number; }; }
172+
>{ doublyNested: { d: 'yes', f: 'no', g: 'ok', h: 'affirmative' }, different: { e: 12 }, other: { g: 101 } } : { doublyNested: { d: string; f: string; g: string; h: string; }; different: { e: number; }; other: { g: number; }; }
173+
174+
doublyNested: {
175+
>doublyNested : { d: string; f: string; g: string; h: string; }
176+
>{ d: 'yes', f: 'no', g: 'ok', h: 'affirmative' } : { d: string; f: string; g: string; h: string; }
177+
178+
d: 'yes',
179+
>d : string
180+
>'yes' : "yes"
181+
182+
f: 'no',
183+
>f : string
184+
>'no' : "no"
185+
186+
g: 'ok',
187+
>g : string
188+
>'ok' : "ok"
189+
190+
h: 'affirmative'
191+
>h : string
192+
>'affirmative' : "affirmative"
193+
194+
},
195+
different: { e: 12 },
196+
>different : { e: number; }
197+
>{ e: 12 } : { e: number; }
198+
>e : number
199+
>12 : 12
200+
201+
other: { g: 101 }
202+
>other : { g: number; }
203+
>{ g: 101 } : { g: number; }
204+
>g : number
205+
>101 : 101
206+
}
207+
}
208+

tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,14): error TS2590: Expression produces a union type that is too complex to represent.
21
tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS7006: Parameter 'x' implicitly has an 'any' type.
2+
tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS2590: Expression produces a union type that is too complex to represent.
33

44

55
==== tests/cases/compiler/normalizedIntersectionTooComplex.ts (2 errors) ====
@@ -39,8 +39,8 @@ tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS7006: P
3939
declare var all: keyof Big;
4040
const ctor = getCtor(all);
4141
const comp = ctor({ common: "ok", ref: x => console.log(x) });
42-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43-
!!! error TS2590: Expression produces a union type that is too complex to represent.
4442
~
4543
!!! error TS7006: Parameter 'x' implicitly has an 'any' type.
44+
~~~~~~~~~~~~~~~~~~~
45+
!!! error TS2590: Expression produces a union type that is too complex to represent.
4646

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [reactTagNameComponentWithPropsNoOOM.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
import * as React from "react";
5+
declare const Tag: keyof React.ReactHTML;
6+
7+
const classes = "";
8+
const rest: {} = {};
9+
const children: any[] = [];
10+
<Tag className={classes} {...rest}>
11+
{children}
12+
</Tag>
13+
14+
//// [reactTagNameComponentWithPropsNoOOM.js]
15+
"use strict";
16+
/// <reference path="react16.d.ts" />
17+
var __assign = (this && this.__assign) || function () {
18+
__assign = Object.assign || function(t) {
19+
for (var s, i = 1, n = arguments.length; i < n; i++) {
20+
s = arguments[i];
21+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
22+
t[p] = s[p];
23+
}
24+
return t;
25+
};
26+
return __assign.apply(this, arguments);
27+
};
28+
exports.__esModule = true;
29+
var React = require("react");
30+
var classes = "";
31+
var rest = {};
32+
var children = [];
33+
React.createElement(Tag, __assign({ className: classes }, rest), children);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import * as React from "react";
5+
>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 2, 6))
6+
7+
declare const Tag: keyof React.ReactHTML;
8+
>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 3, 13))
9+
>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 2, 6))
10+
>ReactHTML : Symbol(React.ReactHTML, Decl(react16.d.ts, 2089, 9))
11+
12+
const classes = "";
13+
>classes : Symbol(classes, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 5, 5))
14+
15+
const rest: {} = {};
16+
>rest : Symbol(rest, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 6, 5))
17+
18+
const children: any[] = [];
19+
>children : Symbol(children, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 7, 5))
20+
21+
<Tag className={classes} {...rest}>
22+
>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 3, 13))
23+
>className : Symbol(className, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 8, 4))
24+
>classes : Symbol(classes, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 5, 5))
25+
>rest : Symbol(rest, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 6, 5))
26+
27+
{children}
28+
>children : Symbol(children, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 7, 5))
29+
30+
</Tag>
31+
>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 3, 13))
32+

0 commit comments

Comments
 (0)