Skip to content

Commit 40f46c0

Browse files
authored
New semantic analyzer: Support more features etc. (#6268)
This adds support for several new features: * Nested classes * Built-in type aliases such as List[T] * Overloaded functions * Decorated functions This also includes some smaller fixes related to generic classes, adds tests for other features, and makes the order of processing module top levels consistent across iterations. Previously we would process top levels in alternating orders if they are deferred (a->b, b->a). This would result in the same module (b) processed twice in a row, which may not be efficient if the deferral is due to another module in the cycle, as the second step may then make no progress. Also remove fake definitions of these aliases from stubs, as they are not necessary.
1 parent 0744075 commit 40f46c0

File tree

6 files changed

+273
-39
lines changed

6 files changed

+273
-39
lines changed

mypy/newsemanal/semanal.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152

153153
# Special cased built-in classes that are needed for basic functionality and need to be
154154
# available very early on.
155-
CORE_BUILTIN_CLASSES = ['object', 'bool'] # type: Final
155+
CORE_BUILTIN_CLASSES = ['object', 'bool', 'tuple'] # type: Final
156156

157157

158158
# Used for tracking incomplete references
@@ -390,6 +390,8 @@ def refresh_top_level(self, file_node: MypyFile) -> None:
390390
self.recurse_into_functions = False
391391
for d in file_node.defs:
392392
self.accept(d)
393+
if file_node.fullname() == 'typing':
394+
self.add_builtin_aliases(file_node)
393395

394396
@contextmanager
395397
def file_context(self, file_node: MypyFile, fnam: str, options: Options,
@@ -432,15 +434,19 @@ def file_context(self, file_node: MypyFile, fnam: str, options: Options,
432434
del self.options
433435

434436
def visit_func_def(self, defn: FuncDef) -> None:
435-
self.add_func_to_symbol_table(defn)
437+
if not defn.is_decorated and not defn.is_overload:
438+
self.add_func_to_symbol_table(defn)
436439

437440
if not self.recurse_into_functions:
438441
return
439442

440443
with self.scope.function_scope(defn):
441444
self._visit_func_def(defn)
442445

443-
def add_func_to_symbol_table(self, func: FuncDef) -> None:
446+
def add_func_to_symbol_table(self, func: Union[FuncDef, OverloadedFuncDef]) -> None:
447+
if self.is_class_scope():
448+
assert self.type is not None
449+
func.info = self.type
444450
func._fullname = self.qualified_name(func.name())
445451
self.add_symbol(func.name(), func, func)
446452

@@ -548,8 +554,11 @@ def update_function_type_variables(self, fun_type: CallableType, defn: FuncItem)
548554
fun_type.variables = a.bind_function_type_variables(fun_type, defn)
549555

550556
def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
557+
self.add_func_to_symbol_table(defn)
558+
551559
if not self.recurse_into_functions:
552560
return
561+
553562
# NB: Since _visit_overloaded_func_def will call accept on the
554563
# underlying FuncDefs, the function might get entered twice.
555564
# This is fine, though, because only the outermost function is
@@ -580,7 +589,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
580589
# This is an a normal overload. Find the item signatures, the
581590
# implementation (if outside a stub), and any missing @overload
582591
# decorators.
583-
types, impl, non_overload_indexes = self.find_overload_sigs_and_impl(defn)
592+
types, impl, non_overload_indexes = self.analyze_overload_sigs_and_impl(defn)
584593
defn.impl = impl
585594
if non_overload_indexes:
586595
self.handle_missing_overload_decorators(defn, non_overload_indexes,
@@ -606,15 +615,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
606615
self.process_final_in_overload(defn)
607616
self.process_static_or_class_method_in_overload(defn)
608617

609-
# TODO: Use add_symbol or similar below (this is inconsistent)
610-
if self.is_class_scope():
611-
assert self.type is not None
612-
self.type.names[defn.name()] = SymbolTableNode(MDEF, defn)
613-
defn.info = self.type
614-
elif self.is_func_scope():
615-
self.add_local(defn, defn)
618+
self.add_symbol(defn.name(), defn, defn)
616619

617-
def find_overload_sigs_and_impl(
620+
def analyze_overload_sigs_and_impl(
618621
self,
619622
defn: OverloadedFuncDef) -> Tuple[List[CallableType],
620623
Optional[OverloadPart],
@@ -1165,10 +1168,11 @@ def analyze_class_typevar_declaration(self, base: Type) -> Optional[TypeVarList]
11651168
sym.node.fullname() == 'typing_extensions.Protocol' and base.args):
11661169
tvars = [] # type: TypeVarList
11671170
for arg in unbound.args:
1171+
tag = self.track_incomplete_refs()
11681172
tvar = self.analyze_unbound_tvar(arg)
11691173
if tvar:
11701174
tvars.append(tvar)
1171-
else:
1175+
elif not self.found_incomplete_ref(tag):
11721176
self.fail('Free type variable expected in %s[...]' %
11731177
sym.node.name(), base)
11741178
return tvars
@@ -1216,7 +1220,7 @@ def prepare_class_def(self, defn: ClassDef, info: Optional[TypeInfo] = None) ->
12161220
info = info or self.make_empty_type_info(defn)
12171221
defn.info = info
12181222
info.defn = defn
1219-
if self.is_module_scope():
1223+
if not self.is_func_scope():
12201224
info._fullname = self.qualified_name(defn.name)
12211225
else:
12221226
info._fullname = info.name()
@@ -2757,6 +2761,9 @@ def process_module_assignment(self, lvals: List[Lvalue], rval: Expression,
27572761
lnode.node = rnode.node
27582762

27592763
def visit_decorator(self, dec: Decorator) -> None:
2764+
if not dec.is_overload:
2765+
self.add_symbol(dec.name(), dec, dec)
2766+
dec.func._fullname = self.qualified_name(dec.name())
27602767
for d in dec.decorators:
27612768
d.accept(self)
27622769
removed = [] # type: List[int]
@@ -3362,7 +3369,9 @@ def visit_index_expr(self, expr: IndexExpr) -> None:
33623369
# may be analysing a type alias definition rvalue. The error will be
33633370
# reported elsewhere if it is not the case.
33643371
typearg2 = self.anal_type(typearg, allow_unbound_tvars=True)
3365-
assert typearg2 is not None # TODO: Deal with None return values
3372+
if typearg2 is None:
3373+
self.defer()
3374+
return
33663375
types.append(typearg2)
33673376
expr.analyzed = TypeApplication(expr.base, types)
33683377
expr.analyzed.line = expr.line
@@ -3726,17 +3735,23 @@ def add_builtin_aliases(self, tree: MypyFile) -> None:
37263735
assert tree.fullname() == 'typing'
37273736
for alias, target_name in type_aliases.items():
37283737
name = alias.split('.')[-1]
3738+
if name in tree.names:
3739+
continue
3740+
tag = self.track_incomplete_refs()
37293741
n = self.lookup_fully_qualified_or_none(target_name)
37303742
if n:
3743+
# Found built-in class target. Create alias.
37313744
target = self.named_type_or_none(target_name, [])
37323745
assert target is not None
37333746
alias_node = TypeAlias(target, alias, line=-1, column=-1, # there is no context
37343747
no_args=True, normalized=True)
3735-
tree.names[name] = SymbolTableNode(GDEF, alias_node)
3748+
self.add_symbol(name, alias_node, tree)
3749+
elif self.found_incomplete_ref(tag):
3750+
# Built-in class target may not ready yet -- defer.
3751+
self.mark_incomplete(name)
37363752
else:
3737-
# Built-in target not defined, remove the original fake
3738-
# definition to trigger a better error message.
3739-
tree.names.pop(name, None)
3753+
# Test fixtures may be missing some builtin classes, which is okay.
3754+
pass
37403755

37413756
def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
37423757
"""Lookup a fully qualified name.
@@ -3769,7 +3784,11 @@ def lookup_fully_qualified_or_none(self, fullname: str) -> Optional[SymbolTableN
37693784
if module not in self.modules:
37703785
return None
37713786
filenode = self.modules[module]
3772-
return filenode.names.get(name)
3787+
result = filenode.names.get(name)
3788+
if result is None and self.is_incomplete_namespace(module):
3789+
# TODO: More explicit handling of incomplete refs?
3790+
self.record_incomplete_ref()
3791+
return result
37733792

37743793
def qualified_name(self, n: str) -> str:
37753794
if self.type is not None:

mypy/newsemanal/semanal_main.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None:
8080
all_deferred += deferred
8181
if not incomplete:
8282
state.manager.incomplete_namespaces.discard(next_id)
83-
worklist = all_deferred
83+
# Reverse to process the targets in the same order on every iteration. This avoids
84+
# processing the same target twice in a row, which is inefficient.
85+
worklist = list(reversed(all_deferred))
8486

8587

8688
def process_functions(graph: 'Graph', scc: List[str]) -> None:
@@ -99,7 +101,7 @@ def process_functions(graph: 'Graph', scc: List[str]) -> None:
99101
assert not incomplete # Ditto
100102

101103

102-
TargetInfo = Tuple[str, Union[MypyFile, FuncDef], Optional[TypeInfo]]
104+
TargetInfo = Tuple[str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]]
103105

104106

105107
def get_all_leaf_targets(symtable: SymbolTable,
@@ -109,9 +111,7 @@ def get_all_leaf_targets(symtable: SymbolTable,
109111
result = [] # type: List[TargetInfo]
110112
for name, node in symtable.items():
111113
new_prefix = prefix + '.' + name
112-
# TODO: Decorated function
113-
# TODO: Overloaded function
114-
if isinstance(node.node, (FuncDef, TypeInfo)):
114+
if isinstance(node.node, (FuncDef, TypeInfo, OverloadedFuncDef, Decorator)):
115115
if node.node.fullname() == new_prefix:
116116
if isinstance(node.node, TypeInfo):
117117
result += get_all_leaf_targets(node.node.names, new_prefix, node.node)
@@ -122,7 +122,7 @@ def get_all_leaf_targets(symtable: SymbolTable,
122122

123123
def semantic_analyze_target(target: str,
124124
state: 'State',
125-
node: Union[MypyFile, FuncDef],
125+
node: Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator],
126126
active_type: Optional[TypeInfo]) -> Tuple[List[str], bool]:
127127
# TODO: Support refreshing function targets (currently only works for module top levels)
128128
tree = state.tree
@@ -137,6 +137,9 @@ def semantic_analyze_target(target: str,
137137
fnam=tree.path,
138138
options=state.options,
139139
active_type=active_type):
140+
if isinstance(node, Decorator):
141+
# Decorator expressions will be processed as part of the module top level.
142+
node = node.func
140143
analyzer.refresh_partial(node, [])
141144
if analyzer.deferred:
142145
return [target], analyzer.incomplete

mypy/newsemanal/typeanal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ def analyze_unbound_type_with_type_info(self, t: UnboundType, info: TypeInfo) ->
334334
# valid count at this point. Thus we may construct an
335335
# Instance with an invalid number of type arguments.
336336
instance = Instance(info, self.anal_array(t.args), t.line, t.column)
337+
# Check type argument count.
338+
if len(instance.args) != len(info.type_vars):
339+
fix_instance(instance, self.fail)
337340
if not t.args and self.options.disallow_any_generics and not self.defining_alias:
338341
# We report/patch invalid built-in instances already during second pass.
339342
# This is done to avoid storing additional state on instances.

0 commit comments

Comments
 (0)