microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.23k stars 12.52k forks source link

Unhelpful TS2322 error message when assigning a type to another type. #50114

Open jespertheend opened 2 years ago

jespertheend commented 2 years ago

Bug Report

🔎 Search Terms

TS2322 unhelpful message

🕗 Version & Regression Information

⏯ Playground Link

Playground link with relevant code

💻 Code

type StringMap = {
    [x: string]: string;
}

type Message<TMap extends StringMap, T extends keyof TMap> = {
    bar: "bar";
    type: T;
}

type MessageHelper<TMap extends StringMap, T extends keyof TMap> =
    T extends keyof TMap ?
        Message<TMap, T> :
        never;

function foo<TMap extends StringMap, T extends keyof TMap>(type: T) {
    const message : Message<TMap, T> = {
        bar: "bar",
        type,
    }

    const message2 : MessageHelper<TMap, T> = message;
    //    ^^^^^^^^------- Type 'Message<TMap, T>' is not assignable to type 'MessageHelper<TMap, T>'.

    message.type; // T extends keyof TMap
    message2.type; // (keyof TMap & string) | (keyof TMap & number) | (keyof TMap & symbol)

    message.type = message2.type;
    // ^^^^^^^^^ This is more helpful, but I had to jump through quite some hoops to finally figure
    // out this was the cause.
}

🙁 Actual behavior

The error that occurs when assigning message to message2 shows just

Type 'Message<TMap, T>' is not assignable to type 'MessageHelper<TMap, T>'.

🙂 Expected behavior

A better explanation of why the types are not assignable. I'm assuming it's because the types of message.type are different. But this isn't clearly shown in the error message.

jespertheend commented 2 years ago

Not really sure what is going on here, I'm trying create an object that is assignable to MessageHelper with as minimal amount of casting as possible:

const message : MessageHelper<TMap, T> = {
    bar: "bar",
    type: type as keyof TMap,
};

(playground)

But even casting to any doesn't seem to work:

const message : MessageHelper<TMap, T> = {
    bar: "bar",
    type: type as any,
};

Only casting the entire message seems to get rid of the error, but this allows for making mistakes, for instance now you can omit bar which might result in runtime errors later on:

const message : MessageHelper<TMap, T> = {
    // bar: "bar",
    type,
} as MessageHelper<TMap, T>;

(playground)

RyanCavanaugh commented 2 years ago

The likely problem is that we're not using the correct cache (error-reporting vs non-error-reporting) somewhere in the stack. Likely an easy fix that's very difficult to find.