Fix TS2783 false positive for union types in object spread expressions (#62656)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
Co-authored-by: Ryan Cavanaugh <RyanCavanaugh@users.noreply.github.com>
Co-authored-by: Ryan Cavanaugh <ryanca@microsoft.com>
This commit is contained in:
Copilot 2025-10-22 16:44:10 -07:00 committed by GitHub
parent cbc2059b53
commit 542b09547f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 740 additions and 1 deletions

View File

@ -33858,7 +33858,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) {
for (const right of getPropertiesOfType(type)) {
if (!(right.flags & SymbolFlags.Optional)) {
if (!(right.flags & SymbolFlags.Optional) && !(getCheckFlags(right) & CheckFlags.Partial)) {
const left = props.get(right.escapedName);
if (left) {
const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));

View File

@ -0,0 +1,81 @@
spreadUnionPropOverride.ts(30,5): error TS2783: 'x' is specified more than once, so this usage will be overwritten.
spreadUnionPropOverride.ts(46,5): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
spreadUnionPropOverride.ts(63,5): error TS2783: 'name' is specified more than once, so this usage will be overwritten.
==== spreadUnionPropOverride.ts (3 errors) ====
// Repro from #62655
type Thing = {
id: string;
label: string;
};
const things: Thing[] = [];
function find(id: string): undefined | Thing {
return things.find(thing => thing.id === id);
}
declare function fun(thing: Thing): void;
fun({
id: 'foo',
...find('foo') ?? {
label: 'Foo',
},
});
// Should not error when spreading a union where one type doesn't have the property
const obj1 = {
x: 1,
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
}; // OK - x might be overwritten
// Should error when the property is in all constituents
const obj2 = {
x: 1,
~~~~
!!! error TS2783: 'x' is specified more than once, so this usage will be overwritten.
!!! related TS2785 spreadUnionPropOverride.ts:31:5: This spread always overwrites this property.
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
}; // Error - x is always overwritten
// Should not error with optional property in union
type Partial1 = { a: string; b?: number };
type Partial2 = { a: string; c: boolean };
declare const partial: Partial1 | Partial2;
const obj3 = {
b: 42,
...partial,
}; // OK - b is optional in Partial1 and missing in Partial2
// Should error when property is required in all types
const obj4 = {
a: "test",
~~~~~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 spreadUnionPropOverride.ts:47:5: This spread always overwrites this property.
...partial,
}; // Error - a is required in both types
// More complex union case
type A = { id: string; name: string };
type B = { name: string; age: number };
type C = { name: string };
declare const abc: A | B | C;
const obj5 = {
id: "123",
...abc,
}; // OK - id is only in A
const obj6 = {
name: "test",
~~~~~~~~~~~~
!!! error TS2783: 'name' is specified more than once, so this usage will be overwritten.
!!! related TS2785 spreadUnionPropOverride.ts:64:5: This spread always overwrites this property.
...abc,
}; // Error - name is in all types

View File

@ -0,0 +1,100 @@
//// [tests/cases/compiler/spreadUnionPropOverride.ts] ////
//// [spreadUnionPropOverride.ts]
// Repro from #62655
type Thing = {
id: string;
label: string;
};
const things: Thing[] = [];
function find(id: string): undefined | Thing {
return things.find(thing => thing.id === id);
}
declare function fun(thing: Thing): void;
fun({
id: 'foo',
...find('foo') ?? {
label: 'Foo',
},
});
// Should not error when spreading a union where one type doesn't have the property
const obj1 = {
x: 1,
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
}; // OK - x might be overwritten
// Should error when the property is in all constituents
const obj2 = {
x: 1,
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
}; // Error - x is always overwritten
// Should not error with optional property in union
type Partial1 = { a: string; b?: number };
type Partial2 = { a: string; c: boolean };
declare const partial: Partial1 | Partial2;
const obj3 = {
b: 42,
...partial,
}; // OK - b is optional in Partial1 and missing in Partial2
// Should error when property is required in all types
const obj4 = {
a: "test",
...partial,
}; // Error - a is required in both types
// More complex union case
type A = { id: string; name: string };
type B = { name: string; age: number };
type C = { name: string };
declare const abc: A | B | C;
const obj5 = {
id: "123",
...abc,
}; // OK - id is only in A
const obj6 = {
name: "test",
...abc,
}; // Error - name is in all types
//// [spreadUnionPropOverride.js]
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var _a;
var things = [];
function find(id) {
return things.find(function (thing) { return thing.id === id; });
}
fun(__assign({ id: 'foo' }, (_a = find('foo')) !== null && _a !== void 0 ? _a : {
label: 'Foo',
}));
// Should not error when spreading a union where one type doesn't have the property
var obj1 = __assign({ x: 1 }, (Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 })); // OK - x might be overwritten
// Should error when the property is in all constituents
var obj2 = __assign({ x: 1 }, (Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 })); // Error - x is always overwritten
var obj3 = __assign({ b: 42 }, partial); // OK - b is optional in Partial1 and missing in Partial2
// Should error when property is required in all types
var obj4 = __assign({ a: "test" }, partial); // Error - a is required in both types
var obj5 = __assign({ id: "123" }, abc); // OK - id is only in A
var obj6 = __assign({ name: "test" }, abc); // Error - name is in all types

View File

@ -0,0 +1,172 @@
//// [tests/cases/compiler/spreadUnionPropOverride.ts] ////
=== spreadUnionPropOverride.ts ===
// Repro from #62655
type Thing = {
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
id: string;
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 1, 14))
label: string;
>label : Symbol(label, Decl(spreadUnionPropOverride.ts, 2, 15))
};
const things: Thing[] = [];
>things : Symbol(things, Decl(spreadUnionPropOverride.ts, 6, 5))
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
function find(id: string): undefined | Thing {
>find : Symbol(find, Decl(spreadUnionPropOverride.ts, 6, 27))
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 8, 14))
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
return things.find(thing => thing.id === id);
>things.find : Symbol(Array.find, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
>things : Symbol(things, Decl(spreadUnionPropOverride.ts, 6, 5))
>find : Symbol(Array.find, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
>thing : Symbol(thing, Decl(spreadUnionPropOverride.ts, 9, 23))
>thing.id : Symbol(id, Decl(spreadUnionPropOverride.ts, 1, 14))
>thing : Symbol(thing, Decl(spreadUnionPropOverride.ts, 9, 23))
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 1, 14))
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 8, 14))
}
declare function fun(thing: Thing): void;
>fun : Symbol(fun, Decl(spreadUnionPropOverride.ts, 10, 1))
>thing : Symbol(thing, Decl(spreadUnionPropOverride.ts, 12, 21))
>Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0))
fun({
>fun : Symbol(fun, Decl(spreadUnionPropOverride.ts, 10, 1))
id: 'foo',
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 14, 5))
...find('foo') ?? {
>find : Symbol(find, Decl(spreadUnionPropOverride.ts, 6, 27))
label: 'Foo',
>label : Symbol(label, Decl(spreadUnionPropOverride.ts, 16, 23))
},
});
// Should not error when spreading a union where one type doesn't have the property
const obj1 = {
>obj1 : Symbol(obj1, Decl(spreadUnionPropOverride.ts, 22, 5))
x: 1,
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 22, 14))
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>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, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(spreadUnionPropOverride.ts, 24, 31))
>y : Symbol(y, Decl(spreadUnionPropOverride.ts, 24, 42))
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 24, 48))
}; // OK - x might be overwritten
// Should error when the property is in all constituents
const obj2 = {
>obj2 : Symbol(obj2, Decl(spreadUnionPropOverride.ts, 28, 5))
x: 1,
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 28, 14))
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>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, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 30, 31))
>y : Symbol(y, Decl(spreadUnionPropOverride.ts, 30, 37))
>x : Symbol(x, Decl(spreadUnionPropOverride.ts, 30, 48))
>z : Symbol(z, Decl(spreadUnionPropOverride.ts, 30, 54))
}; // Error - x is always overwritten
// Should not error with optional property in union
type Partial1 = { a: string; b?: number };
>Partial1 : Symbol(Partial1, Decl(spreadUnionPropOverride.ts, 31, 2))
>a : Symbol(a, Decl(spreadUnionPropOverride.ts, 34, 17))
>b : Symbol(b, Decl(spreadUnionPropOverride.ts, 34, 28))
type Partial2 = { a: string; c: boolean };
>Partial2 : Symbol(Partial2, Decl(spreadUnionPropOverride.ts, 34, 42))
>a : Symbol(a, Decl(spreadUnionPropOverride.ts, 35, 17))
>c : Symbol(c, Decl(spreadUnionPropOverride.ts, 35, 28))
declare const partial: Partial1 | Partial2;
>partial : Symbol(partial, Decl(spreadUnionPropOverride.ts, 36, 13))
>Partial1 : Symbol(Partial1, Decl(spreadUnionPropOverride.ts, 31, 2))
>Partial2 : Symbol(Partial2, Decl(spreadUnionPropOverride.ts, 34, 42))
const obj3 = {
>obj3 : Symbol(obj3, Decl(spreadUnionPropOverride.ts, 38, 5))
b: 42,
>b : Symbol(b, Decl(spreadUnionPropOverride.ts, 38, 14))
...partial,
>partial : Symbol(partial, Decl(spreadUnionPropOverride.ts, 36, 13))
}; // OK - b is optional in Partial1 and missing in Partial2
// Should error when property is required in all types
const obj4 = {
>obj4 : Symbol(obj4, Decl(spreadUnionPropOverride.ts, 44, 5))
a: "test",
>a : Symbol(a, Decl(spreadUnionPropOverride.ts, 44, 14))
...partial,
>partial : Symbol(partial, Decl(spreadUnionPropOverride.ts, 36, 13))
}; // Error - a is required in both types
// More complex union case
type A = { id: string; name: string };
>A : Symbol(A, Decl(spreadUnionPropOverride.ts, 47, 2))
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 50, 10))
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 50, 22))
type B = { name: string; age: number };
>B : Symbol(B, Decl(spreadUnionPropOverride.ts, 50, 38))
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 51, 10))
>age : Symbol(age, Decl(spreadUnionPropOverride.ts, 51, 24))
type C = { name: string };
>C : Symbol(C, Decl(spreadUnionPropOverride.ts, 51, 39))
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 52, 10))
declare const abc: A | B | C;
>abc : Symbol(abc, Decl(spreadUnionPropOverride.ts, 54, 13))
>A : Symbol(A, Decl(spreadUnionPropOverride.ts, 47, 2))
>B : Symbol(B, Decl(spreadUnionPropOverride.ts, 50, 38))
>C : Symbol(C, Decl(spreadUnionPropOverride.ts, 51, 39))
const obj5 = {
>obj5 : Symbol(obj5, Decl(spreadUnionPropOverride.ts, 56, 5))
id: "123",
>id : Symbol(id, Decl(spreadUnionPropOverride.ts, 56, 14))
...abc,
>abc : Symbol(abc, Decl(spreadUnionPropOverride.ts, 54, 13))
}; // OK - id is only in A
const obj6 = {
>obj6 : Symbol(obj6, Decl(spreadUnionPropOverride.ts, 61, 5))
name: "test",
>name : Symbol(name, Decl(spreadUnionPropOverride.ts, 61, 14))
...abc,
>abc : Symbol(abc, Decl(spreadUnionPropOverride.ts, 54, 13))
}; // Error - name is in all types

