"""Information about Python operators"""

from __future__ import annotations

from typing import Final

# Map from binary operator id to related method name (in Python 3).
op_methods: Final = {
    "+": "__add__",
    "-": "__sub__",
    "*": "__mul__",
    "/": "__truediv__",
    "%": "__mod__",
    "divmod": "__divmod__",
    "//": "__floordiv__",
    "**": "__pow__",
    "@": "__matmul__",
    "&": "__and__",
    "|": "__or__",
    "^": "__xor__",
    "<<": "__lshift__",
    ">>": "__rshift__",
    "==": "__eq__",
    "!=": "__ne__",
    "<": "__lt__",
    ">=": "__ge__",
    ">": "__gt__",
    "<=": "__le__",
    "in": "__contains__",
}

op_methods_to_symbols: Final = {v: k for (k, v) in op_methods.items()}

ops_falling_back_to_cmp: Final = {"__ne__", "__eq__", "__lt__", "__le__", "__gt__", "__ge__"}

ops_with_inplace_method: Final = {
    "+",
    "-",
    "*",
    "/",
    "%",
    "//",
    "**",
    "@",
    "&",
    "|",
    "^",
    "<<",
    ">>",
}

inplace_operator_methods: Final = {"__i" + op_methods[op][2:] for op in ops_with_inplace_method}

reverse_op_methods: Final = {
    "__add__": "__radd__",
    "__sub__": "__rsub__",
    "__mul__": "__rmul__",
    "__truediv__": "__rtruediv__",
    "__mod__": "__rmod__",
    "__divmod__": "__rdivmod__",
    "__floordiv__": "__rfloordiv__",
    "__pow__": "__rpow__",
    "__matmul__": "__rmatmul__",
    "__and__": "__rand__",
    "__or__": "__ror__",
    "__xor__": "__rxor__",
    "__lshift__": "__rlshift__",
    "__rshift__": "__rrshift__",
    "__eq__": "__eq__",
    "__ne__": "__ne__",
    "__lt__": "__gt__",
    "__ge__": "__le__",
    "__gt__": "__lt__",
    "__le__": "__ge__",
}

reverse_op_method_names: Final = set(reverse_op_methods.values())

# Suppose we have some class A. When we do A() + A(), Python will only check
# the output of A().__add__(A()) and skip calling the __radd__ method entirely.
# This shortcut is used only for the following methods:
op_methods_that_shortcut: Final = {
    "__add__",
    "__sub__",
    "__mul__",
    "__truediv__",
    "__mod__",
    "__divmod__",
    "__floordiv__",
    "__pow__",
    "__matmul__",
    "__and__",
    "__or__",
    "__xor__",
    "__lshift__",
    "__rshift__",
}

normal_from_reverse_op: Final = {m: n for n, m in reverse_op_methods.items()}
reverse_op_method_set: Final = set(reverse_op_methods.values())

unary_op_methods: Final = {"-": "__neg__", "+": "__pos__", "~": "__invert__"}

int_op_to_method: Final = {
    "==": int.__eq__,
    "is": int.__eq__,
    "<": int.__lt__,
    "<=": int.__le__,
    "!=": int.__ne__,
    "is not": int.__ne__,
    ">": int.__gt__,
    ">=": int.__ge__,
}

flip_ops: Final = {"<": ">", "<=": ">=", ">": "<", ">=": "<="}
neg_ops: Final = {
    "==": "!=",
    "!=": "==",
    "is": "is not",
    "is not": "is",
    "<": ">=",
    "<=": ">",
    ">": "<=",
    ">=": "<",
}


from __future__ import annotations

import pprint
import re
import sys
import sysconfig
from typing import Any, Callable, Final, Mapping, Pattern

from mypy import defaults
from mypy.errorcodes import ErrorCode, error_codes
from mypy.util import get_class_descriptors, replace_object_state

class BuildType:
    STANDARD: Final = 0
    MODULE: Final = 1
    PROGRAM_TEXT: Final = 2

PER_MODULE_OPTIONS: Final = {
    # Please keep this list sorted
    "allow_redefinition",
    "allow_untyped_globals",
    "always_false",
    "always_true",
    "check_untyped_defs",
    "debug_cache",
    "disable_error_code",
    "disabled_error_codes",
    "disallow_any_decorated",
    "disallow_any_explicit",
    "disallow_any_expr",
    "disallow_any_generics",
    "disallow_any_unimported",
    "disallow_incomplete_defs",
    "disallow_subclassing_any",
    "disallow_untyped_calls",
    "disallow_untyped_decorators",
    "disallow_untyped_defs",
    "enable_error_code",
    "enabled_error_codes",
    "extra_checks",
    "follow_imports_for_stubs",
    "follow_imports",
    "ignore_errors",
    "ignore_missing_imports",
    "implicit_optional",
    "implicit_reexport",
    "local_partial_types",
    "mypyc",
    "strict_concatenate",
    "strict_equality",
    "strict_optional",
    "warn_no_return",
    "warn_return_any",
    "warn_unreachable",
    "warn_unused_ignores",
}

OPTIONS_AFFECTING_CACHE: Final = (
    PER_MODULE_OPTIONS
    | {
        "platform",
        "bazel",
        "old_type_inference",
        "plugins",
        "disable_bytearray_promotion",
        "disable_memoryview_promotion",
    }
) - {"debug_cache"}

# Features that are currently (or were recently) incomplete/experimental
TYPE_VAR_TUPLE: Final = "TypeVarTuple"
UNPACK: Final = "Unpack"
PRECISE_TUPLE_TYPES: Final = "PreciseTupleTypes"
NEW_GENERIC_SYNTAX: Final = "NewGenericSyntax"
INLINE_TYPEDDICT: Final = "InlineTypedDict"
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, INLINE_TYPEDDICT))
COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK, NEW_GENERIC_SYNTAX))

class Options:
    """Options collected from flags."""

    def __init__(self) -> None:
        # Cache for clone_for_module()
        self._per_module_cache: dict[str, Options] | None = None

        # -- build options --
        self.build_type = BuildType.STANDARD
        self.python_version: tuple[int, int] = sys.version_info[:2]
        # The executable used to search for PEP 561 packages. If this is None,
        # then mypy does not search for PEP 561 packages.
        self.python_executable: str | None = sys.executable

        # When cross compiling to emscripten, we need to rely on MACHDEP because
        # sys.platform is the host build platform, not emscripten.
        MACHDEP = sysconfig.get_config_var("MACHDEP")
        if MACHDEP == "emscripten":
            self.platform = MACHDEP
        else:
            self.platform = sys.platform

        self.custom_typing_module: str | None = None
        self.custom_typeshed_dir: str | None = None
        # The abspath() version of the above, we compute it once as an optimization.
        self.abs_custom_typeshed_dir: str | None = None
        self.mypy_path: list[str] = []
        self.report_dirs: dict[str, str] = {}
        # Show errors in PEP 561 packages/site-packages modules
        self.no_silence_site_packages = False
        self.no_site_packages = False
        self.ignore_missing_imports = False
        # Is ignore_missing_imports set in a per-module section
        self.ignore_missing_imports_per_module = False
        self.follow_imports = "normal"  # normal|silent|skip|error
        # Whether to respect the follow_imports setting even for stub files.
        # Intended to be used for disabling specific stubs.
        self.follow_imports_for_stubs = False
        # PEP 420 namespace packages
        # This allows definitions of packages without __init__.py and allows packages to span
        # multiple directories. This flag affects both import discovery and the association of
        # input files/modules/packages to the relevant file and fully qualified module name.
        self.namespace_packages = True
        # Use current directory and MYPYPATH to determine fully qualified module names of files
        # passed by automatically considering their subdirectories as packages. This is only
        # relevant if namespace packages are enabled, since otherwise examining __init__.py's is
        # sufficient to determine module names for files. As a possible alternative, add a single
        # top-level __init__.py to your packages.
        self.explicit_package_bases = False
        # File names, directory names or subpaths to avoid checking
        self.exclude: list[str] = []

        # disallow_any options
        self.disallow_any_generics = False
        self.disallow_any_unimported = False
        self.disallow_any_expr = False
        self.disallow_any_decorated = False
        self.disallow_any_explicit = False

        # Disallow calling untyped functions from typed ones
        self.disallow_untyped_calls = False

        # Always allow untyped calls for function coming from modules/packages
        # in this list (each item effectively acts as a prefix match)
        self.untyped_calls_exclude: list[str] = []

        # Disallow defining untyped (or incompletely typed) functions
        self.disallow_untyped_defs = False

        # Disallow defining incompletely typed functions
        self.disallow_incomplete_defs = False

        # Type check unannotated functions
        self.check_untyped_defs = False

        # Disallow decorating typed functions with untyped decorators
        self.disallow_untyped_decorators = False

        # Disallow subclassing values of type 'Any'
        self.disallow_subclassing_any = False

        # Also check typeshed for missing annotations
        self.warn_incomplete_stub = False

        # Warn about casting an expression to its inferred type
        self.warn_redundant_casts = False

        # Warn about falling off the end of a function returning non-None
        self.warn_no_return = True

        # Warn about returning objects of type Any when the function is
        # declared with a precise type
        self.warn_return_any = False

        # Warn about unused '# type: ignore' comments
        self.warn_unused_ignores = False

        # Warn about unused '[mypy-<pattern>]'  or '[[tool.mypy.overrides]]' config sections
        self.warn_unused_configs = False

        # Files in which to ignore all non-fatal errors
        self.ignore_errors = False

        # Apply strict None checking
        self.strict_optional = True

        # Show "note: In function "foo":" messages.
        self.show_error_context = False

        # Use nicer output (when possible).
        self.color_output = True
        self.error_summary = True

        # Assume arguments with default values of None are Optional
        self.implicit_optional = False

        # Don't re-export names unless they are imported with `from ... as ...`
        self.implicit_reexport = True

        # Suppress toplevel errors caused by missing annotations
        self.allow_untyped_globals = False

        # Allow variable to be redefined with an arbitrary type in the same block
        # and the same nesting level as the initialization
        self.allow_redefinition = False

        # Prohibit equality, identity, and container checks for non-overlapping types.
        # This makes 1 == '1', 1 in ['1'], and 1 is '1' errors.
        self.strict_equality = False

        # Deprecated, use extra_checks instead.
        self.strict_concatenate = False

        # Enable additional checks that are technically correct but impractical.
        self.extra_checks = False

        # Report an error for any branches inferred to be unreachable as a result of
        # type analysis.
        self.warn_unreachable = False

        # Variable names considered True
        self.always_true: list[str] = []

        # Variable names considered False
        self.always_false: list[str] = []

        # Error codes to disable
        self.disable_error_code: list[str] = []
        self.disabled_error_codes: set[ErrorCode] = set()

        # Error codes to enable
        self.enable_error_code: list[str] = []
        self.enabled_error_codes: set[ErrorCode] = set()

        # Use script name instead of __main__
        self.scripts_are_modules = False

        # Config file name
        self.config_file: str | None = None

        # A filename containing a JSON mapping from filenames to
        # mtime/size/hash arrays, used to avoid having to recalculate
        # source hashes as often.
        self.quickstart_file: str | None = None

        # A comma-separated list of files/directories for mypy to type check;
        # supports globbing
        self.files: list[str] | None = None

        # A list of packages for mypy to type check
        self.packages: list[str] | None = None

        # A list of modules for mypy to type check
        self.modules: list[str] | None = None

        # Write junit.xml to given file
        self.junit_xml: str | None = None

        self.junit_format: str = "global"  # global|per_file

        # Caching and incremental checking options
        self.incremental = True
        self.cache_dir = defaults.CACHE_DIR
        self.sqlite_cache = False
        self.debug_cache = False
        self.skip_version_check = False
        self.skip_cache_mtime_checks = False
        self.fine_grained_incremental = False
        # Include fine-grained dependencies in written cache files
        self.cache_fine_grained = False
        # Read cache files in fine-grained incremental mode (cache must include dependencies)
        self.use_fine_grained_cache = False

        # Run tree.serialize() even if cache generation is disabled
        self.debug_serialize = False

        # Tune certain behaviors when being used as a front-end to mypyc. Set per-module
        # in modules being compiled. Not in the config file or command line.
        self.mypyc = False

        # An internal flag to modify some type-checking logic while
        # running inspections (e.g. don't expand function definitions).
        # Not in the config file or command line.
        self.inspections = False

        # Disable the memory optimization of freeing ASTs when
        # possible. This isn't exposed as a command line option
        # because it is intended for software integrating with
        # mypy. (Like mypyc.)
        self.preserve_asts = False

        # If True, function and class docstrings will be extracted and retained.
        # This isn't exposed as a command line option
        # because it is intended for software integrating with
        # mypy. (Like stubgen.)
        self.include_docstrings = False

        # Paths of user plugins
        self.plugins: list[str] = []

        # Per-module options (raw)
        self.per_module_options: dict[str, dict[str, object]] = {}
        self._glob_options: list[tuple[str, Pattern[str]]] = []
        self.unused_configs: set[str] = set()

        # -- development options --
        self.verbosity = 0  # More verbose messages (for troubleshooting)
        self.pdb = False
        self.show_traceback = False
        self.raise_exceptions = False
        self.dump_type_stats = False
        self.dump_inference_stats = False
        self.dump_build_stats = False
        self.enable_incomplete_feature: list[str] = []
        self.timing_stats: str | None = None
        self.line_checking_stats: str | None = None

        # -- test options --
        # Stop after the semantic analysis phase
        self.semantic_analysis_only = False

        # Use stub builtins fixtures to speed up tests
        self.use_builtins_fixtures = False

        # This should only be set when running certain mypy tests.
        # Use this sparingly to avoid tests diverging from non-test behavior.
        self.test_env = False

        # -- experimental options --
        self.shadow_file: list[list[str]] | None = None
        self.show_column_numbers: bool = False
        self.show_error_end: bool = False
        self.hide_error_codes = False
        self.show_error_code_links = False
        # Use soft word wrap and show trimmed source snippets with error location markers.
        self.pretty = False
        self.dump_graph = False
        self.dump_deps = False
        self.logical_deps = False
        # If True, partial types can't span a module top level and a function
        self.local_partial_types = False
        # Some behaviors are changed when using Bazel (https://bazel.build).
        self.bazel = False
        # If True, export inferred types for all expressions as BuildResult.types
        self.export_types = False
        # List of package roots -- directories under these are packages even
        # if they don't have __init__.py.
        self.package_root: list[str] = []
        self.cache_map: dict[str, tuple[str, str]] = {}
        # Don't properly free objects on exit, just kill the current process.
        self.fast_exit = True
        # fast path for finding modules from source set
        self.fast_module_lookup = False
        # Allow empty function bodies even if it is not safe, used for testing only.
        self.allow_empty_bodies = False
        # Used to transform source code before parsing if not None
        # TODO: Make the type precise (AnyStr -> AnyStr)
        self.transform_source: Callable[[Any], Any] | None = None
        # Print full path to each file in the report.
        self.show_absolute_path: bool = False
        # Install missing stub packages if True
        self.install_types = False
        # Install missing stub packages in non-interactive mode (don't prompt for
        # confirmation, and don't show any errors)
        self.non_interactive = False
        # When we encounter errors that may cause many additional errors,
        # skip most errors after this many messages have been reported.
        # -1 means unlimited.
        self.many_errors_threshold = defaults.MANY_ERRORS_THRESHOLD
        # Disable new experimental type inference algorithm.
        self.old_type_inference = False
        # Deprecated reverse version of the above, do not use.
        self.new_type_inference = False
        # Export line-level, limited, fine-grained dependency information in cache data
        # (undocumented feature).
        self.export_ref_info = False

        self.disable_bytearray_promotion = False
        self.disable_memoryview_promotion = False
        self.force_uppercase_builtins = False
        self.force_union_syntax = False

        # Sets custom output format
        self.output: str | None = None

    def use_lowercase_names(self) -> bool:
        if self.python_version >= (3, 9):
            return not self.force_uppercase_builtins
        return False

    def use_or_syntax(self) -> bool:
        if self.python_version >= (3, 10):
            return not self.force_union_syntax
        return False

    def use_star_unpack(self) -> bool:
        return self.python_version >= (3, 11)

    # To avoid breaking plugin compatibility, keep providing new_semantic_analyzer
    @property
    def new_semantic_analyzer(self) -> bool:
        return True

    def snapshot(self) -> dict[str, object]:
        """Produce a comparable snapshot of this Option"""
        # Under mypyc, we don't have a __dict__, so we need to do worse things.
        d = dict(getattr(self, "__dict__", ()))
        for k in get_class_descriptors(Options):
            if hasattr(self, k) and k != "new_semantic_analyzer":
                d[k] = getattr(self, k)
        # Remove private attributes from snapshot
        d = {k: v for k, v in d.items() if not k.startswith("_")}
        return d

    def __repr__(self) -> str:
        return f"Options({pprint.pformat(self.snapshot())})"

    def process_error_codes(self, *, error_callback: Callable[[str], Any]) -> None:
        # Process `--enable-error-code` and `--disable-error-code` flags
        disabled_codes = set(self.disable_error_code)
        enabled_codes = set(self.enable_error_code)

        valid_error_codes = set(error_codes.keys())

        invalid_codes = (enabled_codes | disabled_codes) - valid_error_codes
        if invalid_codes:
            error_callback(f"Invalid error code(s): {', '.join(sorted(invalid_codes))}")

        self.disabled_error_codes |= {error_codes[code] for code in disabled_codes}
        self.enabled_error_codes |= {error_codes[code] for code in enabled_codes}

        # Enabling an error code always overrides disabling
        self.disabled_error_codes -= self.enabled_error_codes

    def process_incomplete_features(
        self, *, error_callback: Callable[[str], Any], warning_callback: Callable[[str], Any]
    ) -> None:
        # Validate incomplete features.
        for feature in self.enable_incomplete_feature:
            if feature not in INCOMPLETE_FEATURES | COMPLETE_FEATURES:
                error_callback(f"Unknown incomplete feature: {feature}")
            if feature in COMPLETE_FEATURES:
                warning_callback(f"Warning: {feature} is already enabled by default")

    def apply_changes(self, changes: dict[str, object]) -> Options:
        # Note: effects of this method *must* be idempotent.
        new_options = Options()
        # Under mypyc, we don't have a __dict__, so we need to do worse things.
        replace_object_state(new_options, self, copy_dict=True)
        for key, value in changes.items():
            setattr(new_options, key, value)
        if changes.get("ignore_missing_imports"):
            # This is the only option for which a per-module and a global
            # option sometimes beheave differently.
            new_options.ignore_missing_imports_per_module = True

        # These two act as overrides, so apply them when cloning.
        # Similar to global codes enabling overrides disabling, so we start from latter.
        new_options.disabled_error_codes = self.disabled_error_codes.copy()
        new_options.enabled_error_codes = self.enabled_error_codes.copy()
        for code_str in new_options.disable_error_code:
            code = error_codes[code_str]
            new_options.disabled_error_codes.add(code)
            new_options.enabled_error_codes.discard(code)
        for code_str in new_options.enable_error_code:
            code = error_codes[code_str]
            new_options.enabled_error_codes.add(code)
            new_options.disabled_error_codes.discard(code)

        return new_options

    def compare_stable(self, other_snapshot: dict[str, object]) -> bool:
        """Compare options in a way that is stable for snapshot() -> apply_changes() roundtrip.

        This is needed because apply_changes() has non-trivial effects for some flags, so
        Options().apply_changes(options.snapshot()) may result in a (slightly) different object.
        """
        return (
            Options().apply_changes(self.snapshot()).snapshot()
            == Options().apply_changes(other_snapshot).snapshot()
        )

    def build_per_module_cache(self) -> None:
        self._per_module_cache = {}

        # Config precedence is as follows:
        #  1. Concrete section names: foo.bar.baz
        #  2. "Unstructured" glob patterns: foo.*.baz, in the order
        #     they appear in the file (last wins)
        #  3. "Well-structured" wildcard patterns: foo.bar.*, in specificity order.

        # Since structured configs inherit from structured configs above them in the hierarchy,
        # we need to process per-module configs in a careful order.
        # We have to process foo.* before foo.bar.* before foo.bar,
        # and we need to apply *.bar to foo.bar but not to foo.bar.*.
        # To do this, process all well-structured glob configs before non-glob configs and
        # exploit the fact that foo.* sorts earlier ASCIIbetically (unicodebetically?)
        # than foo.bar.*.
        # (A section being "processed last" results in its config "winning".)
        # Unstructured glob configs are stored and are all checked for each module.
        unstructured_glob_keys = [k for k in self.per_module_options.keys() if "*" in k[:-1]]
        structured_keys = [k for k in self.per_module_options.keys() if "*" not in k[:-1]]
        wildcards = sorted(k for k in structured_keys if k.endswith(".*"))
        concrete = [k for k in structured_keys if not k.endswith(".*")]

        for glob in unstructured_glob_keys:
            self._glob_options.append((glob, self.compile_glob(glob)))

        # We (for ease of implementation) treat unstructured glob
        # sections as used if any real modules use them or if any
        # concrete config sections use them. This means we need to
        # track which get used while constructing.
        self.unused_configs = set(unstructured_glob_keys)

        for key in wildcards + concrete:
            # Find what the options for this key would be, just based
            # on inheriting from parent configs.
            options = self.clone_for_module(key)
            # And then update it with its per-module options.
            self._per_module_cache[key] = options.apply_changes(self.per_module_options[key])

        # Add the more structured sections into unused configs, since
        # they only count as used if actually used by a real module.
        self.unused_configs.update(structured_keys)

    def clone_for_module(self, module: str) -> Options:
        """Create an Options object that incorporates per-module options.

        NOTE: Once this method is called all Options objects should be
        considered read-only, else the caching might be incorrect.
        """
        if self._per_module_cache is None:
            self.build_per_module_cache()
        assert self._per_module_cache is not None

        # If the module just directly has a config entry, use it.
        if module in self._per_module_cache:
            self.unused_configs.discard(module)
            return self._per_module_cache[module]

        # If not, search for glob paths at all the parents. So if we are looking for
        # options for foo.bar.baz, we search foo.bar.baz.*, foo.bar.*, foo.*,
        # in that order, looking for an entry.
        # This is technically quadratic in the length of the path, but module paths
        # don't actually get all that long.
        options = self
        path = module.split(".")
        for i in range(len(path), 0, -1):
            key = ".".join(path[:i] + ["*"])
            if key in self._per_module_cache:
                self.unused_configs.discard(key)
                options = self._per_module_cache[key]
                break

        # OK and *now* we need to look for unstructured glob matches.
        # We only do this for concrete modules, not structured wildcards.
        if not module.endswith(".*"):
            for key, pattern in self._glob_options:
                if pattern.match(module):
                    self.unused_configs.discard(key)
                    options = options.apply_changes(self.per_module_options[key])

        # We could update the cache to directly point to modules once
        # they have been looked up, but in testing this made things
        # slower and not faster, so we don't bother.

        return options

    def compile_glob(self, s: str) -> Pattern[str]:
        # Compile one of the glob patterns to a regex so that '.*' can
        # match *zero or more* module sections. This means we compile
        # '.*' into '(\..*)?'.
        parts = s.split(".")
        expr = re.escape(parts[0]) if parts[0] != "*" else ".*"
        for part in parts[1:]:
            expr += re.escape("." + part) if part != "*" else r"(\..*)?"
        return re.compile(expr + "\\Z")

    def select_options_affecting_cache(self) -> Mapping[str, object]:
        result: dict[str, object] = {}
        for opt in OPTIONS_AFFECTING_CACHE:
            val = getattr(self, opt)
            if opt in ("disabled_error_codes", "enabled_error_codes"):
                val = sorted([code.code for code in val])
            result[opt] = val
        return result


