Skip to content

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

Open
@brettcs

Description

@brettcs

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.

Activity

JelleZijlstra

JelleZijlstra commented on Mar 30, 2020

@JelleZijlstra
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

msullivan commented on Apr 3, 2020

@msullivan
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

brettcs commented on Apr 8, 2020

@brettcs
Author

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

jvdwetering commented on Apr 21, 2020

@jvdwetering

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

zormit commented on May 3, 2021

@zormit

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

hauntsaninja commented on May 3, 2021

@hauntsaninja
Collaborator

#5392 and #9264 are related, I think

slafs

slafs commented on Jun 28, 2023

@slafs

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @slafs@msullivan@JelleZijlstra@zormit@brettcs

        Issue actions

          abs(Union[int, Decimal]) is inferred as object · Issue #8601 · python/mypy