How can I write this function signature in typescript?

47 views Asked by At

I have a minimal reproduction of a recursive type problem I've encountered. Here's some typescript.

type Cloneable = string | number | Cloneable[];

function clone<T extends Cloneable>(arg: T): T {
    if (Array.isArray(arg)) return arg.map(e => clone(e));
    else return arg;
}

Type 'Cloneable[]' is not assignable to type 'T'. 'Cloneable[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Cloneable'.

My intent is that return type of clone should be identical to its argument. In practice, it's going to involve some proxy stuff, but that's not relevant here. Is there a way I can type this correctly without resorting to any?

1

There are 1 answers

1
Daniel Earwicker On

To explain the error message, let's merely declare the type of your clone function without bothering to define it:

type Cloneable = string | number | Cloneable[];

declare function clone<T extends Cloneable>(arg: T): T;

Here's an example of something that conforms to Cloneable, because it is an array containing strings and numbers, but it also happens to have an extra function attached to it:

const test = Object.assign(["dolphin", 42], {
    petunias() {
        console.log("Oh no, not again.");
    } 
});

Sure enough, we can pass it to clone:

const cloneTest = clone(test);

And TS now believes the type of cloneTest will be the complete type, so in your editor you'll see this pop-up:

const cloneTest: (string | number)[] & {
    petunias(): void;
}

Does your implementation ensure that the returned clone reproduces any extra bells and whistles that any random subtype of T might have? No. And what would it mean, in general terms, to clone such things? How do you ensure a deep clone of functions and properties that you have no understanding of?

In other words, TS is pointing out the truth: your function cannot do what it claims to do.