Skip to content

Commit 64c8ca0

Browse files
authored
Refactor: Move semantic analyzer pass 1 to a new module (#4101)
Just move things around, tweak inter-module references and add necessary imports. The first pass still directly accesses the second pass so there is more work to do.
1 parent 192f5df commit 64c8ca0

File tree

4 files changed

+302
-276
lines changed

4 files changed

+302
-276
lines changed

mypy/build.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
from typing import Deque
3232

3333
from mypy.nodes import (MypyFile, Node, ImportBase, Import, ImportFrom, ImportAll)
34-
from mypy.semanal import SemanticAnalyzerPass1, SemanticAnalyzerPass2
34+
from mypy.semanal_pass1 import SemanticAnalyzerPass1
35+
from mypy.semanal import SemanticAnalyzerPass2
3536
from mypy.semanal_pass3 import SemanticAnalyzerPass3
3637
from mypy.checker import TypeChecker
3738
from mypy.indirection import TypeIndirectionVisitor

mypy/semanal.py

Lines changed: 1 addition & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@
1616
Semantic analysis is the first analysis pass after parsing, and it is
1717
subdivided into three passes:
1818
19-
* SemanticAnalyzerPass1 looks up externally visible names defined in a
20-
module but ignores imports and local definitions. It helps enable
21-
(some) cyclic references between modules, such as module 'a' that
22-
imports module 'b' and used names defined in b *and* vice versa. The
23-
first pass can be performed before dependent modules have been
24-
processed.
19+
* SemanticAnalyzerPass1 is defined in mypy.semanal_pass1.
2520
2621
* SemanticAnalyzerPass2 is the second pass. It does the bulk of the work.
2722
It assumes that dependent modules have been semantically analyzed,
@@ -3800,275 +3795,6 @@ def accept(self, node: Node) -> None:
38003795
report_internal_error(err, self.errors.file, node.line, self.errors, self.options)
38013796

38023797

3803-
class SemanticAnalyzerPass1(NodeVisitor[None]):
3804-
"""First phase of semantic analysis.
3805-
3806-
See docstring of 'analyze()' below for a description of what this does.
3807-
"""
3808-
3809-
def __init__(self, sem: SemanticAnalyzerPass2) -> None:
3810-
self.sem = sem
3811-
3812-
def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None:
3813-
"""Perform the first analysis pass.
3814-
3815-
Populate module global table. Resolve the full names of
3816-
definitions not nested within functions and construct type
3817-
info structures, but do not resolve inter-definition
3818-
references such as base classes.
3819-
3820-
Also add implicit definitions such as __name__.
3821-
3822-
In this phase we don't resolve imports. For 'from ... import',
3823-
we generate dummy symbol table nodes for the imported names,
3824-
and these will get resolved in later phases of semantic
3825-
analysis.
3826-
"""
3827-
sem = self.sem
3828-
self.sem.options = options # Needed because we sometimes call into it
3829-
self.pyversion = options.python_version
3830-
self.platform = options.platform
3831-
sem.cur_mod_id = mod_id
3832-
sem.errors.set_file(fnam, mod_id)
3833-
sem.globals = SymbolTable()
3834-
sem.global_decls = [set()]
3835-
sem.nonlocal_decls = [set()]
3836-
sem.block_depth = [0]
3837-
3838-
defs = file.defs
3839-
3840-
with experiments.strict_optional_set(options.strict_optional):
3841-
# Add implicit definitions of module '__name__' etc.
3842-
for name, t in implicit_module_attrs.items():
3843-
# unicode docstrings should be accepted in Python 2
3844-
if name == '__doc__':
3845-
if self.pyversion >= (3, 0):
3846-
typ = UnboundType('__builtins__.str') # type: Type
3847-
else:
3848-
typ = UnionType([UnboundType('__builtins__.str'),
3849-
UnboundType('__builtins__.unicode')])
3850-
else:
3851-
assert t is not None, 'type should be specified for {}'.format(name)
3852-
typ = UnboundType(t)
3853-
v = Var(name, typ)
3854-
v._fullname = self.sem.qualified_name(name)
3855-
self.sem.globals[name] = SymbolTableNode(GDEF, v)
3856-
3857-
for d in defs:
3858-
d.accept(self)
3859-
3860-
# Add implicit definition of literals/keywords to builtins, as we
3861-
# cannot define a variable with them explicitly.
3862-
if mod_id == 'builtins':
3863-
literal_types = [
3864-
('None', NoneTyp()),
3865-
# reveal_type is a mypy-only function that gives an error with
3866-
# the type of its arg.
3867-
('reveal_type', AnyType(TypeOfAny.special_form)),
3868-
] # type: List[Tuple[str, Type]]
3869-
3870-
# TODO(ddfisher): This guard is only needed because mypy defines
3871-
# fake builtins for its tests which often don't define bool. If
3872-
# mypy is fast enough that we no longer need those, this
3873-
# conditional check should be removed.
3874-
if 'bool' in self.sem.globals:
3875-
bool_type = self.sem.named_type('bool')
3876-
literal_types.extend([
3877-
('True', bool_type),
3878-
('False', bool_type),
3879-
('__debug__', bool_type),
3880-
])
3881-
else:
3882-
# We are running tests without 'bool' in builtins.
3883-
# TODO: Find a permanent solution to this problem.
3884-
# Maybe add 'bool' to all fixtures?
3885-
literal_types.append(('True', AnyType(TypeOfAny.special_form)))
3886-
3887-
for name, typ in literal_types:
3888-
v = Var(name, typ)
3889-
v._fullname = self.sem.qualified_name(name)
3890-
self.sem.globals[name] = SymbolTableNode(GDEF, v)
3891-
3892-
del self.sem.options
3893-
3894-
def visit_block(self, b: Block) -> None:
3895-
if b.is_unreachable:
3896-
return
3897-
self.sem.block_depth[-1] += 1
3898-
for node in b.body:
3899-
node.accept(self)
3900-
self.sem.block_depth[-1] -= 1
3901-
3902-
def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
3903-
if self.sem.is_module_scope():
3904-
for lval in s.lvalues:
3905-
self.analyze_lvalue(lval, explicit_type=s.type is not None)
3906-
3907-
def visit_func_def(self, func: FuncDef) -> None:
3908-
sem = self.sem
3909-
func.is_conditional = sem.block_depth[-1] > 0
3910-
func._fullname = sem.qualified_name(func.name())
3911-
at_module = sem.is_module_scope()
3912-
if at_module and func.name() in sem.globals:
3913-
# Already defined in this module.
3914-
original_sym = sem.globals[func.name()]
3915-
if original_sym.kind == UNBOUND_IMPORTED:
3916-
# Ah this is an imported name. We can't resolve them now, so we'll postpone
3917-
# this until the main phase of semantic analysis.
3918-
return
3919-
if not sem.set_original_def(original_sym.node, func):
3920-
# Report error.
3921-
sem.check_no_global(func.name(), func)
3922-
else:
3923-
if at_module:
3924-
sem.globals[func.name()] = SymbolTableNode(GDEF, func)
3925-
# Also analyze the function body (in case there are conditional imports).
3926-
sem.function_stack.append(func)
3927-
sem.errors.push_function(func.name())
3928-
sem.enter()
3929-
func.body.accept(self)
3930-
sem.leave()
3931-
sem.errors.pop_function()
3932-
sem.function_stack.pop()
3933-
3934-
def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None:
3935-
kind = self.kind_by_scope()
3936-
if kind == GDEF:
3937-
self.sem.check_no_global(func.name(), func, True)
3938-
func._fullname = self.sem.qualified_name(func.name())
3939-
if kind == GDEF:
3940-
self.sem.globals[func.name()] = SymbolTableNode(kind, func)
3941-
if func.impl:
3942-
impl = func.impl
3943-
# Also analyze the function body (in case there are conditional imports).
3944-
sem = self.sem
3945-
3946-
if isinstance(impl, FuncDef):
3947-
sem.function_stack.append(impl)
3948-
sem.errors.push_function(func.name())
3949-
sem.enter()
3950-
impl.body.accept(self)
3951-
elif isinstance(impl, Decorator):
3952-
sem.function_stack.append(impl.func)
3953-
sem.errors.push_function(func.name())
3954-
sem.enter()
3955-
impl.func.body.accept(self)
3956-
else:
3957-
assert False, "Implementation of an overload needs to be FuncDef or Decorator"
3958-
sem.leave()
3959-
sem.errors.pop_function()
3960-
sem.function_stack.pop()
3961-
3962-
def visit_class_def(self, cdef: ClassDef) -> None:
3963-
kind = self.kind_by_scope()
3964-
if kind == LDEF:
3965-
return
3966-
elif kind == GDEF:
3967-
self.sem.check_no_global(cdef.name, cdef)
3968-
cdef.fullname = self.sem.qualified_name(cdef.name)
3969-
info = TypeInfo(SymbolTable(), cdef, self.sem.cur_mod_id)
3970-
info.set_line(cdef.line, cdef.column)
3971-
cdef.info = info
3972-
if kind == GDEF:
3973-
self.sem.globals[cdef.name] = SymbolTableNode(kind, info)
3974-
self.process_nested_classes(cdef)
3975-
3976-
def process_nested_classes(self, outer_def: ClassDef) -> None:
3977-
self.sem.enter_class(outer_def.info)
3978-
for node in outer_def.defs.body:
3979-
if isinstance(node, ClassDef):
3980-
node.info = TypeInfo(SymbolTable(), node, self.sem.cur_mod_id)
3981-
if outer_def.fullname:
3982-
node.info._fullname = outer_def.fullname + '.' + node.info.name()
3983-
else:
3984-
node.info._fullname = node.info.name()
3985-
node.fullname = node.info._fullname
3986-
symbol = SymbolTableNode(MDEF, node.info)
3987-
outer_def.info.names[node.name] = symbol
3988-
self.process_nested_classes(node)
3989-
elif isinstance(node, (ImportFrom, Import, ImportAll, IfStmt)):
3990-
node.accept(self)
3991-
self.sem.leave_class()
3992-
3993-
def visit_import_from(self, node: ImportFrom) -> None:
3994-
# We can't bind module names during the first pass, as the target module might be
3995-
# unprocessed. However, we add dummy unbound imported names to the symbol table so
3996-
# that we at least know that the name refers to a module.
3997-
at_module = self.sem.is_module_scope()
3998-
node.is_top_level = at_module
3999-
if not at_module:
4000-
return
4001-
for name, as_name in node.names:
4002-
imported_name = as_name or name
4003-
if imported_name not in self.sem.globals:
4004-
self.sem.add_symbol(imported_name, SymbolTableNode(UNBOUND_IMPORTED, None), node)
4005-
4006-
def visit_import(self, node: Import) -> None:
4007-
node.is_top_level = self.sem.is_module_scope()
4008-
# This is similar to visit_import_from -- see the comment there.
4009-
if not self.sem.is_module_scope():
4010-
return
4011-
for id, as_id in node.ids:
4012-
imported_id = as_id or id
4013-
if imported_id not in self.sem.globals:
4014-
self.sem.add_symbol(imported_id, SymbolTableNode(UNBOUND_IMPORTED, None), node)
4015-
else:
4016-
# If the previous symbol is a variable, this should take precedence.
4017-
self.sem.globals[imported_id] = SymbolTableNode(UNBOUND_IMPORTED, None)
4018-
4019-
def visit_import_all(self, node: ImportAll) -> None:
4020-
node.is_top_level = self.sem.is_module_scope()
4021-
4022-
def visit_while_stmt(self, s: WhileStmt) -> None:
4023-
if self.sem.is_module_scope():
4024-
s.body.accept(self)
4025-
if s.else_body:
4026-
s.else_body.accept(self)
4027-
4028-
def visit_for_stmt(self, s: ForStmt) -> None:
4029-
if self.sem.is_module_scope():
4030-
self.analyze_lvalue(s.index, explicit_type=s.index_type is not None)
4031-
s.body.accept(self)
4032-
if s.else_body:
4033-
s.else_body.accept(self)
4034-
4035-
def visit_with_stmt(self, s: WithStmt) -> None:
4036-
if self.sem.is_module_scope():
4037-
for n in s.target:
4038-
if n:
4039-
self.analyze_lvalue(n, explicit_type=s.target_type is not None)
4040-
s.body.accept(self)
4041-
4042-
def visit_decorator(self, d: Decorator) -> None:
4043-
d.var._fullname = self.sem.qualified_name(d.var.name())
4044-
self.sem.add_symbol(d.var.name(), SymbolTableNode(self.kind_by_scope(), d.var), d)
4045-
4046-
def visit_if_stmt(self, s: IfStmt) -> None:
4047-
infer_reachability_of_if_statement(s, pyversion=self.pyversion, platform=self.platform)
4048-
for node in s.body:
4049-
node.accept(self)
4050-
if s.else_body:
4051-
s.else_body.accept(self)
4052-
4053-
def visit_try_stmt(self, s: TryStmt) -> None:
4054-
if self.sem.is_module_scope():
4055-
self.sem.analyze_try_stmt(s, self, add_global=self.sem.is_module_scope())
4056-
4057-
def analyze_lvalue(self, lvalue: Lvalue, explicit_type: bool = False) -> None:
4058-
self.sem.analyze_lvalue(lvalue, add_global=self.sem.is_module_scope(),
4059-
explicit_type=explicit_type)
4060-
4061-
def kind_by_scope(self) -> int:
4062-
if self.sem.is_module_scope():
4063-
return GDEF
4064-
elif self.sem.is_class_scope():
4065-
return MDEF
4066-
elif self.sem.is_func_scope():
4067-
return LDEF
4068-
else:
4069-
assert False, "Couldn't determine scope"
4070-
4071-
40723798
def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:
40733799
if isinstance(sig, CallableType):
40743800
return sig.copy_modified(arg_types=[new] + sig.arg_types[1:])

0 commit comments

Comments
 (0)