grace
grace

Reputation: 270

In TypeScript, How to define type of method that provide a value returned from member object?

interface Foo<T> {
    getValue(): T;
}

class Foo1 implements Foo<string> {
    __value: string;

    constructor(value: string) {
        this.__value = value;
    }

    getValue(): string {
        return this.__value;
    }
}

class Foo2 implements Foo<number>  {
    __value: number;

    constructor(value: number) {
        this.__value = value;
    }

    getValue(): number {
        return this.__value;
    }
}

class Bar<T extends Foo1 | Foo2> {
    private __foo: T;

    constructor(foo: T) {
        this.__foo = foo;
    }

    getValueOfFoo() {
        return this.__foo.getValue();
    }
}

const bar1 = new Bar(new Foo1('hello'));
const val1: string = bar1.getValueOfFoo();

const bar2 = new Bar(new Foo2(12345));
const val2: number = bar2.getValueOfFoo();

Link to TypeScript Playground

I expected the return values of 42 and 45 line to be string and number but they aren't.

How can I define the type of method that can returns proper type depends on object type be provided thru constructor?

Upvotes: 2

Views: 46

Answers (2)

DAG
DAG

Reputation: 6994

The reason why it is currently not working as expected is, that your generic constraint is defined as T extends Foo1 | Foo2. Typescript does not allow narrowing of generic unions. See this issue #13995.

The gist of the issue is this comment from one of the typescript maintainers:

TL;DR from design discussion: It's somewhat obvious what the "right" thing to do is, but would require a large rework of how we treat type parameters, with concordant negative perf impacts, without a corresponding large positive impact on the actual user-facing behavior side.

If new patterns emerge that make this more frequently problematic, we can take another look.

A quick way to solve your issue at hand is the avoid the generic union like this, not sure if it will fit your actual use case:

class Bar<T> {
    private __foo: Foo<T>;

    constructor(foo: Foo<T>) {
        this.__foo = foo;
    }

    getValueOfFoo() {
        return this.__foo.getValue();
    }
}

Playground Link

Upvotes: 3

gsb22
gsb22

Reputation: 2180

I ran the following piece of code (your code) and see the correct result.

const bar1 = new Bar(new Foo1('hello'));
const val1: string = <string>bar1.getValueOfFoo();
console.log(typeof(val1))

const bar2 = new Bar(new Foo2(12345));
const val2: number = <number>bar2.getValueOfFoo();
console.log(typeof(val2))

Output on console:

string
number

Upvotes: 0

Related Questions