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
Void parameter still required when type extends generic #29131
Comments
This is a design limitation in the way the checks are implemented. Checking for void parameters that can be elided is done prior to generic instantiation, which means that instantiations that produce void parameters are ignored, as is the case in This choice was made to avoid having to check every generic signature twice, and there were some issues with this breaking existing overload selections. I would be interested in understanding real-word examples of this behaviour. Currently I'm not sure whether this behaviour can be implemented using existing methods (overloads), or whether we really need to extend the void checks. |
It was a poor mans method overloading, it just seemed very strange to me that it would not work even though the compiled method signatures were exactly the same. Now that I know when the void eliding is done I was able to solve it using the following: function works3<T extends 1>(n: number, b: void)
function works3<T>(n: number, b: number)
function works3<T>(n: number, b: number | void) { }
works3<2>(12,2);
works3<1>(12); Thanks for your help in understanding the situation! Feel free to close this if you see no reason to fix this as (at least in this case) it can be avoided. |
It's not for me to open/close issues, that's for the team to decide. I would suggest leaving this open as the canonical thread that tracks use-cases that need the generic example to work too. If enough people find it useful then I'd be happy to add it. |
@jack-williams class MyIterator<T> implements Iterator<T> {
protected _value: T;
constructor(value: T) {
this._value = value;
}
next(): IteratorResult<T> {
return {
done: false,
value: this._value
};
}
foo(value: T): void {
}
}
const it: MyIterator<void> = new MyIterator<void>(); // error !
it.next();
it.next();
/// ...
it.foo(); // valid ! It's simply an iterator that send everytime the same value provided in its constructor. What's the strangest: the 'foo' is perfectly valid but the 'constructor' errors. |
Thanks for the example!
The constructor is generic at the call-site; As a workaround you can do: function init(x: void) {
return new MyIterator(x);
}
const it: MyIterator<void> = init();
it.next();
it.next();
/// ...
it.foo(); |
Variable number of function parameters can still be achieved using tuple types (although, parameter identifiers in IntelliSense will be lost unfortunately): declare function fn<Value extends 1 | 2>(...args: Value extends 1 ? [number] : [number, string]): void;
fn<1>(42);
fn<1>(42, "hello world"); // Error: expects 1 argument
fn<2>(42); // Error: expects 2 arguments
fn<2>(42, "hello world"); |
@parzh You can get the parameter names back (mostly) if you don't mind writing a bit more: declare function fn<Value extends 1 | 2>(...args: Value extends 1 ? Parameters<(x: number) =>void> : Parameters<(y:number, name: string) => void>): void;
fn<1>(42);
fn<1>(42, "hello world"); // Error: expects 1 argument
fn<2>(42); // Error: expects 2 arguments
fn<2>(42, "hello world"); |
I have a case where I'm implicitly overloading a function, but can't do explicit overloading due to maintaining closure. type DataStateActions<T, U> = {
load: (parameters: U) => DataActionLoad<T, U>
// omitted
}
const generateDataStateActions = <T, U = void>(
type: string,
): DataStateActions<T, U> => ({
load: (parameters): DataActionLoad<T, U> => ({
type,
payload: { intent: DataActionTypes.LOAD, parameters },
}),
// omitted
}) But then it fails at type AiImplementationManagerContainerFunctionProps = {
fetchCaseSetList: () => void
}
const mapDispatchToProps: AiImplementationManagerContainerFunctionProps = {
fetchCaseSetList: caseSetListDataActions.load,
} with
(Notice that the differing return types are completely fine – it's about the |
@neopostmodern I think the export enum DataActionTypes {
LOAD = 'LOAD',
// omitted
}
export type DataActionLoad<T, U extends [undefined?] | [any] > = {
type: string
payload: { intent: DataActionTypes.LOAD; parameters: U[0] }
}
type DataStateActions<T, U extends [undefined?] | [any]> = {
load: (...parameters: U) => DataActionLoad<T, U>
// omitted
}
const generateDataStateActions = <T, U extends [undefined?] | [any]>(
type: string,
): DataStateActions<T, U> => ({
load: (...parameters: U): DataActionLoad<T, U> => ({
type,
payload: { intent: DataActionTypes.LOAD, parameters: parameters[0] },
}),
// omitted
})
const caseSetListDataActions = generateDataStateActions<number, []>('TYPE_NAME')
type AiImplementationManagerContainerFunctionProps = {
fetchCaseSetList: () => void
}
const mapDispatchToProps: AiImplementationManagerContainerFunctionProps = {
fetchCaseSetList: caseSetListDataActions.load,
} I intentionally made this accept one parameter to keep the same API as your original, but with this approach, you could accept a variable number of parameter. |
I appreciate the flexibility this offers, but IMHO using
TL;DR Yes, alternatives exist – but |
JSDocs don't work either using tuple types. |
This feature would really help the following use-case: Our react codebase has some route objects, of type navigate(routes.createItem, { urlProps: { ... }, componentProps: { ... } }) // You can't illegally navigate now! Some pages are very simple, and they don't need any data. In this case, the type of the second argument of navigate will expand to What we wish we could do was transform that type into void so that you can't pass any data into the page, like this: type MakeForbiddenArgument<T> = T[Exclude<keyof T, undefined>] extends undefined
? void
: T;
navigate(routes.createItem); // This should work! So, yeah, another interesting use-case for this. |
TypeScript Version: 3.2.2
Search Terms:
void parameter type extends generic
Code
Expected behavior:
That I can ignore the second parameter since its void
Actual behavior:
Ts tells me I am missing a parameter
https://www.typescriptlang.org/play/index.html#src=function%20works1(n%3A%20number%2C%20b%3A%20void)%20%7B%20%7D%0D%0A%0D%0Aworks1(12)%3B%0D%0A%0D%0Afunction%20works2(n%3A%20number%2C%20b%3A%201%20extends%201%3F%20void%20%3A%20number)%20%7B%20%7D%0D%0A%0D%0Aworks2(12)%3B%0D%0A%0D%0Afunction%20fails%3CT%3E(n%3A%20number%2C%20b%3A%20T%20extends%201%3Fvoid%3Anumber)%20%7B%20%7D%0D%0A%0D%0Afails%3C2%3E(12%2C2)%3B%20%20%2F%2Fworks%2C%20requires%20both%20params%0D%0Afails%3C1%3E(12)%3B%20%20%2F%2F%20requires%202%20parameters%20even%20though%20second%20param%20is%20void%0D%0A
The text was updated successfully, but these errors were encountered: