TypeScript Version: 3.5.1
Search Terms:
Code
function foo<T extends any[]>() {
const fn1: (x: string, ...rest: T) => unknown = (x, ..._) => x;
const fn2: (...args: never) => unknown = fn1 // error
}
Expected behavior:
Assignment of fn1 to fn2 should pass.
Actual behavior:
Assignment of fn1 to fn2 should fails.
Playground Link: link
Related Issues:
The goal here would be to make all signatures structurally assignable to (...args: never) => unknown.
@ahejlsberg this seems reasonable to me. Thoughts?
I did some experiments in the playground. This seems to break only when the rest parameter is generic. Use a concrete type and all is well:
Interestingly, [ string, ...T ] (where T is generic but extends any[]) isn't even legal and produces the error A rest element type must be an array type. This seems like a pretty big clue: the assignment of fn1 to fn2 may only be failing because it's unable to construct a tuple type for the parameter list of (x: string, ...rest: T) => unknown.
This is where it fails in checking:
const sourceCount = getParameterCount(source);
const sourceRestType = getNonArrayRestType(source);
const targetRestType = getNonArrayRestType(target);
if (sourceRestType && targetRestType && sourceCount !== targetCount) {
// We're not able to relate misaligned complex rest parameters
return Ternary.False;
}
@jack-williams That doesn't seem to explain why it succeeds for the non-generic case. What is the exact definition of "complex" here?
What is the exact definition of "complex" here?
A rest type where you don't know the bounds, or whether it is unbounded---I would guess. I think never needs to be treated like any in that it is effectively known to be unbounded.
FWIW. I think the fix is basically:
function getNonArrayRestType(signature: Signature) {
const restType = getEffectiveRestType(signature);
return restType && !isArrayType(restType) && !isTypeAny(restType) && !isTypeNever(restType) ? restType : undefined;
}
+ isTypeNever.
Repro taken from TS' source code,
https://github.com/microsoft/TypeScript/blob/c447ebc59c49a1b071c099ce6055e94517973eab/src/compiler/core.ts#L1460-L1464
/**
* Safer version of `Function` which should not be called.
* Every function should be assignable to this, but this should not be assignable to every function.
*/
export type AnyFunction = (...args: never[]) => void;
function foo<T extends any[]>() {
const fn1: (...rest: T) => void = (..._) => {};
const fn2: AnyFunction = fn1 // error
}
Every function should be assignable to this
But not every function is assignable to it
Most helpful comment
The goal here would be to make all signatures structurally assignable to
(...args: never) => unknown.