Skip to content

abs(Union[int, Decimal]) is inferred as object #8601

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

Open
brettcs opened this issue Mar 29, 2020 · 7 comments
Open

abs(Union[int, Decimal]) is inferred as object #8601

brettcs opened this issue Mar 29, 2020 · 7 comments
Labels
bug mypy got something wrong topic-join-v-union Using join vs. using unions topic-protocols

Comments

@brettcs
Copy link

brettcs commented Mar 29, 2020

I'm afraid I'm not sure whether I'm reporting a bug or requesting a feature. I'm happy to leave that up for you all to triage. I'll start with what I'm seeing and what I'd like to see, and then get a little bit into the why.

Here's a minimal reproduction:

from decimal import Decimal
from typing import Union

def check(x: Decimal, y: Union[Decimal, int]) -> bool:
    return x < -abs(y)

With Python 3.7.3 (from Debian buster) and mypy 0.770 (from PyPI), checking this code returns an error:

deccomp.py:6: error: Unsupported operand type for unary - ("object")  [operator]
Found 1 error in 1 file (checked 1 source file)

The return type of abs is inferred as object, I think because that's the common supertype of int and Decimal. It would be nice if, one way or another, the return type of abs could be inferred as some higher numeric type.

The context here is I'm working on accounting software where I want to be careful to do decimal math throughout. In other words, I never want to deal with the decimal.FloatOperation signal. For functions that do basic arithmetic or comparisons across numbers, it's fine to accept arguments that are either int or Decimal, and it's convenient for callers if I can annotate that argument type rather than requiring them to convert their integer arguments to Decimal all the time.

@JelleZijlstra
Copy link
Member

For context, the definition of abs in typeshed is

def abs(__n: SupportsAbs[_T]) -> _T: ...

Considering the definition of SupportsAbs, this means that abs returns whatever the __abs__ method on its argument type returns. int.__abs__ is declared as returning int in typeshed, and Decimal.__abs__ returns Decimal.

I feel like mypy should be able to use these stubs to infer that abs(Union[int, Decimal]) returns Union[int, Decimal], but the current type inference isn't up to the task. There are probably already some similar issues.

@msullivan
Copy link
Collaborator

Yeah, I agree that would probably be a better type in this case. But whether to infer a union type or a join is tricky and changing behavior will break code in places; not sure if this one has a fix that would be better.

Fix for your particular issue might be to write a wrapper with a better type.

@brettcs
Copy link
Author

brettcs commented Apr 8, 2020

I ended up just casting y to Decimal at the top of the function. I think y = Decimal(y) should always work given these types, so the cast let me tell mypy that with less runtime overhead.

@jvdwetering
Copy link

jvdwetering commented Apr 21, 2020

I think the following is a related example:

from typing import Union
from fractions import Fraction
import math

def f(a: Union[Fraction, int]) -> Union[Fraction, int]:
    return math.pi*a

This gives the error on the last line:

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

EDIT: to be clear, there is supposed to be an error, but I feel mypy should infer the type float instead of Union[Any, float]

@zormit
Copy link

zormit commented May 3, 2021

It seems to me that python/typeshed#5275 is related too. There, @erictraut describes:

The problem is that mypy uses a "join" operation to widen types in its type constraint solver rather than using a union operation.

Are there any plans to fix this and change the way the constraint solver works?

PS: This is the first issue that I dug up trough the search that seems to be related and I am new to this project. As was said here, there are probably related issues. Feel free to refer me to a more relevant issue/feature if this has been discussed.

@hauntsaninja
Copy link
Collaborator

#5392 and #9264 are related, I think

@slafs
Copy link

slafs commented Jun 28, 2023

Run into this bug today too.

This snippet:

from decimal import Decimal

m = min(Decimal(1), 2)

Decimal(m)

produces:

fiddles/decimal_mypy.py:3: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "object"  [type-var]
fiddles/decimal_mypy.py:5: error: Argument 1 to "Decimal" has incompatible type "object"; expected "Decimal | float | str | tuple[int, Sequence[int], int]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)

using mypy 1.4.1 (compiled: yes) and Python 3.10.9.

I've also read python/typeshed#5275 and it looks that this is fine with Pyright. Relevant quotes from that issue seem to be:

The base problem is that mypy looks for a common base type in case like this, though, and only finds object

and

The problem is that mypy uses a "join" operation to widen types in its type constraint solver rather than using a union operation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-join-v-union Using join vs. using unions topic-protocols
Projects
None yet
Development

No branches or pull requests

8 participants