gcanti / io-ts

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

Typescript 4.7 beta ESM module config messes with io-ts type inference #644

Open florianbepunkt opened 2 years ago

florianbepunkt commented 2 years ago

🐛 Bug report

Using ESM support added in TS 4.7 causes io-ts to wrongly infer types.

Current Behavior

ESM

import * as COD from "io-ts/lib/Codec.js"
const a = COD.string;
const b = a.decode(1);  // b resolves to Kind2<M, E, A>

CJS

import * as COD from "io-ts/Codec"
const a = COD.string;
const b = a.decode(1);  // b resolves to Either

Expected behavior

b should be inferred as Either<DecodeError, string>

Reproducible example

I created a repro repo here: https://github.com/florianbepunkt/io-ts-esm-repro Main branch uses ESM module, a CJS branch also exists – both use typescript 4.7 beta. The example is super basic (three lines).

Your environment

Software Version(s)
io-ts 2.2.16
fp-ts 2.12.1
TypeScript 4.7
florianbepunkt commented 2 years ago

Basically this issue is only about type inference. In ESM the TS compiler infers a Decoder as a Kleisli decoder... meaning this line

https://github.com/gcanti/io-ts/blob/1f23c7c4936690506eda3e1f7ad0066e84aa8bb1/src/Decoder.ts#L78

points the compiler to

https://github.com/gcanti/io-ts/blob/1f23c7c4936690506eda3e1f7ad0066e84aa8bb1/src/Kleisli.ts#L32-L34

Which is the inferred return type of DEC.string.decode().

If the decoder/encoder is explicitly typed like so

export interface Decoder<I, A> extends K.Kleisli<E.URI, I, DecodeError, A> {
  decode: (i: I) => E.Either<DecodeError, A>
}

then the issue is gone. So this is a quick fix. But what strikes me as odd... I have no clue what this has to do with ESM module at all. But as you can see in the link repro repo, it only happens in an ESM config.

florianbepunkt commented 2 years ago

I updated the demo repo here: https://github.com/florianbepunkt/io-ts-esm-repro

The issue still exists with TS 4.7.3