i have a very complex generic function that depends on A, B, C, D with a lot of parameters that involve A | B | C | D one way or another , i want to save some time writing A | B | C | D every time by aliasing it type X = A | B | C | D unfortunately there is no place for such alias within the function signature, however i discovered a hack that seems to make it work:
const never: never = null as never;
function fn<A, B, C, D>(
one: X, // <-- type of X is pulled out from the declaration within the function body
another: X,
X: X = never // <-- thank to this the type X defined in function body is visible in the function signature
): void {
type X = A | B | C | D;
}
i bet it's not how it was supposed to be working but this really saves me a ton of time in maintenance, please either:
same problem is with interfaces and there is no hack like with functions, so i have to write this:
interface I<A, B, C, D> {
doThis(one: A | B | C | D, another: A | B | C | D): void
doThat(ones: (A | B | C | D)[], another: Promise<(A | B | C | D)[]>): void
// ... you got the idea
}
interface I<A, B, C, D> {
type X = A | B | C | D; // <-- i wish!
doThis(one: X, another: X): void
doThat(ones: X[], another: Promise<X[]>): void
// ... you got the idea
}
I feel like I'm asking the obvious, but why not generic defaults?
function fn<A, B, C, D, X = A | B | C | D>(one: X, another: X): void {
...
}
because X defined as default generic X = A | B | C | D isn't assignable (in general case) to A | B | C | D, take a look:
const never: never = null as never;
function fn<A, B, C, D, X = A | B | C | D>(
one: X,
another: X,
test: (value: A | B | C | D) => void
): void {
test(one); // <-- bummer
}
this ugliness works tho:
const never: never = null as never;
function fn<A, B, C, D, X extends A | B | C | D = A | B | C | D>(
one: X,
another: X,
test: (value: A | B | C | D) => void
): void {
test(one);
}
Maybe it works for your use case, but the generic-default-with-constraint still only declares X as a subtype of A | B | C | D, so it's not quite the same as an alias:
Weird alias, works:
function fn<A, B, C, D>(
a: A
test: (value: X) => void // <-- X usable here
X?: undefined // <-- this seems to pull X into visibility also
): void {
type X = A | B | C | D;
test(a); // works, A is assignable to X
}
Generic with constraint, broken:
function fn<A, B, C, D, X extends A | B | C | D = A | B | C | D>(
a: A,
test: (value: X) => void
): void {
test(a); // error! A is not assignable to X
}
Note that in using the "weird but handy" method, I don't think you need to make the X parameter of type X. A parameter named X of any type seems to bring the type X into scope (which must be a bug).
"use strict";
function f(y = (x = 1)) {
var x;
console.log(x, y)
}
f();
VM420:2 Uncaught ReferenceError: x is not defined
at f (<anonymous>:2:19)
at <anonymous>:1:1
vars don't work that way, so it seems like it should be a bug, but I deeply sympathize with how downright annoying it is to deal with complicated local types.
Note that currently we seem to have an emit bug on the above code as well.
@jcalz you are right, damn, hacky way it is, @DanielRosenwasser it's not a bug but a useful feature, take off that bug label! ;)
Most helpful comment
@jcalz you are right, damn, hacky way it is, @DanielRosenwasser it's not a bug but a useful feature, take off that bug label! ;)