mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-10 00:20:22 -06:00
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:
parent
cbc2059b53
commit
542b09547f
@ -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));
|
||||
|
||||
81
tests/baselines/reference/spreadUnionPropOverride.errors.txt
Normal file
81
tests/baselines/reference/spreadUnionPropOverride.errors.txt
Normal 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
|
||||
|
||||
100
tests/baselines/reference/spreadUnionPropOverride.js
Normal file
100
tests/baselines/reference/spreadUnionPropOverride.js
Normal 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
|
||||
172
tests/baselines/reference/spreadUnionPropOverride.symbols
Normal file
172
tests/baselines/reference/spreadUnionPropOverride.symbols
Normal 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
|
||||
|
||||
319
tests/baselines/reference/spreadUnionPropOverride.types
Normal file
319
tests/baselines/reference/spreadUnionPropOverride.types
Normal 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
|
||||
|
||||
67
tests/cases/compiler/spreadUnionPropOverride.ts
Normal file
67
tests/cases/compiler/spreadUnionPropOverride.ts
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user