Skip to content

Commit 4f284a3

Browse files
ilevkivskyiIvan Levkivskyipre-commit-ci[bot]
authored
Add shared checker interface to break import cycle (#18878)
The import cycle is not a real one as `if TYPE_CHECKING: ...` is used, but it would become much bigger if I start using `checkmember` in `subtypes`, essentially it would be one huge import cycle during self-checking. So I decided to do something similar we did for semantic analyzer. --------- Co-authored-by: Ivan Levkivskyi <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 772187f commit 4f284a3

File tree

6 files changed

+384
-122
lines changed

6 files changed

+384
-122
lines changed

mypy/checker.py

Lines changed: 10 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import mypy.checkexpr
1313
from mypy import errorcodes as codes, join, message_registry, nodes, operators
1414
from mypy.binder import ConditionalTypeBinder, Frame, get_declaration
15+
from mypy.checker_shared import CheckerScope, TypeCheckerSharedApi, TypeRange
1516
from mypy.checkmember import (
1617
MemberContext,
1718
analyze_class_attribute_access,
@@ -126,7 +127,7 @@
126127
from mypy.operators import flip_ops, int_op_to_method, neg_ops
127128
from mypy.options import PRECISE_TUPLE_TYPES, Options
128129
from mypy.patterns import AsPattern, StarredPattern
129-
from mypy.plugin import CheckerPluginInterface, Plugin
130+
from mypy.plugin import Plugin
130131
from mypy.plugins import dataclasses as dataclasses_plugin
131132
from mypy.scope import Scope
132133
from mypy.semanal import is_trivial_body, refers_to_fullname, set_callable_name
@@ -258,13 +259,6 @@ class FineGrainedDeferredNode(NamedTuple):
258259
TypeMap: _TypeAlias = Optional[dict[Expression, Type]]
259260

260261

261-
# An object that represents either a precise type or a type with an upper bound;
262-
# it is important for correct type inference with isinstance.
263-
class TypeRange(NamedTuple):
264-
item: Type
265-
is_upper_bound: bool # False => precise type
266-
267-
268262
# Keeps track of partial types in a single scope. In fine-grained incremental
269263
# mode partial types initially defined at the top level cannot be completed in
270264
# a function, and we use the 'is_function' attribute to enforce this.
@@ -274,7 +268,7 @@ class PartialTypeScope(NamedTuple):
274268
is_local: bool
275269

276270

277-
class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
271+
class TypeChecker(NodeVisitor[None], TypeCheckerSharedApi):
278272
"""Mypy type checker.
279273
280274
Type check mypy source files that have been semantically analyzed.
@@ -301,7 +295,7 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
301295
# Helper for managing conditional types
302296
binder: ConditionalTypeBinder
303297
# Helper for type checking expressions
304-
expr_checker: mypy.checkexpr.ExpressionChecker
298+
_expr_checker: mypy.checkexpr.ExpressionChecker
305299

306300
pattern_checker: PatternChecker
307301

@@ -416,14 +410,18 @@ def __init__(
416410
self.allow_abstract_call = False
417411

418412
# Child checker objects for specific AST node types
419-
self.expr_checker = mypy.checkexpr.ExpressionChecker(
413+
self._expr_checker = mypy.checkexpr.ExpressionChecker(
420414
self, self.msg, self.plugin, per_line_checking_time_ns
421415
)
422416
self.pattern_checker = PatternChecker(self, self.msg, self.plugin, options)
423417

418+
@property
419+
def expr_checker(self) -> mypy.checkexpr.ExpressionChecker:
420+
return self._expr_checker
421+
424422
@property
425423
def type_context(self) -> list[Type | None]:
426-
return self.expr_checker.type_context
424+
return self._expr_checker.type_context
427425

428426
def reset(self) -> None:
429427
"""Cleanup stale state that might be left over from a typechecking run.
@@ -8527,75 +8525,6 @@ def is_node_static(node: Node | None) -> bool | None:
85278525
return None
85288526

85298527

8530-
class CheckerScope:
8531-
# We keep two stacks combined, to maintain the relative order
8532-
stack: list[TypeInfo | FuncItem | MypyFile]
8533-
8534-
def __init__(self, module: MypyFile) -> None:
8535-
self.stack = [module]
8536-
8537-
def current_function(self) -> FuncItem | None:
8538-
for e in reversed(self.stack):
8539-
if isinstance(e, FuncItem):
8540-
return e
8541-
return None
8542-
8543-
def top_level_function(self) -> FuncItem | None:
8544-
"""Return top-level non-lambda function."""
8545-
for e in self.stack:
8546-
if isinstance(e, FuncItem) and not isinstance(e, LambdaExpr):
8547-
return e
8548-
return None
8549-
8550-
def active_class(self) -> TypeInfo | None:
8551-
if isinstance(self.stack[-1], TypeInfo):
8552-
return self.stack[-1]
8553-
return None
8554-
8555-
def enclosing_class(self, func: FuncItem | None = None) -> TypeInfo | None:
8556-
"""Is there a class *directly* enclosing this function?"""
8557-
func = func or self.current_function()
8558-
assert func, "This method must be called from inside a function"
8559-
index = self.stack.index(func)
8560-
assert index, "CheckerScope stack must always start with a module"
8561-
enclosing = self.stack[index - 1]
8562-
if isinstance(enclosing, TypeInfo):
8563-
return enclosing
8564-
return None
8565-
8566-
def active_self_type(self) -> Instance | TupleType | None:
8567-
"""An instance or tuple type representing the current class.
8568-
8569-
This returns None unless we are in class body or in a method.
8570-
In particular, inside a function nested in method this returns None.
8571-
"""
8572-
info = self.active_class()
8573-
if not info and self.current_function():
8574-
info = self.enclosing_class()
8575-
if info:
8576-
return fill_typevars(info)
8577-
return None
8578-
8579-
def current_self_type(self) -> Instance | TupleType | None:
8580-
"""Same as active_self_type() but handle functions nested in methods."""
8581-
for item in reversed(self.stack):
8582-
if isinstance(item, TypeInfo):
8583-
return fill_typevars(item)
8584-
return None
8585-
8586-
@contextmanager
8587-
def push_function(self, item: FuncItem) -> Iterator[None]:
8588-
self.stack.append(item)
8589-
yield
8590-
self.stack.pop()
8591-
8592-
@contextmanager
8593-
def push_class(self, info: TypeInfo) -> Iterator[None]:
8594-
self.stack.append(info)
8595-
yield
8596-
self.stack.pop()
8597-
8598-
85998528
TKey = TypeVar("TKey")
86008529
TValue = TypeVar("TValue")
86018530

0 commit comments

Comments
 (0)