Skip to content

Incorrect error message Dict[str, str] not allowed to Dict[Hashable, Any] #12464

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

Closed
Dr-Irv opened this issue Mar 26, 2022 · 9 comments
Closed
Labels
bug mypy got something wrong

Comments

@Dr-Irv
Copy link

Dr-Irv commented Mar 26, 2022

Bug Report

If mypy infers a variable to be Dict[str, str], then it does not allow it to be passed to a function with the type of Mapping[Hashable, Any] or Union[Mapping[Hashable, Any], Hashable]

mypy does not allow {1, 1} to be passed to an argument of type Union[Mapping[Hashable, Any], Hashable], but is OK with a type of Mapping[Hashable, Any]

To Reproduce

(Write your steps here:)

from typing import Mapping, Hashable, Any, Union, Dict


def myfun(m: Mapping[Hashable, Any]):
    pass


def myfun2(m: Union[Mapping[Hashable, Any], Hashable]):
    pass


def myfun3(m: Union[Dict[Hashable, Any], Hashable]):
    pass


def myfun4(m: Dict[Hashable, Any]):
    pass


myfun({1: 2})

myfun2({1: 2})  # incorrect error reported

myfun3({1: 2})

col_map = {"a": "b"}
myfun(col_map)  # incorrect error reported
myfun2(col_map)  # incorrect error reported
myfun3(col_map)  # incorrect error reported
myfun4(col_map)  # incorrect error reported

myfun({"a": "b"})  # no error reported
myfun2({"a": "b"})  # incorrect error reported
myfun3({"a": "b"})  # no error reported
myfun4({"a": "b"})  # no error reported

Expected Behavior
No errors reported

Actual Behavior

mapintint.py:22: error: Argument 1 to "myfun2" has incompatible type "Dict[int, int]"; expected "Union[Mapping[Hashable, Any], Hashable]"
mapintint.py:27: error: Argument 1 to "myfun" has incompatible type "Dict[str, str]"; expected "Mapping[Hashable, Any]"
mapintint.py:28: error: Argument 1 to "myfun2" has incompatible type "Dict[str, str]"; expected "Union[Mapping[Hashable, Any], Hashable]"
mapintint.py:29: error: Argument 1 to "myfun3" has incompatible type "Dict[str, str]"; expected "Union[Dict[Hashable, Any], Hashable]"
mapintint.py:30: error: Argument 1 to "myfun4" has incompatible type "Dict[str, str]"; expected "Dict[Hashable, Any]"
mapintint.py:33: error: Argument 1 to "myfun2" has incompatible type "Dict[str, str]"; expected "Union[Mapping[Hashable, Any], Hashable]"

Your Environment

  • Mypy version used: 0.942
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.9.7
  • Operating system and version: Windows 10
@Dr-Irv Dr-Irv added the bug mypy got something wrong label Mar 26, 2022
@JelleZijlstra
Copy link
Member

This is correct since Mapping is invariant in its key type. See https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance.

As a workaround, use Any as the key type.

@Dr-Irv
Copy link
Author

Dr-Irv commented Mar 26, 2022

OK, you could say that, but what about the examples at the end, where you see this:

col_map = {"a": "b"}
myfun4(col_map)  # incorrect error reported

but this reports no error:

myfun4({"a": "b"})  # no error reported

Here myfun4 has Dict[Hashable, Any] as the type. Mapping is not involved.

@JelleZijlstra
Copy link
Member

That's a frequently observed consequence of how mypy infers types. If you assign it to a variable first, it sets the variable's type to dict[str, str]. But if you put the dict directly in an argument, mypy uses type context to figure out a more precise type. An explicit annotation can help.

@Dr-Irv
Copy link
Author

Dr-Irv commented Mar 26, 2022

Well, I don't see what is more precise than dict[str, str] !!
If I add to the example:

reveal_type(col_map)
reveal_type({"a": "b"})

I get

mapintint.py:37: note: Revealed type is "builtins.dict[builtins.str*, builtins.str*]"
mapintint.py:38: note: Revealed type is "builtins.dict[builtins.str*, builtins.str*]"

In the example, the error message is:

mapintint.py:30: error: Argument 1 to "myfun4" has incompatible type "Dict[str, str]"; expected "Dict[Hashable, Any]"

Why doesn't Dict[str, str] match Dict[Hashable, Any] ?

@JelleZijlstra
Copy link
Member

Why doesn't Dict[str, str] match Dict[Hashable, Any] ?

Because Dict is invariant. I already linked to the explanation of variance in the docs.

@Dr-Irv
Copy link
Author

Dr-Irv commented Mar 26, 2022

But if you put the dict directly in an argument, mypy uses type context to figure out a more precise type. An explicit annotation can help.

So when myfun4({"a": "b"}) is called, what is the "more precise type" that is inferred that matches Dict[Hashable, Any] ?

And why does reveal_type have the same answer for the inferred type of the variable versus an inferred type of an argument give the same result?

@JelleZijlstra
Copy link
Member

So when myfun4({"a": "b"}) is called, what is the "more precise type" that is inferred that matches Dict[Hashable, Any] ?

You can check with myfun4(reveal_type({"a": "b"})). The answer is Dict[Hashable, Any].

And why does reveal_type have the same answer for the inferred type of the variable versus an inferred type of an argument give the same result?

Do you mean that reveal_type({"a": "b"}) produces Dict[str, str]? That would be because reveal_type() doesn't itself create a type context.

@Dr-Irv
Copy link
Author

Dr-Irv commented Mar 26, 2022

Oh, so you're saying that Dict[Hashable, Any] is more precise than Dict[str, str] ?? I would think it would be broader. Hashable includes str, int, float, etc. Any includes str .

Note: pyright isn't complaining about this, so that's where some of the confusion is arising.

@JelleZijlstra
Copy link
Member

"precise" was a bad choice of words on my part. It's more like the type is a better match for the expected type.

I think pyright has a bug here.

A simpler example:

from typing import Hashable


def f(x: list[Hashable]):
    x.append("x")


def g(x: list[object]):
    pass


l: list[int] = [1]
f(l)  # no pyright error
g(l)  # error (as expected)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants