From 13068a74764f44c451410c1b3122e5aa2adfff85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:30:25 +0000 Subject: [PATCH 1/5] Initial plan From d3293afe2196642b8ec64579ce6832e547ef70a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:40:53 +0000 Subject: [PATCH 2/5] Fix TS2783 false positive with union types in spreads Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 2 +- .../spreadUnionPropOverride.errors.txt | 81 +++++ .../reference/spreadUnionPropOverride.js | 100 ++++++ .../reference/spreadUnionPropOverride.symbols | 172 ++++++++++ .../reference/spreadUnionPropOverride.types | 319 ++++++++++++++++++ .../cases/compiler/spreadUnionPropOverride.ts | 67 ++++ 6 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/spreadUnionPropOverride.errors.txt create mode 100644 tests/baselines/reference/spreadUnionPropOverride.js create mode 100644 tests/baselines/reference/spreadUnionPropOverride.symbols create mode 100644 tests/baselines/reference/spreadUnionPropOverride.types create mode 100644 tests/cases/compiler/spreadUnionPropOverride.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48bc0da113816..1740df24a15c6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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)); diff --git a/tests/baselines/reference/spreadUnionPropOverride.errors.txt b/tests/baselines/reference/spreadUnionPropOverride.errors.txt new file mode 100644 index 0000000000000..ca25bbceaec72 --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.errors.txt @@ -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 #61223 + 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 + \ No newline at end of file diff --git a/tests/baselines/reference/spreadUnionPropOverride.js b/tests/baselines/reference/spreadUnionPropOverride.js new file mode 100644 index 0000000000000..85f93c9cc1921 --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.js @@ -0,0 +1,100 @@ +//// [tests/cases/compiler/spreadUnionPropOverride.ts] //// + +//// [spreadUnionPropOverride.ts] +// Repro from #61223 +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 diff --git a/tests/baselines/reference/spreadUnionPropOverride.symbols b/tests/baselines/reference/spreadUnionPropOverride.symbols new file mode 100644 index 0000000000000..1ab424e1870b4 --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.symbols @@ -0,0 +1,172 @@ +//// [tests/cases/compiler/spreadUnionPropOverride.ts] //// + +=== spreadUnionPropOverride.ts === +// Repro from #61223 +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 + diff --git a/tests/baselines/reference/spreadUnionPropOverride.types b/tests/baselines/reference/spreadUnionPropOverride.types new file mode 100644 index 0000000000000..92a1ae982785d --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.types @@ -0,0 +1,319 @@ +//// [tests/cases/compiler/spreadUnionPropOverride.ts] //// + +=== spreadUnionPropOverride.ts === +// Repro from #61223 +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 : { (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 : { (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 + diff --git a/tests/cases/compiler/spreadUnionPropOverride.ts b/tests/cases/compiler/spreadUnionPropOverride.ts new file mode 100644 index 0000000000000..028970c7f92e7 --- /dev/null +++ b/tests/cases/compiler/spreadUnionPropOverride.ts @@ -0,0 +1,67 @@ +// @strict: true + +// Repro from #61223 +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 From c7c22959ad29db6cb6d3cc548aa8d339345b37aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:03:28 +0000 Subject: [PATCH 3/5] Fix line endings in test file to use CRLF Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .../cases/compiler/spreadUnionPropOverride.ts | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/tests/cases/compiler/spreadUnionPropOverride.ts b/tests/cases/compiler/spreadUnionPropOverride.ts index 028970c7f92e7..f6f37e03df23a 100644 --- a/tests/cases/compiler/spreadUnionPropOverride.ts +++ b/tests/cases/compiler/spreadUnionPropOverride.ts @@ -1,67 +1,67 @@ -// @strict: true - -// Repro from #61223 -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 +// @strict: true + +// Repro from #61223 +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 From 8faab9cd890e50ec8eb0d3b054d4d17913c808ea Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 22 Oct 2025 12:27:20 -0700 Subject: [PATCH 4/5] Apply suggestions from code review Fix issue ref --- .../spreadUnionPropOverride.errors.txt | 2 +- .../reference/spreadUnionPropOverride.js | 130 +++++++++--------- .../reference/spreadUnionPropOverride.symbols | 2 +- .../reference/spreadUnionPropOverride.types | 2 +- .../cases/compiler/spreadUnionPropOverride.ts | 2 +- 5 files changed, 69 insertions(+), 69 deletions(-) diff --git a/tests/baselines/reference/spreadUnionPropOverride.errors.txt b/tests/baselines/reference/spreadUnionPropOverride.errors.txt index ca25bbceaec72..120aeb7f1a62e 100644 --- a/tests/baselines/reference/spreadUnionPropOverride.errors.txt +++ b/tests/baselines/reference/spreadUnionPropOverride.errors.txt @@ -4,7 +4,7 @@ spreadUnionPropOverride.ts(63,5): error TS2783: 'name' is specified more than on ==== spreadUnionPropOverride.ts (3 errors) ==== - // Repro from #61223 + // Repro from #62655 type Thing = { id: string; label: string; diff --git a/tests/baselines/reference/spreadUnionPropOverride.js b/tests/baselines/reference/spreadUnionPropOverride.js index 85f93c9cc1921..22ba2c0a07b19 100644 --- a/tests/baselines/reference/spreadUnionPropOverride.js +++ b/tests/baselines/reference/spreadUnionPropOverride.js @@ -1,71 +1,71 @@ //// [tests/cases/compiler/spreadUnionPropOverride.ts] //// //// [spreadUnionPropOverride.ts] -// Repro from #61223 -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 +// 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] diff --git a/tests/baselines/reference/spreadUnionPropOverride.symbols b/tests/baselines/reference/spreadUnionPropOverride.symbols index 1ab424e1870b4..83dbad64a727a 100644 --- a/tests/baselines/reference/spreadUnionPropOverride.symbols +++ b/tests/baselines/reference/spreadUnionPropOverride.symbols @@ -1,7 +1,7 @@ //// [tests/cases/compiler/spreadUnionPropOverride.ts] //// === spreadUnionPropOverride.ts === -// Repro from #61223 +// Repro from #62655 type Thing = { >Thing : Symbol(Thing, Decl(spreadUnionPropOverride.ts, 0, 0)) diff --git a/tests/baselines/reference/spreadUnionPropOverride.types b/tests/baselines/reference/spreadUnionPropOverride.types index 92a1ae982785d..ff6f63848eca3 100644 --- a/tests/baselines/reference/spreadUnionPropOverride.types +++ b/tests/baselines/reference/spreadUnionPropOverride.types @@ -1,7 +1,7 @@ //// [tests/cases/compiler/spreadUnionPropOverride.ts] //// === spreadUnionPropOverride.ts === -// Repro from #61223 +// Repro from #62655 type Thing = { >Thing : Thing > : ^^^^^ diff --git a/tests/cases/compiler/spreadUnionPropOverride.ts b/tests/cases/compiler/spreadUnionPropOverride.ts index f6f37e03df23a..ab1c05a30cbfc 100644 --- a/tests/cases/compiler/spreadUnionPropOverride.ts +++ b/tests/cases/compiler/spreadUnionPropOverride.ts @@ -1,6 +1,6 @@ // @strict: true -// Repro from #61223 +// Repro from #62655 type Thing = { id: string; label: string; From 2af21924a66d9b70fb795dbc6874851907ba6190 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 22 Oct 2025 14:16:00 -0700 Subject: [PATCH 5/5] Fix baseline --- .../reference/spreadUnionPropOverride.js | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/tests/baselines/reference/spreadUnionPropOverride.js b/tests/baselines/reference/spreadUnionPropOverride.js index 22ba2c0a07b19..7a82dfe91213e 100644 --- a/tests/baselines/reference/spreadUnionPropOverride.js +++ b/tests/baselines/reference/spreadUnionPropOverride.js @@ -1,71 +1,71 @@ //// [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 +// 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]