Description
TypeScript Version: 3.4.0-dev.201xxxxx
Search Terms: Object.assign return type
Code
let test1: string = Object.assign({ a: 1 }, { a: "one" }).a
// $ExpectError
let test2: number = Object.assign({ a: 1 }, { a: "one" }).a // No error
// $ExpectError
Object.assign({ a: 1, b: 2 }, "hi").toLowerCase() // No error
// $ExpectError
Object.assign({ a: 1, b: 2 }, "hi").length // No error
Expected behavior:
Type 'string' is not assignable to type 'number'
Property 'toLowerCase' does not exist on type ...
Property 'length' does not exist on type ...
Actual behavior:
No errors raised.
Playground Link: Link
Related Issues:
- Generic type-safe implementation for Object.assign #30481
- Object.assign({}) seems to always return a type any #29348
Proposal
type IndexerReturnType<T extends ArrayLike<any>> = T extends ArrayLike<infer V> ? V : any;
type ArrayLikeIndexer<T> = T extends ArrayLike<any> ? { [k: number]: IndexerReturnType<T> } : never
type PickDefined<T> = {
[P in keyof T]: T[P] extends undefined ? never : T[P]
};
type Assign<A, B> = A extends object ? (
B extends object ?
Pick<A, Exclude<keyof A, keyof B>> & PickDefined<B> :
A & ArrayLikeIndexer<B>
) : (A & B)
declare function assign<A, B>(a: A, b: B): Assign<A, B>
Note 1: PickDefined<T>
is necessary with strictNullChecks
enabled.
Note 2: In the definition of Assign<A, B>
I would have expected the usage of keyof B
to need to be replaced with keyof PickDefined<B>
. It seems, that's presently not the case - so I've opted to omit it and avoid any potential slow-down. Nonetheless, I must admit that I'm hazy as to why this is permissible.
Obviously to keep things brief I've just covered the single source
use-case rather than providing overloads with several source. The definition is nonetheless chainable i.e. Assign<A, Assign<B, C>>
etc.
Playground Link: Link (For posterity please enable strictNullChecks
)