Skip to content

Circularities Only Blocked Sometimes #37426

Closed
@harrysolovay

Description

@harrysolovay

TypeScript Version: 3.8.2

Search Terms: circular, type, mapping, tuple, corresponding, signature

This builds off of the issue I just submitted (tagged as a bug).

In summary, I'm trying to create a virtual type system.

I define the available types:

enum Type {
  Boolean = "Boolean",
  Int = "Int",
  List = "List",
}

I create a Codec type:

type Codec<
  T extends Type,
  C extends Codec<Type> | undefined = undefined
> = C extends undefined ? [T] : [T, C];

And I create a utility type, which can be used to unwrap / gather the corresponding type.

type AnyCodec = Codec<Type, Codec<Type> | undefined>;

type Decode<C extends AnyCodec> = C extends Codec<Type.Boolean>
  ? boolean
  : C extends Codec<Type.Int>
  ? number
  : C extends Codec<Type.List, Codec<Type>>
  ? C extends Codec<Type.List, infer I>
    ? I extends Codec<Type>
      ? Decode<I>[]
      : never
    : never
  : never;

Surely enough, it works for Codec<Type.Int>:

const intCodec: Codec<Type.Int> = [Type.Int];
type IntCodec = typeof intCodec;
type IntCodecDecoded = Decode<IntCodec>; // `string`

It works for Codec<Type.Boolean>:

const booleanCodec: Codec<Type.Boolean> = [Type.Boolean];
type BooleanCodec = typeof booleanCodec;
type BooleanCodecDecoded = Decode<BooleanCodec>; // `boolean`

And it works for Codec<Type.List>:

const listOfIntCodec: Codec<Type.List, Codec<Type.Int>> = [
  Type.List,
  [Type.Int],
];
type ListOfIntCodec = typeof listOfIntCodec;
type ListOfIntCodecDecoded = Decode<ListOfIntCodec>; // `number`

TypeScript Playground of the example up to this point

We see Decode works, even though it's self-referencing. There's no cycle-related error––presumably because of the use of tuples, which seem somewhat cycle-friendly since 3.7. Now let's add another type:

enum Type {
  Boolean = "Boolean",
  Int = "Int",
  List = "List",
+ Union = "Union",
}

We modify the Codec type to support multiple array of type Codec (the types to unite):

type Codec<
  T extends Type,
  // added `Codec<Type>[]`
  C extends Codec<Type> | Codec<Type>[] | undefined = undefined
> = C extends undefined ? [T] : [T, C];

And we update the definition of AnyCodec:

type AnyCodec = Codec<Type, Codec<Type> | Codec<Type>[] | undefined>;

Let's instantiate this type:

const unionOfIntAndBoolean: Codec<
  Type.Union,
  [Codec<Type.Boolean>, Codec<Type.Int>]
> = [Type.Union, [[Type.Boolean], [Type.Int]]];
type UnionOfIntAndBoolean = typeof unionOfIntAndBoolean;

And let's create and use the corresponding DecodeUnion utility:

type DecodeUnion<C extends Codec<Type.Union, [Type][]>> = C extends Codec<
  Type.Union,
  infer T
>
  ? T extends AnyCodec[]
    ? T[number] extends AnyCodec
      ? Decode<T[number]>
      : never
    : never
  : never;

type UnionOfIntAndBooleanDecoded = DecodeUnion<UnionOfIntAndBoolean>; // `number` | `boolean`

TypeScript Playground of this example, continued up to this point

The utility works! UnionOfIntAndBooleanDecoded is inferred as being of type number | boolean. Last but not least, let's integrate DecodeUnion into the more general Decode utility type.

This is where we run into trouble:

type Decode<C extends AnyCodec> = C extends Codec<Type.Boolean>
  ? boolean
  : C extends Codec<Type.Int>
  ? number
+ : C extends Codec<Type.Union, [Type][]>
+ ? DecodeUnion<C>
  : C extends Codec<Type.List, Codec<Type>>
  ? C extends Codec<Type.List, infer I>
    ? I extends Codec<Type>
      ? Decode<I>[]
      : never
    : never
  : never;

TypeScript Playground, with the error-producing code

While the prior self-reference did not result in a circularity error, this one does: Type alias 'Decode' circularly references itself.

Is there a workaround? Could this be related to the aforementioned issue?

I know I've said it many times, but I truly mean it every time when I say: your help is greatly appreciated & thank you!!!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs InvestigationThis issue needs a team member to investigate its status.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions