3

Here's my test script:

class A:
    def __init__(self, config: dict) -> None:
        self.config = config


def test_func(a: int) -> None:
    pass


main_config = {"a": "x"}
a = A(config=main_config)

# value direct
test_func(1)  # line 14
test_func("b")  # line 15

# value with wrong type from dict
test_func(main_config["a"])  # line 18
test_func(main_config.get("a"))  # line 19

# value with wrong type from dict in class instance
test_func(a.config["a"])  # line 22
test_func(a.config.get("a"))  # line 23

If I test it with mypy (0.910) then I get the following result:

> mypy test.py                                                                                                                                                                                                                                           
test.py:15: error: Argument 1 to "test_func" has incompatible type "str"; expected "int"
tests.py:18: error: Argument 1 to "test_func" has incompatible type "str"; expected "int"
tests.py:19: error: Argument 1 to "test_func" has incompatible type "Optional[str]"; expected "int"
tests.py:23: error: Argument 1 to "test_func" has incompatible type "Optional[Any]"; expected "int"
Found 4 errors in 1 file (checked 1 source file)


Why does mypy miss/not report the call on line 22?

rmweiss
  • 716
  • 1
  • 6
  • 16

1 Answers1

1

Just an assumption:

dict.__getitem__() (a.k.a a.config[...]) does not have type annotation. While dict.get has:

def get(self, key: _KT) -> Optional[_VT_co]: ...

Simple proof of my assumption:

from typing import Optional, Any


def test_func(a: int) -> None:
    pass

def foo(a):
    if a == 1:
        return 123
    elif a == 2:
        return 'asd'
    else:
        raise ValueError


def foo_typed(a) -> Optional[Any]:
    if a == 1:
        return 123
    elif a == 2:
        return 'asd'
    else:
        return None


test_func(foo(2))
test_func(foo_typed(2)) # line 26

Produces only:

main.py:26: error: Argument 1 to "test_func" has incompatible type "Optional[Any]"; expected "int"

Even though foo(2) returns string.

Yevhen Kuzmovych
  • 10,940
  • 7
  • 28
  • 48
  • But both 18 and 19 raise errors; 22 is different only because the object being indexed is an instance attribute, rather than an object whose value is known statically. – chepner Jun 24 '21 at 12:19
  • Hmm you are right. I guess that's the limitation of mypy. It looks at `config: dict` and doesn't know the type of `config[...]` at all (as it is not defined in type anotations of `dict.__getattr__`). And it sees that `dict.get` has type annotation `-> Optional[Any]`. – Yevhen Kuzmovych Jun 24 '21 at 12:55
  • Even `mypy --strict` doesn't seem to care about the mismatch. Either it's a bug in `mypy`, or there's some subtlety to how unannotated functions are handled that I don't understand. (I was assuming that `test_func`'s need for an `int` would override the lack of a defined return value for `__getitem__`.) – chepner Jun 24 '21 at 12:58
  • The same lack of an error occurs with `test_func(foo())` where `foo` is unannotated, so I don't think my initial thought about `a.config` being an instance attribute is relevant. – chepner Jun 24 '21 at 12:59