from __future__ import annotations

from mypy.errors import Errors
from mypy.nodes import MypyFile
from mypy.options import Options

def parse(
    source: str | bytes,
    fnam: str,
    module: str | None,
    errors: Errors,
    options: Options,
    raise_on_error: bool = False,
) -> MypyFile:
    """Parse a source file, without doing any semantic analysis.

    Return the parse tree. If errors is not provided, raise ParseError
    on failure. Otherwise, use the errors object to report parse errors.

    The python_version (major, minor) option determines the Python syntax variant.
    """
    if options.transform_source is not None:
        source = options.transform_source(source)
    import mypy.fastparse

    tree = mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options)
    if raise_on_error and errors.is_errors():
        errors.raise_error()
    return tree


from __future__ import annotations

from enum import Enum

from mypy import checker, errorcodes
from mypy.messages import MessageBuilder
from mypy.nodes import (
    AssertStmt,
    AssignmentExpr,
    AssignmentStmt,
    BreakStmt,
    ClassDef,
    Context,
    ContinueStmt,
    DictionaryComprehension,
    Expression,
    ExpressionStmt,
    ForStmt,
    FuncDef,
    FuncItem,
    GeneratorExpr,
    GlobalDecl,
    IfStmt,
    Import,
    ImportFrom,
    LambdaExpr,
    ListExpr,
    Lvalue,
    MatchStmt,
    MypyFile,
    NameExpr,
    NonlocalDecl,
    RaiseStmt,
    ReturnStmt,
    StarExpr,
    SymbolTable,
    TryStmt,
    TupleExpr,
    TypeAliasStmt,
    WhileStmt,
    WithStmt,
    implicit_module_attrs,
)
from mypy.options import Options
from mypy.patterns import AsPattern, StarredPattern
from mypy.reachability import ALWAYS_TRUE, infer_pattern_value
from mypy.traverser import ExtendedTraverserVisitor
from mypy.types import Type, UninhabitedType

class BranchState:
    """BranchState contains information about variable definition at the end of a branching statement.
    `if` and `match` are examples of branching statements.

    `may_be_defined` contains variables that were defined in only some branches.
    `must_be_defined` contains variables that were defined in all branches.
    """

    def __init__(
        self,
        must_be_defined: set[str] | None = None,
        may_be_defined: set[str] | None = None,
        skipped: bool = False,
    ) -> None:
        if may_be_defined is None:
            may_be_defined = set()
        if must_be_defined is None:
            must_be_defined = set()

        self.may_be_defined = set(may_be_defined)
        self.must_be_defined = set(must_be_defined)
        self.skipped = skipped

    def copy(self) -> BranchState:
        return BranchState(
            must_be_defined=set(self.must_be_defined),
            may_be_defined=set(self.may_be_defined),
            skipped=self.skipped,
        )

class BranchStatement:
    def __init__(self, initial_state: BranchState | None = None) -> None:
        if initial_state is None:
            initial_state = BranchState()
        self.initial_state = initial_state
        self.branches: list[BranchState] = [
            BranchState(
                must_be_defined=self.initial_state.must_be_defined,
                may_be_defined=self.initial_state.may_be_defined,
            )
        ]

    def copy(self) -> BranchStatement:
        result = BranchStatement(self.initial_state)
        result.branches = [b.copy() for b in self.branches]
        return result

    def next_branch(self) -> None:
        self.branches.append(
            BranchState(
                must_be_defined=self.initial_state.must_be_defined,
                may_be_defined=self.initial_state.may_be_defined,
            )
        )

    def record_definition(self, name: str) -> None:
        assert len(self.branches) > 0
        self.branches[-1].must_be_defined.add(name)
        self.branches[-1].may_be_defined.discard(name)

    def delete_var(self, name: str) -> None:
        assert len(self.branches) > 0
        self.branches[-1].must_be_defined.discard(name)
        self.branches[-1].may_be_defined.discard(name)

    def record_nested_branch(self, state: BranchState) -> None:
        assert len(self.branches) > 0
        current_branch = self.branches[-1]
        if state.skipped:
            current_branch.skipped = True
            return
        current_branch.must_be_defined.update(state.must_be_defined)
        current_branch.may_be_defined.update(state.may_be_defined)
        current_branch.may_be_defined.difference_update(current_branch.must_be_defined)

    def skip_branch(self) -> None:
        assert len(self.branches) > 0
        self.branches[-1].skipped = True

    def is_possibly_undefined(self, name: str) -> bool:
        assert len(self.branches) > 0
        return name in self.branches[-1].may_be_defined

    def is_undefined(self, name: str) -> bool:
        assert len(self.branches) > 0
        branch = self.branches[-1]
        return name not in branch.may_be_defined and name not in branch.must_be_defined

    def is_defined_in_a_branch(self, name: str) -> bool:
        assert len(self.branches) > 0
        for b in self.branches:
            if name in b.must_be_defined or name in b.may_be_defined:
                return True
        return False

    def done(self) -> BranchState:
        # First, compute all vars, including skipped branches. We include skipped branches
        # because our goal is to capture all variables that semantic analyzer would
        # consider defined.
        all_vars = set()
        for b in self.branches:
            all_vars.update(b.may_be_defined)
            all_vars.update(b.must_be_defined)
        # For the rest of the things, we only care about branches that weren't skipped.
        non_skipped_branches = [b for b in self.branches if not b.skipped]
        if non_skipped_branches:
            must_be_defined = non_skipped_branches[0].must_be_defined
            for b in non_skipped_branches[1:]:
                must_be_defined.intersection_update(b.must_be_defined)
        else:
            must_be_defined = set()
        # Everything that wasn't defined in all branches but was defined
        # in at least one branch should be in `may_be_defined`!
        may_be_defined = all_vars.difference(must_be_defined)
        return BranchState(
            must_be_defined=must_be_defined,
            may_be_defined=may_be_defined,
            skipped=len(non_skipped_branches) == 0,
        )

