Repository navigation
Interface function of arity of zero gets inferred as having two different method signaturesΒ #53508
Description
Activity
- changed the title
[-]Function with arity of zero gets inferred as having two different method signatures[/-][+]Interface function of arity of zero gets inferred as having two different method signatures[/+]on Mar 26, 2023 - addedNot a DefectThis behavior is one of several equally-correct optionsThis behavior is one of several equally-correct options
on Mar 28, 2023 RyanCavanaugh commented
on Mar 28, 2023 MemberMore actionsFWIW trying to do this (inferring from an overload set) in the first place is probably a very bad idea; I don't think anyone has ever done this before and it's really never been considered as a way someone might use
infer.Anyway this inference follows from the inference algorithm: It's correct for
C["hello"]to match against the 2-overload variant, since it can accept some possible instantiation of both overloads. It's correct to assign all of the candidates toA1andR1, leaving zero candidates to assign toA2andR2(or vice versa), and at that point the other parametrs have zero candidates so fall back tounknown, and sinceC['hello']has no parameters, it correctly can accept anunknown[]. The only plausible tweak would be to say that for overloads, the candidate-splitting logic shouldn't apply, but then you'd just get[() => number, () => number]which isn't really desirable in this type either.It's definitely undesirable but I don't see a way to change the rules here without breaking something else much more badly. If someone wants to put together a PR that shows otherwise, happy to review it, but I think the algorithm is broadly working as designed here.
Andarist commented
on Mar 29, 2023 ContributorMore actionsFWIW trying to do this (inferring from an overload set) in the first place is probably a very bad idea; I don't think anyone has ever done this before and it's really never been considered as a way someone might use infer.
I've done it here. I learned about this being possible from this monster.
You know how it is... never say never π€£
steveluscher commented
on Mar 29, 2023 AuthorMore actionssteveluscher commented
on Mar 29, 2023 AuthorMore actionsβ¦since C['hello'] has no parameters, it correctly can accept an
unknown[].Can you explain this to me, Ryan Cavanaugh (@RyanCavanaugh)? Normally, if I take a function that has no parameters (arity zero) and I try to call it with any parameters at all, that's a type error.
function noArgs() {} noArgs(); // OK noArgs(123); // ERROR: Expected 0 arguments but got 1 noArgs.call(undefined, 123); // ERROR Expected 1 argument but got 2 noArgs(...([] as unknown[])); // ERROR A spread argument must either have a tuple type or be passed to a rest parameter. noArgs.apply(undefined, [] as unknown[]); // Target allows only 0 element(s) but source may have more
In that light, why is
(...args: unknown[]) => unknowna suitable overload of() => number?Andarist commented
on Mar 30, 2023 ContributorMore actionsI think that what Ryan Cavanaugh (@RyanCavanaugh) meant here is that:
type Zero = () => void type UnknownArgs = (...args: unknown[]) => void type Test = Zero extends UnknownArgs ? 1 : 0 // 1 const fn: (arg: number) => void = () => {} // this call is OK, the type expects a number, and this is satisfied // it doesn't matter if the implementation uses this param or not // it won't crash on the surplus of arguments fn(100)
Reacted by Ryan CavanaughRyanCavanaugh commented
on Mar 30, 2023 MemberMore actionssteveluscher commented
on Mar 31, 2023 AuthorMore actionsOK. I think I see what's going on here.
Fact 1; we all agree on arity being enforced at the call level
Nobody here disagrees that this should be a type error, because a call is being made that violates the arity of this function:
function noArgs() {} noArgs(123); // ERROR: Expected 0 arguments but got 1
Fact 2; assignability does not concern itself with arity
Whether it's proper to call a function with a given number of arguments is one thing, but whether it should be assignable to a type is quite another. Just because this function only cares about one argument doesn't mean it shouldn't be assignable to a type that provides for more.
function reportError(errorName: string) {} function logStuff(onError: (errorName: string, errorCode: number) => void) { onError('bad thing', 500); } logStuff(reportError); // OK. The callback will only make use of the `errorName`, but that's fine.
Conclusion
So given that everything is working, as designed, the upshot is that I'm just hosed unless #29732 gets implemented. Ultimately the reason that I've built this overload-inferring utility type is because I need to map over every overload to modify the return type of each. In short, what I want to accomplish is this:
interface ThingDoer { doThing(): void; doThing(num: number): string; doThing(str: string): bigint; } type AsycThingDoer = Asyncify<ThingDoer>; // Produces: // { // doThing(): Promise<void>; // doThing(num: number): Promise<string>; // doThing(str: string): Promise<bigint>; // } type Asyncify<T> = // Impossible with Typescript as it is designed, currently.
Andarist commented
on May 31, 2023 ContributorMore actionsFWIW trying to do this (inferring from an overload set) in the first place is probably a very bad idea; I don't think anyone has ever done this before and it's really never been considered as a way someone might use infer.
I'm not sure if this is the first test time that a test case like this is being introduced to the codebase but at the very least this PR "codifies" this as something that is being considered and one that should have some reliable semantics behind it.
Reacted by Steven LuscherThis issue has been marked as 'Not a Defect' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
steveluscher commented
on Mar 17, 2025 AuthorMore actionsThis appears to be fixed (enough) in TypeScript 5.3.3.
Reacted by Ryan Cavanaugh- locked as resolved and limited conversation to collaborators
on Oct 22, 2025
Bug Report
π Search Terms
overloads,
infer, interfaces, arityπ Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
When the interface in question contains a single implementation of a function, having arity zero, the
Overloadsutility type infers it as two different functions:(...args: unknown[]) => unknown, and() => numberπ Expected behavior
I expected Typescript to infer it as
() => numberalone. This would be consistent with how this utility type infers other methods having a single implementation, but arity > 0.