Skip to content

Commit a6d44aa

Browse files
authored
Map stale empty object type in union into fresh empty object type after spread is complete (#34839)
* Map stale empty object type in union into fresh empty object type after spread is complete * Accept minor baseline diff
1 parent 0d993ac commit a6d44aa

6 files changed

+214
-4
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22090,8 +22090,13 @@ namespace ts {
2209022090
if (spread !== emptyObjectType) {
2209122091
if (propertiesArray.length > 0) {
2209222092
spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
22093+
propertiesArray = [];
22094+
propertiesTable = createSymbolTable();
22095+
hasComputedStringProperty = false;
22096+
hasComputedNumberProperty = false;
2209322097
}
22094-
return spread;
22098+
// remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site
22099+
return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t);
2209522100
}
2209622101

2209722102
return createObjectLiteralType();

tests/baselines/reference/objectSpread.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ function conditionalSpreadBoolean(b: boolean) : { x: number, y: number } {
293293
}
294294
let o2 = { ...b && { x: 21 }}
295295
>o2 : {}
296-
>{ ...b && { x: 21 }} : {} | { x: number; }
296+
>{ ...b && { x: 21 }} : { x: number; } | {}
297297
>b && { x: 21 } : false | { x: number; }
298298
>b : boolean
299299
>{ x: 21 } : { x: number; }
@@ -334,7 +334,7 @@ function conditionalSpreadNumber(nt: number): { x: number, y: number } {
334334
}
335335
let o2 = { ...nt && { x: nt }}
336336
>o2 : {}
337-
>{ ...nt && { x: nt }} : {} | { x: number; }
337+
>{ ...nt && { x: nt }} : { x: number; } | {}
338338
>nt && { x: nt } : 0 | { x: number; }
339339
>nt : number
340340
>{ x: nt } : { x: number; }
@@ -375,7 +375,7 @@ function conditionalSpreadString(st: string): { x: string, y: number } {
375375
}
376376
let o2 = { ...st && { x: st }}
377377
>o2 : {}
378-
>{ ...st && { x: st }} : {} | { x: string; }
378+
>{ ...st && { x: st }} : { x: string; } | {}
379379
>st && { x: st } : "" | { x: string; }
380380
>st : string
381381
>{ x: st } : { x: string; }
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [spreadOfObjectLiteralAssignableToIndexSignature.ts]
2+
const foo: Record<never, never> = {} // OK
3+
4+
interface RecordOfRecords extends Record<keyof any, RecordOfRecords> {}
5+
const recordOfRecords: RecordOfRecords = {}
6+
recordOfRecords.propA = {...(foo !== undefined ? {foo} : {})} // OK
7+
recordOfRecords.propB = {...(foo && {foo})} // OK
8+
recordOfRecords.propC = {...(foo !== undefined && {foo})} // error'd in 3.7 beta, should be OK
9+
10+
interface RecordOfRecordsOrEmpty extends Record<keyof any, RecordOfRecordsOrEmpty | {}> {}
11+
const recordsOfRecordsOrEmpty: RecordOfRecordsOrEmpty = {}
12+
recordsOfRecordsOrEmpty.propA = {...(foo !== undefined ? {foo} : {})} // OK
13+
recordsOfRecordsOrEmpty.propB = {...(foo && {foo})} // OK
14+
recordsOfRecordsOrEmpty.propC = {...(foo !== undefined && {foo})} // OK
15+
16+
//// [spreadOfObjectLiteralAssignableToIndexSignature.js]
17+
"use strict";
18+
var __assign = (this && this.__assign) || function () {
19+
__assign = Object.assign || function(t) {
20+
for (var s, i = 1, n = arguments.length; i < n; i++) {
21+
s = arguments[i];
22+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
23+
t[p] = s[p];
24+
}
25+
return t;
26+
};
27+
return __assign.apply(this, arguments);
28+
};
29+
var foo = {}; // OK
30+
var recordOfRecords = {};
31+
recordOfRecords.propA = __assign({}, (foo !== undefined ? { foo: foo } : {})); // OK
32+
recordOfRecords.propB = __assign({}, (foo && { foo: foo })); // OK
33+
recordOfRecords.propC = __assign({}, (foo !== undefined && { foo: foo })); // error'd in 3.7 beta, should be OK
34+
var recordsOfRecordsOrEmpty = {};
35+
recordsOfRecordsOrEmpty.propA = __assign({}, (foo !== undefined ? { foo: foo } : {})); // OK
36+
recordsOfRecordsOrEmpty.propB = __assign({}, (foo && { foo: foo })); // OK
37+
recordsOfRecordsOrEmpty.propC = __assign({}, (foo !== undefined && { foo: foo })); // OK
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/compiler/spreadOfObjectLiteralAssignableToIndexSignature.ts ===
2+
const foo: Record<never, never> = {} // OK
3+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
4+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
5+
6+
interface RecordOfRecords extends Record<keyof any, RecordOfRecords> {}
7+
>RecordOfRecords : Symbol(RecordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 36))
8+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
9+
>RecordOfRecords : Symbol(RecordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 36))
10+
11+
const recordOfRecords: RecordOfRecords = {}
12+
>recordOfRecords : Symbol(recordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 3, 5))
13+
>RecordOfRecords : Symbol(RecordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 36))
14+
15+
recordOfRecords.propA = {...(foo !== undefined ? {foo} : {})} // OK
16+
>recordOfRecords : Symbol(recordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 3, 5))
17+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
18+
>undefined : Symbol(undefined)
19+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 4, 50))
20+
21+
recordOfRecords.propB = {...(foo && {foo})} // OK
22+
>recordOfRecords : Symbol(recordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 3, 5))
23+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
24+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 5, 37))
25+
26+
recordOfRecords.propC = {...(foo !== undefined && {foo})} // error'd in 3.7 beta, should be OK
27+
>recordOfRecords : Symbol(recordOfRecords, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 3, 5))
28+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
29+
>undefined : Symbol(undefined)
30+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 6, 51))
31+
32+
interface RecordOfRecordsOrEmpty extends Record<keyof any, RecordOfRecordsOrEmpty | {}> {}
33+
>RecordOfRecordsOrEmpty : Symbol(RecordOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 6, 57))
34+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
35+
>RecordOfRecordsOrEmpty : Symbol(RecordOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 6, 57))
36+
37+
const recordsOfRecordsOrEmpty: RecordOfRecordsOrEmpty = {}
38+
>recordsOfRecordsOrEmpty : Symbol(recordsOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 9, 5))
39+
>RecordOfRecordsOrEmpty : Symbol(RecordOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 6, 57))
40+
41+
recordsOfRecordsOrEmpty.propA = {...(foo !== undefined ? {foo} : {})} // OK
42+
>recordsOfRecordsOrEmpty : Symbol(recordsOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 9, 5))
43+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
44+
>undefined : Symbol(undefined)
45+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 10, 58))
46+
47+
recordsOfRecordsOrEmpty.propB = {...(foo && {foo})} // OK
48+
>recordsOfRecordsOrEmpty : Symbol(recordsOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 9, 5))
49+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
50+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 11, 45))
51+
52+
recordsOfRecordsOrEmpty.propC = {...(foo !== undefined && {foo})} // OK
53+
>recordsOfRecordsOrEmpty : Symbol(recordsOfRecordsOrEmpty, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 9, 5))
54+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 0, 5))
55+
>undefined : Symbol(undefined)
56+
>foo : Symbol(foo, Decl(spreadOfObjectLiteralAssignableToIndexSignature.ts, 12, 59))
57+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
=== tests/cases/compiler/spreadOfObjectLiteralAssignableToIndexSignature.ts ===
2+
const foo: Record<never, never> = {} // OK
3+
>foo : Record<never, never>
4+
>{} : {}
5+
6+
interface RecordOfRecords extends Record<keyof any, RecordOfRecords> {}
7+
const recordOfRecords: RecordOfRecords = {}
8+
>recordOfRecords : RecordOfRecords
9+
>{} : {}
10+
11+
recordOfRecords.propA = {...(foo !== undefined ? {foo} : {})} // OK
12+
>recordOfRecords.propA = {...(foo !== undefined ? {foo} : {})} : { foo: Record<never, never>; } | {}
13+
>recordOfRecords.propA : RecordOfRecords
14+
>recordOfRecords : RecordOfRecords
15+
>propA : RecordOfRecords
16+
>{...(foo !== undefined ? {foo} : {})} : { foo: Record<never, never>; } | {}
17+
>(foo !== undefined ? {foo} : {}) : { foo: Record<never, never>; } | {}
18+
>foo !== undefined ? {foo} : {} : { foo: Record<never, never>; } | {}
19+
>foo !== undefined : boolean
20+
>foo : Record<never, never>
21+
>undefined : undefined
22+
>{foo} : { foo: Record<never, never>; }
23+
>foo : Record<never, never>
24+
>{} : {}
25+
26+
recordOfRecords.propB = {...(foo && {foo})} // OK
27+
>recordOfRecords.propB = {...(foo && {foo})} : { foo: Record<never, never>; }
28+
>recordOfRecords.propB : RecordOfRecords
29+
>recordOfRecords : RecordOfRecords
30+
>propB : RecordOfRecords
31+
>{...(foo && {foo})} : { foo: Record<never, never>; }
32+
>(foo && {foo}) : { foo: Record<never, never>; }
33+
>foo && {foo} : { foo: Record<never, never>; }
34+
>foo : Record<never, never>
35+
>{foo} : { foo: Record<never, never>; }
36+
>foo : Record<never, never>
37+
38+
recordOfRecords.propC = {...(foo !== undefined && {foo})} // error'd in 3.7 beta, should be OK
39+
>recordOfRecords.propC = {...(foo !== undefined && {foo})} : { foo: Record<never, never>; } | {}
40+
>recordOfRecords.propC : RecordOfRecords
41+
>recordOfRecords : RecordOfRecords
42+
>propC : RecordOfRecords
43+
>{...(foo !== undefined && {foo})} : { foo: Record<never, never>; } | {}
44+
>(foo !== undefined && {foo}) : false | { foo: Record<never, never>; }
45+
>foo !== undefined && {foo} : false | { foo: Record<never, never>; }
46+
>foo !== undefined : boolean
47+
>foo : Record<never, never>
48+
>undefined : undefined
49+
>{foo} : { foo: Record<never, never>; }
50+
>foo : Record<never, never>
51+
52+
interface RecordOfRecordsOrEmpty extends Record<keyof any, RecordOfRecordsOrEmpty | {}> {}
53+
const recordsOfRecordsOrEmpty: RecordOfRecordsOrEmpty = {}
54+
>recordsOfRecordsOrEmpty : RecordOfRecordsOrEmpty
55+
>{} : {}
56+
57+
recordsOfRecordsOrEmpty.propA = {...(foo !== undefined ? {foo} : {})} // OK
58+
>recordsOfRecordsOrEmpty.propA = {...(foo !== undefined ? {foo} : {})} : { foo: Record<never, never>; } | {}
59+
>recordsOfRecordsOrEmpty.propA : {} | RecordOfRecordsOrEmpty
60+
>recordsOfRecordsOrEmpty : RecordOfRecordsOrEmpty
61+
>propA : {} | RecordOfRecordsOrEmpty
62+
>{...(foo !== undefined ? {foo} : {})} : { foo: Record<never, never>; } | {}
63+
>(foo !== undefined ? {foo} : {}) : { foo: Record<never, never>; } | {}
64+
>foo !== undefined ? {foo} : {} : { foo: Record<never, never>; } | {}
65+
>foo !== undefined : boolean
66+
>foo : Record<never, never>
67+
>undefined : undefined
68+
>{foo} : { foo: Record<never, never>; }
69+
>foo : Record<never, never>
70+
>{} : {}
71+
72+
recordsOfRecordsOrEmpty.propB = {...(foo && {foo})} // OK
73+
>recordsOfRecordsOrEmpty.propB = {...(foo && {foo})} : { foo: Record<never, never>; }
74+
>recordsOfRecordsOrEmpty.propB : {} | RecordOfRecordsOrEmpty
75+
>recordsOfRecordsOrEmpty : RecordOfRecordsOrEmpty
76+
>propB : {} | RecordOfRecordsOrEmpty
77+
>{...(foo && {foo})} : { foo: Record<never, never>; }
78+
>(foo && {foo}) : { foo: Record<never, never>; }
79+
>foo && {foo} : { foo: Record<never, never>; }
80+
>foo : Record<never, never>
81+
>{foo} : { foo: Record<never, never>; }
82+
>foo : Record<never, never>
83+
84+
recordsOfRecordsOrEmpty.propC = {...(foo !== undefined && {foo})} // OK
85+
>recordsOfRecordsOrEmpty.propC = {...(foo !== undefined && {foo})} : { foo: Record<never, never>; } | {}
86+
>recordsOfRecordsOrEmpty.propC : {} | RecordOfRecordsOrEmpty
87+
>recordsOfRecordsOrEmpty : RecordOfRecordsOrEmpty
88+
>propC : {} | RecordOfRecordsOrEmpty
89+
>{...(foo !== undefined && {foo})} : { foo: Record<never, never>; } | {}
90+
>(foo !== undefined && {foo}) : false | { foo: Record<never, never>; }
91+
>foo !== undefined && {foo} : false | { foo: Record<never, never>; }
92+
>foo !== undefined : boolean
93+
>foo : Record<never, never>
94+
>undefined : undefined
95+
>{foo} : { foo: Record<never, never>; }
96+
>foo : Record<never, never>
97+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strict: true
2+
const foo: Record<never, never> = {} // OK
3+
4+
interface RecordOfRecords extends Record<keyof any, RecordOfRecords> {}
5+
const recordOfRecords: RecordOfRecords = {}
6+
recordOfRecords.propA = {...(foo !== undefined ? {foo} : {})} // OK
7+
recordOfRecords.propB = {...(foo && {foo})} // OK
8+
recordOfRecords.propC = {...(foo !== undefined && {foo})} // error'd in 3.7 beta, should be OK
9+
10+
interface RecordOfRecordsOrEmpty extends Record<keyof any, RecordOfRecordsOrEmpty | {}> {}
11+
const recordsOfRecordsOrEmpty: RecordOfRecordsOrEmpty = {}
12+
recordsOfRecordsOrEmpty.propA = {...(foo !== undefined ? {foo} : {})} // OK
13+
recordsOfRecordsOrEmpty.propB = {...(foo && {foo})} // OK
14+
recordsOfRecordsOrEmpty.propC = {...(foo !== undefined && {foo})} // OK

0 commit comments

Comments
 (0)