TypeScript
TypeScript copied to clipboard
Class method with conditional return type cannot return types common to both conditional branches when the class also has an interface
Bug Report
🔎 Search Terms
method conditional return type not assignable to type
🕗 Version & Regression Information
3.3.3+
⏯ Playground Link
Playground link with relevant code
💻 Code
export interface Foo<T> {}
export class Foo<T> {
public bar(): T extends string ? string | number : number {
return 1;
}
}
🙁 Actual behavior
Return statement is an error, despite being valid for both branches of the conditional return type.
This ONLY happens if the interface Foo exists. If the interface is commented out, the error goes away.
🙂 Expected behavior
Return statement is not an error, because 1 is assignable to both string | number and number.
Duplicate #27932
You can write
public bar(): number | (T extends string ? string : never) {
@RyanCavanaugh Thanks for looking into it. I'd be really curious to know why the mere presence of the empty interface triggers the issue, and why the issue does not occur if I comment out the interface. I see the linked issue is an abandoned PR - does that mean this is a wontfix?
I missed that - no, that's super weird
Sideways question - what does this construct even mean? Based on my testing this seems like a huge footgun.
export interface Foo<T> {
foo(): number
}
export class Foo<T> {
constructor(private _v: T) {}
public bar(): T {
return this._v
}
}
const inst = new Foo(100)
inst.foo() // ok, but crashes at runtime
inst.bar() // ok
Declaration merging, for declaring when you've e.g. polyfilled foo onto the Foo.prototype
This happens because a literal 1 type (source) is compared against a conditional type (target). When a conditional type is on the target side the types are only related to each other when the conditional type:
- has no infer type parameters
- and is distribution independent
This logic can be found here. In turn, isDistributionDependent checks if the root.checkType type parameter is possibly referenced and, for this case, it will always return true and classify this conditional as dependent on the distribution and thus the source won't be checked against the target at all. The reason why this type param is treated as possibly referenced is that the symbol of this type param has more than one declaration, you can check the logic here