Skip to content

Commit 1bc1047

Browse files
authored
Do pass 1 semanatic analysis on the FuncDef of decorated functions (#5654)
This populates fullname properly, fixing a crash where a decorated function is used as a type annotation in an import cycle, as well as fixing an obscure bug in sys.platform checks inside decorated functions. Fixes #5652.
1 parent 0866d9f commit 1bc1047

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

mypy/semanal_pass1.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,20 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
147147
for lval in s.lvalues:
148148
self.analyze_lvalue(lval, explicit_type=s.type is not None)
149149

150-
def visit_func_def(self, func: FuncDef) -> None:
150+
def visit_func_def(self, func: FuncDef, decorated: bool = False) -> None:
151+
"""Process a func def.
152+
153+
decorated is true if we are processing a func def in a
154+
Decorator that needs a _fullname and to have its body analyzed but
155+
does not need to be added to the symbol table.
156+
"""
151157
sem = self.sem
152158
if sem.type is not None:
153159
# Don't process methods during pass 1.
154160
return
155161
func.is_conditional = sem.block_depth[-1] > 0
156162
func._fullname = sem.qualified_name(func.name())
157-
at_module = sem.is_module_scope()
163+
at_module = sem.is_module_scope() and not decorated
158164
if (at_module and func.name() == '__getattr__' and
159165
self.sem.cur_mod_node.is_package_init_file() and self.sem.cur_mod_node.is_stub):
160166
if isinstance(func.type, CallableType):
@@ -311,6 +317,7 @@ def visit_decorator(self, d: Decorator) -> None:
311317
return
312318
d.var._fullname = self.sem.qualified_name(d.var.name())
313319
self.add_symbol(d.var.name(), SymbolTableNode(self.kind_by_scope(), d), d)
320+
self.visit_func_def(d.func, decorated=True)
314321

315322
def visit_if_stmt(self, s: IfStmt) -> None:
316323
infer_reachability_of_if_statement(s, self.sem.options)

test-data/unit/check-modules.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,27 @@ import m.a
733733
[file m/a.py]
734734
[out]
735735

736+
[case testCheckDecoratedFuncAsAnnotWithImportCycle]
737+
import a
738+
[file a.py]
739+
from typing import TypeVar
740+
import b
741+
742+
T = TypeVar('T')
743+
def idf(x: T) -> T: return x
744+
745+
@idf
746+
def Session() -> None: pass
747+
748+
[file b.py]
749+
MYPY = False
750+
if MYPY:
751+
from a import Session
752+
753+
def f(self, session: Session) -> None: # E: Invalid type "a.Session"
754+
pass
755+
[builtins fixtures/bool.pyi]
756+
736757

737758
-- Checks dealing with submodules and different kinds of imports
738759
-- -------------------------------------------------------------

test-data/unit/check-unreachable-code.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,25 @@ x = 1
416416
[builtins fixtures/ops.pyi]
417417
[out]
418418

419+
[case testSysPlatformInFunctionImport3]
420+
from typing import Callable
421+
import sys
422+
423+
def idf(x: Callable[[], None]) -> Callable[[], None]: return x
424+
425+
@idf
426+
def foo() -> None:
427+
if sys.platform == 'fictional':
428+
import b as a
429+
else:
430+
import a
431+
a.x
432+
[file a.py]
433+
x = 1
434+
[builtins fixtures/ops.pyi]
435+
[out]
436+
437+
419438
[case testSysPlatformInMethodImport2]
420439
import sys
421440
class A:

0 commit comments

Comments
 (0)