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

v1.1.329 incorrectly assuming type of a division is an int #6032

Closed
btalb opened this issue Sep 28, 2023 · 5 comments
Closed

v1.1.329 incorrectly assuming type of a division is an int #6032

btalb opened this issue Sep 28, 2023 · 5 comments
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working

Comments

@btalb
Copy link

btalb commented Sep 28, 2023

In v1.1.328 this would pass linting:

(11 / 5).is_integer()

In v1.1.329 it fails with this error:

  debug.py:1:9 - error: Cannot access member "is_integer" for type "int"
    Member "is_integer" is unknown (reportGeneralTypeIssues)

The release notes suggest some changes here, but just want to confirm this isn't a bug. I can make it go away with something like:

float(11 / 5).is_integer()

But this seems like changing code for the sake of the linter given I can't see any case where the type will actually be an int? e.g.:

In [11]: type(2/1)                                                  
Out[11]: float
@btalb btalb added the bug Something isn't working label Sep 28, 2023
@erictraut
Copy link
Collaborator

One minor point of semantics... Pyright isn't a linter. It's a type checker. Linters (like pylint and ruff) enforce coding conventions and style. Type checkers (like pyright and mypy) enforce a type consistency rules.

Now, to the issue at hand... This is intended behavior, not a bug. PEP 484 indicates that the type annotation float implies both float and int (effectively, float | int) even though int is not an actual subclass of float. This means if you have a value whose type is annotated as float, you cannot safely call a method that is defined for the float class if it is not also defined for the int class. This doesn't come up often because most methods that are available on float are also available on int, but there are a few exceptions. The method is_integer is one such exception — at least prior to Python 3.12, which adds this method to int.

Versions of pyright prior to 1.1.329 did not catch this class of errors. Version 1.1.329 now enforces this type rule, catching a class of potential bugs that were previously undetected.

The __truediv__ method in the class int is defined as follows:

class int:
    ...
    def __truediv__(self, __value: int) -> float: ...

That means the expression 11 / 5 evaluates to float, which type checkers must assume can be either a float or an int even if the runtime limits the answer to only float. Unfortunately, there's currently no way in the Python static type system to specify in a type annotation that a value is a float but not an int, so there's no way to properly annotate the return type of int.__truediv__.

You can guarantee to a type checker that you're talking about a float and not an int by directly calling the float constructor, as you've discovered.

So, your options to work around this issue:

  1. Upgrade to Python 3.12, which provides an is_instance method for the int class.
  2. Call the float constructor, as you've done in your code sample above.
  3. Use a # type: ignore or # pyright: ignore comment at the end of the line to suppress the type error.

@erictraut erictraut added the as designed Not a bug, working as intended label Sep 28, 2023
@erictraut erictraut closed this as not planned Won't fix, can't repro, duplicate, stale Sep 28, 2023
@btalb
Copy link
Author

btalb commented Sep 28, 2023

Nice. That makes perfect sense. Thanks for the great answer @erictraut !

@benjamind2330
Copy link

I am also experiencing this issue. However, i have made a script:

debug.py

float(11 / 5).is_integer()

(1.2).is_integer()

The result from pyright is:

pyright debug.py 
debug.py
  debug.py:1:15 - error: Cannot access member "is_integer" for type "int"
    Member "is_integer" is unknown (reportGeneralTypeIssues)
  debug.py:3:7 - error: Cannot access member "is_integer" for type "int"
    Member "is_integer" is unknown (reportGeneralTypeIssues)
2 errors, 0 warnings, 0 informations 

This is with:

pyright --version
pyright 1.1.329

I think this must be a bug right?

@erictraut erictraut reopened this Sep 29, 2023
@erictraut erictraut removed the as designed Not a bug, working as intended label Sep 29, 2023
@erictraut
Copy link
Collaborator

erictraut commented Sep 29, 2023

@benjamind2330, thanks for your post. Yes, pyright should be able to tell in those two cases that the resulting object is definitively an instance of float rather than some value that is in the set of types described by float | int. This is somewhat different than the original bug report, but it's related. I've reopened the issue and will address this in the next release.

erictraut pushed a commit that referenced this issue Sep 29, 2023
… `float` or `complex` literals or constructor calls and then accessing a member of the resulting object that is valid on that class but not on `int`. This addresses #6032.
erictraut added a commit that referenced this issue Sep 29, 2023
… `float` or `complex` literals or constructor calls and then accessing a member of the resulting object that is valid on that class but not on `int`. This addresses #6032. (#6040)

Co-authored-by: Eric Traut <[email protected]>
@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Sep 29, 2023
erictraut pushed a commit that referenced this issue Sep 30, 2023
…tween a `float` that is really a `float` or a `float` that can also be an `int`. This resulted in false positives when inferring types using float literals. This addresses #6032.
erictraut added a commit that referenced this issue Sep 30, 2023
* Fixed a bug that resulted in a false positive in certain circumstances where a lambda included simple math operations with integer literals. The fix requires disabling literal math within lambdas, since they are often used as callbacks that are called repeatedly in a loop. This addresses #6031.

* Reverted a portion of the recent changes designed to differentiate between a `float` that is really a `float` or a `float` that can also be an `int`. This resulted in false positives when inferring types using float literals. This addresses #6032.

---------

Co-authored-by: Eric Traut <[email protected]>
@erictraut
Copy link
Collaborator

This is addressed in pyright 1.1.330, which I just published. It will also be included in an upcoming release of pylance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants