Typescript: can i rely on this weird yet handy behavior? if not what is a working alternative?

Created on 27 Aug 2017  ·  6Comments  ·  Source: microsoft/TypeScript

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:

  • allow type declarations in function signatures
  • or let this hack be (DON'T FIX IT!!!)

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
}
  • so allow type aliases in interface declarations too!
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
}
Bug

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! ;)

All 6 comments

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! ;)

Was this page helpful?
0 / 5 - 0 ratings