-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature request] Support generic type default values in functions #56315
Comments
This is working as intended. The value If you want to always allow |
So the problem with default values for generic parameters, in general, is that you can write const test_3 = doSomething<"b">(); which returns |
Duplicate of #49158 but that one is closed and I think it would be useful to have a feature request for some way to have default function parameters involved in type inference. People run into this and the current approaches are all pretty clunky. |
@jcalz @MartinJohns But honestly, working with generics and default values is still kind of a hit-or-miss, see this updated example based on your suggestions. Now the types inside of the functions are correct ✅, but the outside types are not ❌ 😢 . export function doSomething<Value extends 'a' | 'b' = 'a'>(value: Value | 'a' = 'a'): Value | 'a' {
return value
}
const test_2 = doSomething(undefined)
// ^?
// ❌ The value is infered as 'a' | 'b'.
const test_3 = doSomething('b')
// ^?
// ❌ The value is infered as 'a' | 'b'. |
@MartinJohns |
There’s no typecasting there. That’s just how generic type argument specification works. I think this should be a feature request for some way to support the underlying use case, and not a request to redefine the way generic type argument specification works. That is, if you want the |
@jcalz But
|
It’s unsafe because you didn’t implement the function properly and it gave you the requisite error about it. The call signature allows the call, and your implementation is wrong. If you want to disallow the call then you have to change the call signature. We can all agree that it’s unsafe to drive a car without wearing the seat belt, but you’re saying the car shouldn’t move when you step on the accelerator in such a situation. That’s not how it works: the car still moves but there’s a warning light on the dashboard about your seat belt. |
@jcalz Can you please provide the correct types for that function? Here is the javascript version: export function doSomething(value = 'a') {
return value
} |
Currently the best you can do looks like this: function doSomething<V extends "a" | "b">(value: V): V;
function doSomething(): "a";
function doSomething(value: "a" | "b" = "a") {
return value
}
const a = doSomething(); // "a"
const a2 = doSomething("a"); // "a"
const a3 = doSomething<"a">("a"); // "a"
const b = doSomething("b"); // "b"
const b2 = doSomething<"b">("b"); // "b"
doSomething<"b">(); // not allowed
doSomething<"a">(); // also not allowed This is all laid out in #49158. We need a feature request here, not a bug report. TS is behaving exactly as intended and changing it to match reasonable but incorrect expectations isn't going to work here. But it would be great to have a feature that handled this use case. In #49158 I half-heartedly suggested // not valid TS, don't try this:
function doSomething<V extends "a" | "b">(value: V = "a" default T = "a"): V {
return value;
} but the exact syntax is less important than the feature. |
@jcalz Thanks, so currently there is no way to represent it. The only way is to use overloads, which brings another set of issues that I wanted to avoid (e.g. only one of the overloads is ever inferred when used in utility types like |
But it's not unsafe type casting at all. Also take this example: declare const val: "b" | undefined
doSomething(val) The inferred generic type will obviously be |
I want to represent
with a TS generic. No overloads (due to limitations with utility types and auto-complete stops working when you start writing for example a prop. name and it yet doesn't match any full property, because no overload match was found, whereas with generic there is only one "source of truth" so TS always tries to auto-complete the property name), no unions (because TS has some limit on complex union types, then it fails to comprehend them). This is probably closest to actually return correct type output, but uses a lots of export function doSomething<Value extends 'a' | 'b' = 'a'>(value: Value = 'a' as any): Value extends undefined ? Exclude<Value, undefined> : Value {
return value as any
}
const x = doSomething<"b">()
const x_0 = doSomething<"b">()
const x_1 = doSomething('b' as 'b' | undefined)
// ❌ Here it infers 'b' as the generic, but I don't think that's correct, since the input can be also undefined -> it should infer as 'a' | 'b' because of the default
// Same as it does with `doSomething(undefined)` or
const x_2 = doSomething<"a">()
const x_3 = doSomething('a')
const x_4 = doSomething('b')
const x_5 = doSomething()
const x_6 = doSomething(undefined)
// ❌ Here as well, we are not passing any value, so the default should be inferred in my opinion |
I've been bumping into this a lot - it seems the primary issue here is that typecasting a function is always prioritised over an inferred type. Thus, you can never have a default value that might conflict with a defined typecasting, even if the intended use of a function is for it to use an inferred type from its values. This results in worse code everywhere a default parameter is needed, either through verbose overloads, judicious use of Would it be possible, then, to mark a generic type as inferable only, that way we can assign a default that matches a generic constraint without worrying that the caller might specify a conflicting type. e.g
Alternatively, a function could treat the default as a given parameter at the call-site if not specified, raising a type error either on the parameters or on the cast type if it conflicts with that default parameter.
Basically, instead of preventing an extremely common pattern because the caller might provide a type that the default cannot be assigned to, move the type error to the call-site if they do. Right now one specific invalid case is invalidating the whole set, even though the vast majority of scenarios would otherwise be perfectly fine. |
Going to track this at #58977 |
This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
🔎 Search Terms
"generics default value", "generics default"
🕗 Version & Regression Information
⏯ Playground Link
https://www.typescriptlang.org/play?ts=5.3.0-beta#code/PTAEhlyUBUAsFNQHM4Ds4CcCWBjUA1AQwBsBXBAZxgHsSiATUAIzmyoFsEByAz0Ad3gpQAF3igAbsTKhM5UCirCkmcagBQcAB4AHKuiUAzEimzDMVIXSoBldnFGYUiADyFSCLcNR053XgB9QTkZeAF4gngA+AApJdwB+AC58KQRwvwBKZLdpAG81AEh0exJ0ITiyNQBfNTVWFHIlL0aAfQBGUHCrWw4HJ2iMtRBQEdGAPXihsEBQcmgxCoRZGRQDDDgGAl8eABomEiUoa1ArOHIUJWIiKj4RKlBi4VKhP1ADdHYRMUYqOgBPUCoBk+CCMJjMFjqFkaIlOwhaACZOscbHY+ohosY6HADI51oNhqMRhMphA5ggFjI5I5VsUNlsAkEQgA6EmQayObAIQ6gerkTBY9ByABECySKXcQtAm1AItS2VSoECmOxuLoktEqGB91OtDMThEv20FGotAYzC1WIMBF1BqNoAAtJEIpwmUA
💻 Code
🙁 Actual behavior
Usage of default parameters with generics is forbidden.
🙂 Expected behavior
We are able to use default parameters with generics. The generic type should use the default type whenever the type is
missing
or isundefined
unless the generic includes undefined in its own type.Additional information about the issue
No response
The text was updated successfully, but these errors were encountered: