masonk
masonk

Reputation: 9778

TypeScript 1.4: Using type unions

I have a function wrap that can take either a T or a Wrapped<T> and returns a Wrapped<T>.

Declared in TS 1.0:

wrap<T>(value: Wrapped<T>, defaultValue?: T): Wrapped<T>;
wrap<T>(value: T, defaultValue?: T): Wrapped<T>;

Now in 1.4, I want to use union types to define the concept of "either a T or a Wrapped<T>"

class Bar {
   thing: Wrapped<number>;
   constructor(thing: number | Wrapped<number>) {
       this.thing = wrap(thing);
   }
}
  

But the type checker errors on the this.thing assingment with:

Type Wrapped<number | Wrapped<number>> is not assignable to Wrapped<number>

Type 'number | Wrapped<number>' is not assignable to type number

I understand that the error is that the value I'm passing to wrap is number | Wrapped<number> and wrap only knows how to handle number and Wrapped<number>;

So my first try was to go declare that it can handle T | Wrapped<T>;

wrap<T>(value: T | Wrapped<T>, defaultValue?: T): Wrapped<T>;
wrap<T>(value: Wrapped<T>, defaultValue?: T): Wrapped<T>;
wrap<T>(value: T, defaultValue?: T): Wrapped<T>;

But the type checker still errors. If I right click and go to definition I see that it's still binding to the third definition in the list rather than to my earlier, higher-precedence signature for T | Wrapped<T>.

Is this the expected behavior?

Am I supposed to resolve this manual with type guards? If so, could someone show me how to do it with this generic Wrapper that I have? How do I get a type parameter from my thing variable?

Upvotes: 0

Views: 79

Answers (2)

Nypan
Nypan

Reputation: 7246

Simply combine the overloaded functions:

class Wrapped<T> {

    private _value: T;
    get value(): T { return this._value; }

    constructor(value: T) {
        this._value = value;
    }
}

class WrappHelper {

    public static wrap<T>(value: Wrapped<T> | T, defaultValue ?: T): Wrapped<T> {
        if (value instanceof Wrapped)
            return value;
        else
            return new Wrapped<T>(<T>value);
    }
}

var a = new Wrapped(100);
var b = 200;

var a1: Wrapped<number> = WrappHelper.wrap(a);
var a2: Wrapped<number> = WrappHelper.wrap(b);

Upvotes: 1

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 220944

This compiles:

interface Wrapped<T> { value: T }
declare function wrap<T>(value: T|Wrapped<T>, defaultValue?: T): Wrapped<T>;

class Bar {
   thing: Wrapped<number>;
   constructor(thing: number | Wrapped<number>) {
       this.thing = wrap(thing); // No error
   }
}

General guidance for choosing between a union type and a function of two overloads is that if you can write it as a single-signature union type, you should.

Upvotes: 1

Related Questions