Skip to content

Commit 264dca7

Browse files
authored
Special case assignment to local variable '_': always infer 'Any' (#4833)
Fixes #465
1 parent 6a8460f commit 264dca7

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

mypy/semanal.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,10 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
18261826
lval.kind = LDEF
18271827
lval.fullname = lval.name
18281828
self.add_local(v, lval)
1829+
if lval.name == '_':
1830+
# Special case for assignment to local named '_': always infer 'Any'.
1831+
typ = AnyType(TypeOfAny.special_form)
1832+
self.store_declared_types(lval, typ)
18291833
elif not self.is_func_scope() and (self.type and
18301834
lval.name not in self.type.names):
18311835
# Define a new attribute within class body.

mypy/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ class TypeOfAny(Enum):
281281
from_error = 'from_error'
282282
# Is this a type that can't be represented in mypy's type system? For instance, type of
283283
# call to NewType...). Even though these types aren't real Anys, we treat them as such.
284+
# Also used for variables named '_'.
284285
special_form = 'special_form'
285286
# Does this Any come from interaction with another Any?
286287
from_another_any = 'from_another_any'

test-data/unit/check-inference.test

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,3 +2303,118 @@ class C:
23032303
def f(self, x) -> None:
23042304
# TODO: It would be better for the type to be Any here
23052305
self.a.y # E: "None" has no attribute "y"
2306+
2307+
-- Special case for assignment to '_'
2308+
-- ----------------------------------
2309+
2310+
[case testUnusedTargetLocal]
2311+
def foo() -> None:
2312+
_ = 0
2313+
_ = ''
2314+
2315+
[case testUnusedTargetNotGlobal]
2316+
_ = 0
2317+
_ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
2318+
2319+
[case testUnusedTargetNotClass]
2320+
class C:
2321+
_ = 0
2322+
_ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
2323+
2324+
[case testUnusedTargetTupleUnpacking]
2325+
def foo() -> None:
2326+
_, _ = (0, '')
2327+
_ = 0
2328+
_ = ''
2329+
def bar() -> None:
2330+
t = (0, '')
2331+
_, _ = t
2332+
_ = 0
2333+
_ = ''
2334+
2335+
[case testUnusedTargetMultipleTargets]
2336+
def foo() -> None:
2337+
_ = x = 0
2338+
_ = y = ''
2339+
_ = 0
2340+
_ = ''
2341+
def bar() -> None:
2342+
x = _ = 0
2343+
y = _ = ''
2344+
_ = 0
2345+
_ = ''
2346+
x + 0
2347+
y + ''
2348+
x + '' # E: Unsupported operand types for + ("int" and "str")
2349+
y + 0 # E: Unsupported operand types for + ("str" and "int")
2350+
2351+
[case testUnusedTargetNotImport]
2352+
import d, c, b, a
2353+
[file _.py]
2354+
def f(): pass
2355+
[file m.py]
2356+
def f(): pass
2357+
_ = f
2358+
_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]")
2359+
[file a.py]
2360+
def foo() -> None:
2361+
import _
2362+
_.f()
2363+
_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type Module)
2364+
[file b.py]
2365+
def foo() -> None:
2366+
import m as _
2367+
_.f()
2368+
_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type Module)
2369+
[file c.py]
2370+
def foo() -> None:
2371+
from m import _
2372+
_()
2373+
_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]")
2374+
[file d.py]
2375+
def foo() -> None:
2376+
from m import f as _
2377+
_()
2378+
_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]")
2379+
[builtins fixtures/module.pyi]
2380+
2381+
[case testUnusedTargetNotClass]
2382+
def foo() -> None:
2383+
class _:
2384+
pass
2385+
_().method() # E: "_" has no attribute "method"
2386+
2387+
[case testUnusedTargetNotDef]
2388+
def foo() -> None:
2389+
def _() -> int:
2390+
pass
2391+
_() + '' # E: Unsupported operand types for + ("int" and "str")
2392+
2393+
[case testUnusedTargetForLoop]
2394+
def f() -> None:
2395+
a = [(0, '', 0)]
2396+
for _, _, x in a:
2397+
x = 0
2398+
x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
2399+
_ = 0
2400+
_ = ''
2401+
[builtins fixtures/list.pyi]
2402+
2403+
[case testUnusedTargetWithClause]
2404+
class C:
2405+
def __enter__(self) -> int: pass
2406+
def __exit__(self, *args): pass
2407+
def f() -> None:
2408+
with C() as _: pass
2409+
_ = 0
2410+
_ = ''
2411+
2412+
[case testUnusedTargetNotExceptClause]
2413+
# Things don't work for except clauses.
2414+
# This is due to the implementation, but it's just as well.
2415+
def f() -> None:
2416+
try: pass
2417+
except BaseException as _:
2418+
_ = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "BaseException")
2419+
_ = '' # E: Incompatible types in assignment (expression has type "str", variable has type "BaseException")
2420+
[builtins fixtures/exception.pyi]

0 commit comments

Comments
 (0)