Skip to content

Commit 1527499

Browse files
committed
Merge pull request #8605 from Microsoft/recursively-remove-object-literal-freshness
Recursively mark object literals as non-fresh when checking assignability to intersections
2 parents 0a54a3b + 462607c commit 1527499

File tree

5 files changed

+196
-29
lines changed

5 files changed

+196
-29
lines changed

src/compiler/checker.ts

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6831,41 +6831,59 @@ namespace ts {
68316831
getSignaturesOfType(type, SignatureKind.Construct).length === 0;
68326832
}
68336833

6834+
function createTransientSymbol(source: Symbol, type: Type) {
6835+
const symbol = <TransientSymbol>createSymbol(source.flags | SymbolFlags.Transient, source.name);
6836+
symbol.declarations = source.declarations;
6837+
symbol.parent = source.parent;
6838+
symbol.type = type;
6839+
symbol.target = source;
6840+
if (source.valueDeclaration) {
6841+
symbol.valueDeclaration = source.valueDeclaration;
6842+
}
6843+
return symbol;
6844+
}
6845+
6846+
function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) {
6847+
const members: SymbolTable = {};
6848+
for (const property of getPropertiesOfObjectType(type)) {
6849+
const original = getTypeOfSymbol(property);
6850+
const updated = f(original);
6851+
members[property.name] = updated === original ? property : createTransientSymbol(property, updated);
6852+
};
6853+
return members;
6854+
}
6855+
6856+
/**
6857+
* If the the provided object literal is subject to the excess properties check,
6858+
* create a new that is exempt. Recursively mark object literal members as exempt.
6859+
* Leave signatures alone since they are not subject to the check.
6860+
*/
68346861
function getRegularTypeOfObjectLiteral(type: Type): Type {
6835-
if (type.flags & TypeFlags.FreshObjectLiteral) {
6836-
let regularType = (<FreshObjectLiteralType>type).regularType;
6837-
if (!regularType) {
6838-
regularType = <ResolvedType>createType((<ResolvedType>type).flags & ~TypeFlags.FreshObjectLiteral);
6839-
regularType.symbol = (<ResolvedType>type).symbol;
6840-
regularType.members = (<ResolvedType>type).members;
6841-
regularType.properties = (<ResolvedType>type).properties;
6842-
regularType.callSignatures = (<ResolvedType>type).callSignatures;
6843-
regularType.constructSignatures = (<ResolvedType>type).constructSignatures;
6844-
regularType.stringIndexInfo = (<ResolvedType>type).stringIndexInfo;
6845-
regularType.numberIndexInfo = (<ResolvedType>type).numberIndexInfo;
6846-
(<FreshObjectLiteralType>type).regularType = regularType;
6847-
}
6862+
if (!(type.flags & TypeFlags.FreshObjectLiteral)) {
6863+
return type;
6864+
}
6865+
const regularType = (<FreshObjectLiteralType>type).regularType;
6866+
if (regularType) {
68486867
return regularType;
68496868
}
6850-
return type;
6869+
6870+
const resolved = <ResolvedType>type;
6871+
const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral);
6872+
const regularNew = createAnonymousType(resolved.symbol,
6873+
members,
6874+
resolved.callSignatures,
6875+
resolved.constructSignatures,
6876+
resolved.stringIndexInfo,
6877+
resolved.numberIndexInfo);
6878+
regularNew.flags = resolved.flags & ~TypeFlags.FreshObjectLiteral;
6879+
(<FreshObjectLiteralType>type).regularType = regularNew;
6880+
return regularNew;
68516881
}
68526882

