Skip to content

Assignment of incompatible types missed by the compiler in combination with generic interface with function template parameter #53568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Davidiusdadi opened this issue Mar 29, 2023 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@Davidiusdadi
Copy link

Bug Report

🔎 Search Terms

  • template inference
  • no type error for bad assignment
  • using extends clause causes bad typecheck to pass / not to fail
  • using extends T suppresses failing type check

🕗 Version & Regression Information

  • compiler fails to detect incompatible function signature
  • tested using versions 5.1, 5.1 nightly but also 1.8, 2.9 and 3.9

⏯ Playground Link

Playground link with relevant code

💻 Code

interface Foo<T> {
  bar<P extends T>(payload: P): number;
  
  // without the extends we actually get a compiler error 
  // bar(payload: T): number;
}

type PayloadA = { valueA: number };
const foo_a: Foo<PayloadA> = {
    bar: (val) => val.valueA
};

type PayloadB = { valueB: string };
let foo_b: Foo<PayloadB> = {
    bar: (val) =>  val.valueB.length
};

const payload_a = { valueA: 42 } satisfies PayloadA
const payload_b = { valueB: 'test'} satisfies PayloadB


foo_a.bar(payload_a); // OK - valid
foo_a.bar(payload_b); // OK - does not compile
foo_b.bar(payload_b); // OK - valid
foo_b.bar(payload_a); // OK - does not compile


foo_b = foo_a; // ERROR - does compile BUT should NOT

foo_b.bar(payload_b) // this will throw at runtime because the above assignment

🙁 Actual behavior

The assignment foo_b = foo_a compiles without errors even though it should not, because Foo and Foo are not compatible (because their functions Foo['bar'] and Foo['bar'] each expect different parameters)

🙂 Expected behavior

The assignment foo_b = foo_a should give a compiler error as their signatures do not match.
Error message should be e.g:

TS2322: Type 'Foo<PayloadA>' is not assignable to type 'Foo<PayloadB>'.   Property 'valueB' is missing in type 'PayloadA' but required in type 'PayloadB'

Note

Interestingly we would get the expected behaviour if we used the bar(payload: T): number; instead of bar<P extends T>(payload: P): number;

@RyanCavanaugh
Copy link
Member

This is the same root cause as the fix at #48092

You can add a variance annotation to Foo to work around this:

interface Foo<in T> {
  bar<P extends T>(payload: P): number;
}

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Mar 30, 2023
@Davidiusdadi
Copy link
Author

Davidiusdadi commented Mar 30, 2023

Thank you @RyanCavanaugh 🙏 for the quick response and the "variance annotation" tip.

Happy to have the confirmation the described issue is indeed a bug and not somehow feature.

I spent like 20min to look for a duplicate - would never have found the duplicates with the keywords I was using. 😅

Since the duplicates have so radically different wording I hope that this issue still helps others who encounter this bug. Therefore I'd not close this - plz go ahead to close though in case that is standard procedure.

Just for the mention: this also a duplicate of / related to #48070
Looking forward to #48092 getting fixed.

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants