Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decoder.sum is not typesafe, What is recommended to use between Decoder.union and Decoder.sum? #523

Closed
kpritam opened this issue Oct 15, 2020 · 4 comments
Labels
experimental something related to the experimental features

Comments

@kpritam
Copy link

kpritam commented Oct 15, 2020

I have two doubts:

  1. Difference between Decoder.union and Decoder.sum

    • IsDecoder.sum optimized and should be used whenever possible?
  2. Decoder.sum is not typesafe

    • In second argument of sum, key of object can be different than the corresponding value
    • Fails at runtime, shown in following example
const CircleD = D.type({
  kind: D.literal('Circle'),
  radius: D.number
})

const SquareD = D.type({
  kind: D.literal('Square'),
  x: D.number
})

// note here, key 'Circle1' is not valid but does not issue compile time error
const ShapeD = D.sum('kind')({
  Circle1: CircleD,
  Square: SquareD
})

// fails at runtime
ShapeD.decode({
  kind: 'Circle',
  radius: 10
 })

Above issue can be resolved if signature of sum is changed to following:

import * as D from 'io-ts/lib/Decoder'

const sum = <T extends string>(tag: T) => <A>(
  members: { [K in keyof A]: Decoder<A[K] extends { [Key in T]: K } ? A[K] : never> }
): Decoder<A[keyof A]> => D.sum(tag)(members)

// fails to compile
sum('kind')({
  Circle1: CircleD,
  Square: SquareD
})

Happy to submit PR if this make sense.

@leighman
Copy link

import { TypeOf, make } from 'io-ts/lib/Schema'

export const A = make((S) =>
  S.type({
    whatever: S.string,
    type: S.literal('a'),
  })
)
export type A = TypeOf<typeof A>

export const B = make((S) =>
  S.type({
    blah: S.string,
    type: S.literal('b'),
  })
)
export type B = TypeOf<typeof B>

export const AOrB = make((S) =>
  S.sum('type')({
    a: A(S),
    b: A(S),
  })
)

Came here to report this issue with sum. Similar potential to make errors due to gap in type safety.

@gcanti
Copy link
Owner

gcanti commented Oct 20, 2020

Is Decoder.sum optimized

Yes

is not typesafe

Alas that's right, note that the proposed change would make non-string tag values impossible to manage though:

import * as D from 'io-ts/lib/Decoder'

const sum = <T extends string>(tag: T) => <A>(
  members: { [K in keyof A]: D.Decoder<unknown, A[K] extends { [Key in T]: K } ? A[K] : never> }
): D.Decoder<unknown, A[keyof A]> => D.sum(tag)(members)

const CircleD = D.type({
  kind: D.literal(1),
  radius: D.number
})

const SquareD = D.type({
  kind: D.literal(2),
  x: D.number
})

const ShapeD = sum('kind')({
  1: CircleD, // error
  2: SquareD // error
})

@kpritam
Copy link
Author

kpritam commented Oct 20, 2020

@gcanti following does work though (not sure how, maybe typescript bug?)

const ShapeD = sum('kind')({
  [1]: CircleD,
  [2]: SquareD
})

gcanti added a commit that referenced this issue Oct 21, 2020
@gcanti
Copy link
Owner

gcanti commented Oct 21, 2020

@kpritam nice trick!

@gcanti gcanti added the experimental something related to the experimental features label Oct 21, 2020
@gcanti gcanti closed this as completed in ff59c8b Oct 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experimental something related to the experimental features
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants