Skip to content

Commit 725a24a

Browse files
Use context manager for Scope (#11053)
Related issue: #1184 Follows up to #10569, #10685 This PR: * Refactors `Scope` - Replaces `Scope.enter_file` with `Scope.module_scope` - Replaces `Scope.enter_function` with `Scope.function_scope` - Splits `Scope.leave()` into corresponding context manager * Deletes unused files
1 parent b17ae30 commit 725a24a

File tree

7 files changed

+157
-185
lines changed

7 files changed

+157
-185
lines changed

mypy/checker.py

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -297,29 +297,26 @@ def check_first_pass(self) -> None:
297297
self.recurse_into_functions = True
298298
with state.strict_optional_set(self.options.strict_optional):
299299
self.errors.set_file(self.path, self.tree.fullname, scope=self.tscope)
300-
self.tscope.enter_file(self.tree.fullname)
301-
with self.enter_partial_types():
302-
with self.binder.top_frame_context():
300+
with self.tscope.module_scope(self.tree.fullname):
301+
with self.enter_partial_types(), self.binder.top_frame_context():
303302
for d in self.tree.defs:
304303
self.accept(d)
305304

306-
assert not self.current_node_deferred
305+
assert not self.current_node_deferred
307306

308-
all_ = self.globals.get('__all__')
309-
if all_ is not None and all_.type is not None:
310-
all_node = all_.node
311-
assert all_node is not None
312-
seq_str = self.named_generic_type('typing.Sequence',
313-
[self.named_type('builtins.str')])
314-
if self.options.python_version[0] < 3:
307+
all_ = self.globals.get('__all__')
308+
if all_ is not None and all_.type is not None:
309+
all_node = all_.node
310+
assert all_node is not None
315311
seq_str = self.named_generic_type('typing.Sequence',
316-
[self.named_type('builtins.unicode')])
317-
if not is_subtype(all_.type, seq_str):
318-
str_seq_s, all_s = format_type_distinctly(seq_str, all_.type)
319-
self.fail(message_registry.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s),
320-
all_node)
321-
322-
self.tscope.leave()
312+
[self.named_type('builtins.str')])
313+
if self.options.python_version[0] < 3:
314+
seq_str = self.named_generic_type('typing.Sequence',
315+
[self.named_type('builtins.unicode')])
316+
if not is_subtype(all_.type, seq_str):
317+
str_seq_s, all_s = format_type_distinctly(seq_str, all_.type)
318+
self.fail(message_registry.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s),
319+
all_node)
323320

324321
def check_second_pass(self,
325322
todo: Optional[Sequence[Union[DeferredNode,
@@ -334,25 +331,24 @@ def check_second_pass(self,
334331
if not todo and not self.deferred_nodes:
335332
return False
336333
self.errors.set_file(self.path, self.tree.fullname, scope=self.tscope)
337-
self.tscope.enter_file(self.tree.fullname)
338-
self.pass_num += 1
339-
if not todo:
340-
todo = self.deferred_nodes
341-
else:
342-
assert not self.deferred_nodes
343-
self.deferred_nodes = []
344-
done: Set[Union[DeferredNodeType, FineGrainedDeferredNodeType]] = set()
345-
for node, active_typeinfo in todo:
346-
if node in done:
347-
continue
348-
# This is useful for debugging:
349-
# print("XXX in pass %d, class %s, function %s" %
350-
# (self.pass_num, type_name, node.fullname or node.name))
351-
done.add(node)
352-
with self.tscope.class_scope(active_typeinfo) if active_typeinfo else nothing():
353-
with self.scope.push_class(active_typeinfo) if active_typeinfo else nothing():
354-
self.check_partial(node)
355-
self.tscope.leave()
334+
with self.tscope.module_scope(self.tree.fullname):
335+
self.pass_num += 1
336+
if not todo:
337+
todo = self.deferred_nodes
338+
else:
339+
assert not self.deferred_nodes
340+
self.deferred_nodes = []
341+
done: Set[Union[DeferredNodeType, FineGrainedDeferredNodeType]] = set()
342+
for node, active_typeinfo in todo:
343+
if node in done:
344+
continue
345+
# This is useful for debugging:
346+
# print("XXX in pass %d, class %s, function %s" %
347+
# (self.pass_num, type_name, node.fullname or node.name))
348+
done.add(node)
349+
with self.tscope.class_scope(active_typeinfo) if active_typeinfo else nothing():
350+
with self.scope.push_class(active_typeinfo) if active_typeinfo else nothing():
351+
self.check_partial(node)
356352
return True
357353

358354
def check_partial(self, node: Union[DeferredNodeType, FineGrainedDeferredNodeType]) -> None:
@@ -874,7 +870,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
874870
if isinstance(typ.ret_type, TypeVarType):
875871
if typ.ret_type.variance == CONTRAVARIANT:
876872
self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT,
877-
typ.ret_type)
873+
typ.ret_type)
878874

879875
# Check that Generator functions have the appropriate return type.
880876
if defn.is_generator:
@@ -992,7 +988,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
992988
self.accept(item.body)
993989
unreachable = self.binder.is_unreachable()
994990

995-
if (self.options.warn_no_return and not unreachable):
991+
if self.options.warn_no_return and not unreachable:
996992
if (defn.is_generator or
997993
is_named_instance(self.return_types[-1], 'typing.AwaitableGenerator')):
998994
return_type = self.get_generator_return_type(self.return_types[-1],
@@ -1083,7 +1079,7 @@ def is_unannotated_any(t: Type) -> bool:
10831079
code=codes.NO_UNTYPED_DEF)
10841080
elif fdef.is_generator:
10851081
if is_unannotated_any(self.get_generator_return_type(ret_type,
1086-
fdef.is_coroutine)):
1082+
fdef.is_coroutine)):
10871083
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
10881084
code=codes.NO_UNTYPED_DEF)
10891085
elif fdef.is_coroutine and isinstance(ret_type, Instance):
@@ -2641,8 +2637,7 @@ def check_rvalue_count_in_assignment(self, lvalues: List[Lvalue], rvalue_count:
26412637
len(lvalues) - 1, context)
26422638
return False
26432639
elif rvalue_count != len(lvalues):
2644-
self.msg.wrong_number_values_to_unpack(rvalue_count,
2645-
len(lvalues), context)
2640+
self.msg.wrong_number_values_to_unpack(rvalue_count, len(lvalues), context)
26462641
return False
26472642
return True
26482643

