Skip to content

Implement TypeVar(..., bound=<boundary_type>) #689

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

Closed
gvanrossum opened this issue May 18, 2015 · 7 comments
Closed

Implement TypeVar(..., bound=<boundary_type>) #689

gvanrossum opened this issue May 18, 2015 · 7 comments

Comments

@gvanrossum
Copy link
Member

Now specified in PEP 484; see python/typing#59.

@gvanrossum
Copy link
Member Author

(Reid: we talked about you implementing this, you're probably a better match than I am for this.)

@JukkaL
Copy link
Collaborator

JukkaL commented Apr 8, 2016

Reid, please look at using upper_bound of TypeVarType and TypeVarDef for the bound. It might already be pretty close.

@rwbarton
Copy link
Contributor

This is almost done, but I ran into one tricky interaction with the way mypy handles the Void type used to represent the result of a function that does not return anything. In particular it arose in the visitor pattern used extensively by mypy itself. Recall that the type named None in mypy input is really the type internally called Void, and it is not a subtype of most other types, in order to flag any attempt to use the result of a function that does not return a value. (This is likely an error even though it is legal Python. The value None has a different type NoneTyp, which is a subclass of every other type.)

from typing import TypeVar, Generic
T = TypeVar('T')

class Foo():
    def accept(self, visitor: FooVisitor[T]) -> T:
        return visitor.visit(self)

class FooVisitor(Generic[T]):
    def visit(self, x: Foo) -> T:
        pass

class V(FooVisitor[None]):
    def visit(self, x: Foo) -> None:
        pass

foo = Foo()
v = V()
foo.accept(v)

This program is correctly accepted by current mypy, but with my implementation of bound, the call to accept is rejected. The reason is that the type variable T, which implicitly has an upper bound of object, is instantiated with Void, but Void is not a subtype of object for the reason mentioned above.

For now I have just added a special case to allow any type variable to be instantiated with Void, but there might be a better solution here. For example, even Void is a subtype of Any; should the default upper bound on type variables be Any perhaps? Any is a bit special in other ways so I'm not sure whether that is a good idea.

@JukkaL
Copy link
Collaborator

JukkaL commented Apr 11, 2016

Using Any as the upper bound would require special casing elsewhere, as we fall back to the upper bound when accessing attributes of a value with a type variable type. I'd rather not use Any even if we could get it to work, since Any implies dynamic typing or the lack of typing precision and this is not the case here.

@rwbarton
Copy link
Contributor

I noticed an inconsistency in the treatment of type variables with specific value restrictions like T = TypeVar('T', float, str). When T is the type variable of a function, then instantiating T at a subtype of one of the legal values (like int) in a call to that function is legal. But when T is the type variable of a generic class C, then the only legal applications of C are to the exact types float and str, not to subtypes. Is that intentional?

In any case, what should bounds on type variables of generic classes mean? At a minimum they become bounds on the types of the methods of the class, including the constructor(s); should it also be illegal to even write down the type C[x] unless x is a subtype of the upper bound of the type variable of the class?

@JukkaL
Copy link
Collaborator

JukkaL commented Apr 11, 2016

The values should be restricted to the specified values in every context when using the form TypeVar(T, float, str), otherwise it's a bug. So only float and str are valid for T -- not int (which is a subtype of float). T can only be substituted with the exact types float and str here.

The type C[x] should be rejected unless x is a subtype of the bound y when using TypeVar('S', bound=y):

S = TypeVar('S', bound=str)

class C(Generic[S]): ...

def f(x: C[int]) -> None: ... # error, int not subtype of str

C[Any] is always valid. The bound means basically that S can only ever be substituted with a subtype of str (here subtyping is understood to include Any compatibility, as is usual).

@rwbarton
Copy link
Contributor

The values should be restricted to the specified values in every context when using the form TypeVar(T, float, str), otherwise it's a bug. So only float and str are valid for T -- not int (which is a subtype of float). T can only be substituted with the exact types float and str here.

Aha, I was fooled by one of the examples in the documentation, thinking it was instantiating a typevar to a subtype of one of the allowed types, when actually it was "implicitly upcasting" the arguments to the allowed type instead. An easy trap to fall into for a Haskell programmer :)

I now agree that the behavior is consistent and as you describe.

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

No branches or pull requests

5 participants