Skip to content

Union types and generics #4432

Closed
Closed
@mitar

Description

@mitar

I have this example:

import typing

A = typing.TypeVar('A')

class Base(typing.Generic[A]):
    def do(self, input: A) -> A:
        return input

Inputs1 = typing.Union[int, str]

class Numbers1(Base[Inputs1]):
    pass

Inputs2 = typing.TypeVar('Inputs2', bound=typing.Union[int, str])

class Numbers2(Base[Inputs2]):
    pass

class Numbers3(Base[Inputs1]):
    def do(self, input: Inputs2) -> Inputs2:
        return input

reveal_type(1)  # This is line 23
reveal_type(Numbers1().do(1))
reveal_type(Numbers1().do('foobar'))
reveal_type(Numbers2().do(1))
reveal_type(Numbers2().do('foobar'))
reveal_type(Numbers3().do(1))
reveal_type(Numbers3().do('foobar'))

When I run mypy (mypy===0.570-dev-1aba774cc00b49627b86f95c8172adbb12b52891), I get:

test.py:23: error: Revealed type is 'builtins.int'
test.py:24: error: Revealed type is 'Union[builtins.int, builtins.str]'
test.py:25: error: Revealed type is 'Union[builtins.int, builtins.str]'
test.py:26: error: Revealed type is '<nothing>'
test.py:26: error: Argument 1 to "do" of "Base" has incompatible type "int"; expected <nothing>
test.py:27: error: Revealed type is '<nothing>'
test.py:27: error: Argument 1 to "do" of "Base" has incompatible type "str"; expected <nothing>
test.py:28: error: Revealed type is 'builtins.int*'
test.py:29: error: Revealed type is 'builtins.str*'

Ideally, Numbers1 should work like Numbers3, or at least Numbers2 should. But currently it seems like only Numbers3 make a strict return type and not union type. It is sad that I have to reimplement do method just to get correct return types. This is really going against DRY principle.

Activity

gvanrossum

gvanrossum commented on Jan 6, 2018

@gvanrossum
Member

That seems wishful thinking. If you expand the generics in Numbers1, you end up with a do() method that takes an arg of type Union[int, str] and returns Union[int, str], so the behavior is as it should be.

The problem with Numbers2 seems to be that you've made Numbers2 a generic class. Now instantiating a generic class without giving it a specific type typically defaults to Any, but that doesn't fly since the generic var has a union bound. There's some bug here that causes this to come out as <nothing> (probably already in the tracker somewhere else) but at best it would behave the same as Numbers1, not Numbers3.

mitar

mitar commented on Jan 6, 2018

@mitar
Author

Yea, I think I understand why it is not working at the moment. But hopefully it will could work. So how I see is that A is a placeholder and that then a subclass can specify what it is. And it seems there is no way to tell currently that it can be or type A or type B, but whatever it will be, it will be the same type. So it seems you cannot mix generic class with methods using type variables? Maybe there could be some syntax for this?

class Numbers2(Base.params(Inputs2)):
    pass

Less fancy than [...] but could tell to do really replace A with Inputs and not create a new generic?

ilevkivskyi

ilevkivskyi commented on Jan 6, 2018

@ilevkivskyi
Member

TBH, I don't see an issue here (apart from Uninhabited instead of Any for an unconstrained TypeVar, but we have an issue for this). @mitar could you please provide some more details, what is the code you have, mypy output, and expected output?

As a side note, maybe we should add an issue template? (GitHub supports this.) For me personally it would be easier to see these points:

  • The code (or a simplified repro if the source is private)
  • Actual behavior
  • Expected behavior
  • mypy/Python version
  • Has a user tried running mypy from master
  • Full command line with all flags used
  • If there was a traceback - paste full traceback

@gvanrossum @JukkaL What do you think?

gvanrossum

gvanrossum commented on Jan 6, 2018

@gvanrossum
Member
ilevkivskyi

ilevkivskyi commented on Jan 6, 2018

@ilevkivskyi
Member

But feel free to set up a template, as long as it's also easy to ignore when people don't need the guidance.

It is jut plain text placed initially in the issue, one can edit it or remove completely. I will make one now, and we will see how it goes.

ilevkivskyi

ilevkivskyi commented on Jan 6, 2018

@ilevkivskyi
Member

OK, here is the PR #4433

mitar

mitar commented on Jan 6, 2018

@mitar
Author

could you please provide some more details, what is the code you have, mypy output, and expected output?

I provided that in the initial issue text? There is code, which mypy I ran, current output, and expected output is the last two lines of the mypy output.

Well, Mitar has a feature request formulated as a bug report.

Oh, sorry, I thought it was clear that is a feature request. Is there some other/better way to submit features requests? In README it says that issues are for feature requests as well:

Please report any bugs and enhancement ideas using the mypy issue tracker:

emmatyping

emmatyping commented on Jan 6, 2018

@emmatyping
Member

Is there some other/better way to submit features requests?

You don't explicitly say whether or not this is a feature request or bug report, so being explicit about that at the beginning would be helpful.

mitar

mitar commented on Jan 6, 2018

@mitar
Author

Sorry about that. Yea, it is a feature request.

mitar

mitar commented on Apr 30, 2019

@mitar
Author

I am closing this issue in favor of #6746 where I tried to describe in more detail what I am doing, what I tried, and how it does not work.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @mitar@gvanrossum@emmatyping@ilevkivskyi

        Issue actions

          Union types and generics · Issue #4432 · python/mypy