class ScopeType(Enum):
    Global = 1
    Class = 2
    Func = 3
    Generator = 4

class Scope:
    def __init__(self, stmts: list[BranchStatement], scope_type: ScopeType) -> None:
        self.branch_stmts: list[BranchStatement] = stmts
        self.scope_type = scope_type
        self.undefined_refs: dict[str, set[NameExpr]] = {}

    def copy(self) -> Scope:
        result = Scope([s.copy() for s in self.branch_stmts], self.scope_type)
        result.undefined_refs = self.undefined_refs.copy()
        return result

    def record_undefined_ref(self, o: NameExpr) -> None:
        if o.name not in self.undefined_refs:
            self.undefined_refs[o.name] = set()
        self.undefined_refs[o.name].add(o)

    def pop_undefined_ref(self, name: str) -> set[NameExpr]:
        return self.undefined_refs.pop(name, set())

class DefinedVariableTracker:
    """DefinedVariableTracker manages the state and scope for the UndefinedVariablesVisitor."""

    def __init__(self) -> None:
        # There's always at least one scope. Within each scope, there's at least one "global" BranchingStatement.
        self.scopes: list[Scope] = [Scope([BranchStatement()], ScopeType.Global)]
        # disable_branch_skip is used to disable skipping a branch due to a return/raise/etc. This is useful
        # in things like try/except/finally statements.
        self.disable_branch_skip = False

    def copy(self) -> DefinedVariableTracker:
        result = DefinedVariableTracker()
        result.scopes = [s.copy() for s in self.scopes]
        result.disable_branch_skip = self.disable_branch_skip
        return result

    def _scope(self) -> Scope:
        assert len(self.scopes) > 0
        return self.scopes[-1]

    def enter_scope(self, scope_type: ScopeType) -> None:
        assert len(self._scope().branch_stmts) > 0
        initial_state = None
        if scope_type == ScopeType.Generator:
            # Generators are special because they inherit the outer scope.
            initial_state = self._scope().branch_stmts[-1].branches[-1]
        self.scopes.append(Scope([BranchStatement(initial_state)], scope_type))

    def exit_scope(self) -> None:
        self.scopes.pop()

    def in_scope(self, scope_type: ScopeType) -> bool:
        return self._scope().scope_type == scope_type

    def start_branch_statement(self) -> None:
        assert len(self._scope().branch_stmts) > 0
        self._scope().branch_stmts.append(
            BranchStatement(self._scope().branch_stmts[-1].branches[-1])
        )

    def next_branch(self) -> None:
        assert len(self._scope().branch_stmts) > 1
        self._scope().branch_stmts[-1].next_branch()

    def end_branch_statement(self) -> None:
        assert len(self._scope().branch_stmts) > 1
        result = self._scope().branch_stmts.pop().done()
        self._scope().branch_stmts[-1].record_nested_branch(result)

    def skip_branch(self) -> None:
        # Only skip branch if we're outside of "root" branch statement.
        if len(self._scope().branch_stmts) > 1 and not self.disable_branch_skip:
            self._scope().branch_stmts[-1].skip_branch()

    def record_definition(self, name: str) -> None:
        assert len(self.scopes) > 0
        assert len(self.scopes[-1].branch_stmts) > 0
        self._scope().branch_stmts[-1].record_definition(name)

    def delete_var(self, name: str) -> None:
        assert len(self.scopes) > 0
        assert len(self.scopes[-1].branch_stmts) > 0
        self._scope().branch_stmts[-1].delete_var(name)

    def record_undefined_ref(self, o: NameExpr) -> None:
        """Records an undefined reference. These can later be retrieved via `pop_undefined_ref`."""
        assert len(self.scopes) > 0
        self._scope().record_undefined_ref(o)

    def pop_undefined_ref(self, name: str) -> set[NameExpr]:
        """If name has previously been reported as undefined, the NameExpr that was called will be returned."""
        assert len(self.scopes) > 0
        return self._scope().pop_undefined_ref(name)

    def is_possibly_undefined(self, name: str) -> bool:
        assert len(self._scope().branch_stmts) > 0
        # A variable is undefined if it's in a set of `may_be_defined` but not in `must_be_defined`.
        return self._scope().branch_stmts[-1].is_possibly_undefined(name)

    def is_defined_in_different_branch(self, name: str) -> bool:
        """This will return true if a variable is defined in a branch that's not the current branch."""
        assert len(self._scope().branch_stmts) > 0
        stmt = self._scope().branch_stmts[-1]
        if not stmt.is_undefined(name):
            return False
        for stmt in self._scope().branch_stmts:
            if stmt.is_defined_in_a_branch(name):
                return True
        return False

    def is_undefined(self, name: str) -> bool:
        assert len(self._scope().branch_stmts) > 0
        return self._scope().branch_stmts[-1].is_undefined(name)

class Loop:
    def __init__(self) -> None:
        self.has_break = False