View File

@ -0,0 +1,319 @@
//// [tests/cases/compiler/spreadUnionPropOverride.ts] ////
=== spreadUnionPropOverride.ts ===
// Repro from #62655
type Thing = {
>Thing : Thing
> : ^^^^^
id: string;
>id : string
> : ^^^^^^
label: string;
>label : string
> : ^^^^^^
};
const things: Thing[] = [];
>things : Thing[]
> : ^^^^^^^
>[] : never[]
> : ^^^^^^^
function find(id: string): undefined | Thing {
>find : (id: string) => undefined | Thing
> : ^ ^^ ^^^^^
>id : string
> : ^^^^^^
return things.find(thing => thing.id === id);
>things.find(thing => thing.id === id) : Thing | undefined
> : ^^^^^^^^^^^^^^^^^
>things.find : { <S extends Thing>(predicate: (value: Thing, index: number, obj: Thing[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: Thing, index: number, obj: Thing[]) => unknown, thisArg?: any): Thing | undefined; }
> : ^^^ ^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^
>things : Thing[]
> : ^^^^^^^
>find : { <S extends Thing>(predicate: (value: Thing, index: number, obj: Thing[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: Thing, index: number, obj: Thing[]) => unknown, thisArg?: any): Thing | undefined; }
> : ^^^ ^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^
>thing => thing.id === id : (thing: Thing) => boolean
> : ^ ^^^^^^^^^^^^^^^^^^^
>thing : Thing
> : ^^^^^
>thing.id === id : boolean
> : ^^^^^^^
>thing.id : string
> : ^^^^^^
>thing : Thing
> : ^^^^^
>id : string
> : ^^^^^^
>id : string
> : ^^^^^^
}
declare function fun(thing: Thing): void;
>fun : (thing: Thing) => void
> : ^ ^^ ^^^^^
>thing : Thing
> : ^^^^^
fun({
>fun({ id: 'foo', ...find('foo') ?? { label: 'Foo', },}) : void
> : ^^^^
>fun : (thing: Thing) => void
> : ^ ^^ ^^^^^
>{ id: 'foo', ...find('foo') ?? { label: 'Foo', },} : { id: string; label: string; } | { label: string; id: string; }
> : ^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
id: 'foo',
>id : string
> : ^^^^^^
>'foo' : "foo"
> : ^^^^^
...find('foo') ?? {
>find('foo') ?? { label: 'Foo', } : Thing | { label: string; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
>find('foo') : Thing | undefined
> : ^^^^^^^^^^^^^^^^^
>find : (id: string) => undefined | Thing
> : ^ ^^ ^^^^^
>'foo' : "foo"
> : ^^^^^
>{ label: 'Foo', } : { label: string; }
> : ^^^^^^^^^^^^^^^^^^
label: 'Foo',
>label : string
> : ^^^^^^
>'Foo' : "Foo"
> : ^^^^^
},
});
// Should not error when spreading a union where one type doesn't have the property
const obj1 = {
>obj1 : { y: number; x: number; } | { y: number; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ x: 1, ...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),} : { y: number; x: number; } | { y: number; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
x: 1,
>x : number
> : ^^^^^^
>1 : 1
> : ^
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
>(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }) : { y: number; } | { y: number; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 } : { y: number; } | { y: number; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Math.random() > 0.5 : boolean
> : ^^^^^^^
>Math.random() : number
> : ^^^^^^
>Math.random : () => number
> : ^^^^^^
>Math : Math
> : ^^^^
>random : () => number
> : ^^^^^^
>0.5 : 0.5
> : ^^^
>{ y: 2 } : { y: number; }
> : ^^^^^^^^^^^^^^
>y : number
> : ^^^^^^
>2 : 2
> : ^
>{ y: 2, x: 3 } : { y: number; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^
>y : number
> : ^^^^^^
>2 : 2
> : ^
>x : number
> : ^^^^^^
>3 : 3
> : ^
}; // OK - x might be overwritten
// Should error when the property is in all constituents
const obj2 = {
>obj2 : { x: number; y: number; } | { x: number; z: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ x: 1, ...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),} : { x: number; y: number; } | { x: number; z: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
x: 1,
>x : number
> : ^^^^^^
>1 : 1
> : ^
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
>(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }) : { x: number; y: number; } | { x: number; z: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 } : { x: number; y: number; } | { x: number; z: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Math.random() > 0.5 : boolean
> : ^^^^^^^
>Math.random() : number
> : ^^^^^^
>Math.random : () => number
> : ^^^^^^
>Math : Math
> : ^^^^
>random : () => number
> : ^^^^^^
>0.5 : 0.5
> : ^^^
>{ x: 2, y: 3 } : { x: number; y: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^
>x : number
> : ^^^^^^
>2 : 2
> : ^
>y : number
> : ^^^^^^
>3 : 3
> : ^
>{ x: 4, z: 5 } : { x: number; z: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^
>x : number
> : ^^^^^^
>4 : 4
> : ^
>z : number
> : ^^^^^^
>5 : 5
> : ^
}; // Error - x is always overwritten
// Should not error with optional property in union
type Partial1 = { a: string; b?: number };
>Partial1 : Partial1
> : ^^^^^^^^
>a : string
> : ^^^^^^
>b : number | undefined
> : ^^^^^^^^^^^^^^^^^^
type Partial2 = { a: string; c: boolean };
>Partial2 : Partial2
> : ^^^^^^^^
>a : string
> : ^^^^^^
>c : boolean
> : ^^^^^^^
declare const partial: Partial1 | Partial2;
>partial : Partial1 | Partial2
> : ^^^^^^^^^^^^^^^^^^^
const obj3 = {
>obj3 : { a: string; b: number; } | { a: string; c: boolean; b: number; }
> : ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^
>{ b: 42, ...partial,} : { a: string; b: number; } | { a: string; c: boolean; b: number; }
> : ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^
b: 42,
>b : number
> : ^^^^^^
>42 : 42
> : ^^
...partial,
>partial : Partial1 | Partial2
> : ^^^^^^^^^^^^^^^^^^^
}; // OK - b is optional in Partial1 and missing in Partial2
// Should error when property is required in all types
const obj4 = {
>obj4 : { a: string; b?: number; } | { a: string; c: boolean; }
> : ^^^^^ ^^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^
>{ a: "test", ...partial,} : { a: string; b?: number; } | { a: string; c: boolean; }
> : ^^^^^ ^^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^
a: "test",
>a : string
> : ^^^^^^
>"test" : "test"
> : ^^^^^^
...partial,
>partial : Partial1 | Partial2
> : ^^^^^^^^^^^^^^^^^^^
}; // Error - a is required in both types
// More complex union case
type A = { id: string; name: string };
>A : A
> : ^
>id : string
> : ^^^^^^
>name : string
> : ^^^^^^
type B = { name: string; age: number };
>B : B
> : ^
>name : string
> : ^^^^^^
>age : number
> : ^^^^^^
type C = { name: string };
>C : C
> : ^
>name : string
> : ^^^^^^
declare const abc: A | B | C;
>abc : A | B | C
> : ^^^^^^^^^
const obj5 = {
>obj5 : { id: string; name: string; } | { name: string; age: number; id: string; } | { name: string; id: string; }
> : ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
>{ id: "123", ...abc,} : { id: string; name: string; } | { name: string; age: number; id: string; } | { name: string; id: string; }
> : ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
id: "123",
>id : string
> : ^^^^^^
>"123" : "123"
> : ^^^^^
...abc,
>abc : A | B | C
> : ^^^^^^^^^
}; // OK - id is only in A
const obj6 = {
>obj6 : { id: string; name: string; } | { name: string; age: number; } | { name: string; }
> : ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^ ^^^
>{ name: "test", ...abc,} : { id: string; name: string; } | { name: string; age: number; } | { name: string; }
> : ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^ ^^^
name: "test",
>name : string
> : ^^^^^^
>"test" : "test"
> : ^^^^^^
...abc,
>abc : A | B | C
> : ^^^^^^^^^
}; // Error - name is in all types

View File

@ -0,0 +1,67 @@
// @strict: true
// Repro from #62655
type Thing = {
id: string;
label: string;
};
const things: Thing[] = [];
function find(id: string): undefined | Thing {
return things.find(thing => thing.id === id);
}
declare function fun(thing: Thing): void;
fun({
id: 'foo',
...find('foo') ?? {
label: 'Foo',
},
});
// Should not error when spreading a union where one type doesn't have the property
const obj1 = {
x: 1,
...(Math.random() > 0.5 ? { y: 2 } : { y: 2, x: 3 }),
}; // OK - x might be overwritten
// Should error when the property is in all constituents
const obj2 = {
x: 1,
...(Math.random() > 0.5 ? { x: 2, y: 3 } : { x: 4, z: 5 }),
}; // Error - x is always overwritten
// Should not error with optional property in union
type Partial1 = { a: string; b?: number };
type Partial2 = { a: string; c: boolean };
declare const partial: Partial1 | Partial2;
const obj3 = {
b: 42,
...partial,
}; // OK - b is optional in Partial1 and missing in Partial2
// Should error when property is required in all types
const obj4 = {
a: "test",
...partial,
}; // Error - a is required in both types
// More complex union case
type A = { id: string; name: string };
type B = { name: string; age: number };
type C = { name: string };
declare const abc: A | B | C;
const obj5 = {
id: "123",
...abc,
}; // OK - id is only in A
const obj6 = {
name: "test",
...abc,
}; // Error - name is in all types