diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index b504d96b7919..505b1092ac2e 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -491,8 +491,15 @@ def file_context(self, file_node: MypyFile, fnam: str, options: Options, del self.options def visit_func_def(self, defn: FuncDef) -> None: - if not defn.is_decorated and not defn.is_overload: - self.add_func_to_symbol_table(defn) + defn.is_conditional = self.block_depth[-1] > 0 + + # We don't add module top-level functions to symbol tables + # when we analyze their bodies in the second phase on analysis, + # since they were added in the first phase. Nested functions + # get always added, since they aren't separate targets. + if not self.recurse_into_functions or len(self.function_stack) > 0: + if not defn.is_decorated and not defn.is_overload: + self.add_func_to_symbol_table(defn) if not self.recurse_into_functions: return @@ -515,8 +522,6 @@ def _visit_func_def(self, defn: FuncDef) -> None: self.update_function_type_variables(defn.type, defn) self.function_stack.pop() - defn.is_conditional = self.block_depth[-1] > 0 - if self.is_class_scope(): # Method definition assert self.type is not None @@ -574,7 +579,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: leading_type = self.class_type(leading_type) func.type = replace_implicit_first_type(functype, leading_type) - def set_original_def(self, previous: Optional[Node], new: FuncDef) -> bool: + def set_original_def(self, previous: Optional[Node], new: Union[FuncDef, Decorator]) -> bool: """If 'new' conditionally redefine 'previous', set 'previous' as original We reject straight redefinitions of functions, as they are usually @@ -583,6 +588,8 @@ def set_original_def(self, previous: Optional[Node], new: FuncDef) -> bool: def f(): ... def f(): ... # Error: 'f' redefined """ + if isinstance(new, Decorator): + new = new.func if isinstance(previous, (FuncDef, Var, Decorator)) and new.is_conditional: new.original_def = previous return True @@ -2789,6 +2796,7 @@ def process_module_assignment(self, lvals: List[Lvalue], rval: Expression, lnode.node = rnode.node def visit_decorator(self, dec: Decorator) -> None: + dec.func.is_conditional = self.block_depth[-1] > 0 if not dec.is_overload: self.add_symbol(dec.name(), dec, dec) dec.func._fullname = self.qualified_name(dec.name()) @@ -3954,12 +3962,39 @@ def add_symbol_table_node(self, name: str, symbol: SymbolTableNode, and context is not None and not isinstance(existing.node, PlaceholderNode)): if existing.node != symbol.node: - self.name_already_defined(name, context, existing) + if isinstance(symbol.node, (FuncDef, Decorator)): + self.add_func_redefinition(names, name, symbol) + if not (isinstance(symbol.node, (FuncDef, Decorator)) + and self.set_original_def(existing.node, symbol.node)): + self.name_already_defined(name, context, existing) elif name not in self.missing_names and '*' not in self.missing_names: names[name] = symbol return True return False + def add_func_redefinition(self, names: SymbolTable, name: str, + symbol: SymbolTableNode) -> None: + """Add a symbol table node that reflects a redefinition of a function. + + Redefinitions need to be added to the symbol table so that they can be found + through AST traversal, but they have dummy names of form 'name-redefinition[N]', + where N ranges over 2, 3, ... (omitted for the first redefinition). + """ + i = 1 + while True: + if i == 1: + new_name = '{}-redefinition'.format(name) + else: + new_name = '{}-redefinition{}'.format(name, i) + existing = names.get(new_name) + if existing is None: + names[new_name] = symbol + return + elif existing.node is symbol.node: + # Already there + return + i += 1 + def add_module_symbol(self, id: str, as_id: str, module_public: bool, context: Context, module_hidden: bool = False) -> None: """Add symbol that is a reference to a module object.""" diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index ce8586651ce4..5244aca0a25f 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -160,7 +160,11 @@ def get_all_leaf_targets(symtable: SymbolTable, """Return all leaf targets in a symbol table (module-level and methods).""" result = [] # type: List[TargetInfo] for name, node in symtable.items(): - new_prefix = prefix + '.' + name + shortname = name + if '-redef' in name: + # Restore original name from mangled name of multiply defined function + shortname = name.split('-redef')[0] + new_prefix = prefix + '.' + shortname if isinstance(node.node, (FuncDef, TypeInfo, OverloadedFuncDef, Decorator)): if node.node.fullname() == new_prefix: if isinstance(node.node, TypeInfo): diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index ec108f9f0252..5368354c6647 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1417,3 +1417,68 @@ reveal_type(x) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' [file b.py] import a x = (1, 2) + +[case testNewAnalyzerConditionalFunc] +if int(): + def f(x: int) -> None: + pass + def g(x: int) -> None: + pass +elif bool(): + def f(x: int) -> None: + 1() # E: "int" not callable + def g(x: str) -> None: # E: All conditional function variants must have identical signatures + pass +else: + def f(x: int) -> None: + ''() # E: "str" not callable + +reveal_type(g) # E: Revealed type is 'def (x: builtins.int)' + +[case testNewAnalyzerConditionalFuncDefer] +if int(): + def f(x: A) -> None: + pass + def g(x: A) -> None: + pass +else: + def f(x: A) -> None: + 1() # E: "int" not callable + def g(x: str) -> None: # E: All conditional function variants must have identical signatures + pass + +reveal_type(g) # E: Revealed type is 'def (x: __main__.A)' + +class A: pass + +[case testNewAnalyzerConditionalDecoratedFunc] +from typing import Callable + +def dec(f: Callable[[int], None]) -> Callable[[str], None]: + pass + +if int(): + from m import f +else: + @dec + def f(x: int) -> None: + 1() # E: "int" not callable +reveal_type(f) # E: Revealed type is 'def (x: builtins.str)' +[file m.py] +def f(x: str) -> None: pass + +[case testNewAnalyzerConditionallyDefineFuncOverVar] +from typing import Callable + +if int(): + f: Callable[[str], None] +else: + def f(x: str) -> None: + ... +reveal_type(f) # E: Revealed type is 'def (builtins.str)' + +[case testNewAnalyzerConditionallyDefineFuncOverClass] +class C: + 1() # E: "int" not callable +def C() -> None: # E: Name 'C' already defined on line 1 + ''() # E: "str" not callable