class PossiblyUndefinedVariableVisitor(ExtendedTraverserVisitor):
    """Detects the following cases:
    - A variable that's defined only part of the time.
    - If a variable is used before definition

    An example of a partial definition:
    if foo():
        x = 1
    print(x)  # Error: "x" may be undefined.

    Example of a used before definition:
    x = y
    y: int = 2

    Note that this code does not detect variables not defined in any of the branches -- that is
    handled by the semantic analyzer.
    """

    def __init__(
        self,
        msg: MessageBuilder,
        type_map: dict[Expression, Type],
        options: Options,
        names: SymbolTable,
    ) -> None:
        self.msg = msg
        self.type_map = type_map
        self.options = options
        self.builtins = SymbolTable()
        builtins_mod = names.get("__builtins__", None)
        if builtins_mod:
            assert isinstance(builtins_mod.node, MypyFile)
            self.builtins = builtins_mod.node.names
        self.loops: list[Loop] = []
        self.try_depth = 0
        self.tracker = DefinedVariableTracker()
        for name in implicit_module_attrs:
            self.tracker.record_definition(name)

    def var_used_before_def(self, name: str, context: Context) -> None:
        if self.msg.errors.is_error_code_enabled(errorcodes.USED_BEFORE_DEF):
            self.msg.var_used_before_def(name, context)

    def variable_may_be_undefined(self, name: str, context: Context) -> None:
        if self.msg.errors.is_error_code_enabled(errorcodes.POSSIBLY_UNDEFINED):
            self.msg.variable_may_be_undefined(name, context)

    def process_definition(self, name: str) -> None:
        # Was this name previously used? If yes, it's a used-before-definition error.
        if not self.tracker.in_scope(ScopeType.Class):
            refs = self.tracker.pop_undefined_ref(name)
            for ref in refs:
                if self.loops:
                    self.variable_may_be_undefined(name, ref)
                else:
                    self.var_used_before_def(name, ref)
        else:
            # Errors in class scopes are caught by the semantic analyzer.
            pass
        self.tracker.record_definition(name)

    def visit_global_decl(self, o: GlobalDecl) -> None:
        for name in o.names:
            self.process_definition(name)
        super().visit_global_decl(o)

    def visit_nonlocal_decl(self, o: NonlocalDecl) -> None:
        for name in o.names:
            self.process_definition(name)
        super().visit_nonlocal_decl(o)

    def process_lvalue(self, lvalue: Lvalue | None) -> None:
        if isinstance(lvalue, NameExpr):
            self.process_definition(lvalue.name)
        elif isinstance(lvalue, StarExpr):
            self.process_lvalue(lvalue.expr)
        elif isinstance(lvalue, (ListExpr, TupleExpr)):
            for item in lvalue.items:
                self.process_lvalue(item)

    def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
        for lvalue in o.lvalues:
            self.process_lvalue(lvalue)
        super().visit_assignment_stmt(o)

    def visit_assignment_expr(self, o: AssignmentExpr) -> None:
        o.value.accept(self)
        self.process_lvalue(o.target)

    def visit_if_stmt(self, o: IfStmt) -> None:
        for e in o.expr:
            e.accept(self)
        self.tracker.start_branch_statement()
        for b in o.body:
            if b.is_unreachable:
                continue
            b.accept(self)
            self.tracker.next_branch()
        if o.else_body:
            if not o.else_body.is_unreachable:
                o.else_body.accept(self)
            else:
                self.tracker.skip_branch()
        self.tracker.end_branch_statement()

    def visit_match_stmt(self, o: MatchStmt) -> None:
        o.subject.accept(self)
        self.tracker.start_branch_statement()
        for i in range(len(o.patterns)):
            pattern = o.patterns[i]
            pattern.accept(self)
            guard = o.guards[i]
            if guard is not None:
                guard.accept(self)
            if not o.bodies[i].is_unreachable:
                o.bodies[i].accept(self)
            else:
                self.tracker.skip_branch()
            is_catchall = infer_pattern_value(pattern) == ALWAYS_TRUE
            if not is_catchall:
                self.tracker.next_branch()
        self.tracker.end_branch_statement()

    def visit_func_def(self, o: FuncDef) -> None:
        self.process_definition(o.name)
        super().visit_func_def(o)

    def visit_func(self, o: FuncItem) -> None:
        if o.is_dynamic() and not self.options.check_untyped_defs:
            return

        args = o.arguments or []
        # Process initializers (defaults) outside the function scope.
        for arg in args:
            if arg.initializer is not None:
                arg.initializer.accept(self)

        self.tracker.enter_scope(ScopeType.Func)
        for arg in args:
            self.process_definition(arg.variable.name)
            super().visit_var(arg.variable)
        o.body.accept(self)
        self.tracker.exit_scope()

    def visit_generator_expr(self, o: GeneratorExpr) -> None:
        self.tracker.enter_scope(ScopeType.Generator)
        for idx in o.indices:
            self.process_lvalue(idx)
        super().visit_generator_expr(o)
        self.tracker.exit_scope()

    def visit_dictionary_comprehension(self, o: DictionaryComprehension) -> None:
        self.tracker.enter_scope(ScopeType.Generator)
        for idx in o.indices:
            self.process_lvalue(idx)
        super().visit_dictionary_comprehension(o)
        self.tracker.exit_scope()

    def visit_for_stmt(self, o: ForStmt) -> None:
        o.expr.accept(self)
        self.process_lvalue(o.index)
        o.index.accept(self)
        self.tracker.start_branch_statement()
        loop = Loop()
        self.loops.append(loop)
        o.body.accept(self)
        self.tracker.next_branch()
        self.tracker.end_branch_statement()
        if o.else_body is not None:
            # If the loop has a `break` inside, `else` is executed conditionally.
            # If the loop doesn't have a `break` either the function will return or
            # execute the `else`.
            has_break = loop.has_break
            if has_break:
                self.tracker.start_branch_statement()
                self.tracker.next_branch()
            o.else_body.accept(self)
            if has_break:
                self.tracker.end_branch_statement()
        self.loops.pop()

    def visit_return_stmt(self, o: ReturnStmt) -> None:
        super().visit_return_stmt(o)
        self.tracker.skip_branch()

    def visit_lambda_expr(self, o: LambdaExpr) -> None:
        self.tracker.enter_scope(ScopeType.Func)
        super().visit_lambda_expr(o)
        self.tracker.exit_scope()

    def visit_assert_stmt(self, o: AssertStmt) -> None:
        super().visit_assert_stmt(o)
        if checker.is_false_literal(o.expr):
            self.tracker.skip_branch()

    def visit_raise_stmt(self, o: RaiseStmt) -> None:
        super().visit_raise_stmt(o)
        self.tracker.skip_branch()

    def visit_continue_stmt(self, o: ContinueStmt) -> None:
        super().visit_continue_stmt(o)
        self.tracker.skip_branch()

    def visit_break_stmt(self, o: BreakStmt) -> None:
        super().visit_break_stmt(o)
        if self.loops:
            self.loops[-1].has_break = True
        self.tracker.skip_branch()

    def visit_expression_stmt(self, o: ExpressionStmt) -> None:
        if isinstance(self.type_map.get(o.expr, None), (UninhabitedType, type(None))):
            self.tracker.skip_branch()
        super().visit_expression_stmt(o)

    def visit_try_stmt(self, o: TryStmt) -> None:
        """
        Note that finding undefined vars in `finally` requires different handling from
        the rest of the code. In particular, we want to disallow skipping branches due to jump
        statements in except/else clauses for finally but not for other cases. Imagine a case like:
        def f() -> int:
            try:
                x = 1
            except:
                # This jump statement needs to be handled differently depending on whether or
                # not we're trying to process `finally` or not.
                return 0
            finally:
                # `x` may be undefined here.
                pass
            # `x` is always defined here.
            return x
        """
        self.try_depth += 1
        if o.finally_body is not None:
            # In order to find undefined vars in `finally`, we need to
            # process try/except with branch skipping disabled. However, for the rest of the code
            # after finally, we need to process try/except with branch skipping enabled.
            # Therefore, we need to process try/finally twice.
            # Because processing is not idempotent, we should make a copy of the tracker.
            old_tracker = self.tracker.copy()
            self.tracker.disable_branch_skip = True
            self.process_try_stmt(o)
            self.tracker = old_tracker
        self.process_try_stmt(o)
        self.try_depth -= 1

    def process_try_stmt(self, o: TryStmt) -> None:
        """
        Processes try statement decomposing it into the following:
        if ...:
            body
            else_body
        elif ...:
            except 1
        elif ...:
            except 2
        else:
            except n
        finally
        """
        self.tracker.start_branch_statement()
        o.body.accept(self)
        if o.else_body is not None:
            o.else_body.accept(self)
        if len(o.handlers) > 0:
            assert len(o.handlers) == len(o.vars) == len(o.types)
            for i in range(len(o.handlers)):
                self.tracker.next_branch()
                exc_type = o.types[i]
                if exc_type is not None:
                    exc_type.accept(self)
                var = o.vars[i]
                if var is not None:
                    self.process_definition(var.name)
                    var.accept(self)
                o.handlers[i].accept(self)
                if var is not None:
                    self.tracker.delete_var(var.name)
        self.tracker.end_branch_statement()

        if o.finally_body is not None:
            o.finally_body.accept(self)

    def visit_while_stmt(self, o: WhileStmt) -> None:
        o.expr.accept(self)
        self.tracker.start_branch_statement()
        loop = Loop()
        self.loops.append(loop)
        o.body.accept(self)
        has_break = loop.has_break
        if not checker.is_true_literal(o.expr):
            # If this is a loop like `while True`, we can consider the body to be
            # a single branch statement (we're guaranteed that the body is executed at least once).
            # If not, call next_branch() to make all variables defined there conditional.
            self.tracker.next_branch()
        self.tracker.end_branch_statement()
        if o.else_body is not None:
            # If the loop has a `break` inside, `else` is executed conditionally.
            # If the loop doesn't have a `break` either the function will return or
            # execute the `else`.
            if has_break:
                self.tracker.start_branch_statement()
                self.tracker.next_branch()
            if o.else_body:
                o.else_body.accept(self)
            if has_break:
                self.tracker.end_branch_statement()
        self.loops.pop()

    def visit_as_pattern(self, o: AsPattern) -> None:
        if o.name is not None:
            self.process_lvalue(o.name)
        super().visit_as_pattern(o)

    def visit_starred_pattern(self, o: StarredPattern) -> None:
        if o.capture is not None:
            self.process_lvalue(o.capture)
        super().visit_starred_pattern(o)

    def visit_name_expr(self, o: NameExpr) -> None:
        if o.name in self.builtins and self.tracker.in_scope(ScopeType.Global):
            return
        if self.tracker.is_possibly_undefined(o.name):
            # A variable is only defined in some branches.
            self.variable_may_be_undefined(o.name, o)
            # We don't want to report the error on the same variable multiple times.
            self.tracker.record_definition(o.name)
        elif self.tracker.is_defined_in_different_branch(o.name):
            # A variable is defined in one branch but used in a different branch.
            if self.loops or self.try_depth > 0:
                # If we're in a loop or in a try, we can't be sure that this variable
                # is undefined. Report it as "may be undefined".
                self.variable_may_be_undefined(o.name, o)
            else:
                self.var_used_before_def(o.name, o)
        elif self.tracker.is_undefined(o.name):
            # A variable is undefined. It could be due to two things:
            # 1. A variable is just totally undefined
            # 2. The variable is defined later in the code.
            # Case (1) will be caught by semantic analyzer. Case (2) is a forward ref that should
            # be caught by this visitor. Save the ref for later, so that if we see a definition,
            # we know it's a used-before-definition scenario.
            self.tracker.record_undefined_ref(o)
        super().visit_name_expr(o)

    def visit_with_stmt(self, o: WithStmt) -> None:
        for expr, idx in zip(o.expr, o.target):
            expr.accept(self)
            self.process_lvalue(idx)
        o.body.accept(self)

    def visit_class_def(self, o: ClassDef) -> None:
        self.process_definition(o.name)
        self.tracker.enter_scope(ScopeType.Class)
        super().visit_class_def(o)
        self.tracker.exit_scope()

    def visit_import(self, o: Import) -> None:
        for mod, alias in o.ids:
            if alias is not None:
                self.tracker.record_definition(alias)
            else:
                # When you do `import x.y`, only `x` becomes defined.
                names = mod.split(".")
                if names:
                    # `names` should always be nonempty, but we don't want mypy
                    # to crash on invalid code.
                    self.tracker.record_definition(names[0])
        super().visit_import(o)

    def visit_import_from(self, o: ImportFrom) -> None:
        for mod, alias in o.names:
            name = alias
            if name is None:
                name = mod
            self.tracker.record_definition(name)
        super().visit_import_from(o)

    def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None:
        # Type alias target may contain forward references
        self.tracker.record_definition(o.name.name)


"""Classes for representing match statement patterns."""

from __future__ import annotations

from typing import TypeVar

from mypy_extensions import trait

from mypy.nodes import Expression, NameExpr, Node, RefExpr
from mypy.visitor import PatternVisitor

T = TypeVar("T")

@trait
class Pattern(Node):
    """A pattern node."""

    __slots__ = ()

    def accept(self, visitor: PatternVisitor[T]) -> T:
        raise RuntimeError("Not implemented", type(self))

class AsPattern(Pattern):
    """The pattern <pattern> as <name>"""

    # The python ast, and therefore also our ast merges capture, wildcard and as patterns into one
    # for easier handling.
    # If pattern is None this is a capture pattern. If name and pattern are both none this is a
    # wildcard pattern.
    # Only name being None should not happen but also won't break anything.
    pattern: Pattern | None
    name: NameExpr | None

    def __init__(self, pattern: Pattern | None, name: NameExpr | None) -> None:
        super().__init__()
        self.pattern = pattern
        self.name = name

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_as_pattern(self)

class OrPattern(Pattern):
    """The pattern <pattern> | <pattern> | ..."""

    patterns: list[Pattern]

    def __init__(self, patterns: list[Pattern]) -> None:
        super().__init__()
        self.patterns = patterns

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_or_pattern(self)

class ValuePattern(Pattern):
    """The pattern x.y (or x.y.z, ...)"""

    expr: Expression

    def __init__(self, expr: Expression) -> None:
        super().__init__()
        self.expr = expr

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_value_pattern(self)

