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

Binary arithmetic on nested Unions of numeric types fails to typecheck #8125

Closed
appleby opened this issue Dec 10, 2019 · 3 comments · Fixed by #8146
Closed

Binary arithmetic on nested Unions of numeric types fails to typecheck #8125

appleby opened this issue Dec 10, 2019 · 3 comments · Fixed by #8146
Labels
bug mypy got something wrong priority-1-normal

Comments

@appleby
Copy link

appleby commented Dec 10, 2019

Are you reporting a bug, or opening a feature request?

bug (I think)

Please insert below the code you are checking with mypy, or a mock-up repro if the source is private. We would appreciate if you try to simplify your case to a minimal repro.

from typing import Union

T1 = Union[int, float]
T2 = Union[T1, complex]

def foo(a: T2, b: T2) -> T2:
    return a + b

What is the actual behavior/output?

$ mypy ~/tmp/foo.py
tmp/foo.py:7: error: Unsupported operand types for + ("int" and "Union[int, float]")
tmp/foo.py:7: note: Both left and right operands are unions
Found 1 error in 1 file (checked 1 source file)

What is the behavior/output you expect?

To typecheck without errors.

What are the versions of mypy and Python you are using? Do you see the same issue after installing mypy from Git master?

python 3.6.9
mypy 0.750 and master produce the above error.
mypy 0.740 does not produce an error.

What are the mypy flags you are using? (For example --strict-optional)

The issue reproduces when running mypy both without any flags and when running with --strict.

Additional info

In case it's helpful, here are some possibly/loosely-related issues I found while searching the issue tracker:

#2128
#3196

@appleby
Copy link
Author

appleby commented Dec 10, 2019

As a workaround, if I manually "flatten" the Union, mypy is happy again. That is, the following typechecks even on mypy 0.750 / master

from typing import Union

T3 = Union[int, float, complex]

def bar(a: T3, b: T3) -> T3:
    return a + b  # ok

@msullivan
Copy link
Collaborator

It looks like the issue is that in check_op in mypy/checkexpr.py, it collects all of the union elements into left_variants and right_variants, but should flatten out any unions inside those as well.

Interested in submitting a PR?

@appleby
Copy link
Author

appleby commented Dec 12, 2019

It looks like the issue is that in check_op in mypy/checkexpr.py, it collects all of the union elements into left_variants and right_variants, but should flatten out any unions inside those as well.

Interested in submitting a PR?

Not at this time, no. I spent a few minutes looking into it, but being new to the mypy codebase, I suspect it'll take longer than I want to spend on it. For now, I'm happy to stick with mypy 0.740.

Here are some notes in case it's useful for whoever picks this up (maybe future me). Warning: I have no understanding of mypy internals and didn't attempt to properly debug this, just skimmed the source code.

At first I was confused because UnionType does call flatten_nested_unions in it's constructor (#3196, #3298). But then I noticed that UnionType is a subclass of ProperType and the ProperType docstring says Every type except TypeAliasType must inherit from this type.

Since my example above is using nested type aliases, I assume these get turned into TypeAliasTypes? The docstring for TypeAliasType warns NOTE: this is not being used yet, and the implementation is still incomplete, but a quick grep seems to indicate that it is used and the docstring might just be out of date.

Following on with @msullivan's tip about check_op in mypy/checkexpr.py, I notice that get_proper_type is called before expanding the actual UnionType for both the left_variants and the right_variants. However,

  1. get_proper_type only does a single expansion of the type alias by calling TypeAlias._expand_once which doesn't do a full expansion of nested aliases, and

  2. The call to flatten_nested_unions in UnionType.__init__ only recursively flattens nested ProperTypes, not nested TypeAliasTypes.

I'm am not sure what the right fix would be here, whether it's (a) flattening nested type alias Unions "by hand" in ExpressionChecker.check_op, or (b) updating flatten_nested_unions to also flatten nested TypeAliasTypes, or (c) updating get_proper_type to expand_all_if_possible rather than _expand_once, or (d) something else.

ilevkivskyi pushed a commit that referenced this issue Dec 20, 2019
Resolves #8125 

The main problem is not about flattening unions inside variants since the following code generates no error
```python
from typing import Union

T1 = Union[int, float]
T2 = Union[Union[Union[int, float], float], Union[float, complex], complex]

def foo(a: T2, b: T2) -> T2:
    return a + b
```

The problem, however, is because when using `TypeAliasType` to alias a Union, the `TypeAliasType` will not get flattened, so this PR fixes this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-1-normal
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants