question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Can we cut down on `Object.assign` overloads?

See original GitHub issue

We can convert unions into intersections (thanks @jcalz!), and we can model variadic args pretty well, so I figured I’d try to model Object.assign with a single overload. Here’s what I came up with:

// Maps elements of a tuple to contravariant inference sites.
type MapContravariant<T> = {
    [K in keyof T]: (x: T[K]) => void
}

type TupletoIntersection<T, Temp = MapContravariant<T>> =
    // Ensure we can index with a number.
    Temp extends Record<number, unknown>
        // Infer from every element now.
        ? Temp[number] extends (x: infer U) => unknown ? U : never
        : never;

declare function assign<T extends object, Rest extends object[]>(
    x: T,
    ...xs: Rest
): T & TupletoIntersection<Rest>;

Unfortunately this doesn’t quite give the right results on the following:

let asdf = assign({x: "hello"}, Math.random() ? {x: "hello"} : {z: true });

Currently the type of asdf is:

| ({ x: string; } & { x: string; z?: undefined; })
| ({ x: string; } & { z: boolean; x?: undefined; })

What a beautiful type! Unfortunately you can see that the second element of the union tries to intersect types with conflicting properties for x which is nonsense.

Additionally, @weswigham pointed out that this really won’t work for the case where you started off with just a plain array whose elements are eventually spread into Object.assign.

I do wonder if there’s anything better we can do here.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:5
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
lazytypecommented, Nov 2, 2020

After the culmination of several TS versions, I think I found a typing that leads to correct answers in the cases I can think of:

type ExtractNonNever<T> = {[K in keyof T as undefined extends T[K] ? K : never]?: T[K]}
type OptionalKeyOf<T> = keyof ExtractNonNever<{[K in keyof T]: never}>
type RequiredKeyOf<T> = Exclude<keyof T, OptionalKeyOf<T>>

type AssignProperties<T, U> = {
    [K in keyof T | keyof U]:
        K extends RequiredKeyOf<U> ? U[K] :
            | (K extends keyof T ? T[K] : never)
            | (K extends OptionalKeyOf<U> ? U[K] : never)
}

type DistributiveAssignProperties<T, U> =
    T extends infer S
        ? U extends infer V
            ? AssignProperties<S, V> : never : never;

type AssignAllProperties<T, U extends any[]> = 
    U extends [infer Next, ...infer Rest]
        ? AssignAllProperties<DistributiveAssignProperties<T, Next>, Rest>
        : T

declare function assign<T extends object, Rest extends object[]>(
    x: T,
    ...xs: Rest
): AssignAllProperties<T, Rest>;

The type of

let asdf = assign({x: "hello"}, Math.random() ? {x: "hello"} : {z: true});

is correctly inferred to be { x: string, z?: undefined } | { x: string | undefined, z: boolean }

Playground link which also has type tests: https://tsplay.dev/4w1LyW

I do wonder if there’s anything better we can do here.

@DanielRosenwasser does this count for some definition of “better”?

0reactions
tvlercommented, Nov 2, 2020

Cool, thank you for the explanation! Yeah like I said I didn’t see your work on this until I found this issue myself, happy to see other people have been thinking more about it

Read more comments on GitHub >

github_iconTop Results From Across the Web

c++ - dealing with assignment operator overloads; can you re ...
No, you cannot re-seat a reference. Consider: int a = 42, b = 43; int &ar = a; ar = b;. How can...
Read more >
14.15 — Overloading the assignment operator - Learn C++
The assignment operator (operator=) is used to copy values from one object to another already existing object.
Read more >
Object.assign() - JavaScript - MDN Web Docs
The Object.assign() static method copies all enumerable own properties from one or more source objects to a target object.
Read more >
Overloading - C# in Depth
Overloading. Just as a reminder, overloading is what happens when you have two methods with the same name but different signatures.
Read more >
Operator Overloading, C++ FAQ - Standard C++
Can I overload operator== so it lets me compare two char[] using a string comparison? ... For example, after an assignment, the two...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found