1

I am not able to fully grasp the use of extends keyword in case of union types. Here is a code snippet explaining the confusion that I have.

class SomeClass {
    someClassProp: string;
};

class SomeExtendedClass extends SomeClass {
    someExtendedClassProp: string;
};

function someClassFunction<T extends SomeClass>(args: T): T {
    return args;
};

let someClass: SomeClass, someExtendedClass: SomeExtendedClass;

someClassFunction(someClass);
someClassFunction(someExtendedClass); // Works just fine(case 1)

type someType = 'a' | 'b';
type someExtendedType = someType | 'c';

function someTypeFunction<T extends someType>(args: T): T {
    return args;
};

let someType: someType, someExtendedType: someExtendedType;

someTypeFunction(someType);
someTypeFunction(someExtendedType); // Gives me an error(case 2)

So I was wondering why such design decisions are made and what are the implications of the same.

changing function someTypeFunction<T extends someType>(args: T): T {return args;}; to function someTypeFunction<T extends someExtendedType>(args: T): T {return args;}; works but I am not able to understand how this thing is actually working.

EDIT 1

type someType = 'a' | 'b';
type someOtherType = 'c';
type someUnionType = someType | someOtherType;

type someTypeCore<T extends someUnionType> = { type: T };

type someTypeObj<T extends someType> = { a: string } & someTypeCore<T>;
type someOtherTypeObj<T extends someOtherType> = { b: string, c: string } & someTypeCore<T>;

function typeAssertion<T extends someUnionType>(args: someTypeCore<T>): args is someTypeObj<T> {
    return (args as someTypeObj<T>).a !== undefined; // Gives an error
};

function someTypeFunction<T extends someUnionType>(args: someTypeCore<T>): T {
    if (typeAssertion(args)) {
        // Do something for someTypeObj 
    } else {
        // Do Something for someOtherTypeObj 
    };
    return args.type;
};

How do we resolve this.

Amol Gupta
  • 2,054
  • 1
  • 14
  • 29
  • When a class extends another class it means it inherits everything that was there plus may add something more. When you use union - you do not extend a type, you just allow some other independent type to pass as well. – zerkms Mar 26 '19 at 10:08
  • so the question is why does case 2 fail ? – Amol Gupta Mar 26 '19 at 10:12
  • Because `type someExtendedType = someType | 'c';` does not extend `someType`. – zerkms Mar 26 '19 at 10:43

1 Answers1

5

The extends in the function constains T to be a subtype of the specified type. What a subtype means may be a bit surprising for unions.

You have to think of what a subtype relationship really means. When we say that SomeExtendedClass is a subclass of SomeClass the implication is that any instance of SomeExtendedClass is also an instance of SomeClass

So the set of all SomeClass instances contains the set of all SomeExtendedClass instances.

enter image description here

But if we take this view of subtypes to unions we see that the union with fewer memebers ("a" | "b") is actually subtype of the one with more members ("a" | "b" | "c") since all instances of "a" | "b" are instances of "a" | "b" | "c".

enter image description here

We intuitively think the subtype is the one that "has more stuff" but when thinking about unions this intuition fails us , we need to think about the subtype relationship in terms of sets.

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • Hmmm I don't get it. The SomeClass circle need to be the other way round. SomeExtendedClass Instance should be the bigger circle encapsulating SomeClass Instance as SomeExtendedClass Instance will have all the properties of SomeClass Instance – Amol Gupta Mar 26 '19 at 11:37
  • 1
    @AmolGupta You are looking at it wrong. The outer circle contains all instances of `SomeClass` in that circle some instances that are `SomeClass` are also `SomeExtendedClass` instances, but not all. The set of `SomeClass` instances is bigger and contains the set of all `SomeExtendedClass` instances. It does not matter who has more properties, the diagrams ilustrate sets.https://en.wikipedia.org/wiki/Set_(mathematics) – Titian Cernicova-Dragomir Mar 26 '19 at 11:43
  • could you please look at the edit above and how can i resolve it ? – Amol Gupta Mar 26 '19 at 12:18
  • @AmolGupta The errors you are getting seem valid to me. If `someTypeObj` has a type parameter that ca be `"a" | "b"` why would it be valid to pass in `"c"` ? Also, there is o `typeAsserion` syntax .. only type guards. Not sure how you can specifically fix that code.. it seems inconsistent to me and without a more real world example I am not sure how to fix it. – Titian Cernicova-Dragomir Mar 26 '19 at 12:28
  • So the type assertion would be in an if statement and i would like to conditionally branch based on `type` that was passed. Please see the edit. – Amol Gupta Mar 26 '19 at 13:26