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..120aeb7f1a62e --- /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 #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 + \ 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..7a82dfe91213e --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.js @@ -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 diff --git a/tests/baselines/reference/spreadUnionPropOverride.symbols b/tests/baselines/reference/spreadUnionPropOverride.symbols new file mode 100644 index 0000000000000..83dbad64a727a --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.symbols @@ -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 + diff --git a/tests/baselines/reference/spreadUnionPropOverride.types b/tests/baselines/reference/spreadUnionPropOverride.types new file mode 100644 index 0000000000000..ff6f63848eca3 --- /dev/null +++ b/tests/baselines/reference/spreadUnionPropOverride.types @@ -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 : { (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..ab1c05a30cbfc --- /dev/null +++ b/tests/cases/compiler/spreadUnionPropOverride.ts @@ -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