Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Using string literal types to distinguish overloads - Advanced Types Section - String Literal Types Subsection #235

Open
gburdeti opened this issue Apr 9, 2016 · 6 comments
Labels

Comments

@gburdeti
Copy link
Contributor

gburdeti commented Apr 9, 2016

Obviously the code in the handbook about using string literal types to distinguish overloads doesn't work out of the box, so I did my own tests using this code which looks like the one in the handbook:

class Element_ {
}

class HTMLImageElement_ extends Element_ {
    constructor() {
        super();
    }
}

function createUIElement(tagName: "img"): HTMLImageElement_;

function createUIElement(tagName: string): Element_ {
    switch (tagName) {
        case 'img':
            return new HTMLImageElement_();
        default:
            return new Element_();
    }
}

The code above doesn't compile. VS Code will show the following compile error:
"Specialized overload signature is not assignable to any non-specialized signature."

I found two ways to fix this:

1- Use the default way of overloading and make a function that takes any.

function createUIElement(tagName: "img"): HTMLImageElement_;

function createUIElement(tagName: string): Element_;

function createUIElement(tagName: any): Element_ {
    switch (tagName as string) {
        case 'img':
            return new HTMLImageElement_();
        default:
            return new Element_();
    }
}

2- Define an overload that takes a string type of the function that already takes a string.

function createUIElement(tagName: "img"): HTMLImageElement_;

function createUIElement(tagName: string): Element_; // if we remove this overload, we get the compile error

function createUIElement(tagName: string): Element_ {
    switch (tagName) {
        case 'img':
            return new HTMLImageElement_();
        default:
            return new Element_();
    }
}

Is this a handbook code error or a compiler error and the code in the handbook is correct?

@DanielRosenwasser
Copy link
Member

It used to be the case that a "specialized" overload (one that has a parameter whose type is a string literal) needed to be assignable to some non-specialized overload.

The solution you found in (2) is the one we usually recommend, since it looks like you were forgetting to provide a general overload anyway (i.e. you could never call this with a random string because you didn't have an overload that just takes string).

In TypeScript 2.0, this will be fixed (see microsoft/TypeScript#6278). However, be careful, because this old check was the only thing that reminded you to provide the overload that just takes a string.

@DanielRosenwasser
Copy link
Member

Actually, I'm going to reopen this issue. You're definitely not the only one who's going to run into this.

@olegdunkan
Copy link

class A {
    x:number;
}
class B {
    y:number;
}
type z = "a" | "b"
function f(x:string, obj:A | B);
function f(x:"a", obj:A);
function f(x:"b", obj:B);
function f(x:string, obj:A | B){
    if (x === "a") {
        obj.x = 1;//   point 1 <-------- code completion        
    } else if (x === "b") {
        obj.y = 1;//  point 2  <-------- code completion                
    } else {
        }
}

We know at point 1 that obj has type A, and we know at point 2 that obj has type B, how about type completion in these points?

@DanielRosenwasser
Copy link
Member

The type system doesn't work to reconcile implementation signatures with overload signatures. There's no obvious way to do so right now.

You can use type predicates to model this instead:

function isA(tag: "a" | "b", obj: A | B): obj is A {
    return tag === "a";
}
function isB(tag: "a" | "b", obj: A | B): obj is B {
    return tag === "b";
}

function f(x: "a", obj: A);
function f(x: "b", obj: B);
function f(x: "a" | "b", obj: A | B){
    if (isA(x, obj)) {
        obj.x = 1;
    }
    else if (isB(x, obj)) {
        obj.y = 1;
    }
    else {
        throw "wat";
    }
}

Or in 2.0 beta, you can just add the tags to A & B themselves: https://twitter.com/drosenwasser/status/744243827158835200

@olegdunkan
Copy link

olegdunkan commented Aug 5, 2016

Of course an answer to the question about why there is no obvious way to do so is out of the scope of this thread. But it would be nice to understand why not obvious. Thanks.

My view of the problem is about multiple parameters.
Suppose we have

function f(x: "x1",      y:"y1"|"y2",   z:"z1"|"z2", obj: X1);
function f(x: "x1"|"x2", y:"y1",        z:"z1"|"z2", obj: Y1);
function f(x: "x1"|"x2", y:"y1"|"y2",   z:"z1",      obj: Z1);

function f(x:"x1"| "x2", y: "y1"|"y2", z:"z1"|"z2") {
    ... a lot of combinatoric stuff to analyze goes to here 
}

@DanielRosenwasser
Copy link
Member

It's partially about the combinatoric nature of overloads. It's also just in need of a proposal with how the type system works today. Simply unioning the types of each parameter seems easy enough, but it's not clear how helpful that would be, because in your use-case, you wanted to be able to relate each of the parameter types back to the original overloads. I've considered this problem and I'm not sure how we'd go about doing so in a clean & reasonable way.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants