0

I'm having issues compiling the following. it's included in an old project with the comment - "This is a TS1.8 feature. Leave it in to ensure the environment is running the right ts".

  function assign<T extends U, U>(target: T, source: U): T {
        for (let id in source) {
            target[id] = source[id]; // error TS2322: Type 'U[Extract<keyof U, string>]' is not assignable to type 'T[Extract<keyof U, string>]'.
        } 
        return target;
    }

I'm compiling it with the following command

tsc -p tsconfig.json

And this tsconfig.json

{
  "include": [
    "Scripts/TypeScripts/**/*"
  ],
  "compilerOptions": {
    "target": "es5",
    "module": "amd",
    "sourceMap": false,
    "watch": false
  }
}

tsc -v yields Version 3.4.5.

When I try it in the playground, I also see the error, which makes me think that it is indeed invalid code. However, this poses the questions what was I thinking when I wrote that comment and how come it's been compiling for 2 years (or has it??)

So - my question: Is this valid TS Code? If not, was it ever?

Thanks :-)

2 Answers 2

2

This doesn't look valid to me, but I haven't used TS1.8 (I think I started in 2.4 or so). The problem with having T extends U is that, while all of U's property keys must also exist in T, the values at those property keys could be narrower. That is, given this:

function badAssign<T extends U, U>(target: T, source: U): T {
  for (let id in source) {
    target[id] = source[id] as any; // assert to remove error, but
  }
  return target;
}

You can do this:

interface Ewe {
  w: string;
  x: number;
  y: boolean;
}
interface Tee extends Ewe {
  w: "hey";
  x: 1;
  y: true;
  z: object;
}

const t: Tee = { w: "hey", x: 1, y: true, z: {} };
const u: Ewe = { w: "you", x: 2, y: false };
const newT = badAssign(t, u); // compiles, but
newT.w // "hey" at compile time, "you" at runtime !! 
newT.x // 1 at compile time, 2 at runtime !!
newT.y // true at compile time, false at runtime !!

That's bad... by assigning source[id] to target[id] you are assuming that the property type of target[id] is the same as or wider than the type of source[id], but when T extends U it means the opposite: target[id] is the same as or narrower than the type of source[id]. So you've lied to the compiler.

The way I'd fix this is by replacing U with Pick<T, K> for some K that extends keyof T. That guarantees that every key of target exists on source as before, and additionally it guarantees that for every key of target, the value of the corresponding property of source is assignable to it:

function assign<T, K extends keyof T>(target: T, source: Pick<T, K>): T {
  for (let id in source) {
    target[id] = source[id]; // okay
  }
  return target;
}

This catches the error of the bad call:

assign(t, u); // now an error, string is not assignable to "hey"

But still lets you use assign() as presumably intended:

let target = { a: "hey", b: 123, c: true };
let source = { a: "you", c: false };
const ret = assign(target, source);

Okay, hope that helps; good luck!

Link to code

Sign up to request clarification or add additional context in comments.

2 Comments

Excellent answer!
Thanks for the detailed response! I was kind of wondering if it was a dumb question.
1

I'm guessing you inserted it as a guard to throw errors if TypeScript < v1.8 used.

Via "What's new in TypeScript" for v1.8, subsection "Type parameters as constraints":

With TypeScript 1.8 it becomes possible for a type parameter constraint to reference type parameters from the same type parameter list. Previously this was an error. This capability is usually referred to as F-Bounded Polymorphism.

Example
function assign<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = source[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 });  // Error

1 Comment

ah! Yes, that's very likely where I got it from. So, it was valid in 1.8 but isn't now?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.