Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

type A is not assignable to type B with typescript 2.8 conditional types

Tags:

typescript

this is actually a followup on typescript 2.8 Exclude: possible to supercharge the 'partition' type? -- I'm having issues introducing conditional types in my code.

Note that conditional types are new with typescript 2.8, so currently this must be built with typescript 2.8rc.

I've reduced my current issue to this pretty small test case:

export class Option<T> {
    toVector(): Vector<T> {
        return <any>undefined;
    }
}

interface Seq<T> {
    tail(): Option<Seq<T>>;
}

class Vector<T> implements Seq<T> {

    tail(): Option<Vector<T>> {
        return <any>undefined;
    }

     // the next line breaks the compilation
    partition2<U extends T>(predicate:(v:T)=>v is U): [Vector<U>,Vector<Exclude<T,U>>];
    partition2(predicate:(x:T)=>boolean): [Vector<T>,Vector<T>];
    partition2<U extends T>(predicate:(v:T)=>boolean): [Vector<U>,Vector<any>] {
        return <any>undefined;
    }
}

The new line is the first overload for partition2. If you comment it, all builds fine.

If you compile this with --strict, it fails with a rather baffling build error:

t.ts(13,5): error TS2416: Property 'tail' in type 'Vector<T>' is not assignable to the same property in base type 'Seq<T>'.
  Type '() => Option<Vector<T>>' is not assignable to type '() => Option<Seq<T>>'.
    Type 'Option<Vector<T>>' is not assignable to type 'Option<Seq<T>>'.
      Types of property 'toVector' are incompatible.
        Type '() => Vector<Vector<T>>' is not assignable to type '() => Vector<Seq<T>>'.
          Type 'Vector<Vector<T>>' is not assignable to type 'Vector<Seq<T>>'.
            Types of property 'tail' are incompatible.
              Type '() => Option<Vector<Vector<T>>>' is not assignable to type '() => Option<Vector<Seq<T>>>'.
                Type 'Option<Vector<Vector<T>>>' is not assignable to type 'Option<Vector<Seq<T>>>'.
                  Types of property 'toVector' are incompatible.
                    Type '() => Vector<Vector<Vector<T>>>' is not assignable to type '() => Vector<Vector<Seq<T>>>'.
                      Type 'Vector<Vector<Vector<T>>>' is not assignable to type 'Vector<Vector<Seq<T>>>'.
                        Types of property 'tail' are incompatible.
                          Type '() => Option<Vector<Vector<Vector<T>>>>' is not assignable to type '() => Option<Vector<Vector<Seq<T>>>>'.
                            Type 'Option<Vector<Vector<Vector<T>>>>' is not assignable to type 'Option<Vector<Vector<Seq<T>>>>'.
                              Types of property 'toVector' are incompatible.
                                Type '() => Vector<Vector<Vector<Vector<T>>>>' is not assignable to type '() => Vector<Vector<Vector<Seq<T>>>>'.
                                  Type 'Vector<Vector<Vector<Vector<T>>>>' is not assignable to type 'Vector<Vector<Vector<Seq<T>>>>'.
                                    Types of property 'tail' are incompatible.
                                      Type '() => Option<Vector<Vector<Vector<Vector<T>>>>>' is not assignable to type '() => Option<Vector<Vector<Vector<Seq<T>>>>>'.
                                        Type 'Option<Vector<Vector<Vector<Vector<T>>>>>' is not assignable to type 'Option<Vector<Vector<Vector<Seq<T>>>>>'.
                                          Types of property 'toVector' are incompatible.
                                            Type '() => Vector<Vector<Vector<Vector<Vector<T>>>>>' is not assignable to type '() => Vector<Vector<Vector<Vector<Seq<T>>>>>'.
                                              Type 'Vector<Vector<Vector<Vector<Vector<T>>>>>' is not assignable to type 'Vector<Vector<Vector<Vector<Seq<T>>>>>'.
                                                Type 'Vector<Vector<Vector<Seq<T>>>>' is not assignable to type 'Vector<Vector<Vector<Vector<T>>>>'.

I don't really know what to do to make this work :-(

like image 567
Emmanuel Touzery Avatar asked Mar 24 '18 19:03

Emmanuel Touzery


2 Answers

As I mentioned in comments, this looks like a compiler bug (or at least a design limitation). The minimal reproduction I can find looks like this:

interface A<T> {
    bat: B<A<T>>;
}

interface B<T> extends A<T> {
    bat: B<B<T>>;
    boom: true
}

Everything's okay here. All the type parameters are in covariant positions, which means that if Y is a subtype of X, then A<Y> is a subtype of A<X>, and B<Y> is a subtype of B<X>. And since B<T> is a subtype of A<T>, you can deduce that, specifically, B<B<T>> is a subtype of B<A<T>>. Therefore it is fine that the bat property of B narrows B<A<T>> to B<B<T>>.


Now watch what happens when we add a conditional type (that doesn't get immediately evaluated) to B:

interface A<T> {
    bat: B<A<T>>;
}
interface B<T> extends A<T> {
//        ^ error
// Interface 'B<T>' incorrectly extends interface 'A<T>'.
    bat: B<B<T>>;

    boom: T extends any ? true : true
}

It explodes. boom will undoubtedly still be of type true, but somehow the deferred conditional type is causing trouble. From the error, it's clear that the compiler is chasing down whether B<T> is compatible with A<T> by checking whether B<B<T>> is compatible with B<A<T>> by checking whether B<B<B<T>>> is compatible with B<B<A<T>>> by... uh oh.

So there's some kind of infinite regress in the type checker which bails out early... that's good, since otherwise the compiler would just hang. But when it bails out it decides that B<T> is not compatible with A<T>, which is bad for us (although a rule like "bail out with success" is probably bad too).


So, my suggestion is to do what you did and file the issue in GitHub. We'll see what they say about it there.

I'm not sure if there are any really easy workarounds. I'll edit this answer if I find one.

Good luck!

like image 148
jcalz Avatar answered Nov 15 '22 10:11

jcalz


The problem is that Seq<T>.tail returns Option<Seq<T>> while Vector<T>.tail returns Option<Vector<T>>. These two types are not compatible, as the compiler to verbosely tells us. The simplest solution, is to change Seq<T> to return an Option of the same type as the current type, whatever that type may be. This means that the implementation of tail in Vector<T> will return Option<Vector<T>> as expected, but will be compatible with the definition of tail in Seq. We can do this using the this type:

export class Option<T> {
    toVector(): Vector<T> {
        return <any>undefined;
    }
}

interface Seq<T> {
    tail(): Option<this>;
}

class Vector<T> implements Seq<T> {

    tail(): Option<this> {
        return <any>undefined;
    }

    // the next line breaks the compilation
    partition2<U extends T>(predicate:(v:T)=>v is U): [Vector<U>,Vector<Exclude<T,U>>];
    partition2(predicate:(x:T)=>boolean): [Vector<T>,Vector<T>];
    partition2<U extends T>(predicate:(v:T)=>boolean): [Vector<U>,Vector<any>] {
        return <any>undefined;
    }
}

declare var v: Vector<number>;
var d = v.tail().toVector() // will be Vector<Vector<number>>
like image 33
Titian Cernicova-Dragomir Avatar answered Nov 15 '22 08:11

Titian Cernicova-Dragomir