aeson-typescript icon indicating copy to clipboard operation
aeson-typescript copied to clipboard

Generate user-defined type guards

Open christianlavoie opened this issue 1 year ago • 6 comments

In cases where a sum type is transcribed as a series of interfaces and a union type:

export type U = A | B | C;

export interface A {
  tag: "A";
  contents: ...
}

export interface B {
  tag: "B";
  contents: ...
}

export interface C {
  tag: "C";
  contents: ...
}

Type predicates (https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) would be useful:

function isA(u: A | B | C): U is A {
    return (u as A).tag == "A";
}

christianlavoie avatar Nov 28 '23 20:11 christianlavoie

Interesting idea. In my experience, TypeScript understands discriminated unions perfectly well without explicit type guards. I.e.

if (u.tag === "A") {
  // Now TSC knows u is an A
}

I have no real objection to this feature, though it would only make sense with the ExportEach export mode (for making TS modules, not d.ts files).

I'd be curious about when you'd want this over the normal u.tag === "A" kind of test? If it's just a style thing, I personally wouldn't want to turn it on for all interfaces and bloat my declaration files with lots of boilerplate isX functions...

thomasjm avatar Nov 28 '23 23:11 thomasjm

I have no real objection to this feature, though it would only make sense with the ExportEach export mode (for making TS modules, not d.ts files).

Indeed.

I'd be curious about when you'd want this over the normal u.tag === "A" kind of test?

Partially stylistic, partially I triggered a bug where one of the checkers I'm using is complaining that u.tag is not defined on U.

If it's just a style thing, I personally wouldn't want to turn it on for all interfaces and bloat my declaration files with lots of boilerplate isX functions...

I'd expect it to be optional.

christianlavoie avatar Nov 28 '23 23:11 christianlavoie

Huh, what checker?

I'd expect it to be optional.

As in, on a per-type basis? Perhaps opted into in the deriveTypeScript' call with ExtraTypeScriptOptions?

thomasjm avatar Nov 28 '23 23:11 thomasjm

Quoth the relevant package.json:

 "svelte-check": "^3.6.2",

And yeah, ExtraTypeScriptOptions is what makes the most sense to me.

christianlavoie avatar Nov 29 '23 00:11 christianlavoie

A few more notes:

Seems to be something the Svelte community is working on: https://github.com/sveltejs/svelte/issues/9130

After further thought, adding formatting-type options in the ExtraTypeScriptOptions isn't something we're really set up to do. Piping such an option through as a TSDeclaration is probably more invasive than we'd want for such a niche feature.

Another alternative would be to do it at the formatting level, by adding an option to FormattingOptions and then using formatTSDeclarations' with that option for the types you want to do this with.

If you need this immediately, I'd recommend using the TSDeclaration constructors from Data.Aeson.TypeScript.Internal and looping over the return value of getTypeScriptDeclarations yourself to generate the functions you need. You can use the TSRawDeclaration escape hatch to add these to the normal declarations.

thomasjm avatar Nov 29 '23 00:11 thomasjm

If you need this immediately, I'd recommend using the TSDeclaration constructors from Data.Aeson.TypeScript.Internal and looping over the return value of getTypeScriptDeclarations yourself to generate the functions you need. You can use the TSRawDeclaration escape hatch to add these to the normal declarations.

At this point I'm using ts-auto-guard as a workaround, but it's shoving more stuff in the CI pipeline and I'd much rather do it at the source than as another step in CI.

christianlavoie avatar Nov 29 '23 00:11 christianlavoie