Closed
Description
Bug Report
Defining an overloaded method in an interface raises an error during implementation if overloads have different return types.
🔎 Search Terms
overload
, method
, interface
, factory function
🕗 Version & Regression Information
Doesn't work in v3, v4 or v5.
⏯ Playground Link
Playground link with relevant code
💻 Code
interface Shape {
area(x: number): number;
area(x: number, y: number): string;
}
🙁 Actual behavior
class ShapeImpl implements Shape {
// ❌
area(_x: number, _y?: number): number | string {
return 0;
}
}
function createShape(): Shape {
return {
// ❌
area(_x: number, _y?: number): number | string {
return 0;
},
};
}
🙂 Expected behavior
class ShapeImpl implements Shape {
// ✅
area(_x: number, _y?: number): number | string {
return 0;
}
}
function createShape(): Shape {
return {
// ✅
area(_x: number, _y?: number): number | string {
return 0;
},
};
}
More details
If the return type does not change, overloads work as expected.
✅
interface Shape {
area(x: number): number;
area(x: number, y: number): number; // 👈
}
class ShapeImpl implements Shape {
area(_x: number, _y?: number): number {
return 0;
}
}
function createShape(): Shape {
return {
area(_x: number, _y?: number): number {
return 0;
},
};
}
Also works if the overloads are defined in the class or on root level functions
✅
class Shape {
area(x: number): number;
area(x: number, y: number): string;
area(_x: number, _y?: number): number | string {
return 0;
}
}
function area(x: number): number;
function area(x: number, y: number): string;
function area(_x: number, _y?: number): number | string {
return 0;
}
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
[-]Cannot overload interface method when using factory functions[/-][+]Cannot overload interface method with different return types[/+]jcalz commentedon May 23, 2023
Essentially #47669 (focuses on arrow functions but it’s the same issue: we currently have no way to say “check this the same loose way that overload implementation are checked”)
RyanCavanaugh commentedon May 23, 2023
These are just correct errors; the function provided doesn't match the overloads you wrote and a valid call could result in an unsound result.
fatcerberus commentedon May 23, 2023
I mean to be fair that's true of regular overloads too, and
area(x: number, y?: number): number | string
is otherwise a valid implementation signature for this overload set, since it works if you copy and paste the overload signatures from the interface into the class definition.enisdenjo commentedon May 23, 2023
Hmm, but how doesn't it match? Also, why do the inline overflows work then?
RyanCavanaugh commentedon May 23, 2023
I can't figure out what you're proposing. Are you saying that any overload implementation signature should be an error too?
enisdenjo commentedon May 23, 2023
Basically this:
RyanCavanaugh commentedon May 23, 2023
People would complain if we did that because it would be inconsistent, since this program would have to error:
enisdenjo commentedon May 23, 2023
Ok, but, whats the difference here?
^ playground link
^ playground link
typescript-bot commentedon May 25, 2023
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
enisdenjo commentedon May 26, 2023
@RyanCavanaugh I don't want to be a nuisance, I just want to understand - can you please explain #54354 (comment)? Thanks in advance!
RyanCavanaugh commentedon May 26, 2023
I don't understand the question. You're basically saying that if you an
implements
clause, then your methods you write should not have the types that you wrote on them?fatcerberus commentedon May 28, 2023
@enisdenjo I think I understand what Ryan is getting at - currently,
implements
is merely a hint to the compiler to check that the class is a valid implementation of the type, but doesn’t change the type at all. Under your proposal the singlearea
signature would become an overload implementation and thus the presence ofimplements
theoretically changes the type (by hiding that signature from callers).@RyanCavanaugh The above being said, I don’t think that’s necessarily true - I’m envisioning that if you call the method directly through a
ShapeImpl
instance, you’d get the declared signature (with or without theimplements
clause), while if you call it through the interface you get the overloads. So I thus read this is just a request to make theimplements
check more loose in consideringShapeImpl
a valid implementation. Which makes sense to me since overloads only ever get one implementation anyway. It would just mean that calling methods through the class type is more permissive than calling through the interface, which is generally already the status quo for interfaces anyway.enisdenjo commentedon May 31, 2023
@RyanCavanaugh no, I am saying they should. This also doesn't work:
^ playground link
Why are overloads different when defined in interfaces compared to directly on functions/methods?
RyanCavanaugh commentedon May 31, 2023
implements
clauses don't change the shape of your class; this fact is intentional. IOW, for these two snippetsand
ShapeImpl.area
has the same type in both.RyanCavanaugh commentedon May 31, 2023
Uh, but then what? As soon as you write
you'll get a type error, which is the exact error you were trying to ensure wouldn't happen by writing
implements
fatcerberus commentedon Jun 3, 2023
@RyanCavanaugh I’d probably just expect that assignment to succeed.
RyanCavanaugh commentedon Jun 5, 2023
Are you just saying that any signature which roughly matches an overload list should be assignable to it? Or that classes with
implements
lists would allow extra stuff that wouldn't normally be allowed to happen?