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

Inferred type used instead of type hint with Optional for dict.get #5233

Closed
last-partizan opened this issue Jun 5, 2023 · 4 comments
Closed
Labels
as designed Not a bug, working as intended

Comments

@last-partizan
Copy link
Contributor

Describe the bug

In this example type is inferred as Any, instead of using type hint.

To Reproduce

from typing import Any, Optional, reveal_type

data: dict[str, Any] = {}
number: Optional[int] = data.get("k")
reveal_type(number)
> pyright example/example.py
...
  /home/serg/src/pyright-examples/example/example.py:7:13 - information: Type of "number" is "Any | None"
...

Expected behavior
Mypy does this correctly:

> mypy example/example.py
example/example.py:5: note: Revealed type is "Union[builtins.int, None]"

VS Code extension or command-line
pyright version: 1.1.311 and main

@erictraut
Copy link
Collaborator

erictraut commented Jun 5, 2023

Pyright is correct here. The declared type of number is int | None, but it is assigned a value of type Any | None, so its type is narrowed on assignment.

Mypy gets this wrong. Its behavior is inconsistent depending on whether the assignment takes place on the same line as a type declaration. If you move the assignment to the next line, mypy's behavior will change (and will now match pyright).

data: dict[str, Any] = {}
number: Optional[int]
number = data.get("k")
reveal_type(number) # mypy: Any | None

Refer to this documentation for more details.

There is an open issue to fix this in mypy, but it hasn't been addressed yet.

@erictraut erictraut added the as designed Not a bug, working as intended label Jun 5, 2023
@last-partizan
Copy link
Contributor Author

last-partizan commented Jun 5, 2023

"narrowing" implies going from more abstract type (Any), to less abstract. And pyright in this case does otherwise.

Consider following example:

v: Any = data.get("k")
num: int = v  # revealed type is `int`, it's narrowed, that's expected behaviour

But:

v: Optional[Any] = data.get("k")
num: Optional[int] = v  # This is unexpected, revealed type is `Any | None`

Also, old pyright 1.1.280 handles this case correctly, Type of "num" is "int | None".

@last-partizan
Copy link
Contributor Author

What i want to point out, is: assigning Any to the variable works correct, it still has correct type. But when it's optional, instead of narrowing the type is "expanded" to Any | None, and it is not useful by any means, and unexpected, considering previous example.

@erictraut
Copy link
Collaborator

Any acts as both a bottom type and a top type in the Python type system, so it is correct to refer to this as "narrowing".

There was a bug in a previous version of pyright that caused inconsistent behavior and possible loss of an Any on some assignments. This was fixed since 1.1.280. It is now consistent.

But when it's optional, instead of narrowing the type is "expanded" to Any | None

That's not an "expansion" of the type. The type Optional[int] is the same as int | None. You are then assigning a value of type Any | None, so the resulting narrowed type is Any | None.

As I said, there are plans for mypy to match this (correct) behavior. You can already observe this same behavior in mypy if you move the assignment to a second line. This isn't a bug. It's working as intended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended
Projects
None yet
Development

No branches or pull requests

2 participants