hxtsdgen icon indicating copy to clipboard operation
hxtsdgen copied to clipboard

Thinking about typing enum values

Open jcward opened this issue 5 years ago • 4 comments

I have a first prototype implementation of Enum support (for js-enums-as-arrays) as follows.

Note: these definitions are not sufficient if you wanted to create / instantiate an enum from typescript (e.g. there is no __enum__ parameter), but they're good enough to read the data out of enum values / instances. My purpose is to use a Haxe codebase from TypeScript with usable type definitions - I don't expect TS to generate enum values, but I just want type definitions to use them in a sensible way.

enum Result {
  VALID;
  INVALID(msg:String);
  PENDING(tbd:Promise<Result>)
}

Which (would) generate as follows:

export type Result =
		[ 'VALID', number ] |
		[ 'INVALID', number, /* msg */string ] |
		[ 'PENDING', number, /* tbd */Promise<Result> ]

As you can see (while not entirely pretty), this accurately describes the enum values, and it's sufficient for TS to determine the types of the arguments in a switch-like way, by identifying the first parameter:

image

However, this is not ready to be merged for three reasons.

First, the above generates the following TypeScript error:

Type alias 'Result' circularly references itself.ts(2456)

Checking out the TS repo, I found an issue and a PR for recursive type definitions, but that's very recent. So I had to put in a hack (specifically to support Promise<T> -- you could also support Array<T>, but not T itself.)

Second, my enum declarations are generating in the .d.ts file. While the abstract enums being generated are being put in the enum.ts companion file. In my project, there are both kinds of enums in the same packages, which puts some namespace pkg declarations in the .d.ts file, and some namespace pkg declarations in the .ts file. So then the .d.ts file tries to import { pkg } from enums.ts - the imported namespace collides with the local namespace.

To get around this, I munge both output files into a single .ts file, and change all my export class to export declare class. But there may be a better way...

Third, we should probably detect if the user used Haxe's -D js-enums-as-arrays or not. The above is for the array syntax (which is what my project uses.) But I wonder if it's possible to support the object syntax... I'll think about it.

jcward avatar Nov 01 '19 23:11 jcward

Ah, just realized I should be specifying that second parameter, the number, as a literal also (which Haxe generates as the "position of the constructor"):

export type Result =
		[ 'VALID', 0] |
		[ 'INVALID', 1, /* msg */string ] |
		[ 'PENDING', 2, /* tbd */Promise<Result> ]

See it on the TypeScript playground. The recursive type is working in ts v3.7-beta.

jcward avatar Nov 02 '19 03:11 jcward

A possible "enums as objects" syntax is a little more bulky.

jcward avatar Nov 02 '19 04:11 jcward

Just a little note that in Typescript you can use types and values of the same name in declarations. This can help generate "values" for the constructors too so they can be used from TS. That is if Haxe can actually @:expose/export the enum - for which I think support was lacking. I realized that after typing this out so maybe this is useless :) In genes I generate the declaration of the example enum to:

export declare namespace Result {
  export type VALID = {_hx_index: 0, __enum__: "tests.Result"}
  export const VALID: VALID
  export type PENDING = {_hx_index: 2, tbd: Promise<Result>, __enum__: "tests.Result"}
  export const PENDING: (tbd: Promise<Result>) => Result
  export type INVALID = {_hx_index: 1, msg: string, __enum__: "tests.Result"}
  export const INVALID: (msg: string) => Result
}

export declare type Result = 
  | Result.VALID
  | Result.PENDING
  | Result.INVALID

This doesn't include the separate [EnumName]Constructor type. But you could generate it as well and have full support for enums (ie. we can actually construct the enum values properly): example

Although I'm hoping Haxe would deliver a nicer name than _hx_index :) https://github.com/HaxeFoundation/haxe/issues/8591

benmerckx avatar Nov 02 '19 11:11 benmerckx

Very (too? 😁) clever, @benmerckx.

Switching in the enum still looks really awkward though... Maybe you it could be solved using a TypeScript transform.

elsassph avatar Nov 02 '19 19:11 elsassph