Skip to content

Commit

Permalink
Do pass 1 semanatic analysis on the FuncDef of decorated functions (p…
Browse files Browse the repository at this point in the history
…ython#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 python#5652.
  • Loading branch information
msullivan authored Sep 21, 2018
1 parent 0866d9f commit 1bc1047
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 2 deletions.
11 changes: 9 additions & 2 deletions mypy/semanal_pass1.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,20 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
for lval in s.lvalues:
self.analyze_lvalue(lval, explicit_type=s.type is not None)

def visit_func_def(self, func: FuncDef) -> None:
def visit_func_def(self, func: FuncDef, decorated: bool = False) -> None:
"""Process a func def.
decorated is true if we are processing a func def in a
Decorator that needs a _fullname and to have its body analyzed but
does not need to be added to the symbol table.
"""
sem = self.sem
if sem.type is not None:
# Don't process methods during pass 1.
return
func.is_conditional = sem.block_depth[-1] > 0
func._fullname = sem.qualified_name(func.name())
at_module = sem.is_module_scope()
at_module = sem.is_module_scope() and not decorated
if (at_module and func.name() == '__getattr__' and
self.sem.cur_mod_node.is_package_init_file() and self.sem.cur_mod_node.is_stub):
if isinstance(func.type, CallableType):
Expand Down Expand Up @@ -311,6 +317,7 @@ def visit_decorator(self, d: Decorator) -> None:
return
d.var._fullname = self.sem.qualified_name(d.var.name())
self.add_symbol(d.var.name(), SymbolTableNode(self.kind_by_scope(), d), d)
self.visit_func_def(d.func, decorated=True)

def visit_if_stmt(self, s: IfStmt) -> None:
infer_reachability_of_if_statement(s, self.sem.options)
Expand Down
21 changes: 21 additions & 0 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,27 @@ import m.a
[file m/a.py]
[out]

[case testCheckDecoratedFuncAsAnnotWithImportCycle]
import a
[file a.py]
from typing import TypeVar
import b

T = TypeVar('T')
def idf(x: T) -> T: return x

@idf
def Session() -> None: pass

[file b.py]
MYPY = False
if MYPY:
from a import Session

def f(self, session: Session) -> None: # E: Invalid type "a.Session"
pass
[builtins fixtures/bool.pyi]


-- Checks dealing with submodules and different kinds of imports
-- -------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,25 @@ x = 1
[builtins fixtures/ops.pyi]
[out]

[case testSysPlatformInFunctionImport3]
from typing import Callable
import sys

def idf(x: Callable[[], None]) -> Callable[[], None]: return x

@idf
def foo() -> None:
if sys.platform == 'fictional':
import b as a
else:
import a
a.x
[file a.py]
x = 1
[builtins fixtures/ops.pyi]
[out]


[case testSysPlatformInMethodImport2]
import sys
class A:
Expand Down

0 comments on commit 1bc1047

Please sign in to comment.