microsoft / TypeScript

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

Compiler allows narrower method signature than implemented interface #58315

Closed CComparon closed 2 weeks ago

CComparon commented 2 weeks ago

πŸ”Ž Search Terms

interface implements allows narrower argument type

πŸ•— Version & Regression Information

This is the behavior in every version I tried (4.7.x and 5.4.x)

⏯ Playground Link

https://www.typescriptlang.org/play/?noUnusedLocals=true&noUnusedParameters=true&target=8&jsx=0&useUnknownInCatchVariables=true&noImplicitOverride=true&noFallthroughCasesInSwitch=true&suppressImplicitAnyIndexErrors=false&exactOptionalPropertyTypes=true&ts=5.4.5#code/JYOwLgpgTgZghgYwgAgJLIN4ChnJgCjgC5kQBXAWwCNpkAfZAZzClAHMBKE866AbiwBfLFgQAbOI0bIAwsmAUADmIgUI4aemy4CxUpRpQu+3lEw5cyKBDBkoIZHGQAqZACYBuYcNEB7EMzICCToALykEADusvgcAgj+jL4qAHRivmz4CCkEAKwcADRBOfgARAAsbqUcccgA9HXIvr6KjFhAA

πŸ’» Code

interface I {
  f(a: number | string): number;
}

class C implements I {
  f(a: number): number {
    return a * 2;
  }
}

const c: I = new C();
console.log(c.f(5), c.f("42")); // ouch

πŸ™ Actual behavior

Compiler accepts code

πŸ™‚ Expected behavior

Compiler should reject code with an error stating that the implementation method signature is not assignable to the interface method signature.

Additional information about the issue

No response

MartinJohns commented 2 weeks ago

This is working as intended and mentioned in the FAQ: https://github.com/microsoft/TypeScript/wiki/FAQ#parameter-arity-variance-is-correct

CComparon commented 2 weeks ago

Thank you @MartinJohns for your quick reply. I'm confused though, as my case is not about difference in arity but rather in the actual type of a positional method argument. My apologies if I'm still overlooking a by-design behavior.

nmain commented 2 weeks ago

That's not quite the right part of that document, see https://github.com/microsoft/TypeScript/wiki/FAQ#:~:text=A%20method%20and%20a%20function%20property%20of%20the%20same%20type%20behave%20differently.

RyanCavanaugh commented 2 weeks ago

Rare MartinJohns L πŸ˜…

Anyway I just updated the FAQ with a new entry about method bivariance; see https://github.com/microsoft/TypeScript/wiki/FAQ#why-method-bivariance

BayanBennett commented 2 weeks ago

ℹ️ @CComparon it works as you expect if you use the property syntax for class methods

class B {
  f = (a: number) => {}
}

class C { // same result when using an interface
  f = (a: number | string) => {}
}

async function main(): Promise<void> {
  const b: C = new B(); // Error: Type 'B' is not assignable to type 'C'... Type 'string' is not assignable to type 'number'.
  console.log(b.f(5));
  console.log(b.f("5"));
}

TS Playground

MartinJohns commented 2 weeks ago

@RyanCavanaugh The new FAQ is missing a TOC, that threw me off and made finding things a lot harder.

CComparon commented 2 weeks ago

Thanks both for pointing me to the rationale behind this language tradeoff. Cheers