Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Prev Previous commit
Next Next commit
Put type checker global state in separate module
  • Loading branch information
ilevkivskyi committed May 10, 2025
commit 5b3190a1670ee5ddcc0e9f0d4d92c251d3243a63
5 changes: 3 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from mypy import errorcodes as codes, join, message_registry, nodes, operators
from mypy.binder import ConditionalTypeBinder, Frame, get_declaration
from mypy.checker_shared import CheckerScope, TypeCheckerSharedApi, TypeRange
from mypy.checker_state import checker_state
from mypy.checkmember import (
MemberContext,
analyze_class_attribute_access,
Expand Down Expand Up @@ -455,7 +456,7 @@ def check_first_pass(self) -> None:
Deferred functions will be processed by check_second_pass().
"""
self.recurse_into_functions = True
with state.strict_optional_set(self.options.strict_optional), state.type_checker_set(self):
with state.strict_optional_set(self.options.strict_optional), checker_state.set(self):
self.errors.set_file(
self.path, self.tree.fullname, scope=self.tscope, options=self.options
)
Expand Down Expand Up @@ -496,7 +497,7 @@ def check_second_pass(
This goes through deferred nodes, returning True if there were any.
"""
self.recurse_into_functions = True
with state.strict_optional_set(self.options.strict_optional), state.type_checker_set(self):
with state.strict_optional_set(self.options.strict_optional), checker_state.set(self):
if not todo and not self.deferred_nodes:
return False
self.errors.set_file(
Expand Down
30 changes: 30 additions & 0 deletions mypy/checker_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

from collections.abc import Iterator
from contextlib import contextmanager
from typing import Final

from mypy.checker_shared import TypeCheckerSharedApi

# This is global mutable state. Don't add anything here unless there's a very
# good reason.


class TypeCheckerState:
# Wrap this in a class since it's faster that using a module-level attribute.

def __init__(self, type_checker: TypeCheckerSharedApi | None) -> None:
# Value varies by file being processed
self.type_checker = type_checker

@contextmanager
def set(self, value: TypeCheckerSharedApi) -> Iterator[None]:
saved = self.type_checker
self.type_checker = value
try:
yield
finally:
self.type_checker = saved


checker_state: Final = TypeCheckerState(type_checker=None)
3 changes: 1 addition & 2 deletions mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Final, TypeVar, cast, overload

from mypy.nodes import ARG_STAR, FakeInfo, Var
from mypy.state import state
from mypy.types import (
ANY_STRATEGY,
AnyType,
Expand Down Expand Up @@ -543,8 +544,6 @@ def remove_trivial(types: Iterable[Type]) -> list[Type]:
* Remove everything else if there is an `object`
* Remove strict duplicate types
"""
from mypy.state import state

removed_none = False
new_types = []
all_types = set()
Expand Down
20 changes: 4 additions & 16 deletions mypy/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@
from contextlib import contextmanager
from typing import Final

from mypy.checker_shared import TypeCheckerSharedApi

# These are global mutable state. Don't add anything here unless there's a very
# good reason.


class SubtypeState:
class StrictOptionalState:
# Wrap this in a class since it's faster that using a module-level attribute.

def __init__(self, strict_optional: bool, type_checker: TypeCheckerSharedApi | None) -> None:
# Values vary by file being processed
def __init__(self, strict_optional: bool) -> None:
# Value varies by file being processed
self.strict_optional = strict_optional
self.type_checker = type_checker

@contextmanager
def strict_optional_set(self, value: bool) -> Iterator[None]:
Expand All @@ -27,15 +24,6 @@ def strict_optional_set(self, value: bool) -> Iterator[None]:
finally:
self.strict_optional = saved

@contextmanager
def type_checker_set(self, value: TypeCheckerSharedApi) -> Iterator[None]:
saved = self.type_checker
self.type_checker = value
try:
yield
finally:
self.type_checker = saved


state: Final = SubtypeState(strict_optional=True, type_checker=None)
state: Final = StrictOptionalState(strict_optional=True)
find_occurrences: tuple[str, str] | None = None
3 changes: 2 additions & 1 deletion mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import mypy.applytype
import mypy.constraints
import mypy.typeops
from mypy.checker_state import checker_state
from mypy.erasetype import erase_type
from mypy.expandtype import (
expand_self_type,
Expand Down Expand Up @@ -1267,7 +1268,7 @@ def find_member(
class_obj: bool = False,
is_lvalue: bool = False,
) -> Type | None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Most of the remaining performance regression comes from find_member. Would it be feasible to add a fast path that could be used in the majority of (simple) cases? This only makes sense if the semantics would remain identical. We'd first try the fast path, and if it can't be used (not a simple case), we'd fall back to the general implementation that is added here (after from mypy.checkmember import ...). The fast path might look a bit like find_member_simple.

That fast path might cover access to normal attribute/method via instance when there are no self types or properties, for example. Maybe we can avoid creating MemberContext and using filter_errors.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This may be harder to tune, so although I agree we should do this, i would leave this optimization for later.

type_checker = state.type_checker
type_checker = checker_state.type_checker
if type_checker is None:
# Unfortunately, there are many scenarios where someone calls is_subtype() before
# type checking phase. In this case we fallback to old (incomplete) logic.
Expand Down
3 changes: 1 addition & 2 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
SymbolNode,
)
from mypy.options import Options
from mypy.state import state
from mypy.util import IdMapper

T = TypeVar("T")
Expand Down Expand Up @@ -2978,8 +2979,6 @@ def accept(self, visitor: TypeVisitor[T]) -> T:

def relevant_items(self) -> list[Type]:
"""Removes NoneTypes from Unions when strict Optional checking is off."""
from mypy.state import state

if state.strict_optional:
return self.items
else:
Expand Down