class SingletonPattern(Pattern):
    # This can be exactly True, False or None
    value: bool | None

    def __init__(self, value: bool | None) -> None:
        super().__init__()
        self.value = value

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_singleton_pattern(self)

class SequencePattern(Pattern):
    """The pattern [<pattern>, ...]"""

    patterns: list[Pattern]

    def __init__(self, patterns: list[Pattern]) -> None:
        super().__init__()
        self.patterns = patterns

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_sequence_pattern(self)

class StarredPattern(Pattern):
    # None corresponds to *_ in a list pattern. It will match multiple items but won't bind them to
    # a name.
    capture: NameExpr | None

    def __init__(self, capture: NameExpr | None) -> None:
        super().__init__()
        self.capture = capture

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_starred_pattern(self)

class MappingPattern(Pattern):
    keys: list[Expression]
    values: list[Pattern]
    rest: NameExpr | None

    def __init__(
        self, keys: list[Expression], values: list[Pattern], rest: NameExpr | None
    ) -> None:
        super().__init__()
        assert len(keys) == len(values)
        self.keys = keys
        self.values = values
        self.rest = rest

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_mapping_pattern(self)

class ClassPattern(Pattern):
    """The pattern Cls(...)"""

    class_ref: RefExpr
    positionals: list[Pattern]
    keyword_keys: list[str]
    keyword_values: list[Pattern]

    def __init__(
        self,
        class_ref: RefExpr,
        positionals: list[Pattern],
        keyword_keys: list[str],
        keyword_values: list[Pattern],
    ) -> None:
        super().__init__()
        assert len(keyword_keys) == len(keyword_values)
        self.class_ref = class_ref
        self.positionals = positionals
        self.keyword_keys = keyword_keys
        self.keyword_values = keyword_values

    def accept(self, visitor: PatternVisitor[T]) -> T:
        return visitor.visit_class_pattern(self)


"""Plugin system for extending mypy.

At large scale the plugin system works as following:

* Plugins are collected from the corresponding mypy config file option
  (either via paths to Python files, or installed Python modules)
  and imported using importlib.

* Every module should get an entry point function (called 'plugin' by default,
  but may be overridden in the config file) that should accept a single string
  argument that is a full mypy version (includes git commit hash for dev
  versions) and return a subclass of mypy.plugins.Plugin.

* All plugin class constructors should match the signature of mypy.plugin.Plugin
  (i.e. should accept an mypy.options.Options object), and *must* call
  super().__init__().

* At several steps during semantic analysis and type checking mypy calls
  special `get_xxx` methods on user plugins with a single string argument that
  is a fully qualified name (full name) of a relevant definition
  (see mypy.plugin.Plugin method docstrings for details).

* The plugins are called in the order they are passed in the config option.
  Every plugin must decide whether to act on a given full name. The first
  plugin that returns non-None object will be used.

* The above decision should be made using the limited common API specified by
  mypy.plugin.CommonPluginApi.

* The callback returned by the plugin will be called with a larger context that
  includes relevant current state (e.g. a default return type, or a default
  attribute type) and a wider relevant API provider (e.g.
  SemanticAnalyzerPluginInterface or CheckerPluginInterface).

* The result of this is used for further processing. See various `XxxContext`
  named tuples for details about which information is given to each hook.

Plugin developers should ensure that their plugins work well in incremental and
daemon modes. In particular, plugins should not hold global state, and should
always call add_plugin_dependency() in plugin hooks called during semantic
analysis. See the method docstring for more details.

There is no dedicated cache storage for plugins, but plugins can store
per-TypeInfo data in a special .metadata attribute that is serialized to the
mypy caches between incremental runs. To avoid collisions between plugins, they
are encouraged to store their state under a dedicated key coinciding with
plugin name in the metadata dictionary. Every value stored there must be
JSON-serializable.

## Notes about the semantic analyzer

Mypy 0.710 introduced a new semantic analyzer that changed how plugins are
expected to work in several notable ways (from mypy 0.730 the old semantic
analyzer is no longer available):

1. The order of processing AST nodes in modules is different. The old semantic
   analyzer processed modules in textual order, one module at a time. The new
   semantic analyzer first processes the module top levels, including bodies of
   any top-level classes and classes nested within classes. ("Top-level" here
   means "not nested within a function/method".) Functions and methods are
   processed only after module top levels have been finished. If there is an
   import cycle, all module top levels in the cycle are processed before
   processing any functions or methods. Each unit of processing (a module top
   level or a function/method) is called a *target*.

   This also means that function signatures in the same module have not been
   analyzed yet when analyzing the module top level. If you need access to
   a function signature, you'll need to explicitly analyze the signature first
   using `anal_type()`.

2. Each target can be processed multiple times. This may happen if some forward
   references are not ready yet, for example. This means that semantic analyzer
   related plugin hooks can be called multiple times for the same full name.
   These plugin methods must thus be idempotent.

3. The `anal_type` API function returns None if some part of the type is not
   available yet. If this happens, the current target being analyzed will be
   *deferred*, which means that it will be processed again soon, in the hope
   that additional dependencies will be available. This may happen if there are
   forward references to types or inter-module references to types within an
   import cycle.

   Note that if there is a circular definition, mypy may decide to stop
   processing to avoid an infinite number of iterations. When this happens,
   `anal_type` will generate an error and return an `AnyType` type object
   during the final iteration (instead of None).

4. There is a new API method `defer()`. This can be used to explicitly request
   the current target to be reprocessed one more time. You don't need this
   to call this if `anal_type` returns None, however.

5. There is a new API property `final_iteration`, which is true once mypy
   detected no progress during the previous iteration or if the maximum
   semantic analysis iteration count has been reached. You must never
   defer during the final iteration, as it will cause a crash.

6. The `node` attribute of SymbolTableNode objects may contain a reference to
   a PlaceholderNode object. This object means that this definition has not
   been fully processed yet. If you encounter a PlaceholderNode, you should
   defer unless it's the final iteration. If it's the final iteration, you
   should generate an error message. It usually means that there's a cyclic
   definition that cannot be resolved by mypy. PlaceholderNodes can only refer
   to references inside an import cycle. If you are looking up things from
   another module, such as the builtins, that is outside the current module or
   import cycle, you can safely assume that you won't receive a placeholder.

When testing your plugin, you should have a test case that forces a module top
level to be processed multiple times. The easiest way to do this is to include
a forward reference to a class in a top-level annotation. Example:

    c: C  # Forward reference causes second analysis pass
    class C: pass

Note that a forward reference in a function signature won't trigger another
pass, since all functions are processed only after the top level has been fully
analyzed.

You can use `api.options.new_semantic_analyzer` to check whether the new
semantic analyzer is enabled (it's always true in mypy 0.730 and later).
"""

from __future__ import annotations

from abc import abstractmethod
from typing import Any, Callable, NamedTuple, TypeVar

from mypy_extensions import mypyc_attr, trait

from mypy.errorcodes import ErrorCode
from mypy.lookup import lookup_fully_qualified
from mypy.message_registry import ErrorMessage
from mypy.messages import MessageBuilder
from mypy.nodes import (
    ArgKind,
    CallExpr,
    ClassDef,
    Context,
    Expression,
    MypyFile,
    SymbolTableNode,
    TypeInfo,
)
from mypy.options import Options
from mypy.tvar_scope import TypeVarLikeScope
from mypy.types import (
    CallableType,
    FunctionLike,
    Instance,
    ProperType,
    Type,
    TypeList,
    UnboundType,
)