@@ -2896,8 +2891,7 @@ def check_lvalue(self, lvalue: Lvalue) -> Tuple[Optional[Type],
28962891
elif isinstance(lvalue, IndexExpr):
28972892
index_lvalue = lvalue
28982893
elif isinstance(lvalue, MemberExpr):
2899-
lvalue_type = self.expr_checker.analyze_ordinary_member_access(lvalue,
2900-
True)
2894+
lvalue_type = self.expr_checker.analyze_ordinary_member_access(lvalue, True)
29012895
self.store_type(lvalue, lvalue_type)
29022896
elif isinstance(lvalue, NameExpr):
29032897
lvalue_type = self.expr_checker.analyze_ref_expr(lvalue, lvalue=True)
@@ -4144,6 +4138,7 @@ def is_type_call(expr: CallExpr) -> bool:
41444138
"""Is expr a call to type with one argument?"""
41454139
return (refers_to_fullname(expr.callee, 'builtins.type')
41464140
and len(expr.args) == 1)
4141+
41474142
# exprs that are being passed into type
41484143
exprs_in_type_calls: List[Expression] = []
41494144
# type that is being compared to type(expr)
@@ -4194,6 +4189,7 @@ def combine_maps(list_maps: List[TypeMap]) -> TypeMap:
41944189
if d is not None:
41954190
result_map.update(d)
41964191
return result_map
4192+
41974193
if_map = combine_maps(if_maps)
41984194
# type(x) == T is only true when x has the same type as T, meaning
41994195
# that it can be false if x is an instance of a subclass of T. That means

mypy/nullcontext.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

mypy/ordered_dict.py

Whitespace-only changes.

mypy/scope.py

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from contextlib import contextmanager
77
from typing import List, Optional, Iterator, Tuple
88

9+
from mypy.backports import nullcontext
910
from mypy.nodes import TypeInfo, FuncBase
1011

1112

@@ -51,18 +52,30 @@ def current_function_name(self) -> Optional[str]:
5152
"""Return the current function's short name if it exists"""
5253
return self.function.name if self.function else None
5354

54-
def enter_file(self, prefix: str) -> None:
55+
@contextmanager
56+
def module_scope(self, prefix: str) -> Iterator[None]:
5557
self.module = prefix
5658
self.classes = []
5759
self.function = None
5860
self.ignored = 0
61+
yield
62+
assert self.module
63+
self.module = None
5964

60-
def enter_function(self, fdef: FuncBase) -> None:
65+
@contextmanager
66+
def function_scope(self, fdef: FuncBase) -> Iterator[None]:
6167
if not self.function:
6268
self.function = fdef
6369
else:
6470
# Nested functions are part of the topmost function target.
6571
self.ignored += 1
72+
yield
73+
if self.ignored:
74+
# Leave a scope that's included in the enclosing target.
75+
self.ignored -= 1
76+
else:
77+
assert self.function
78+
self.function = None
6679

6780
def enter_class(self, info: TypeInfo) -> None:
6881
"""Enter a class target scope."""
@@ -72,53 +85,34 @@ def enter_class(self, info: TypeInfo) -> None:
7285
# Classes within functions are part of the enclosing function target.
7386
self.ignored += 1
7487

75-
def leave(self) -> None:
76-
"""Leave the innermost scope (can be any kind of scope)."""
88+
def leave_class(self) -> None:
89+
"""Leave a class target scope."""
7790
if self.ignored:
7891
# Leave a scope that's included in the enclosing target.
7992
self.ignored -= 1
80-
elif self.function:
81-
# Function is always the innermost target.
82-
self.function = None
83-
elif self.classes:
93+
else:
94+
assert self.classes
8495
# Leave the innermost class.
8596
self.classes.pop()
86-
else:
87-
# Leave module.
88-
assert self.module
89-
self.module = None
97+
98+
@contextmanager
99+
def class_scope(self, info: TypeInfo) -> Iterator[None]:
100+
self.enter_class(info)
101+
yield
102+
self.leave_class()
90103

91104
def save(self) -> SavedScope:
92105
"""Produce a saved scope that can be entered with saved_scope()"""
93106
assert self.module
94107
# We only save the innermost class, which is sufficient since
95108
# the rest are only needed for when classes are left.
96109
cls = self.classes[-1] if self.classes else None
97-
return (self.module, cls, self.function)
98-
99-
@contextmanager
100-
def function_scope(self, fdef: FuncBase) -> Iterator[None]:
101-
self.enter_function(fdef)
102-
yield
103-
self.leave()
104-
105-
@contextmanager
106-
def class_scope(self, info: TypeInfo) -> Iterator[None]:
107-
self.enter_class(info)
108-
yield
109-
self.leave()
110+
return self.module, cls, self.function
110111

111112
@contextmanager
112113
def saved_scope(self, saved: SavedScope) -> Iterator[None]:
113114
module, info, function = saved
114-
self.enter_file(module)
115-
if info:
116-
self.enter_class(info)
117-
if function:
118-
self.enter_function(function)
119-
yield
120-
if function:
121-
self.leave()
122-
if info:
123-
self.leave()
124-
self.leave()
115+
with self.module_scope(module):
116+
with self.class_scope(info) if info else nullcontext():
117+
with self.function_scope(function) if function else nullcontext():
118+
yield

mypy/semanal.py

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -529,36 +529,35 @@ def file_context(self,
529529
self.errors.set_file(file_node.path, file_node.fullname, scope=scope)
530530
self.cur_mod_node = file_node
531531
self.cur_mod_id = file_node.fullname
532-
scope.enter_file(self.cur_mod_id)
533-
self._is_stub_file = file_node.path.lower().endswith('.pyi')
534-
self._is_typeshed_stub_file = is_typeshed_file(file_node.path)
535-
self.globals = file_node.names
536-
self.tvar_scope = TypeVarLikeScope()
537-
538-
self.named_tuple_analyzer = NamedTupleAnalyzer(options, self)
539-
self.typed_dict_analyzer = TypedDictAnalyzer(options, self, self.msg)
540-
self.enum_call_analyzer = EnumCallAnalyzer(options, self)
541-
self.newtype_analyzer = NewTypeAnalyzer(options, self, self.msg)
542-
543-
# Counter that keeps track of references to undefined things potentially caused by
544-
# incomplete namespaces.
545-
self.num_incomplete_refs = 0
546-
547-
if active_type:
548-
self.incomplete_type_stack.append(False)
549-
scope.enter_class(active_type)
550-
self.enter_class(active_type.defn.info)
551-
for tvar in active_type.defn.type_vars:
552-
self.tvar_scope.bind_existing(tvar)
553-
554-
yield
555-
556-
if active_type:
557-
scope.leave()
558-
self.leave_class()
559-
self.type = None
560-
self.incomplete_type_stack.pop()
561-
scope.leave()
532+
with scope.module_scope(self.cur_mod_id):
533+
self._is_stub_file = file_node.path.lower().endswith('.pyi')
534+
self._is_typeshed_stub_file = is_typeshed_file(file_node.path)
535+
self.globals = file_node.names
536+
self.tvar_scope = TypeVarLikeScope()
537+
538+
self.named_tuple_analyzer = NamedTupleAnalyzer(options, self)
539+
self.typed_dict_analyzer = TypedDictAnalyzer(options, self, self.msg)
540+
self.enum_call_analyzer = EnumCallAnalyzer(options, self)
541+
self.newtype_analyzer = NewTypeAnalyzer(options, self, self.msg)
542+
543+
# Counter that keeps track of references to undefined things potentially caused by
544+
# incomplete namespaces.
545+
self.num_incomplete_refs = 0
546+
547+
if active_type:
548+
self.incomplete_type_stack.append(False)
549+
scope.enter_class(active_type)
550+
self.enter_class(active_type.defn.info)
551+
for tvar in active_type.defn.type_vars:
552+
self.tvar_scope.bind_existing(tvar)
553+
554+
yield
555+
556+
if active_type:
557+
scope.leave_class()
558+
self.leave_class()
559+
self.type = None
560+
self.incomplete_type_stack.pop()
562561
del self.options
563562

564563
#

mypy/semanal_typeargs.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ def __init__(self, errors: Errors, options: Options, is_typeshed_file: bool) ->
3535

3636
def visit_mypy_file(self, o: MypyFile) -> None:
3737
self.errors.set_file(o.path, o.fullname, scope=self.scope)
38-
self.scope.enter_file(o.fullname)
39-
super().visit_mypy_file(o)
40-
self.scope.leave()
38+
with self.scope.module_scope(o.fullname):
39+
super().visit_mypy_file(o)
4140

4241
def visit_func(self, defn: FuncItem) -> None:
4342
if not self.recurse_into_functions:

0 commit comments

Comments
 (0)