gcanti / io-ts

Runtime type system for IO decoding/encoding
https://gcanti.github.io/io-ts/
MIT License
6.68k stars 331 forks source link

Difficulties with generic serialisable type #693

Closed silasdavis closed 1 year ago

silasdavis commented 1 year ago

I am struggling with a type:

export type AnySerialisableType = t.Type<any, Json>;

The idea is to use this in a similar way to AnyType but with the requirement that it can be encoded to Json.

I have a container type:

export class Extractor<
  ScopeSchema extends AnySerialisableType = AnySerialisableType,
  OutputSchema extends AnySerialisableType = AnySerialisableType,
>

What I was hoping is that a variable of bare type Extractor is assignable from any more reified Extractor but it does not seem to be the case.

I'm struggling to come up with a minimal example, though I promise to try, but feel like I might need some pointers.

I have a function function newExtractor(...) => new Extractor(...) that compiles fine on its own (it's type is narrowed based on some specific t.Type schemas that are passed in.

However if I add function newExtractor(...): Extractor => new Extractor(...) I get a compile error like:

 Type 'Extractor<ExactC<TypeC<{ id: StringC; contractAddresses: ArrayC<BrandC<StringC, AddressBrand>>; eventFragments: ArrayC<ExactC<PartialC<{ type: LiteralC<"event">; anonymous: LiteralC<...>; name: StringC; inputs: ArrayC<...>; }>>>; }>>, TypeC<...>>' is not assignable to type 'Extractor<AnySerialisableType, AnySerialisableType>'.   Type 'Type<any, Json, unknown>' is missing the following properties from type 'ExactC<TypeC<{ id: StringC; contractAddresses: ArrayC<BrandC<StringC, AddressBrand>>; eventFragments: ArrayC<ExactC<PartialC<{ type: LiteralC<"event">; anonymous: LiteralC<...>; name: StringC; inputs: ArrayC<...>; }>>>; }>>': type, _tag

It feels like this could be s contravariance thing?

Perhaps the issue with _tag is a stronger clue here.

Anyway I appreciate this is a terrible report, I just want to not go off in the wrong direction if there is some direction I could get on how to craft a more minimal case.

silasdavis commented 1 year ago

After a sleep I realise I am being dumb here.

io-ts runtime types may be isomorphic to compile-time types but they do not themselves obey a covariant runtime hierarchy over the schemas.

exit stage left

silasdavis commented 1 year ago

Well in case anyone else finds themselves in a similar quagmire the 'solution' is to use the compile-time A, O, I types directly in the dependent classes like:

export class Extractor<
  Scope,
  ScopeO extends Json,
  Output,
  OutputO extends Json,
>

Then reference them in the schema types like t.Type<Scope, ScopeO> etc