@trait
class TypeAnalyzerPluginInterface:
    """Interface for accessing semantic analyzer functionality in plugins.

    Methods docstrings contain only basic info. Look for corresponding implementation
    docstrings in typeanal.py for more details.
    """

    # An options object. Note: these are the cloned options for the current file.
    # This might be different from Plugin.options (that contains default/global options)
    # if there are per-file options in the config. This applies to all other interfaces
    # in this file.
    options: Options

    @abstractmethod
    def fail(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None:
        """Emit an error message at given location."""
        raise NotImplementedError

    @abstractmethod
    def named_type(self, name: str, args: list[Type]) -> Instance:
        """Construct an instance of a builtin type with given name."""
        raise NotImplementedError

    @abstractmethod
    def analyze_type(self, typ: Type) -> Type:
        """Analyze an unbound type using the default mypy logic."""
        raise NotImplementedError

    @abstractmethod
    def analyze_callable_args(
        self, arglist: TypeList
    ) -> tuple[list[Type], list[ArgKind], list[str | None]] | None:
        """Find types, kinds, and names of arguments from extended callable syntax."""
        raise NotImplementedError

# A context for a hook that semantically analyzes an unbound type.
class AnalyzeTypeContext(NamedTuple):
    type: UnboundType  # Type to analyze
    context: Context  # Relevant location context (e.g. for error messages)
    api: TypeAnalyzerPluginInterface

@mypyc_attr(allow_interpreted_subclasses=True)
class CommonPluginApi:
    """
    A common plugin API (shared between semantic analysis and type checking phases)
    that all plugin hooks get independently of the context.
    """

    # Global mypy options.
    # Per-file options can be only accessed on various
    # XxxPluginInterface classes.
    options: Options

    @abstractmethod
    def lookup_fully_qualified(self, fullname: str) -> SymbolTableNode | None:
        """Lookup a symbol by its full name (including module).

        This lookup function available for all plugins. Return None if a name
        is not found. This function doesn't support lookup from current scope.
        Use SemanticAnalyzerPluginInterface.lookup_qualified() for this."""
        raise NotImplementedError

@trait
class CheckerPluginInterface:
    """Interface for accessing type checker functionality in plugins.

    Methods docstrings contain only basic info. Look for corresponding implementation
    docstrings in checker.py for more details.
    """

    msg: MessageBuilder
    options: Options
    path: str

    # Type context for type inference
    @property
    @abstractmethod
    def type_context(self) -> list[Type | None]:
        """Return the type context of the plugin"""
        raise NotImplementedError

    @abstractmethod
    def fail(
        self, msg: str | ErrorMessage, ctx: Context, /, *, code: ErrorCode | None = None
    ) -> None:
        """Emit an error message at given location."""
        raise NotImplementedError

    @abstractmethod
    def named_generic_type(self, name: str, args: list[Type]) -> Instance:
        """Construct an instance of a generic type with given type arguments."""
        raise NotImplementedError

    @abstractmethod
    def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type:
        """Checks the type of the given expression."""
        raise NotImplementedError

@trait
class SemanticAnalyzerPluginInterface:
    """Interface for accessing semantic analyzer functionality in plugins.

    Methods docstrings contain only basic info. Look for corresponding implementation
    docstrings in semanal.py for more details.

    # TODO: clean-up lookup functions.
    """

    modules: dict[str, MypyFile]
    # Options for current file.
    options: Options
    cur_mod_id: str
    msg: MessageBuilder

    @abstractmethod
    def named_type(self, fullname: str, args: list[Type] | None = None) -> Instance:
        """Construct an instance of a builtin type with given type arguments."""
        raise NotImplementedError

    @abstractmethod
    def builtin_type(self, fully_qualified_name: str) -> Instance:
        """Legacy function -- use named_type() instead."""
        # NOTE: Do not delete this since many plugins may still use it.
        raise NotImplementedError

    @abstractmethod
    def named_type_or_none(self, fullname: str, args: list[Type] | None = None) -> Instance | None:
        """Construct an instance of a type with given type arguments.

        Return None if a type could not be constructed for the qualified
        type name. This is possible when the qualified name includes a
        module name and the module has not been imported.
        """
        raise NotImplementedError

    @abstractmethod
    def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
        raise NotImplementedError

    @abstractmethod
    def parse_bool(self, expr: Expression) -> bool | None:
        """Parse True/False literals."""
        raise NotImplementedError

    @abstractmethod
    def parse_str_literal(self, expr: Expression) -> str | None:
        """Parse string literals."""

    @abstractmethod
    def fail(
        self,
        msg: str,
        ctx: Context,
        serious: bool = False,
        *,
        blocker: bool = False,
        code: ErrorCode | None = None,
    ) -> None:
        """Emit an error message at given location."""
        raise NotImplementedError

    @abstractmethod
    def anal_type(
        self,
        t: Type,
        *,
        tvar_scope: TypeVarLikeScope | None = None,
        allow_tuple_literal: bool = False,
        allow_unbound_tvars: bool = False,
        report_invalid_types: bool = True,
    ) -> Type | None:
        """Analyze an unbound type.

        Return None if some part of the type is not ready yet. In this
        case the current target being analyzed will be deferred and
        analyzed again.
        """
        raise NotImplementedError

    @abstractmethod
    def class_type(self, self_type: Type) -> Type:
        """Generate type of first argument of class methods from type of self."""
        raise NotImplementedError

    @abstractmethod
    def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
        """Lookup a symbol by its fully qualified name.

        Raise an error if not found.
        """
        raise NotImplementedError

    @abstractmethod
    def lookup_fully_qualified_or_none(self, name: str) -> SymbolTableNode | None:
        """Lookup a symbol by its fully qualified name.

        Return None if not found.
        """
        raise NotImplementedError

    @abstractmethod
    def lookup_qualified(
        self, name: str, ctx: Context, suppress_errors: bool = False
    ) -> SymbolTableNode | None:
        """Lookup symbol using a name in current scope.

        This follows Python local->non-local->global->builtins rules.
        """
        raise NotImplementedError

    @abstractmethod
    def add_plugin_dependency(self, trigger: str, target: str | None = None) -> None:
        """Specify semantic dependencies for generated methods/variables.

        If the symbol with full name given by trigger is found to be stale by mypy,
        then the body of node with full name given by target will be re-checked.
        By default, this is the node that is currently analyzed.

        For example, the dataclass plugin adds a generated __init__ method with
        a signature that depends on types of attributes in ancestor classes. If any
        attribute in an ancestor class gets stale (modified), we need to reprocess
        the subclasses (and thus regenerate __init__ methods).

        This is used by fine-grained incremental mode (mypy daemon). See mypy/server/deps.py
        for more details.
        """
        raise NotImplementedError

    @abstractmethod
    def add_symbol_table_node(self, name: str, stnode: SymbolTableNode) -> Any:
        """Add node to global symbol table (or to nearest class if there is one)."""
        raise NotImplementedError

    @abstractmethod
    def qualified_name(self, n: str) -> str:
        """Make qualified name using current module and enclosing class (if any)."""
        raise NotImplementedError

    @abstractmethod
    def defer(self) -> None:
        """Call this to defer the processing of the current node.

        This will request an additional iteration of semantic analysis.
        """
        raise NotImplementedError

    @property
    @abstractmethod
    def final_iteration(self) -> bool:
        """Is this the final iteration of semantic analysis?"""
        raise NotImplementedError

    @property
    @abstractmethod
    def is_stub_file(self) -> bool:
        raise NotImplementedError

    @abstractmethod
    def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Type | None:
        raise NotImplementedError

# A context for querying for configuration data about a module for
# cache invalidation purposes.
class ReportConfigContext(NamedTuple):
    id: str  # Module name
    path: str  # Module file path
    is_check: bool  # Is this invocation for checking whether the config matches

# A context for a function signature hook that infers a better signature for a
# function.  Note that argument types aren't available yet.  If you need them,
# you have to use a method hook instead.
class FunctionSigContext(NamedTuple):
    args: list[list[Expression]]  # Actual expressions for each formal argument
    default_signature: CallableType  # Original signature of the method
    context: Context  # Relevant location context (e.g. for error messages)
    api: CheckerPluginInterface

# A context for a function hook that infers the return type of a function with
# a special signature.
#
# A no-op callback would just return the inferred return type, but a useful
# callback at least sometimes can infer a more precise type.
class FunctionContext(NamedTuple):
    arg_types: list[list[Type]]  # List of actual caller types for each formal argument
    arg_kinds: list[list[ArgKind]]  # Ditto for argument kinds, see nodes.ARG_* constants
    # Names of formal parameters from the callee definition,
    # these will be sufficient in most cases.
    callee_arg_names: list[str | None]
    # Names of actual arguments in the call expression. For example,
    # in a situation like this:
    #     def func(**kwargs) -> None:
    #         pass
    #     func(kw1=1, kw2=2)
    # callee_arg_names will be ['kwargs'] and arg_names will be [['kw1', 'kw2']].
    arg_names: list[list[str | None]]
    default_return_type: Type  # Return type inferred from signature
    args: list[list[Expression]]  # Actual expressions for each formal argument
    context: Context  # Relevant location context (e.g. for error messages)
    api: CheckerPluginInterface

# A context for a method signature hook that infers a better signature for a
# method.  Note that argument types aren't available yet.  If you need them,
# you have to use a method hook instead.
# TODO: document ProperType in the plugin changelog/update issue.
class MethodSigContext(NamedTuple):
    type: ProperType  # Base object type for method call
    args: list[list[Expression]]  # Actual expressions for each formal argument
    default_signature: CallableType  # Original signature of the method
    context: Context  # Relevant location context (e.g. for error messages)
    api: CheckerPluginInterface

# A context for a method hook that infers the return type of a method with a
# special signature.
#
# This is very similar to FunctionContext (only differences are documented).
class MethodContext(NamedTuple):
    type: ProperType  # Base object type for method call
    arg_types: list[list[Type]]  # List of actual caller types for each formal argument
    # see FunctionContext for details about names and kinds
    arg_kinds: list[list[ArgKind]]
    callee_arg_names: list[str | None]
    arg_names: list[list[str | None]]
    default_return_type: Type  # Return type inferred by mypy
    args: list[list[Expression]]  # Lists of actual expressions for every formal argument
    context: Context
    api: CheckerPluginInterface

# A context for an attribute type hook that infers the type of an attribute.
class AttributeContext(NamedTuple):
    type: ProperType  # Type of object with attribute
    default_attr_type: Type  # Original attribute type
    context: Context  # Relevant location context (e.g. for error messages)
    api: CheckerPluginInterface

# A context for a class hook that modifies the class definition.
class ClassDefContext(NamedTuple):
    cls: ClassDef  # The class definition
    reason: Expression  # The expression being applied (decorator, metaclass, base class)
    api: SemanticAnalyzerPluginInterface

# A context for dynamic class definitions like
# Base = declarative_base()
class DynamicClassDefContext(NamedTuple):
    call: CallExpr  # The r.h.s. of dynamic class definition
    name: str  # The name this class is being assigned to
    api: SemanticAnalyzerPluginInterface

@mypyc_attr(allow_interpreted_subclasses=True)
class Plugin(CommonPluginApi):
    """Base class of all type checker plugins.

    This defines a no-op plugin.  Subclasses can override some methods to
    provide some actual functionality.

    All get_ methods are treated as pure functions (you should assume that
    results might be cached). A plugin should return None from a get_ method
    to give way to other plugins.

    Look at the comments of various *Context objects for additional information on
    various hooks.
    """

    def __init__(self, options: Options) -> None:
        self.options = options
        self.python_version = options.python_version
        # This can't be set in __init__ because it is executed too soon in build.py.
        # Therefore, build.py *must* set it later before graph processing starts
        # by calling set_modules().
        self._modules: dict[str, MypyFile] | None = None

    def set_modules(self, modules: dict[str, MypyFile]) -> None:
        self._modules = modules

    def lookup_fully_qualified(self, fullname: str) -> SymbolTableNode | None:
        assert self._modules is not None
        return lookup_fully_qualified(fullname, self._modules)

    def report_config_data(self, ctx: ReportConfigContext) -> Any:
        """Get representation of configuration data for a module.

        The data must be encodable as JSON and will be stored in the
        cache metadata for the module. A mismatch between the cached
        values and the returned will result in that module's cache
        being invalidated and the module being rechecked.

        This can be called twice for each module, once after loading
        the cache to check if it is valid and once while writing new
        cache information.

        If is_check in the context is true, then the return of this
        call will be checked against the cached version. Otherwise the
        call is being made to determine what to put in the cache. This
        can be used to allow consulting extra cache files in certain
        complex situations.

        This can be used to incorporate external configuration information
        that might require changes to typechecking.
        """
        return None

    def get_additional_deps(self, file: MypyFile) -> list[tuple[int, str, int]]:
        """Customize dependencies for a module.

        This hook allows adding in new dependencies for a module. It
        is called after parsing a file but before analysis. This can
        be useful if a library has dependencies that are dynamic based
        on configuration information, for example.

        Returns a list of (priority, module name, line number) tuples.

        The line number can be -1 when there is not a known real line number.

        Priorities are defined in mypy.build (but maybe shouldn't be).
        10 is a good choice for priority.
        """
        return []

    def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext], Type] | None:
        """Customize behaviour of the type analyzer for given full names.

        This method is called during the semantic analysis pass whenever mypy sees an
        unbound type. For example, while analysing this code:

            from lib import Special, Other

            var: Special
            def func(x: Other[int]) -> None:
                ...

        this method will be called with 'lib.Special', and then with 'lib.Other'.
        The callback returned by plugin must return an analyzed type,
        i.e. an instance of `mypy.types.Type`.
        """
        return None

    def get_function_signature_hook(
        self, fullname: str
    ) -> Callable[[FunctionSigContext], FunctionLike] | None:
        """Adjust the signature of a function.

        This method is called before type checking a function call. Plugin
        may infer a better type for the function.

            from lib import Class, do_stuff

            do_stuff(42)
            Class()

        This method will be called with 'lib.do_stuff' and then with 'lib.Class'.
        """
        return None

    def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
        """Adjust the return type of a function call.

        This method is called after type checking a call. Plugin may adjust the return
        type inferred by mypy, and/or emit some error messages. Note, this hook is also
        called for class instantiation calls, so that in this example:

            from lib import Class, do_stuff

            do_stuff(42)
            Class()

        This method will be called with 'lib.do_stuff' and then with 'lib.Class'.
        """
        return None

    def get_method_signature_hook(
        self, fullname: str
    ) -> Callable[[MethodSigContext], FunctionLike] | None:
        """Adjust the signature of a method.

        This method is called before type checking a method call. Plugin
        may infer a better type for the method. The hook is also called for special
        Python dunder methods except __init__ and __new__ (use get_function_hook to customize
        class instantiation). This function is called with the method full name using
        the class where it was _defined_. For example, in this code:

            from lib import Special

            class Base:
                def method(self, arg: Any) -> Any:
                    ...
            class Derived(Base):
                ...

            var: Derived
            var.method(42)

            x: Special
            y = x[0]

        this method is called with '__main__.Base.method', and then with
        'lib.Special.__getitem__'.
        """
        return None

    def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None:
        """Adjust return type of a method call.

        This is the same as get_function_hook(), but is called with the
        method full name (again, using the class where the method is defined).
        """
        return None

    def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
        """Adjust type of an instance attribute.

        This method is called with attribute full name using the class of the instance where
        the attribute was defined (or Var.info.fullname for generated attributes).

        For classes without __getattr__ or __getattribute__, this hook is only called for
        names of fields/properties (but not methods) that exist in the instance MRO.

        For classes that implement __getattr__ or __getattribute__, this hook is called
        for all fields/properties, including nonexistent ones (but still not methods).

        For example:

            class Base:
                x: Any
                def __getattr__(self, attr: str) -> Any: ...

            class Derived(Base):
                ...

            var: Derived
            var.x
            var.y

        get_attribute_hook is called with '__main__.Base.x' and '__main__.Base.y'.
        However, if we had not implemented __getattr__ on Base, you would only get
        the callback for 'var.x'; 'var.y' would produce an error without calling the hook.
        """
        return None

    def get_class_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
        """
        Adjust type of a class attribute.

        This method is called with attribute full name using the class where the attribute was
        defined (or Var.info.fullname for generated attributes).

        For example:

            class Cls:
                x: Any

            Cls.x

        get_class_attribute_hook is called with '__main__.Cls.x' as fullname.
        """
        return None

    def get_class_decorator_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
        """Update class definition for given class decorators.

        The plugin can modify a TypeInfo _in place_ (for example add some generated
        methods to the symbol table). This hook is called after the class body was
        semantically analyzed, but *there may still be placeholders* (typically
        caused by forward references).

        NOTE: Usually get_class_decorator_hook_2 is the better option, since it
              guarantees that there are no placeholders.

        The hook is called with full names of all class decorators.

        The hook can be called multiple times per class, so it must be
        idempotent.
        """
        return None

    def get_class_decorator_hook_2(
        self, fullname: str
    ) -> Callable[[ClassDefContext], bool] | None:
        """Update class definition for given class decorators.

        Similar to get_class_decorator_hook, but this runs in a later pass when
        placeholders have been resolved.

        The hook can return False if some base class hasn't been
        processed yet using class hooks. It causes all class hooks
        (that are run in this same pass) to be invoked another time for
        the file(s) currently being processed.

        The hook can be called multiple times per class, so it must be
        idempotent.
        """
        return None

    def get_metaclass_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
        """Update class definition for given declared metaclasses.

        Same as get_class_decorator_hook() but for metaclasses. Note:
        this hook will be only called for explicit metaclasses, not for
        inherited ones.

        TODO: probably it should also be called on inherited metaclasses.
        """
        return None

    def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
        """Update class definition for given base classes.

        Same as get_class_decorator_hook() but for base classes. Base classes
        don't need to refer to TypeInfos, if a base class refers to a variable with
        Any type, this hook will still be called.
        """
        return None

    def get_customize_class_mro_hook(
        self, fullname: str
    ) -> Callable[[ClassDefContext], None] | None:
        """Customize MRO for given classes.

        The plugin can modify the class MRO _in place_. This method is called
        with the class full name before its body was semantically analyzed.
        """
        return None

    def get_dynamic_class_hook(
        self, fullname: str
    ) -> Callable[[DynamicClassDefContext], None] | None:
        """Semantically analyze a dynamic class definition.

        This plugin hook allows one to semantically analyze dynamic class definitions like:

            from lib import dynamic_class

            X = dynamic_class('X', [])

        For such definition, this hook will be called with 'lib.dynamic_class'.
        The plugin should create the corresponding TypeInfo, and place it into a relevant
        symbol table, e.g. using ctx.api.add_symbol_table_node().
        """
        return None

T = TypeVar("T")

class ChainedPlugin(Plugin):
    """A plugin that represents a sequence of chained plugins.

    Each lookup method returns the hook for the first plugin that
    reports a match.

    This class should not be subclassed -- use Plugin as the base class
    for all plugins.
    """

    # TODO: Support caching of lookup results (through a LRU cache, for example).

    def __init__(self, options: Options, plugins: list[Plugin]) -> None:
        """Initialize chained plugin.

        Assume that the child plugins aren't mutated (results may be cached).
        """
        super().__init__(options)
        self._plugins = plugins

    def set_modules(self, modules: dict[str, MypyFile]) -> None:
        for plugin in self._plugins:
            plugin.set_modules(modules)

    def report_config_data(self, ctx: ReportConfigContext) -> Any:
        config_data = [plugin.report_config_data(ctx) for plugin in self._plugins]
        return config_data if any(x is not None for x in config_data) else None

    def get_additional_deps(self, file: MypyFile) -> list[tuple[int, str, int]]:
        deps = []
        for plugin in self._plugins:
            deps.extend(plugin.get_additional_deps(file))
        return deps

    def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext], Type] | None:
        return self._find_hook(lambda plugin: plugin.get_type_analyze_hook(fullname))

    def get_function_signature_hook(
        self, fullname: str
    ) -> Callable[[FunctionSigContext], FunctionLike] | None:
        return self._find_hook(lambda plugin: plugin.get_function_signature_hook(fullname))

    def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
        return self._find_hook(lambda plugin: plugin.get_function_hook(fullname))

    def get_method_signature_hook(
        self, fullname: str
    ) -> Callable[[MethodSigContext], FunctionLike] | None:
        return self._find_hook(lambda plugin: plugin.get_method_signature_hook(fullname))

    def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None:
        return self._find_hook(lambda plugin: plugin.get_method_hook(fullname))

    def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
        return self._find_hook(lambda plugin: plugin.get_attribute_hook(fullname))

    def get_class_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
        return self._find_hook(lambda plugin: plugin.get_class_attribute_hook(fullname))

    def get_class_decorator_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
        return self._find_hook(lambda plugin: plugin.get_class_decorator_hook(fullname))

    def get_class_decorator_hook_2(
        self, fullname: str
    ) -> Callable[[ClassDefContext], bool] | None:
        return self._find_hook(lambda plugin: plugin.get_class_decorator_hook_2(fullname))

    def get_metaclass_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
        return self._find_hook(lambda plugin: plugin.get_metaclass_hook(fullname))

    def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
        return self._find_hook(lambda plugin: plugin.get_base_class_hook(fullname))

    def get_customize_class_mro_hook(
        self, fullname: str
    ) -> Callable[[ClassDefContext], None] | None:
        return self._find_hook(lambda plugin: plugin.get_customize_class_mro_hook(fullname))

    def get_dynamic_class_hook(
        self, fullname: str
    ) -> Callable[[DynamicClassDefContext], None] | None:
        return self._find_hook(lambda plugin: plugin.get_dynamic_class_hook(fullname))

    def _find_hook(self, lookup: Callable[[Plugin], T]) -> T | None:
        for plugin in self._plugins:
            hook = lookup(plugin)
            if hook:
                return hook
        return None