Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions mypy/newsemanal/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This explanation is not very clear. I would rather say that we don't add function top-level functions when we just enter each of it during second phase of analysis -- processing of functions.

Also I would rather explain why this is needed (I agree that this looks reasonable).

if not defn.is_decorated and not defn.is_overload:
self.add_func_to_symbol_table(defn)

if not self.recurse_into_functions:
return
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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."""
Expand Down
6 changes: 5 additions & 1 deletion mypy/newsemanal/semanal_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
65 changes: 65 additions & 0 deletions test-data/unit/check-newsemanal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test case for when this causes a type error? (Decorated function signature differs from original one.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work correctly with the old semantic analyzer either. I'll create an issue about this.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a test that a function with same name as a class is still semantically analyzed. In general, do we have enough tests for something like:

x = 1
class C:
    pass
class C:
    x + 'no way'

etc?


[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)'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does old semantic analyzer support the opposite order? I mean:

if int():
    def f(x: str) -> None:
        ...
else:
      f: Callable[[str], None]

If yes, then the new one should probably support this too. Otherwise, this is probably not important.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generates an error with the old semantic analyzer as well.


[case testNewAnalyzerConditionallyDefineFuncOverClass]
class C:
1() # E: "int" not callable
def C() -> None: # E: Name 'C' already defined on line 1
''() # E: "str" not callable