Io-ts: Broken build with [email protected]

Created on 17 May 2018  路  15Comments  路  Source: gcanti/io-ts

Here's the error

export type mixed = object | number | string | boolean | symbol | undefined | null
export type Is<A> = (m: mixed) => m is A // <= new error
/*
A type predicate's type must be assignable to its parameter's type.
  Type 'A' is not assignable to type 'mixed'.
    Type 'A' is not assignable to type 'object'.
*/    

Related: section "Unconstrained type parameters are no longer assignable to object in strictNullChecks" in https://blogs.msdn.microsoft.com/typescript/2018/05/16/announcing-typescript-2-9-rc/

What's the better fix? Changing mixed to

export type mixed = {} | undefined | null

?

EDIT: best bet so far is

export type mixed = { [key: string]: any } | object | number | string | boolean | symbol | undefined | null
help wanted high priority

Most helpful comment

I must admit I don't even use this library

Well, then thank you for taking the time to chime in, more opinions/suggestions are always helpful.

that definition of mixed leaves a dangerous loophole

You are right, my candidate is not a perfect fit for a general purpose definition of mixed.
However looks good enough for io-ts, let me explain what I mean. I chose { [key: string]: any } because it plays well with t.Dictionary. In fact, in "idiomatic" io-ts you would define foo as

function foo(m: t.mixed) {
  if (t.Dictionary.is(m) && 'bar' in m) {
    const bar = m['bar'] // bar has type mixed
    bar.baz // and the compiler DOES complain here
  }
}

Finally, as @sledorze pointed out, in the TypeScript repo there's some work related to unknown (https://github.com/Microsoft/TypeScript/pull/24439) and any (https://github.com/Microsoft/TypeScript/pull/24423) so maybe we should just wait the next release and then try to change mixed to

type mixed = unknown

All 15 comments

Why don't you add an upper-bound constraint? (As also mentioned in the linked blog post)

export type Is<A extends mixed> = (m: mixed) => m is A

The quest for the perfect definition of mixed still remains, but with a lower priority.

@dippi because that would mean to add an upper-bound constraint everywhere. It would be awkward and, in practice, a breaking change.

I'd rather prefer to find a good definition for mixed. The above candidate seems to work fine based on my experiments but I need some help to test it out in real code bases, especially those containing custom types / combinators.

I see, I'm aware of that issue and I agree with your concerns.
Still I don't know which is the lesser evil, because that definition of mixed leaves a dangerous loophole:

function foo(m: mixed) {
    if (m !== null && typeof m === "object" && "bar" in m) {
        const bar = m["bar"] // bar has type any
        bar.baz // and the compiler won't complain here
    }
}

Following my gut I like better the following definition, that needs the upper-bound constraint, but it's less awkward / dangerous to check and access.

export type mixed = { [key: string]: mixed } | number | string | boolean | symbol | undefined | null;

(note: I'm aware of recursive types limitations and the use of interfaces as a workaround, however I just tried it and, to my surprise, iff the type is exported it seems to work fine)

However I honestly cannot fully picture how much awkward it would become and I must admit I don't even use this library, I was just searching for a good definition of mixed and inspiration for run-time type checking when I stumbled upon this issue. So I'll leave the answer to the owners of real code bases.

Unknown type is currently under consideration in typescript

I must admit I don't even use this library

Well, then thank you for taking the time to chime in, more opinions/suggestions are always helpful.

that definition of mixed leaves a dangerous loophole

You are right, my candidate is not a perfect fit for a general purpose definition of mixed.
However looks good enough for io-ts, let me explain what I mean. I chose { [key: string]: any } because it plays well with t.Dictionary. In fact, in "idiomatic" io-ts you would define foo as

function foo(m: t.mixed) {
  if (t.Dictionary.is(m) && 'bar' in m) {
    const bar = m['bar'] // bar has type mixed
    bar.baz // and the compiler DOES complain here
  }
}

Finally, as @sledorze pointed out, in the TypeScript repo there's some work related to unknown (https://github.com/Microsoft/TypeScript/pull/24439) and any (https://github.com/Microsoft/TypeScript/pull/24423) so maybe we should just wait the next release and then try to change mixed to

type mixed = unknown

maybe we should just wait...

FYI just tried [email protected] (which already contains unknown) and type mixed = unknown works like a charm

@gcanti what do you think about the proper release / schedule (make it available in 'next' ?)

@sledorze my plan would be

  • wait for the next stable release (I guess [email protected]) which should contain unknown
  • change mixed into an unknown alias (*)
  • gather feedback: is everthing ok in your code bases?
  • release the change as [email protected]

(*) you can try it now editing node_modules/io-ts/lib/index.d.ts

No errors at compilation time.
Looks good to me 馃憤

wait for the next stable release (I guess [email protected])

My guess was wrong, unknown is coming in TypeScript 3, see Announcing TypeScript 2.9.1

@sledorze so in the meanwhile I would go for

export type mixed = { [key: string]: any } | object | number | string | boolean | symbol | undefined | null

WDYT?

@gcanti I need to test it!

@gcanti not compilation error on the code base using your latest proposal.

My project uses io-ts. With that updated definition for mixed I can compile and run it fine with typescript 2.9.1.

@gcanti works also for me with[email protected].

Thanks @sledorze @spacejack @mlegenhausen, I'm going to release a patch

Was this page helpful?
0 / 5 - 0 ratings