Repository navigation
Inconsistent Parameter Type Inference for Functions with Default Values #60222
Description
🔎 Search Terms
"parameter type inference" "default parameters" "function type inference issue" "any type" "inconsistent type inference" "default value"
🕗 Version & Regression Information
- When did you start seeing this bug occur?
I started observing this behavior in TypeScript version 5.5.4, and it has continued in the latest stable release 5.6.3.
- Have you tried using the nightly version of TypeScript?
Yes, I have tested the latest nightly version (5.7.0-dev.20241014), and the issue persists.
- Bisecting the issue using every-ts:
I have not yet bisected the issue with every-ts, but I will consider doing this to identify the specific change.
- Using the Playground:
I have tested the issue in the TypeScript Playground with versions back to 3.3, and the problem is reproducible across multiple versions.
⏯ Playground Link
💻 Code
// The Functions `this` keyword context
type FnContext = {
source: Record<string, any>
}
// The function definition
type Fn<T = FnContext, P extends any[] = any[], R = void> = (this: T, ...params: P) => R
type AnyFn = Record<string, Fn>
type Options<F extends AnyFn> = {
actions?: F
}
function program<T extends AnyFn>(options: Options<T>) { }
// Our initial attempt to achieve proper type inference
program({
actions: {
foo(p = 2) {}, // Instead of inferring 'number', TypeScript opts for 'any'. A curious choice, indeed.
esp(p: number = 2) {}, // Explicit typing allows this to work as intended.
bob(s: number) {} // This one operates smoothly, reflecting the expected behavior of TypeScript.
}
})🙁 Actual behavior
The foo function is inferred as (method) foo(this: FnContext, p?: any): void, which is unexpected and undesired.
The esp and bob functions behave as expected.
🙂 Expected behavior
- The
foofunction should have itspparameter type inferred asnumber, due to the default value provided. - The
espfunction behaves correctly, withpinferred asnumber. - The
bobfunction should remain unaffected and should correctly infersasnumber.
Additional information about the issue
I have attempted various approaches, including changing the default parameter type to an empty tuple (TypeScript Playground) and using conditional types (TypeScript Playground), but none resolved the underlying issue.
The issue seems to stem from how TypeScript handles parameter inference in the context of default values, leading to unexpected any types.
If possible, I would appreciate any guidance on how to achieve the desired inference without resorting to explicit typing for each parameter. Additionally, any insights into whether this behavior is expected or if it may be addressed in future versions would be greatly appreciated.
Activity
jcalz commentedon Oct 14, 2024
Looks like a duplicate of #59643
RyanCavanaugh commentedon Oct 14, 2024
There's an interaction here with the contextual type that's a bit subtle. Consider this sequence of calls:
If
p1is ok, thenp2should be OK, and ifp2is OK thenp3should be OK, and ifp3is OK thenp4should also be OK. But you're basically sayingp4should not be OK, which implies that one of these equivalences is wrong, or thatp1should be rejected (how?)For the most part, TS views parameter defaults as an implementation detail of the function, not an intrinsic part of the signature.
jcalz commentedon Oct 14, 2024
( Presumably you either want
declare function foo<T extends string | number>(callback: (arg?: T) => void): (arg: T) => T;ordeclare function foo<T extends string | number | undefined>(callback: (arg: T) => void): (arg: T) => T;in that example )Sure, but given that
function bar(n = 2) { return n }infersnas being of typenumber, and thatconst p5 = foo(bar)("hello")is the errorp4would give, there's some kind of interplay between default arguments and the function signature, right? To me this looks like #59643, which is interpreted as a feature request of the form "wouldn't it be nice if default arguments could, I don't know, maybe be consulted when inferring a parameter in the presence of a particularly unhelpful contextual type?"RyanCavanaugh commentedon Oct 14, 2024
It's also close to #58977 which basically proposes the same interaction at call sites
dukasn commentedon Oct 14, 2024
Thanks, Joe Calzaretta (@jcalz) and Ryan Cavanaugh (@RyanCavanaugh)! I've made a workaround that works perfectly for my needs:
TypeScript Playground Example
Thanks again! I believe the issue can be closed now.