diff --git a/mypy/semanal.py b/mypy/semanal.py index 2860bd164392..a8469ae53ba3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1826,6 +1826,10 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, lval.kind = LDEF lval.fullname = lval.name self.add_local(v, lval) + if lval.name == '_': + # Special case for assignment to local named '_': always infer 'Any'. + typ = AnyType(TypeOfAny.special_form) + self.store_declared_types(lval, typ) elif not self.is_func_scope() and (self.type and lval.name not in self.type.names): # Define a new attribute within class body. diff --git a/mypy/types.py b/mypy/types.py index 96b5849ffee2..85c0e5aae906 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -281,6 +281,7 @@ class TypeOfAny(Enum): from_error = 'from_error' # Is this a type that can't be represented in mypy's type system? For instance, type of # call to NewType...). Even though these types aren't real Anys, we treat them as such. + # Also used for variables named '_'. special_form = 'special_form' # Does this Any come from interaction with another Any? from_another_any = 'from_another_any' diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index f89a0e9513a4..1971fa40bcf8 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2303,3 +2303,118 @@ class C: def f(self, x) -> None: # TODO: It would be better for the type to be Any here self.a.y # E: "None" has no attribute "y" + +-- Special case for assignment to '_' +-- ---------------------------------- + +[case testUnusedTargetLocal] +def foo() -> None: + _ = 0 + _ = '' + +[case testUnusedTargetNotGlobal] +_ = 0 +_ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testUnusedTargetNotClass] +class C: + _ = 0 + _ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testUnusedTargetTupleUnpacking] +def foo() -> None: + _, _ = (0, '') + _ = 0 + _ = '' +def bar() -> None: + t = (0, '') + _, _ = t + _ = 0 + _ = '' + +[case testUnusedTargetMultipleTargets] +def foo() -> None: + _ = x = 0 + _ = y = '' + _ = 0 + _ = '' +def bar() -> None: + x = _ = 0 + y = _ = '' + _ = 0 + _ = '' + x + 0 + y + '' + x + '' # E: Unsupported operand types for + ("int" and "str") + y + 0 # E: Unsupported operand types for + ("str" and "int") + +[case testUnusedTargetNotImport] +import d, c, b, a +[file _.py] +def f(): pass +[file m.py] +def f(): pass +_ = f +_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]") +[file a.py] +def foo() -> None: + import _ + _.f() + _ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type Module) +[file b.py] +def foo() -> None: + import m as _ + _.f() + _ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type Module) +[file c.py] +def foo() -> None: + from m import _ + _() + _ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]") +[file d.py] +def foo() -> None: + from m import f as _ + _() + _ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]") +[builtins fixtures/module.pyi] + +[case testUnusedTargetNotClass] +def foo() -> None: + class _: + pass + _().method() # E: "_" has no attribute "method" + +[case testUnusedTargetNotDef] +def foo() -> None: + def _() -> int: + pass + _() + '' # E: Unsupported operand types for + ("int" and "str") + +[case testUnusedTargetForLoop] +def f() -> None: + a = [(0, '', 0)] + for _, _, x in a: + x = 0 + x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + _ = 0 + _ = '' +[builtins fixtures/list.pyi] + +[case testUnusedTargetWithClause] +class C: + def __enter__(self) -> int: pass + def __exit__(self, *args): pass +def f() -> None: + with C() as _: pass + _ = 0 + _ = '' + +[case testUnusedTargetNotExceptClause] +# Things don't work for except clauses. +# This is due to the implementation, but it's just as well. +def f() -> None: + try: pass + except BaseException as _: + _ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "BaseException") + _ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "BaseException") +[builtins fixtures/exception.pyi]