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

Infer type argument of an instance from arguments to a generic method called on it #6746

Open
mitar opened this issue Apr 30, 2019 · 3 comments

Comments

@mitar
Copy link

mitar commented Apr 30, 2019

I think this is a feature request to allow specifying typing for the following. See the following example:

from abc import *
from fractions import *
from typing import *

Input = TypeVar('Input')
Output = TypeVar('Output')

class Base(Generic[Input, Output]):
    def do(self, input: Input) -> Output:
        # In reality do a bit more than just call "_operator".
        return self._operator(input)

    @abstractmethod
    def _operator(self, input: Input) -> Output:
        pass

class IntAdd(Base[int, int]):
    def _operator(self, input: int) -> int:
        return input + 1

class UnionAdd(Base[Union[int, Fraction], Union[int, Fraction]]):
    def _operator(self, input: Union[int, Fraction]) -> Union[int, Fraction]:
        return input + 1

reveal_type(IntAdd().do(1))
reveal_type(UnionAdd().do(1))

A = TypeVar('A', bound=Union[int, Fraction])

def do(input: A) -> A:
    return input + 1

reveal_type(do(1))
reveal_type(do(Fraction(2, 3)))

class SuperUnionAdd(Base[A, A]):
    def _operator(self, input: A) -> A:
        return input + 1

reveal_type(SuperUnionAdd().do(1))
reveal_type(SuperUnionAdd().do(Fraction(2, 3)))

The output I get is:

demo.py:25: error: Revealed type is 'builtins.int*'
demo.py:26: error: Revealed type is 'Union[builtins.int, fractions.Fraction]'
demo.py:31: error: Incompatible return value type (got "Union[int, Any]", expected "A")
demo.py:33: error: Revealed type is 'builtins.int*'
demo.py:34: error: Revealed type is 'fractions.Fraction*'
demo.py:38: error: Incompatible return value type (got "Union[int, Any]", expected "A")
demo.py:40: error: Revealed type is '<nothing>'
demo.py:40: error: Argument 1 to "do" of "Base" has incompatible type "int"; expected <nothing>
demo.py:41: error: Revealed type is '<nothing>'
demo.py:41: error: Argument 1 to "do" of "Base" has incompatible type "Fraction"; expected <nothing>

I am running mypy 0.701 on Python 3.7.1. I am on purpose use Fraction here so that we do not get automatic compatibility between numbers into effect.

What I would like to define is a UnionAdd class where if I pass to do an int, the return type would also be an int. And if I pass in Fraction, return type would be Fraction. But at the same time, I would like to use a Generic base class.

I know that I could define something like the do function I defined at the end. That one returns correctly the revealed type on lines 33 and 34 based on the input type. (Not sure what is the Incompatible return value type on line 31 about and how to fix it). But what I do not see is a way to combine the effect what I am doing for function do with the UnionAdd class and generic Base class. The example SuperUnionAdd where I blindly combine A and Base does not work.

@ilevkivskyi
Copy link
Member

I think I now understand what is your feature request. You want type inference to work across instantiation + attribute access (i.e. type of instance should be inferred from arguments to a generic instance method). This is actually tricky to implement with the current type inference logic. But it might be much easier if we will switch "single bin" inference (which will likely naturally allow instance types to be generic, currently only callable types can have free/unbound type variables, see also #5738).

For now, you can try working around by using class methods, this may help in some cases. For example if it is possible to make do class method, SuperUnionAdd.do(...) should work as expected (note I call class method directly on the class).

@ilevkivskyi ilevkivskyi changed the title Unable to define a generic type for a method which returns same output type as it gets on input Infer type argument of an instance from arguments to a generic method called on it May 6, 2019
@mitar
Copy link
Author

mitar commented May 6, 2019

I will have to read/parse what you wrote few more times (it is all a bit cryptic to me), but thanks for looking into my issue. :-)

Do you understand why I get:

Incompatible return value type (got "Union[int, Any]", expected "A")

That use case (for a regular function) should already work, no?

@ilevkivskyi
Copy link
Member

That use case (for a regular function) should already work, no?

No, technically this is not safe, __add__() can be overridden in an int subclass in such way that your function will no longer return an A. For example, you have an int subclass AInt, and then AInt.__add__() can return an int. You can probably cast the return to A if this never happens in your code.

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

2 participants