Skip to content

Commit 542b095

Browse files
Fix TS2783 false positive for union types in object spread expressions (#62656)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: RyanCavanaugh <[email protected]> Co-authored-by: Ryan Cavanaugh <[email protected]> Co-authored-by: Ryan Cavanaugh <[email protected]>
1 parent cbc2059 commit 542b095

File tree

6 files changed

+740
-1
lines changed

6 files changed

+740
-1
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33858,7 +33858,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3385833858

3385933859
function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) {
3386033860
for (const right of getPropertiesOfType(type)) {
33861-
if (!(right.flags & SymbolFlags.Optional)) {
33861+
if (!(right.flags & SymbolFlags.Optional) && !(getCheckFlags(right) & CheckFlags.Partial)) {
3386233862
const left = props.get(right.escapedName);
3386333863
if (left) {
3386433864
const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
spreadUnionPropOverride.ts(30,5): error TS2783: 'x' is specified more than once, so this usage will be overwritten.
2+
spreadUnionPropOverride.ts(46,5): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
3+
spreadUnionPropOverride.ts(63,5): error TS2783: 'name' is specified more than once, so this usage will be overwritten.
4+
5+
6+
==== spreadUnionPropOverride.ts (3 errors) ====
7+
// Repro from #62655
8+
type Thing = {
9+
id: string;
10+
label: string;
11+
};
12+
13+
const things: Thing[] = [];
14+
15+
function find(id: string): undefined | Thing {
16+
return things.find(thing => thing.id === id);
17+
}
18+
19+
declare function fun(thing: Thing): void;
20+
21+
fun({
22+
id: 'foo',
23+
...find('foo') ?? {
24+
label: 'Foo',
25+
},
26+
});
27+
28+
// Should not error when spreading a union where one type doesn't have the property
29+
const obj1 = {
30+
x: 1,
31+
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
32+
}; // OK - x might be overwritten
33+
34+
// Should error when the property is in all constituents
35+
const obj2 = {
36+
x: 1,
37+
~~~~
38+
!!! error TS2783: 'x' is specified more than once, so this usage will be overwritten.
39+
!!! related TS2785 spreadUnionPropOverride.ts:31:5: This spread always overwrites this property.
40+
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
41+
}; // Error - x is always overwritten
42+
43+
// Should not error with optional property in union
44+
type Partial1 = { a: string; b?: number };
45+
type Partial2 = { a: string; c: boolean };
46+
declare const partial: Partial1 | Partial2;
47+
48+
const obj3 = {
49+
b: 42,
50+
...partial,
51+
}; // OK - b is optional in Partial1 and missing in Partial2
52+
53+
// Should error when property is required in all types
54+
const obj4 = {
55+
a: "test",
56+
~~~~~~~~~
57+
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
58+
!!! related TS2785 spreadUnionPropOverride.ts:47:5: This spread always overwrites this property.
59+
...partial,
60+
}; // Error - a is required in both types
61+
62+
// More complex union case
63+
type A = { id: string; name: string };
64+
type B = { name: string; age: number };
65+
type C = { name: string };
66+
67+
declare const abc: A | B | C;
68+
69+
const obj5 = {
70+
id: "123",
71+
...abc,
72+
}; // OK - id is only in A
73+
74+
const obj6 = {
75+
name: "test",
76+
~~~~~~~~~~~~
77+
!!! error TS2783: 'name' is specified more than once, so this usage will be overwritten.
78+
!!! related TS2785 spreadUnionPropOverride.ts:64:5: This spread always overwrites this property.
79+
...abc,
80+
}; // Error - name is in all types
81+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//// [tests/cases/compiler/spreadUnionPropOverride.ts] ////
2+
3+
//// [spreadUnionPropOverride.ts]
4+
// Repro from #62655
5+
type Thing = {
6+
id: string;
7+
label: string;
8+
};
9+
10+
const things: Thing[] = [];
11+
12+
function find(id: string): undefined | Thing {
13+
return things.find(thing => thing.id === id);
14+
}
15+
16+
declare function fun(thing: Thing): void;
17+
18+
fun({
19+
id: 'foo',
20+
...find('foo') ?? {
21+
label: 'Foo',
22+
},
23+
});
24+
25+
// Should not error when spreading a union where one type doesn't have the property
26+
const obj1 = {
27+
x: 1,
28+
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
29+
}; // OK - x might be overwritten
30+
31+
// Should error when the property is in all constituents
32+
const obj2 = {
33+
x: 1,
34+
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
35+
}; // Error - x is always overwritten
36+
37+
// Should not error with optional property in union
38+
type Partial1 = { a: string; b?: number };
39+
type Partial2 = { a: string; c: boolean };
40+
declare const partial: Partial1 | Partial2;
41+
42+
const obj3 = {
43+
b: 42,
44+
...partial,
45+
}; // OK - b is optional in Partial1 and missing in Partial2
46+
47+
// Should error when property is required in all types
48+
const obj4 = {
49+
a: "test",
50+
...partial,
51+
}; // Error - a is required in both types
52+
53+
// More complex union case
54+
type A = { id: string; name: string };
55+
type B = { name: string; age: number };
56+
type C = { name: string };
57+
58+
declare const abc: A | B | C;
59+
60+
const obj5 = {
61+
id: "123",
62+
...abc,
63+
}; // OK - id is only in A
64+
65+
const obj6 = {
66+
name: "test",
67+
...abc,
68+
}; // Error - name is in all types
69+
70+
71+
//// [spreadUnionPropOverride.js]
72+
"use strict";
73+
var __assign = (this && this.__assign) || function () {
74+
__assign = Object.assign || function(t) {
75+
for (var s, i = 1, n = arguments.length; i < n; i++) {
76+
s = arguments[i];
77+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
78+
t[p] = s[p];
79+
}
80+
return t;
81+
};
82+
return __assign.apply(this, arguments);
83+
};
84+
var _a;
85+
var things = [];
86+
function find(id) {
87+
return things.find(function (thing) { return thing.id === id; });
88+
}
89+
fun(__assign({ id: 'foo' }, (_a = find('foo')) !== null && _a !== void 0 ? _a : {
90+
label: 'Foo',
91+
}));
92+
// Should not error when spreading a union where one type doesn't have the property
93+
var obj1 = __assign({ x: 1 }, (Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 })); // OK - x might be overwritten
94+
// Should error when the property is in all constituents
95+
var obj2 = __assign({ x: 1 }, (Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 })); // Error - x is always overwritten
96+
var obj3 = __assign({ b: 42 }, partial); // OK - b is optional in Partial1 and missing in Partial2
97+
// Should error when property is required in all types
98+
var obj4 = __assign({ a: "test" }, partial); // Error - a is required in both types
99+
var obj5 = __assign({ id: "123" }, abc); // OK - id is only in A
100+
var obj6 = __assign({ name: "test" }, abc); // Error - name is in all types
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//// [tests/cases/compiler/spreadUnionPropOverride.ts] ////
2+
3+
=== spreadUnionPropOverride.ts ===
4+
// Repro from #62655
5+
type Thing = {
6+
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
7+
8+
id: string;
9+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 1, 14))
10+
11+
label: string;
12+
>label : Symbol(label, Decl(spreadUnionPropOverride.ts, 2, 15))
13+
14+
};
15+
16+
const things: Thing[] = [];
17+
>things : Symbol(things, Decl(spreadUnionPropOverride.ts, 6, 5))
18+
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
19+
20+
function find(id: string): undefined | Thing {
21+
>find : Symbol(find, Decl(spreadUnionPropOverride.ts, 6, 27))
22+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 8, 14))
23+
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
24+
25+
return things.find(thing => thing.id === id);
26+
>things.find : Symbol(Array.find, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
27+
>things : Symbol(things, Decl(spreadUnionPropOverride.ts, 6, 5))
28+
>find : Symbol(Array.find, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
29+
>thing : Symbol(thing, Decl(spreadUnionPropOverride.ts, 9, 23))
30+
>thing.id : Symbol(id, Decl(spreadUnionPropOverride.ts, 1, 14))
31+
>thing : Symbol(thing, Decl(spreadUnionPropOverride.ts, 9, 23))
32+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 1, 14))
33+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 8, 14))
34+
}
35+
36+
declare function fun(thing: Thing): void;
37+
>fun : Symbol(fun, Decl(spreadUnionPropOverride.ts, 10, 1))
38+
>thing : Symbol(thing, Decl(spreadUnionPropOverride.ts, 12, 21))
39+
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
40+
41+
fun({
42+
>fun : Symbol(fun, Decl(spreadUnionPropOverride.ts, 10, 1))
43+
44+
id: 'foo',
45+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 14, 5))
46+
47+
...find('foo') ?? {
48+
>find : Symbol(find, Decl(spreadUnionPropOverride.ts, 6, 27))
49+
50+
label: 'Foo',
51+
>label : Symbol(label, Decl(spreadUnionPropOverride.ts, 16, 23))
52+
53+
},
54+
});
55+
56+
// Should not error when spreading a union where one type doesn't have the property
57+
const obj1 = {
58+
>obj1 : Symbol(obj1, Decl(spreadUnionPropOverride.ts, 22, 5))
59+
60+
x: 1,
61+
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 22, 14))
62+
63+
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
64+
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
65+
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
66+
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
67+
>y : Symbol(y, Decl(spreadUnionPropOverride.ts, 24, 31))
68+
>y : Symbol(y, Decl(spreadUnionPropOverride.ts, 24, 42))
69+
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 24, 48))
70+
71+
}; // OK - x might be overwritten
72+
73+
// Should error when the property is in all constituents
74+
const obj2 = {
75+
>obj2 : Symbol(obj2, Decl(spreadUnionPropOverride.ts, 28, 5))
76+
77+
x: 1,
78+
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 28, 14))
79+
80+
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
81+
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
82+
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
83+
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
84+
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 30, 31))
85+
>y : Symbol(y, Decl(spreadUnionPropOverride.ts, 30, 37))
86+
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 30, 48))
87+
>z : Symbol(z, Decl(spreadUnionPropOverride.ts, 30, 54))
88+
89+
}; // Error - x is always overwritten
90+
91+
// Should not error with optional property in union
92+
type Partial1 = { a: string; b?: number };
93+
>Partial1 : Symbol(Partial1, Decl(spreadUnionPropOverride.ts, 31, 2))
94+
>a : Symbol(a, Decl(spreadUnionPropOverride.ts, 34, 17))
95+
>b : Symbol(b, Decl(spreadUnionPropOverride.ts, 34, 28))
96+
97+
type Partial2 = { a: string; c: boolean };
98+
>Partial2 : Symbol(Partial2, Decl(spreadUnionPropOverride.ts, 34, 42))
99+
>a : Symbol(a, Decl(spreadUnionPropOverride.ts, 35, 17))
100+
>c : Symbol(c, Decl(spreadUnionPropOverride.ts, 35, 28))
101+
102+
declare const partial: Partial1 | Partial2;
103+
>partial : Symbol(partial, Decl(spreadUnionPropOverride.ts, 36, 13))
104+
>Partial1 : Symbol(Partial1, Decl(spreadUnionPropOverride.ts, 31, 2))
105+
>Partial2 : Symbol(Partial2, Decl(spreadUnionPropOverride.ts, 34, 42))
106+
107+
const obj3 = {
108+
>obj3 : Symbol(obj3, Decl(spreadUnionPropOverride.ts, 38, 5))
109+
110+
b: 42,
111+
>b : Symbol(b, Decl(spreadUnionPropOverride.ts, 38, 14))
112+
113+
...partial,
114+
>partial : Symbol(partial, Decl(spreadUnionPropOverride.ts, 36, 13))
115+
116+
}; // OK - b is optional in Partial1 and missing in Partial2
117+
118+
// Should error when property is required in all types
119+
const obj4 = {
120+
>obj4 : Symbol(obj4, Decl(spreadUnionPropOverride.ts, 44, 5))
121+
122+
a: "test",
123+
>a : Symbol(a, Decl(spreadUnionPropOverride.ts, 44, 14))
124+
125+
...partial,
126+
>partial : Symbol(partial, Decl(spreadUnionPropOverride.ts, 36, 13))
127+
128+
}; // Error - a is required in both types
129+
130+
// More complex union case
131+
type A = { id: string; name: string };
132+
>A : Symbol(A, Decl(spreadUnionPropOverride.ts, 47, 2))
133+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 50, 10))
134+
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 50, 22))
135+
136+
type B = { name: string; age: number };
137+
>B : Symbol(B, Decl(spreadUnionPropOverride.ts, 50, 38))
138+
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 51, 10))
139+
>age : Symbol(age, Decl(spreadUnionPropOverride.ts, 51, 24))
140+
141+
type C = { name: string };
142+
>C : Symbol(C, Decl(spreadUnionPropOverride.ts, 51, 39))
143+
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 52, 10))
144+
145+
declare const abc: A | B | C;
146+
>abc : Symbol(abc, Decl(spreadUnionPropOverride.ts, 54, 13))
147+
>A : Symbol(A, Decl(spreadUnionPropOverride.ts, 47, 2))
148+
>B : Symbol(B, Decl(spreadUnionPropOverride.ts, 50, 38))
149+
>C : Symbol(C, Decl(spreadUnionPropOverride.ts, 51, 39))
150+
151+
const obj5 = {
152+
>obj5 : Symbol(obj5, Decl(spreadUnionPropOverride.ts, 56, 5))
153+
154+
id: "123",
155+
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 56, 14))
156+
157+
...abc,
158+
>abc : Symbol(abc, Decl(spreadUnionPropOverride.ts, 54, 13))
159+
160+
}; // OK - id is only in A
161+
162+
const obj6 = {
163+
>obj6 : Symbol(obj6, Decl(spreadUnionPropOverride.ts, 61, 5))
164+
165+
name: "test",
166+
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 61, 14))
167+
168+
...abc,
169+
>abc : Symbol(abc, Decl(spreadUnionPropOverride.ts, 54, 13))
170+
171+
}; // Error - name is in all types
172+

0 commit comments

Comments
 (0)