68536883
function getWidenedTypeOfObjectLiteral(type: Type): Type {
6854-
const properties = getPropertiesOfObjectType(type);
6855-
const members: SymbolTable = {};
6856-
forEach(properties, p => {
6857-
const propType = getTypeOfSymbol(p);
6858-
const widenedType = getWidenedType(propType);
6859-
if (propType !== widenedType) {
6860-
const symbol = <TransientSymbol>createSymbol(p.flags | SymbolFlags.Transient, p.name);
6861-
symbol.declarations = p.declarations;
6862-
symbol.parent = p.parent;
6863-
symbol.type = widenedType;
6864-
symbol.target = p;
6865-
if (p.valueDeclaration) symbol.valueDeclaration = p.valueDeclaration;
6866-
p = symbol;
6867-
}
6868-
members[p.name] = p;
6884+
const members = transformTypeOfMembers(type, prop => {
6885+
const widened = getWidenedType(prop);
6886+
return prop === widened ? prop : widened;
68696887
});
68706888
const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String);
68716889
const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number);

tests/baselines/reference/intersectionTypeMembers.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ type F2 = (x: number) => number;
2626
var f: F1 & F2;
2727
var s = f("hello");
2828
var n = f(42);
29+
30+
interface D {
31+
nested: { doublyNested: { d: string; }, different: { e: number } };
32+
}
33+
interface E {
34+
nested: { doublyNested: { f: string; }, other: {g: number } };
35+
}
36+
const de: D & E = {
37+
nested: {
38+
doublyNested: {
39+
d: 'yes',
40+
f: 'no'
41+
},
42+
different: { e: 12 },
43+
other: { g: 101 }
44+
}
45+
}
2946

3047

3148
//// [intersectionTypeMembers.js]
@@ -42,3 +59,13 @@ xyz.x.c = "hello";
4259
var f;
4360
var s = f("hello");
4461
var n = f(42);
62+
var de = {
63+
nested: {
64+
doublyNested: {
65+
d: 'yes',
66+
f: 'no'
67+
},
68+
different: { e: 12 },
69+
other: { g: 101 }
70+
}
71+
};

tests/baselines/reference/intersectionTypeMembers.symbols

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,51 @@ var n = f(42);
9898
>n : Symbol(n, Decl(intersectionTypeMembers.ts, 26, 3))
9999
>f : Symbol(f, Decl(intersectionTypeMembers.ts, 24, 3))
100100

101+
interface D {
102+
>D : Symbol(D, Decl(intersectionTypeMembers.ts, 26, 14))
103+
104+
nested: { doublyNested: { d: string; }, different: { e: number } };
105+
>nested : Symbol(D.nested, Decl(intersectionTypeMembers.ts, 28, 13))
106+
>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 29, 13))
107+
>d : Symbol(d, Decl(intersectionTypeMembers.ts, 29, 29))
108+
>different : Symbol(different, Decl(intersectionTypeMembers.ts, 29, 43))
109+
>e : Symbol(e, Decl(intersectionTypeMembers.ts, 29, 56))
110+
}
111+
interface E {
112+
>E : Symbol(E, Decl(intersectionTypeMembers.ts, 30, 1))
113+
114+
nested: { doublyNested: { f: string; }, other: {g: number } };
115+
>nested : Symbol(E.nested, Decl(intersectionTypeMembers.ts, 31, 13))
116+
>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 32, 13))
117+
>f : Symbol(f, Decl(intersectionTypeMembers.ts, 32, 29))
118+
>other : Symbol(other, Decl(intersectionTypeMembers.ts, 32, 43))
119+
>g : Symbol(g, Decl(intersectionTypeMembers.ts, 32, 52))
120+
}
121+
const de: D & E = {
122+
>de : Symbol(de, Decl(intersectionTypeMembers.ts, 34, 5))
123+
>D : Symbol(D, Decl(intersectionTypeMembers.ts, 26, 14))
124+
>E : Symbol(E, Decl(intersectionTypeMembers.ts, 30, 1))
125+
126+
nested: {
127+
>nested : Symbol(nested, Decl(intersectionTypeMembers.ts, 34, 19))
128+
129+
doublyNested: {
130+
>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 35, 13))
131+
132+
d: 'yes',
133+
>d : Symbol(d, Decl(intersectionTypeMembers.ts, 36, 23))
134+
135+
f: 'no'
136+
>f : Symbol(f, Decl(intersectionTypeMembers.ts, 37, 21))
137+
138+
},
139+
different: { e: 12 },
140+
>different : Symbol(different, Decl(intersectionTypeMembers.ts, 39, 10))
141+
>e : Symbol(e, Decl(intersectionTypeMembers.ts, 40, 20))
142+
143+
other: { g: 101 }
144+
>other : Symbol(other, Decl(intersectionTypeMembers.ts, 40, 29))
145+
>g : Symbol(g, Decl(intersectionTypeMembers.ts, 41, 16))
146+
}
147+
}
148+

tests/baselines/reference/intersectionTypeMembers.types

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,60 @@ var n = f(42);
114114
>f : ((x: string) => string) & ((x: number) => number)
115115
>42 : number
116116

117+
interface D {
118+
>D : D
119+
120+
nested: { doublyNested: { d: string; }, different: { e: number } };
121+
>nested : { doublyNested: { d: string; }; different: { e: number; }; }
122+
>doublyNested : { d: string; }
123+
>d : string
124+
>different : { e: number; }
125+
>e : number
126+
}
127+
interface E {
128+
>E : E
129+
130+
nested: { doublyNested: { f: string; }, other: {g: number } };
131+
>nested : { doublyNested: { f: string; }; other: { g: number; }; }
132+
>doublyNested : { f: string; }
133+
>f : string
134+
>other : { g: number; }
135+
>g : number
136+
}
137+
const de: D & E = {
138+
>de : D & E
139+
>D : D
140+
>E : E
141+
>{ nested: { doublyNested: { d: 'yes', f: 'no' }, different: { e: 12 }, other: { g: 101 } }} : { nested: { doublyNested: { d: string; f: string; }; different: { e: number; }; other: { g: number; }; }; }
142+
143+
nested: {
144+
>nested : { doublyNested: { d: string; f: string; }; different: { e: number; }; other: { g: number; }; }
145+
>{ doublyNested: { d: 'yes', f: 'no' }, different: { e: 12 }, other: { g: 101 } } : { doublyNested: { d: string; f: string; }; different: { e: number; }; other: { g: number; }; }
146+
147+
doublyNested: {
148+
>doublyNested : { d: string; f: string; }
149+
>{ d: 'yes', f: 'no' } : { d: string; f: string; }
150+
151+
d: 'yes',
152+
>d : string
153+
>'yes' : string
154+
155+
f: 'no'
156+
>f : string
157+
>'no' : string
158+
159+
},
160+
different: { e: 12 },
161+
>different : { e: number; }
162+
>{ e: 12 } : { e: number; }
163+
>e : number
164+
>12 : number
165+
166+
other: { g: 101 }
167+
>other : { g: number; }
168+
>{ g: 101 } : { g: number; }
169+
>g : number
170+
>101 : number
171+
}
172+
}
173+

tests/cases/conformance/types/intersection/intersectionTypeMembers.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,20 @@ type F2 = (x: number) => number;
2525
var f: F1 & F2;
2626
var s = f("hello");
2727
var n = f(42);
28+
29+
interface D {
30+
nested: { doublyNested: { d: string; }, different: { e: number } };
31+
}
32+
interface E {
33+
nested: { doublyNested: { f: string; }, other: {g: number } };
34+
}
35+
const de: D & E = {
36+
nested: {
37+
doublyNested: {
38+
d: 'yes',
39+
f: 'no'
40+
},
41+
different: { e: 12 },
42+
other: { g: 101 }
43+
}
44+
}

0 commit comments

Comments
 (0)