Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type parameter inference not working with union types #19634

Closed
jchitel opened this issue Nov 1, 2017 · 5 comments
Closed

Type parameter inference not working with union types #19634

jchitel opened this issue Nov 1, 2017 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@jchitel
Copy link

jchitel commented Nov 1, 2017

TypeScript Version: 2.7.0-dev.20171031

Code

// generic interface for a visitor, all visitor methods return the same type
interface IVisitor<T> {
    visitThing(thing: Thing): T;
    visitOtherThing(other: OtherThing): T;
}

// "visitee" classes
class Thing {
    other: OtherThing;

    visit<T>(visitor: IVisitor<T>) {
        return visitor.visitThing(this);
    }
}

class OtherThing {
    visit<T>(visitor: IVisitor<T>) {
        return visitor.visitOtherThing(this);
    }
}

// an implementation of the above interface
// visitor methods can return void or number
class SomeVisitor implements IVisitor<number | void> {
    visitThing(thing: Thing) {
        // type parameter of 'thing.other.visit' resolves to 'void', not 'number | void'
        // cast to 'number' fails
        const result = thing.other.visit(this) as number;
    }

    visitOtherThing(other: OtherThing) {
        return 4;
    }
}

Expected behavior:

I have a simple setup for the visitor pattern, but with a generic interface so that I can create visitors that return different types. This works perfectly if I implement the interface with a single type.

However, I have a situation where I have two types of "visitee" classes for a particular visitor. One type will return a number, and the other will return void. So I decided I would specify number | void as the return type of the visitor. I am fine with having to do a cast when calling a number visitee from a void visitee, as I do above.

I would expect that when I call thing.other.visit(this), the type parameter for visit<T>() would use the type of SomeVisitor, which is IVisitor<number | void>, and T would infer to number | void.

Actual behavior:

However, for some reason, the type for T is inferred to be void, which leads me to believe that some other mechanism is being used to determine the type parameter. Since number is not assignable to void, the cast to number fails.

I can work around this by specifying the type parameter explicitly:

const result = thing.other.visit<number | void>(this) as number;

or casting this to the expected visitor type:

const result = thing.other.visit(this as IVisitor<number | void>) as number;

but I believe that TypeScript should be able to correctly infer the type given the information that it has.

@ghost
Copy link

ghost commented Nov 1, 2017

This is one of the pitfalls of a structural type system -- the implements clause doesn't determine the type, the actual method types do. So you can fix the error by declaring explicit return types on visitThing and visitOtherThing to return number | void.

@jchitel
Copy link
Author

jchitel commented Nov 1, 2017

Oh yea, sure enough. If I put an explicit return type of number | void on at least one method in SomeVisitor, then it infers correctly.

Still seems weird though...

@ghost
Copy link

ghost commented Nov 1, 2017

Basically, the implements clause asserts type compatibility but doesn't actually affect the type of the class. Related: #10077

@mhegazy
Copy link
Contributor

mhegazy commented Nov 1, 2017

duplicate of #1373

@mhegazy mhegazy added the Duplicate An existing issue was already created label Nov 1, 2017
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants