"""
This module is for (more basic) type operations that should not depend on is_subtype(),
meet_types(), join_types() etc. We don't want to keep them in mypy/types.py for two reasons:
* Reduce the size of that module.
* Reduce use of get_proper_type() in types.py to avoid cyclic imports
  expand_type <-> types, if we move get_proper_type() to the former.
"""

from __future__ import annotations

from typing import Callable, Iterable, cast

from mypy.nodes import ARG_STAR, ARG_STAR2, FuncItem, TypeAlias
from mypy.types import (
    AnyType,
    CallableType,
    Instance,
    NoneType,
    Overloaded,
    ParamSpecType,
    ProperType,
    TupleType,
    Type,
    TypeAliasType,
    TypeType,
    TypeVarType,
    UnionType,
    UnpackType,
    flatten_nested_unions,
    get_proper_type,
    get_proper_types,
)

def flatten_types(types: Iterable[Type]) -> Iterable[Type]:
    for t in types:
        tp = get_proper_type(t)
        if isinstance(tp, UnionType):
            yield from flatten_types(tp.items)
        else:
            yield t

def strip_type(typ: Type) -> Type:
    """Make a copy of type without 'debugging info' (function name)."""
    orig_typ = typ
    typ = get_proper_type(typ)
    if isinstance(typ, CallableType):
        return typ.copy_modified(name=None)
    elif isinstance(typ, Overloaded):
        return Overloaded([cast(CallableType, strip_type(item)) for item in typ.items])
    else:
        return orig_typ

def is_invalid_recursive_alias(seen_nodes: set[TypeAlias], target: Type) -> bool:
    """Flag aliases like A = Union[int, A], T = tuple[int, *T] (and similar mutual aliases).

    Such aliases don't make much sense, and cause problems in later phases.
    """
    if isinstance(target, TypeAliasType):
        if target.alias in seen_nodes:
            return True
        assert target.alias, f"Unfixed type alias {target.type_ref}"
        return is_invalid_recursive_alias(seen_nodes | {target.alias}, get_proper_type(target))
    assert isinstance(target, ProperType)
    if not isinstance(target, (UnionType, TupleType)):
        return False
    if isinstance(target, UnionType):
        return any(is_invalid_recursive_alias(seen_nodes, item) for item in target.items)
    for item in target.items:
        if isinstance(item, UnpackType):
            if is_invalid_recursive_alias(seen_nodes, item.type):
                return True
    return False

def is_bad_type_type_item(item: Type) -> bool:
    """Prohibit types like Type[Type[...]].

    Such types are explicitly prohibited by PEP 484. Also, they cause problems
    with recursive types like T = Type[T], because internal representation of
    TypeType item is normalized (i.e. always a proper type).
    """
    item = get_proper_type(item)
    if isinstance(item, TypeType):
        return True
    if isinstance(item, UnionType):
        return any(
            isinstance(get_proper_type(i), TypeType) for i in flatten_nested_unions(item.items)
        )
    return False

def is_union_with_any(tp: Type) -> bool:
    """Is this a union with Any or a plain Any type?"""
    tp = get_proper_type(tp)
    if isinstance(tp, AnyType):
        return True
    if not isinstance(tp, UnionType):
        return False
    return any(is_union_with_any(t) for t in get_proper_types(tp.items))

def is_generic_instance(tp: Type) -> bool:
    tp = get_proper_type(tp)
    return isinstance(tp, Instance) and bool(tp.args)

def is_overlapping_none(t: Type) -> bool:
    t = get_proper_type(t)
    return isinstance(t, NoneType) or (
        isinstance(t, UnionType) and any(isinstance(get_proper_type(e), NoneType) for e in t.items)
    )

def remove_optional(typ: Type) -> Type:
    typ = get_proper_type(typ)
    if isinstance(typ, UnionType):
        return UnionType.make_union(
            [t for t in typ.items if not isinstance(get_proper_type(t), NoneType)]
        )
    else:
        return typ

def is_self_type_like(typ: Type, *, is_classmethod: bool) -> bool:
    """Does this look like a self-type annotation?"""
    typ = get_proper_type(typ)
    if not is_classmethod:
        return isinstance(typ, TypeVarType)
    if not isinstance(typ, TypeType):
        return False
    return isinstance(typ.item, TypeVarType)

def store_argument_type(
    defn: FuncItem, i: int, typ: CallableType, named_type: Callable[[str, list[Type]], Instance]
) -> None:
    arg_type = typ.arg_types[i]
    if typ.arg_kinds[i] == ARG_STAR:
        if isinstance(arg_type, ParamSpecType):
            pass
        elif isinstance(arg_type, UnpackType):
            unpacked_type = get_proper_type(arg_type.type)
            if isinstance(unpacked_type, TupleType):
                # Instead of using Tuple[Unpack[Tuple[...]]], just use Tuple[...]
                arg_type = unpacked_type
            elif (
                isinstance(unpacked_type, Instance)
                and unpacked_type.type.fullname == "builtins.tuple"
            ):
                arg_type = unpacked_type
            else:
                # TODO: verify that we can only have a TypeVarTuple here.
                arg_type = TupleType(
                    [arg_type],
                    fallback=named_type("builtins.tuple", [named_type("builtins.object", [])]),
                )
        else:
            # builtins.tuple[T] is typing.Tuple[T, ...]
            arg_type = named_type("builtins.tuple", [arg_type])
    elif typ.arg_kinds[i] == ARG_STAR2:
        if not isinstance(arg_type, ParamSpecType) and not typ.unpack_kwargs:
            arg_type = named_type("builtins.dict", [named_type("builtins.str", []), arg_type])
    defn.arguments[i].variable.type = arg_type


"""
A shared state for all TypeInfos that holds global cache and dependency information,
and potentially other mutable TypeInfo state. This module contains mutable global state.
"""

from __future__ import annotations

from typing import Dict, Final, Set, Tuple
from typing_extensions import TypeAlias as _TypeAlias

from mypy.nodes import VARIANCE_NOT_READY, TypeInfo
from mypy.server.trigger import make_trigger
from mypy.types import Instance, Type, TypeVarId, TypeVarType, get_proper_type

MAX_NEGATIVE_CACHE_TYPES: Final = 1000
MAX_NEGATIVE_CACHE_ENTRIES: Final = 10000

# Represents that the 'left' instance is a subtype of the 'right' instance
SubtypeRelationship: _TypeAlias = Tuple[Instance, Instance]

# A tuple encoding the specific conditions under which we performed the subtype check.
# (e.g. did we want a proper subtype? A regular subtype while ignoring variance?)
SubtypeKind: _TypeAlias = Tuple[bool, ...]

# A cache that keeps track of whether the given TypeInfo is a part of a particular
# subtype relationship
SubtypeCache: _TypeAlias = Dict[TypeInfo, Dict[SubtypeKind, Set[SubtypeRelationship]]]

class TypeState:
    """This class provides subtype caching to improve performance of subtype checks.
    It also holds protocol fine grained dependencies.

    Note: to avoid leaking global state, 'reset_all_subtype_caches()' should be called
    after a build has finished and after a daemon shutdown. This subtype cache only exists for
    performance reasons, resetting subtype caches for a class has no semantic effect.
    The protocol dependencies however are only stored here, and shouldn't be deleted unless
    not needed any more (e.g. during daemon shutdown).
    """

    # '_subtype_caches' keeps track of (subtype, supertype) pairs where supertypes are
    # instances of the given TypeInfo. The cache also keeps track of whether the check
    # was done in strict optional mode and of the specific *kind* of subtyping relationship,
    # which we represent as an arbitrary hashable tuple.
    # We need the caches, since subtype checks for structural types are very slow.
    _subtype_caches: Final[SubtypeCache]

    # Same as above but for negative subtyping results.
    _negative_subtype_caches: Final[SubtypeCache]

    # This contains protocol dependencies generated after running a full build,
    # or after an update. These dependencies are special because:
    #   * They are a global property of the program; i.e. some dependencies for imported
    #     classes can be generated in the importing modules.
    #   * Because of the above, they are serialized separately, after a full run,
    #     or a full update.
    # `proto_deps` can be None if after deserialization it turns out that they are
    # inconsistent with the other cache files (or an error occurred during deserialization).
    # A blocking error will be generated in this case, since we can't proceed safely.
    # For the description of kinds of protocol dependencies and corresponding examples,
    # see _snapshot_protocol_deps.
    proto_deps: dict[str, set[str]] | None

    # Protocols (full names) a given class attempted to implement.
    # Used to calculate fine grained protocol dependencies and optimize protocol
    # subtype cache invalidation in fine grained mode. For example, if we pass a value
    # of type a.A to a function expecting something compatible with protocol p.P,
    # we'd have 'a.A' -> {'p.P', ...} in the map. This map is flushed after every incremental
    # update.
    _attempted_protocols: Final[dict[str, set[str]]]
    # We also snapshot protocol members of the above protocols. For example, if we pass
    # a value of type a.A to a function expecting something compatible with Iterable, we'd have
    # 'a.A' -> {'__iter__', ...} in the map. This map is also flushed after every incremental
    # update. This map is needed to only generate dependencies like <a.A.__iter__> -> <a.A>
    # instead of a wildcard to avoid unnecessarily invalidating classes.
    _checked_against_members: Final[dict[str, set[str]]]
    # TypeInfos that appeared as a left type (subtype) in a subtype check since latest
    # dependency snapshot update. This is an optimisation for fine grained mode; during a full
    # run we only take a dependency snapshot at the very end, so this set will contain all
    # subtype-checked TypeInfos. After a fine grained update however, we can gather only new
    # dependencies generated from (typically) few TypeInfos that were subtype-checked
    # (i.e. appeared as r.h.s. in an assignment or an argument in a function call in
    # a re-checked target) during the update.
    _rechecked_types: Final[set[TypeInfo]]

    # The two attributes below are assumption stacks for subtyping relationships between
    # recursive type aliases. Normally, one would pass type assumptions as an additional
    # arguments to is_subtype(), but this would mean updating dozens of related functions
    # threading this through all callsites (see also comment for TypeInfo.assuming).
    _assuming: Final[list[tuple[Type, Type]]]
    _assuming_proper: Final[list[tuple[Type, Type]]]
    # Ditto for inference of generic constraints against recursive type aliases.
    inferring: Final[list[tuple[Type, Type]]]
    # Whether to use joins or unions when solving constraints, see checkexpr.py for details.
    infer_unions: bool
    # Whether to use new type inference algorithm that can infer polymorphic types.
    # This is temporary and will be removed soon when new algorithm is more polished.
    infer_polymorphic: bool

    # N.B: We do all of the accesses to these properties through
    # TypeState, instead of making these classmethods and accessing
    # via the cls parameter, since mypyc can optimize accesses to
    # Final attributes of a directly referenced type.

    def __init__(self) -> None:
        self._subtype_caches = {}
        self._negative_subtype_caches = {}
        self.proto_deps = {}
        self._attempted_protocols = {}
        self._checked_against_members = {}
        self._rechecked_types = set()
        self._assuming = []
        self._assuming_proper = []
        self.inferring = []
        self.infer_unions = False
        self.infer_polymorphic = False

    def is_assumed_subtype(self, left: Type, right: Type) -> bool:
        for l, r in reversed(self._assuming):
            if get_proper_type(l) == get_proper_type(left) and get_proper_type(
                r
            ) == get_proper_type(right):
                return True
        return False

    def is_assumed_proper_subtype(self, left: Type, right: Type) -> bool:
        for l, r in reversed(self._assuming_proper):
            if get_proper_type(l) == get_proper_type(left) and get_proper_type(
                r
            ) == get_proper_type(right):
                return True
        return False

    def get_assumptions(self, is_proper: bool) -> list[tuple[Type, Type]]:
        if is_proper:
            return self._assuming_proper
        return self._assuming

    def reset_all_subtype_caches(self) -> None:
        """Completely reset all known subtype caches."""
        self._subtype_caches.clear()
        self._negative_subtype_caches.clear()

    def reset_subtype_caches_for(self, info: TypeInfo) -> None:
        """Reset subtype caches (if any) for a given supertype TypeInfo."""
        if info in self._subtype_caches:
            self._subtype_caches[info].clear()
        if info in self._negative_subtype_caches:
            self._negative_subtype_caches[info].clear()

    def reset_all_subtype_caches_for(self, info: TypeInfo) -> None:
        """Reset subtype caches (if any) for a given supertype TypeInfo and its MRO."""
        for item in info.mro:
            self.reset_subtype_caches_for(item)

    def is_cached_subtype_check(self, kind: SubtypeKind, left: Instance, right: Instance) -> bool:
        if left.last_known_value is not None or right.last_known_value is not None:
            # If there is a literal last known value, give up. There
            # will be an unbounded number of potential types to cache,
            # making caching less effective.
            return False
        info = right.type
        cache = self._subtype_caches.get(info)
        if cache is None:
            return False
        subcache = cache.get(kind)
        if subcache is None:
            return False
        return (left, right) in subcache

    def is_cached_negative_subtype_check(
        self, kind: SubtypeKind, left: Instance, right: Instance
    ) -> bool:
        if left.last_known_value is not None or right.last_known_value is not None:
            # If there is a literal last known value, give up. There
            # will be an unbounded number of potential types to cache,
            # making caching less effective.
            return False
        info = right.type
        cache = self._negative_subtype_caches.get(info)
        if cache is None:
            return False
        subcache = cache.get(kind)
        if subcache is None:
            return False
        return (left, right) in subcache

    def record_subtype_cache_entry(
        self, kind: SubtypeKind, left: Instance, right: Instance
    ) -> None:
        if left.last_known_value is not None or right.last_known_value is not None:
            # These are unlikely to match, due to the large space of
            # possible values.  Avoid uselessly increasing cache sizes.
            return
        if any(
            (isinstance(tv, TypeVarType) and tv.variance == VARIANCE_NOT_READY)
            for tv in right.type.defn.type_vars
        ):
            # Variance indeterminate -- don't know the result
            return
        cache = self._subtype_caches.setdefault(right.type, {})
        cache.setdefault(kind, set()).add((left, right))

    def record_negative_subtype_cache_entry(
        self, kind: SubtypeKind, left: Instance, right: Instance
    ) -> None:
        if left.last_known_value is not None or right.last_known_value is not None:
            # These are unlikely to match, due to the large space of
            # possible values.  Avoid uselessly increasing cache sizes.
            return
        if len(self._negative_subtype_caches) > MAX_NEGATIVE_CACHE_TYPES:
            self._negative_subtype_caches.clear()
        cache = self._negative_subtype_caches.setdefault(right.type, {})
        subcache = cache.setdefault(kind, set())
        if len(subcache) > MAX_NEGATIVE_CACHE_ENTRIES:
            subcache.clear()
        cache.setdefault(kind, set()).add((left, right))

    def reset_protocol_deps(self) -> None:
        """Reset dependencies after a full run or before a daemon shutdown."""
        self.proto_deps = {}
        self._attempted_protocols.clear()
        self._checked_against_members.clear()
        self._rechecked_types.clear()

    def record_protocol_subtype_check(self, left_type: TypeInfo, right_type: TypeInfo) -> None:
        assert right_type.is_protocol
        self._rechecked_types.add(left_type)
        self._attempted_protocols.setdefault(left_type.fullname, set()).add(right_type.fullname)
        self._checked_against_members.setdefault(left_type.fullname, set()).update(
            right_type.protocol_members
        )

    def _snapshot_protocol_deps(self) -> dict[str, set[str]]:
        """Collect protocol attribute dependencies found so far from registered subtype checks.

        There are three kinds of protocol dependencies. For example, after a subtype check:

            x: Proto = C()

        the following dependencies will be generated:
            1. ..., <SuperProto[wildcard]>, <Proto[wildcard]> -> <Proto>
            2. ..., <B.attr>, <C.attr> -> <C> [for every attr in Proto members]
            3. <C> -> Proto  # this one to invalidate the subtype cache

        The first kind is generated immediately per-module in deps.py (see also an example there
        for motivation why it is needed). While two other kinds are generated here after all
        modules are type checked and we have recorded all the subtype checks. To understand these
        two kinds, consider a simple example:

            class A:
                def __iter__(self) -> Iterator[int]:
                    ...

            it: Iterable[int] = A()

        We add <a.A.__iter__> -> <a.A> to invalidate the assignment (module target in this case),
        whenever the signature of a.A.__iter__ changes. We also add <a.A> -> typing.Iterable,
        to invalidate the subtype caches of the latter. (Note that the same logic applies to
        proper subtype checks, and calculating meets and joins, if this involves calling
        'subtypes.is_protocol_implementation').
        """
        deps: dict[str, set[str]] = {}
        for info in self._rechecked_types:
            for attr in self._checked_against_members[info.fullname]:
                # The need for full MRO here is subtle, during an update, base classes of
                # a concrete class may not be reprocessed, so not all <B.x> -> <C.x> deps
                # are added.
                for base_info in info.mro[:-1]:
                    trigger = make_trigger(f"{base_info.fullname}.{attr}")
                    if "typing" in trigger or "builtins" in trigger:
                        # TODO: avoid everything from typeshed
                        continue
                    deps.setdefault(trigger, set()).add(make_trigger(info.fullname))
            for proto in self._attempted_protocols[info.fullname]:
                trigger = make_trigger(info.fullname)
                if "typing" in trigger or "builtins" in trigger:
                    continue
                # If any class that was checked against a protocol changes,
                # we need to reset the subtype cache for the protocol.
                #
                # Note: strictly speaking, the protocol doesn't need to be
                # re-checked, we only need to reset the cache, and its uses
                # elsewhere are still valid (unless invalidated by other deps).
                deps.setdefault(trigger, set()).add(proto)
        return deps

    def update_protocol_deps(self, second_map: dict[str, set[str]] | None = None) -> None:
        """Update global protocol dependency map.

        We update the global map incrementally, using a snapshot only from recently
        type checked types. If second_map is given, update it as well. This is currently used
        by FineGrainedBuildManager that maintains normal (non-protocol) dependencies.
        """
        assert self.proto_deps is not None, "This should not be called after failed cache load"
        new_deps = self._snapshot_protocol_deps()
        for trigger, targets in new_deps.items():
            self.proto_deps.setdefault(trigger, set()).update(targets)
        if second_map is not None:
            for trigger, targets in new_deps.items():
                second_map.setdefault(trigger, set()).update(targets)
        self._rechecked_types.clear()
        self._attempted_protocols.clear()
        self._checked_against_members.clear()

    def add_all_protocol_deps(self, deps: dict[str, set[str]]) -> None:
        """Add all known protocol dependencies to deps.

        This is used by tests and debug output, and also when collecting
        all collected or loaded dependencies as part of build.
        """
        self.update_protocol_deps()  # just in case
        if self.proto_deps is not None:
            for trigger, targets in self.proto_deps.items():
                deps.setdefault(trigger, set()).update(targets)

type_state: Final = TypeState()

def reset_global_state() -> None:
    """Reset most existing global state.

    Currently most of it is in this module. Few exceptions are strict optional status
    and functools.lru_cache.
    """
    type_state.reset_all_subtype_caches()
    type_state.reset_protocol_deps()
    TypeVarId.next_raw_id = 1


from __future__ import annotations

from typing import Iterable

from mypy_extensions import trait

from mypy.types import (
    AnyType,
    CallableArgument,
    CallableType,
    DeletedType,
    EllipsisType,
    ErasedType,
    Instance,
    LiteralType,
    NoneType,
    Overloaded,
    Parameters,
    ParamSpecType,
    PartialType,
    PlaceholderType,
    RawExpressionType,
    SyntheticTypeVisitor,
    TupleType,
    Type,
    TypeAliasType,
    TypedDictType,
    TypeList,
    TypeType,
    TypeVarTupleType,
    TypeVarType,
    UnboundType,
    UninhabitedType,
    UnionType,
    UnpackType,
)

@trait
class TypeTraverserVisitor(SyntheticTypeVisitor[None]):
    """Visitor that traverses all components of a type"""

    # Atomic types

    def visit_any(self, t: AnyType) -> None:
        pass

    def visit_uninhabited_type(self, t: UninhabitedType) -> None:
        pass

    def visit_none_type(self, t: NoneType) -> None:
        pass

    def visit_erased_type(self, t: ErasedType) -> None:
        pass

    def visit_deleted_type(self, t: DeletedType) -> None:
        pass

    def visit_type_var(self, t: TypeVarType) -> None:
        # Note that type variable values and upper bound aren't treated as
        # components, since they are components of the type variable
        # definition. We want to traverse everything just once.
        t.default.accept(self)

    def visit_param_spec(self, t: ParamSpecType) -> None:
        t.default.accept(self)

    def visit_parameters(self, t: Parameters) -> None:
        self.traverse_types(t.arg_types)

    def visit_type_var_tuple(self, t: TypeVarTupleType) -> None:
        t.default.accept(self)

    def visit_literal_type(self, t: LiteralType) -> None:
        t.fallback.accept(self)

    # Composite types

    def visit_instance(self, t: Instance) -> None:
        self.traverse_types(t.args)

    def visit_callable_type(self, t: CallableType) -> None:
        # FIX generics
        self.traverse_types(t.arg_types)
        t.ret_type.accept(self)
        t.fallback.accept(self)

        if t.type_guard is not None:
            t.type_guard.accept(self)

        if t.type_is is not None:
            t.type_is.accept(self)

    def visit_tuple_type(self, t: TupleType) -> None:
        self.traverse_types(t.items)
        t.partial_fallback.accept(self)

    def visit_typeddict_type(self, t: TypedDictType) -> None:
        self.traverse_types(t.items.values())
        t.fallback.accept(self)

    def visit_union_type(self, t: UnionType) -> None:
        self.traverse_types(t.items)

    def visit_overloaded(self, t: Overloaded) -> None:
        self.traverse_types(t.items)

    def visit_type_type(self, t: TypeType) -> None:
        t.item.accept(self)

    # Special types (not real types)

    def visit_callable_argument(self, t: CallableArgument) -> None:
        t.typ.accept(self)

    def visit_unbound_type(self, t: UnboundType) -> None:
        self.traverse_types(t.args)

    def visit_type_list(self, t: TypeList) -> None:
        self.traverse_types(t.items)

    def visit_ellipsis_type(self, t: EllipsisType) -> None:
        pass

    def visit_placeholder_type(self, t: PlaceholderType) -> None:
        self.traverse_types(t.args)

    def visit_partial_type(self, t: PartialType) -> None:
        pass

    def visit_raw_expression_type(self, t: RawExpressionType) -> None:
        pass

    def visit_type_alias_type(self, t: TypeAliasType) -> None:
        # TODO: sometimes we want to traverse target as well
        # We need to find a way to indicate explicitly the intent,
        # maybe make this method abstract (like for TypeTranslator)?
        self.traverse_types(t.args)

    def visit_unpack_type(self, t: UnpackType) -> None:
        t.type.accept(self)

    # Helpers

    def traverse_types(self, types: Iterable[Type]) -> None:
        for typ in types:
            typ.accept(self)


from __future__ import annotations

from mypy.erasetype import erase_typevars
from mypy.nodes import TypeInfo
from mypy.types import (
    Instance,
    ParamSpecType,
    ProperType,
    TupleType,
    Type,
    TypeOfAny,
    TypeVarLikeType,
    TypeVarTupleType,
    TypeVarType,
    UnpackType,
)
from mypy.typevartuples import erased_vars

def fill_typevars(typ: TypeInfo) -> Instance | TupleType:
    """For a non-generic type, return instance type representing the type.

    For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn].
    """
    tvs: list[Type] = []
    # TODO: why do we need to keep both typ.type_vars and typ.defn.type_vars?
    for i in range(len(typ.defn.type_vars)):
        tv: TypeVarLikeType | UnpackType = typ.defn.type_vars[i]
        # Change the line number
        if isinstance(tv, TypeVarType):
            tv = tv.copy_modified(line=-1, column=-1)
        elif isinstance(tv, TypeVarTupleType):
            tv = UnpackType(
                TypeVarTupleType(
                    tv.name,
                    tv.fullname,
                    tv.id,
                    tv.upper_bound,
                    tv.tuple_fallback,
                    tv.default,
                    line=-1,
                    column=-1,
                )
            )
        else:
            assert isinstance(tv, ParamSpecType)
            tv = ParamSpecType(
                tv.name,
                tv.fullname,
                tv.id,
                tv.flavor,
                tv.upper_bound,
                tv.default,
                line=-1,
                column=-1,
            )
        tvs.append(tv)
    inst = Instance(typ, tvs)
    # TODO: do we need to also handle typeddict_type here and below?
    if typ.tuple_type is None:
        return inst
    return typ.tuple_type.copy_modified(fallback=inst)

def fill_typevars_with_any(typ: TypeInfo) -> Instance | TupleType:
    """Apply a correct number of Any's as type arguments to a type."""
    inst = Instance(typ, erased_vars(typ.defn.type_vars, TypeOfAny.special_form))
    if typ.tuple_type is None:
        return inst
    erased_tuple_type = erase_typevars(typ.tuple_type, {tv.id for tv in typ.defn.type_vars})
    assert isinstance(erased_tuple_type, ProperType)
    if isinstance(erased_tuple_type, TupleType):
        return typ.tuple_type.copy_modified(fallback=inst)
    return inst

def has_no_typevars(typ: Type) -> bool:
    # We test if a type contains type variables by erasing all type variables
    # and comparing the result to the original type. We use comparison by equality that
    # in turn uses `__eq__` defined for types. Note: we can't use `is_same_type` because
    # it is not safe with unresolved forward references, while this function may be called
    # before forward references resolution patch pass. Note also that it is not safe to use
    # `is` comparison because `erase_typevars` doesn't preserve type identity.
    return typ == erase_typevars(typ)


"""Helpers for interacting with type var tuples."""

from __future__ import annotations

from typing import Sequence

from mypy.types import (
    AnyType,
    Instance,
    ProperType,
    Type,
    TypeVarLikeType,
    TypeVarTupleType,
    UnpackType,
    get_proper_type,
    split_with_prefix_and_suffix,
)

def split_with_instance(
    typ: Instance,
) -> tuple[tuple[Type, ...], tuple[Type, ...], tuple[Type, ...]]:
    assert typ.type.type_var_tuple_prefix is not None
    assert typ.type.type_var_tuple_suffix is not None
    return split_with_prefix_and_suffix(
        typ.args, typ.type.type_var_tuple_prefix, typ.type.type_var_tuple_suffix
    )

def extract_unpack(types: Sequence[Type]) -> ProperType | None:
    """Given a list of types, extracts either a single type from an unpack, or returns None."""
    if len(types) == 1:
        if isinstance(types[0], UnpackType):
            return get_proper_type(types[0].type)
    return None

def erased_vars(type_vars: Sequence[TypeVarLikeType], type_of_any: int) -> list[Type]:
    args: list[Type] = []
    for tv in type_vars:
        # Valid erasure for *Ts is *tuple[Any, ...], not just Any.
        if isinstance(tv, TypeVarTupleType):
            args.append(UnpackType(tv.tuple_fallback.copy_modified(args=[AnyType(type_of_any)])))
        else:
            args.append(AnyType(type_of_any))
    return args


"""Utility functions with no non-trivial dependencies."""

from __future__ import annotations

import hashlib
import io
import json
import os
import re
import shutil
import sys
import time
from importlib import resources as importlib_resources
from typing import IO, Any, Callable, Container, Final, Iterable, Sequence, Sized, TypeVar
from typing_extensions import Literal

orjson: Any
try:
    import orjson  # type: ignore[import-not-found, no-redef, unused-ignore]
except ImportError:
    orjson = None

try:
    import curses

    import _curses  # noqa: F401

    CURSES_ENABLED = True
except ImportError:
    CURSES_ENABLED = False

T = TypeVar("T")

if sys.version_info >= (3, 9):
    TYPESHED_DIR: Final = str(importlib_resources.files("mypy") / "typeshed")
else:
    with importlib_resources.path(
        "mypy",  # mypy-c doesn't support __package__
        "py.typed",  # a marker file for type information, we assume typeshed to live in the same dir
    ) as _resource:
        TYPESHED_DIR = str(_resource.parent / "typeshed")

ENCODING_RE: Final = re.compile(rb"([ \t\v]*#.*(\r\n?|\n))??[ \t\v]*#.*coding[:=][ \t]*([-\w.]+)")

DEFAULT_SOURCE_OFFSET: Final = 4
DEFAULT_COLUMNS: Final = 80

# At least this number of columns will be shown on each side of
# error location when printing source code snippet.
MINIMUM_WIDTH: Final = 20

# VT100 color code processing was added in Windows 10, but only the second major update,
# Threshold 2. Fortunately, everyone (even on LTSB, Long Term Support Branch) should
# have a version of Windows 10 newer than this. Note that Windows 8 and below are not
# supported, but are either going out of support, or make up only a few % of the market.
MINIMUM_WINDOWS_MAJOR_VT100: Final = 10
MINIMUM_WINDOWS_BUILD_VT100: Final = 10586

SPECIAL_DUNDERS: Final = frozenset(
    ("__init__", "__new__", "__call__", "__init_subclass__", "__class_getitem__")
)

def is_dunder(name: str, exclude_special: bool = False) -> bool:
    """Returns whether name is a dunder name.

    Args:
        exclude_special: Whether to return False for a couple special dunder methods.

    """
    if exclude_special and name in SPECIAL_DUNDERS:
        return False
    return name.startswith("__") and name.endswith("__")

def is_sunder(name: str) -> bool:
    return not is_dunder(name) and name.startswith("_") and name.endswith("_")

def split_module_names(mod_name: str) -> list[str]:
    """Return the module and all parent module names.

    So, if `mod_name` is 'a.b.c', this function will return
    ['a.b.c', 'a.b', and 'a'].
    """
    out = [mod_name]
    while "." in mod_name:
        mod_name = mod_name.rsplit(".", 1)[0]
        out.append(mod_name)
    return out

def module_prefix(modules: Iterable[str], target: str) -> str | None:
    result = split_target(modules, target)
    if result is None:
        return None
    return result[0]

def split_target(modules: Iterable[str], target: str) -> tuple[str, str] | None:
    remaining: list[str] = []
    while True:
        if target in modules:
            return target, ".".join(remaining)
        components = target.rsplit(".", 1)
        if len(components) == 1:
            return None
        target = components[0]
        remaining.insert(0, components[1])

def short_type(obj: object) -> str:
    """Return the last component of the type name of an object.

    If obj is None, return 'nil'. For example, if obj is 1, return 'int'.
    """
    if obj is None:
        return "nil"
    t = str(type(obj))
    return t.split(".")[-1].rstrip("'>")

def find_python_encoding(text: bytes) -> tuple[str, int]:
    """PEP-263 for detecting Python file encoding"""
    result = ENCODING_RE.match(text)
    if result:
        line = 2 if result.group(1) else 1
        encoding = result.group(3).decode("ascii")
        # Handle some aliases that Python is happy to accept and that are used in the wild.
        if encoding.startswith(("iso-latin-1-", "latin-1-")) or encoding == "iso-latin-1":
            encoding = "latin-1"
        return encoding, line
    else:
        default_encoding = "utf8"
        return default_encoding, -1

def bytes_to_human_readable_repr(b: bytes) -> str:
    """Converts bytes into some human-readable representation. Unprintable
    bytes such as the nul byte are escaped. For example:

        >>> b = bytes([102, 111, 111, 10, 0])
        >>> s = bytes_to_human_readable_repr(b)
        >>> print(s)
        foo\n\x00
        >>> print(repr(s))
        'foo\\n\\x00'
    """
    return repr(b)[2:-1]

class DecodeError(Exception):
    """Exception raised when a file cannot be decoded due to an unknown encoding type.

    Essentially a wrapper for the LookupError raised by `bytearray.decode`
    """

def decode_python_encoding(source: bytes) -> str:
    """Read the Python file with while obeying PEP-263 encoding detection.

    Returns the source as a string.
    """
    # check for BOM UTF-8 encoding and strip it out if present
    if source.startswith(b"\xef\xbb\xbf"):
        encoding = "utf8"
        source = source[3:]
    else:
        # look at first two lines and check if PEP-263 coding is present
        encoding, _ = find_python_encoding(source)

    try:
        source_text = source.decode(encoding)
    except LookupError as lookuperr:
        raise DecodeError(str(lookuperr)) from lookuperr
    return source_text

def read_py_file(path: str, read: Callable[[str], bytes]) -> list[str] | None:
    """Try reading a Python file as list of source lines.

    Return None if something goes wrong.
    """
    try:
        source = read(path)
    except OSError:
        return None
    else:
        try:
            source_lines = decode_python_encoding(source).splitlines()
        except DecodeError:
            return None
        return source_lines

def trim_source_line(line: str, max_len: int, col: int, min_width: int) -> tuple[str, int]:
    """Trim a line of source code to fit into max_len.

    Show 'min_width' characters on each side of 'col' (an error location). If either
    start or end is trimmed, this is indicated by adding '...' there.
    A typical result looks like this:
        ...some_variable = function_to_call(one_arg, other_arg) or...

    Return the trimmed string and the column offset to to adjust error location.
    """
    if max_len < 2 * min_width + 1:
        # In case the window is too tiny it is better to still show something.
        max_len = 2 * min_width + 1

    # Trivial case: line already fits in.
    if len(line) <= max_len:
        return line, 0

    # If column is not too large so that there is still min_width after it,
    # the line doesn't need to be trimmed at the start.
    if col + min_width < max_len:
        return line[:max_len] + "...", 0

    # Otherwise, if the column is not too close to the end, trim both sides.
    if col < len(line) - min_width - 1:
        offset = col - max_len + min_width + 1
        return "..." + line[offset : col + min_width + 1] + "...", offset - 3

    # Finally, if the column is near the end, just trim the start.
    return "..." + line[-max_len:], len(line) - max_len - 3

def get_mypy_comments(source: str) -> list[tuple[int, str]]:
    PREFIX = "# mypy: "
    # Don't bother splitting up the lines unless we know it is useful
    if PREFIX not in source:
        return []
    lines = source.split("\n")
    results = []
    for i, line in enumerate(lines):
        if line.startswith(PREFIX):
            results.append((i + 1, line[len(PREFIX) :]))

    return results

JUNIT_HEADER_TEMPLATE: Final = """<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="{errors}" failures="{failures}" name="mypy" skips="0" tests="{tests}" time="{time:.3f}">
"""

JUNIT_TESTCASE_FAIL_TEMPLATE: Final = """  <testcase classname="mypy" file="{filename}" line="1" name="{name}" time="{time:.3f}">
    <failure message="mypy produced messages">{text}</failure>
  </testcase>
"""

JUNIT_ERROR_TEMPLATE: Final = """  <testcase classname="mypy" file="mypy" line="1" name="mypy-py{ver}-{platform}" time="{time:.3f}">
    <error message="mypy produced errors">{text}</error>
  </testcase>
"""

JUNIT_TESTCASE_PASS_TEMPLATE: Final = """  <testcase classname="mypy" file="mypy" line="1" name="mypy-py{ver}-{platform}" time="{time:.3f}">
  </testcase>
"""

JUNIT_FOOTER: Final = """</testsuite>
"""

def _generate_junit_contents(
    dt: float,
    serious: bool,
    messages_by_file: dict[str | None, list[str]],
    version: str,
    platform: str,
) -> str:
    from xml.sax.saxutils import escape

    if serious:
        failures = 0
        errors = len(messages_by_file)
    else:
        failures = len(messages_by_file)
        errors = 0

    xml = JUNIT_HEADER_TEMPLATE.format(
        errors=errors,
        failures=failures,
        time=dt,
        # If there are no messages, we still write one "test" indicating success.
        tests=len(messages_by_file) or 1,
    )

    if not messages_by_file:
        xml += JUNIT_TESTCASE_PASS_TEMPLATE.format(time=dt, ver=version, platform=platform)
    else:
        for filename, messages in messages_by_file.items():
            if filename is not None:
                xml += JUNIT_TESTCASE_FAIL_TEMPLATE.format(
                    text=escape("\n".join(messages)),
                    filename=filename,
                    time=dt,
                    name="mypy-py{ver}-{platform} {filename}".format(
                        ver=version, platform=platform, filename=filename
                    ),
                )
            else:
                xml += JUNIT_TESTCASE_FAIL_TEMPLATE.format(
                    text=escape("\n".join(messages)),
                    filename="mypy",
                    time=dt,
                    name=f"mypy-py{version}-{platform}",
                )

    xml += JUNIT_FOOTER

    return xml

def write_junit_xml(
    dt: float,
    serious: bool,
    messages_by_file: dict[str | None, list[str]],
    path: str,
    version: str,
    platform: str,
) -> None:
    xml = _generate_junit_contents(dt, serious, messages_by_file, version, platform)

    # creates folders if needed
    xml_dirs = os.path.dirname(os.path.abspath(path))
    os.makedirs(xml_dirs, exist_ok=True)

    with open(path, "wb") as f:
        f.write(xml.encode("utf-8"))

class IdMapper:
    """Generate integer ids for objects.

    Unlike id(), these start from 0 and increment by 1, and ids won't
    get reused across the life-time of IdMapper.

    Assume objects don't redefine __eq__ or __hash__.
    """

    def __init__(self) -> None:
        self.id_map: dict[object, int] = {}
        self.next_id = 0

    def id(self, o: object) -> int:
        if o not in self.id_map:
            self.id_map[o] = self.next_id
            self.next_id += 1
        return self.id_map[o]

def get_prefix(fullname: str) -> str:
    """Drop the final component of a qualified name (e.g. ('x.y' -> 'x')."""
    return fullname.rsplit(".", 1)[0]

def correct_relative_import(
    cur_mod_id: str, relative: int, target: str, is_cur_package_init_file: bool
) -> tuple[str, bool]:
    if relative == 0:
        return target, True
    parts = cur_mod_id.split(".")
    rel = relative
    if is_cur_package_init_file:
        rel -= 1
    ok = len(parts) >= rel
    if rel != 0:
        cur_mod_id = ".".join(parts[:-rel])
    return cur_mod_id + (("." + target) if target else ""), ok

fields_cache: Final[dict[type[object], list[str]]] = {}

def get_class_descriptors(cls: type[object]) -> Sequence[str]:
    import inspect  # Lazy import for minor startup speed win

    # Maintain a cache of type -> attributes defined by descriptors in the class
    # (that is, attributes from __slots__ and C extension classes)
    if cls not in fields_cache:
        members = inspect.getmembers(
            cls, lambda o: inspect.isgetsetdescriptor(o) or inspect.ismemberdescriptor(o)
        )
        fields_cache[cls] = [x for x, y in members if x != "__weakref__" and x != "__dict__"]
    return fields_cache[cls]

def replace_object_state(
    new: object, old: object, copy_dict: bool = False, skip_slots: tuple[str, ...] = ()
) -> None:
    """Copy state of old node to the new node.

    This handles cases where there is __dict__ and/or attribute descriptors
    (either from slots or because the type is defined in a C extension module).

    Assume that both objects have the same __class__.
    """
    if hasattr(old, "__dict__"):
        if copy_dict:
            new.__dict__ = dict(old.__dict__)
        else:
            new.__dict__ = old.__dict__

    for attr in get_class_descriptors(old.__class__):
        if attr in skip_slots:
            continue
        try:
            if hasattr(old, attr):
                setattr(new, attr, getattr(old, attr))
            elif hasattr(new, attr):
                delattr(new, attr)
        # There is no way to distinguish getsetdescriptors that allow
        # writes from ones that don't (I think?), so we just ignore
        # AttributeErrors if we need to.
        # TODO: What about getsetdescriptors that act like properties???
        except AttributeError:
            pass

def is_sub_path_normabs(path: str, dir: str) -> bool:
    """Given two paths, return if path is a sub-path of dir.

    Moral equivalent of: Path(dir) in Path(path).parents

    Similar to the pathlib version:
    - Treats paths case-sensitively
    - Does not fully handle unnormalised paths (e.g. paths with "..")
    - Does not handle a mix of absolute and relative paths
    Unlike the pathlib version:
    - Fast
    - On Windows, assumes input has been slash normalised
    - Handles even fewer unnormalised paths (e.g. paths with "." and "//")

    As a result, callers should ensure that inputs have had os.path.abspath called on them
    (note that os.path.abspath will normalise)
    """
    if not dir.endswith(os.sep):
        dir += os.sep
    return path.startswith(dir)

if sys.platform == "linux" or sys.platform == "darwin":

    def os_path_join(path: str, b: str) -> str:
        # Based off of os.path.join, but simplified to str-only, 2 args and mypyc can compile it.
        if b.startswith("/") or not path:
            return b
        elif path.endswith("/"):
            return path + b
        else:
            return path + "/" + b

else:

    def os_path_join(a: str, p: str) -> str:
        return os.path.join(a, p)

def hard_exit(status: int = 0) -> None:
    """Kill the current process without fully cleaning up.

    This can be quite a bit faster than a normal exit() since objects are not freed.
    """
    sys.stdout.flush()
    sys.stderr.flush()
    os._exit(status)

def unmangle(name: str) -> str:
    """Remove internal suffixes from a short name."""
    return name.rstrip("'")

def get_unique_redefinition_name(name: str, existing: Container[str]) -> str:
    """Get a simple redefinition name not present among existing.

    For example, for name 'foo' we try 'foo-redefinition', 'foo-redefinition2',
    'foo-redefinition3', etc. until we find one that is not in existing.
    """
    r_name = name + "-redefinition"
    if r_name not in existing:
        return r_name

    i = 2
    while r_name + str(i) in existing:
        i += 1
    return r_name + str(i)

def check_python_version(program: str) -> None:
    """Report issues with the Python used to run mypy, dmypy, or stubgen"""
    # Check for known bad Python versions.
    if sys.version_info[:2] < (3, 8):  # noqa: UP036
        sys.exit(
            "Running {name} with Python 3.7 or lower is not supported; "
            "please upgrade to 3.8 or newer".format(name=program)
        )

def count_stats(messages: list[str]) -> tuple[int, int, int]:
    """Count total number of errors, notes and error_files in message list."""
    errors = [e for e in messages if ": error:" in e]
    error_files = {e.split(":")[0] for e in errors}
    notes = [e for e in messages if ": note:" in e]
    return len(errors), len(notes), len(error_files)

def split_words(msg: str) -> list[str]:
    """Split line of text into words (but not within quoted groups)."""
    next_word = ""
    res: list[str] = []
    allow_break = True
    for c in msg:
        if c == " " and allow_break:
            res.append(next_word)
            next_word = ""
            continue
        if c == '"':
            allow_break = not allow_break
        next_word += c
    res.append(next_word)
    return res

def get_terminal_width() -> int:
    """Get current terminal width if possible, otherwise return the default one."""
    return (
        int(os.getenv("MYPY_FORCE_TERMINAL_WIDTH", "0"))
        or shutil.get_terminal_size().columns
        or DEFAULT_COLUMNS
    )

def soft_wrap(msg: str, max_len: int, first_offset: int, num_indent: int = 0) -> str:
    """Wrap a long error message into few lines.

    Breaks will only happen between words, and never inside a quoted group
    (to avoid breaking types such as "Union[int, str]"). The 'first_offset' is
    the width before the start of first line.

    Pad every next line with 'num_indent' spaces. Every line will be at most 'max_len'
    characters, except if it is a single word or quoted group.

    For example:
               first_offset
        ------------------------
        path/to/file: error: 58: Some very long error message
            that needs to be split in separate lines.
            "Long[Type, Names]" are never split.
        ^^^^--------------------------------------------------
        num_indent           max_len
    """
    words = split_words(msg)
    next_line = words.pop(0)
    lines: list[str] = []
    while words:
        next_word = words.pop(0)
        max_line_len = max_len - num_indent if lines else max_len - first_offset
        # Add 1 to account for space between words.
        if len(next_line) + len(next_word) + 1 <= max_line_len:
            next_line += " " + next_word
        else:
            lines.append(next_line)
            next_line = next_word
    lines.append(next_line)
    padding = "\n" + " " * num_indent
    return padding.join(lines)

def hash_digest(data: bytes) -> str:
    """Compute a hash digest of some data.

    We use a cryptographic hash because we want a low probability of
    accidental collision, but we don't really care about any of the
    cryptographic properties.
    """
    return hashlib.sha1(data).hexdigest()

def parse_gray_color(cup: bytes) -> str:
    """Reproduce a gray color in ANSI escape sequence"""
    if sys.platform == "win32":
        assert False, "curses is not available on Windows"
    set_color = "".join([cup[:-1].decode(), "m"])
    gray = curses.tparm(set_color.encode("utf-8"), 1, 9).decode()
    return gray

def should_force_color() -> bool:
    env_var = os.getenv("MYPY_FORCE_COLOR", os.getenv("FORCE_COLOR", "0"))
    try:
        return bool(int(env_var))
    except ValueError:
        return bool(env_var)

class FancyFormatter:
    """Apply color and bold font to terminal output.

    This currently only works on Linux and Mac.
    """

    def __init__(
        self, f_out: IO[str], f_err: IO[str], hide_error_codes: bool, hide_success: bool = False
    ) -> None:
        self.hide_error_codes = hide_error_codes
        self.hide_success = hide_success

        # Check if we are in a human-facing terminal on a supported platform.
        if sys.platform not in ("linux", "darwin", "win32", "emscripten"):
            self.dummy_term = True
            return
        if not should_force_color() and (not f_out.isatty() or not f_err.isatty()):
            self.dummy_term = True
            return
        if sys.platform == "win32":
            self.dummy_term = not self.initialize_win_colors()
        elif sys.platform == "emscripten":
            self.dummy_term = not self.initialize_vt100_colors()
        else:
            self.dummy_term = not self.initialize_unix_colors()
        if not self.dummy_term:
            self.colors = {
                "red": self.RED,
                "green": self.GREEN,
                "blue": self.BLUE,
                "yellow": self.YELLOW,
                "none": "",
            }

    def initialize_vt100_colors(self) -> bool:
        """Return True if initialization was successful and we can use colors, False otherwise"""
        # Windows and Emscripten can both use ANSI/VT100 escape sequences for color
        assert sys.platform in ("win32", "emscripten")
        self.BOLD = "\033[1m"
        self.UNDER = "\033[4m"
        self.BLUE = "\033[94m"
        self.GREEN = "\033[92m"
        self.RED = "\033[91m"
        self.YELLOW = "\033[93m"
        self.NORMAL = "\033[0m"
        self.DIM = "\033[2m"
        return True

    def initialize_win_colors(self) -> bool:
        """Return True if initialization was successful and we can use colors, False otherwise"""
        # Windows ANSI escape sequences are only supported on Threshold 2 and above.
        # we check with an assert at runtime and an if check for mypy, as asserts do not
        # yet narrow platform
        assert sys.platform == "win32"
        if sys.platform == "win32":
            winver = sys.getwindowsversion()
            if (
                winver.major < MINIMUM_WINDOWS_MAJOR_VT100
                or winver.build < MINIMUM_WINDOWS_BUILD_VT100
            ):
                return False
            import ctypes

            kernel32 = ctypes.windll.kernel32
            ENABLE_PROCESSED_OUTPUT = 0x1
            ENABLE_WRAP_AT_EOL_OUTPUT = 0x2
            ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
            STD_OUTPUT_HANDLE = -11
            kernel32.SetConsoleMode(
                kernel32.GetStdHandle(STD_OUTPUT_HANDLE),
                ENABLE_PROCESSED_OUTPUT
                | ENABLE_WRAP_AT_EOL_OUTPUT
                | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
            )
            self.initialize_vt100_colors()
            return True
        return False

    def initialize_unix_colors(self) -> bool:
        """Return True if initialization was successful and we can use colors, False otherwise"""
        if sys.platform == "win32" or not CURSES_ENABLED:
            return False
        try:
            # setupterm wants a fd to potentially write an "initialization sequence".
            # We override sys.stdout for the daemon API so if stdout doesn't have an fd,
            # just give it /dev/null.
            try:
                fd = sys.stdout.fileno()
            except io.UnsupportedOperation:
                with open("/dev/null", "rb") as f:
                    curses.setupterm(fd=f.fileno())
            else:
                curses.setupterm(fd=fd)
        except curses.error:
            # Most likely terminfo not found.
            return False
        bold = curses.tigetstr("bold")
        under = curses.tigetstr("smul")
        set_color = curses.tigetstr("setaf")
        set_eseq = curses.tigetstr("cup")
        normal = curses.tigetstr("sgr0")

        if not (bold and under and set_color and set_eseq and normal):
            return False

        self.NORMAL = normal.decode()
        self.BOLD = bold.decode()
        self.UNDER = under.decode()
        self.DIM = parse_gray_color(set_eseq)
        self.BLUE = curses.tparm(set_color, curses.COLOR_BLUE).decode()
        self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode()
        self.RED = curses.tparm(set_color, curses.COLOR_RED).decode()
        self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode()
        return True

    def style(
        self,
        text: str,
        color: Literal["red", "green", "blue", "yellow", "none"],
        bold: bool = False,
        underline: bool = False,
        dim: bool = False,
    ) -> str:
        """Apply simple color and style (underlined or bold)."""
        if self.dummy_term:
            return text
        if bold:
            start = self.BOLD
        else:
            start = ""
        if underline:
            start += self.UNDER
        if dim:
            start += self.DIM
        return start + self.colors[color] + text + self.NORMAL

    def fit_in_terminal(
        self, messages: list[str], fixed_terminal_width: int | None = None
    ) -> list[str]:
        """Improve readability by wrapping error messages and trimming source code."""
        width = fixed_terminal_width or get_terminal_width()
        new_messages = messages.copy()
        for i, error in enumerate(messages):
            if ": error:" in error:
                loc, msg = error.split("error:", maxsplit=1)
                msg = soft_wrap(msg, width, first_offset=len(loc) + len("error: "))
                new_messages[i] = loc + "error:" + msg
            if error.startswith(" " * DEFAULT_SOURCE_OFFSET) and "^" not in error:
                # TODO: detecting source code highlights through an indent can be surprising.
                # Restore original error message and error location.
                error = error[DEFAULT_SOURCE_OFFSET:]
                marker_line = messages[i + 1]
                marker_column = marker_line.index("^")
                column = marker_column - DEFAULT_SOURCE_OFFSET
                if "~" not in marker_line:
                    marker = "^"
                else:
                    # +1 because both ends are included
                    marker = marker_line[marker_column : marker_line.rindex("~") + 1]

                # Let source have some space also on the right side, plus 6
                # to accommodate ... on each side.
                max_len = width - DEFAULT_SOURCE_OFFSET - 6
                source_line, offset = trim_source_line(error, max_len, column, MINIMUM_WIDTH)

                new_messages[i] = " " * DEFAULT_SOURCE_OFFSET + source_line
                # Also adjust the error marker position and trim error marker is needed.
                new_marker_line = " " * (DEFAULT_SOURCE_OFFSET + column - offset) + marker
                if len(new_marker_line) > len(new_messages[i]) and len(marker) > 3:
                    new_marker_line = new_marker_line[: len(new_messages[i]) - 3] + "..."
                new_messages[i + 1] = new_marker_line
        return new_messages

    def colorize(self, error: str) -> str:
        """Colorize an output line by highlighting the status and error code."""
        if ": error:" in error:
            loc, msg = error.split("error:", maxsplit=1)
            if self.hide_error_codes:
                return (
                    loc + self.style("error:", "red", bold=True) + self.highlight_quote_groups(msg)
                )
            codepos = msg.rfind("[")
            if codepos != -1:
                code = msg[codepos:]
                msg = msg[:codepos]
            else:
                code = ""  # no error code specified
            return (
                loc
                + self.style("error:", "red", bold=True)
                + self.highlight_quote_groups(msg)
                + self.style(code, "yellow")
            )
        elif ": note:" in error:
            loc, msg = error.split("note:", maxsplit=1)
            formatted = self.highlight_quote_groups(self.underline_link(msg))
            return loc + self.style("note:", "blue") + formatted
        elif error.startswith(" " * DEFAULT_SOURCE_OFFSET):
            # TODO: detecting source code highlights through an indent can be surprising.
            if "^" not in error:
                return self.style(error, "none", dim=True)
            return self.style(error, "red")
        else:
            return error

    def highlight_quote_groups(self, msg: str) -> str:
        """Make groups quoted with double quotes bold (including quotes).

        This is used to highlight types, attribute names etc.
        """
        if msg.count('"') % 2:
            # Broken error message, don't do any formatting.
            return msg
        parts = msg.split('"')
        out = ""
        for i, part in enumerate(parts):
            if i % 2 == 0:
                out += self.style(part, "none")
            else:
                out += self.style('"' + part + '"', "none", bold=True)
        return out

    def underline_link(self, note: str) -> str:
        """Underline a link in a note message (if any).

        This assumes there is at most one link in the message.
        """
        match = re.search(r"https?://\S*", note)
        if not match:
            return note
        start = match.start()
        end = match.end()
        return note[:start] + self.style(note[start:end], "none", underline=True) + note[end:]

    def format_success(self, n_sources: int, use_color: bool = True) -> str:
        """Format short summary in case of success.

        n_sources is total number of files passed directly on command line,
        i.e. excluding stubs and followed imports.
        """
        if self.hide_success:
            return ""

        msg = f"Success: no issues found in {n_sources} source file{plural_s(n_sources)}"
        if not use_color:
            return msg
        return self.style(msg, "green", bold=True)

    def format_error(
        self,
        n_errors: int,
        n_files: int,
        n_sources: int,
        *,
        blockers: bool = False,
        use_color: bool = True,
    ) -> str:
        """Format a short summary in case of errors."""
        msg = f"Found {n_errors} error{plural_s(n_errors)} in {n_files} file{plural_s(n_files)}"
        if blockers:
            msg += " (errors prevented further checking)"
        else:
            msg += f" (checked {n_sources} source file{plural_s(n_sources)})"
        if not use_color:
            return msg
        return self.style(msg, "red", bold=True)

def is_typeshed_file(typeshed_dir: str | None, file: str) -> bool:
    typeshed_dir = typeshed_dir if typeshed_dir is not None else TYPESHED_DIR
    try:
        return os.path.commonpath((typeshed_dir, os.path.abspath(file))) == typeshed_dir
    except ValueError:  # Different drives on Windows
        return False

def is_stub_package_file(file: str) -> bool:
    # Use hacky heuristics to check whether file is part of a PEP 561 stub package.
    if not file.endswith(".pyi"):
        return False
    return any(component.endswith("-stubs") for component in os.path.split(os.path.abspath(file)))

def unnamed_function(name: str | None) -> bool:
    return name is not None and name == "_"

time_ref = time.perf_counter_ns

def time_spent_us(t0: int) -> int:
    return int((time.perf_counter_ns() - t0) / 1000)

def plural_s(s: int | Sized) -> str:
    count = s if isinstance(s, int) else len(s)
    if count != 1:
        return "s"
    else:
        return ""

def quote_docstring(docstr: str) -> str:
    """Returns docstring correctly encapsulated in a single or double quoted form."""
    # Uses repr to get hint on the correct quotes and escape everything properly.
    # Creating multiline string for prettier output.
    docstr_repr = "\n".join(re.split(r"(?<=[^\\])\\n", repr(docstr)))

    if docstr_repr.startswith("'"):
        # Enforce double quotes when it's safe to do so.
        # That is when double quotes are not in the string
        # or when it doesn't end with a single quote.
        if '"' not in docstr_repr[1:-1] and docstr_repr[-2] != "'":
            return f'"""{docstr_repr[1:-1]}"""'
        return f"''{docstr_repr}''"
    else:
        return f'""{docstr_repr}""'

def json_dumps(obj: object, debug: bool = False) -> bytes:
    if orjson is not None:
        if debug:
            return orjson.dumps(obj, option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS)  # type: ignore[no-any-return]
        else:
            # TODO: If we don't sort keys here, testIncrementalInternalScramble fails
            # We should document exactly what is going on there
            return orjson.dumps(obj, option=orjson.OPT_SORT_KEYS)  # type: ignore[no-any-return]

    if debug:
        return json.dumps(obj, indent=2, sort_keys=True).encode("utf-8")
    else:
        # See above for sort_keys comment
        return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8")

def json_loads(data: bytes) -> Any:
    if orjson is not None:
        return orjson.loads(data)
    return json.loads(data)


from __future__ import annotations

import os

from mypy import git

# Base version.
# - Release versions have the form "1.2.3".
# - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440).
# - Before 1.0 we had the form "0.NNN".
__version__ = "1.13.0"
base_version = __version__

mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
if __version__.endswith("+dev") and git.is_git_repo(mypy_dir) and git.have_git():
    __version__ += "." + git.git_revision(mypy_dir).decode("utf-8")
    if git.is_dirty(mypy_dir):
        __version__ += ".dirty"
del mypy_dir


"""Generic abstract syntax tree node visitor"""

from __future__ import annotations

from abc import abstractmethod
from typing import TYPE_CHECKING, Generic, TypeVar

from mypy_extensions import mypyc_attr, trait

if TYPE_CHECKING:
    # break import cycle only needed for mypy
    import mypy.nodes
    import mypy.patterns

T = TypeVar("T")

@trait
@mypyc_attr(allow_interpreted_subclasses=True)
class ExpressionVisitor(Generic[T]):
    @abstractmethod
    def visit_int_expr(self, o: mypy.nodes.IntExpr) -> T:
        pass

    @abstractmethod
    def visit_str_expr(self, o: mypy.nodes.StrExpr) -> T:
        pass

    @abstractmethod
    def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> T:
        pass

    @abstractmethod
    def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> T:
        pass

    @abstractmethod
    def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> T:
        pass

    @abstractmethod
    def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> T:
        pass

    @abstractmethod
    def visit_star_expr(self, o: mypy.nodes.StarExpr) -> T:
        pass

    @abstractmethod
    def visit_name_expr(self, o: mypy.nodes.NameExpr) -> T:
        pass

    @abstractmethod
    def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> T:
        pass

    @abstractmethod
    def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> T:
        pass

    @abstractmethod
    def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> T:
        pass

    @abstractmethod
    def visit_call_expr(self, o: mypy.nodes.CallExpr) -> T:
        pass

    @abstractmethod
    def visit_op_expr(self, o: mypy.nodes.OpExpr) -> T:
        pass

    @abstractmethod
    def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> T:
        pass

    @abstractmethod
    def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> T:
        pass

    @abstractmethod
    def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> T:
        pass

    @abstractmethod
    def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> T:
        pass

    @abstractmethod
    def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> T:
        pass

    @abstractmethod
    def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> T:
        pass

    @abstractmethod
    def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> T:
        pass

    @abstractmethod
    def visit_list_expr(self, o: mypy.nodes.ListExpr) -> T:
        pass

    @abstractmethod
    def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> T:
        pass

    @abstractmethod
    def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> T:
        pass

    @abstractmethod
    def visit_set_expr(self, o: mypy.nodes.SetExpr) -> T:
        pass

    @abstractmethod
    def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> T:
        pass

    @abstractmethod
    def visit_type_application(self, o: mypy.nodes.TypeApplication) -> T:
        pass

    @abstractmethod
    def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> T:
        pass

    @abstractmethod
    def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> T:
        pass

    @abstractmethod
    def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> T:
        pass

    @abstractmethod
    def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> T:
        pass

    @abstractmethod
    def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> T:
        pass

    @abstractmethod
    def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> T:
        pass

    @abstractmethod
    def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> T:
        pass

    @abstractmethod
    def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> T:
        pass

    @abstractmethod
    def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> T:
        pass

    @abstractmethod
    def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> T:
        pass

    @abstractmethod
    def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> T:
        pass

    @abstractmethod
    def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> T:
        pass

    @abstractmethod
    def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> T:
        pass

    @abstractmethod
    def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> T:
        pass

    @abstractmethod
    def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> T:
        pass

    @abstractmethod
    def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> T:
        pass

    @abstractmethod
    def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> T:
        pass

    @abstractmethod
    def visit_temp_node(self, o: mypy.nodes.TempNode) -> T:
        pass

@trait
@mypyc_attr(allow_interpreted_subclasses=True)
class StatementVisitor(Generic[T]):
    # Definitions

    @abstractmethod
    def visit_assignment_stmt(self, o: mypy.nodes.AssignmentStmt) -> T:
        pass

    @abstractmethod
    def visit_for_stmt(self, o: mypy.nodes.ForStmt) -> T:
        pass

    @abstractmethod
    def visit_with_stmt(self, o: mypy.nodes.WithStmt) -> T:
        pass

    @abstractmethod
    def visit_del_stmt(self, o: mypy.nodes.DelStmt) -> T:
        pass

    @abstractmethod
    def visit_func_def(self, o: mypy.nodes.FuncDef) -> T:
        pass

    @abstractmethod
    def visit_overloaded_func_def(self, o: mypy.nodes.OverloadedFuncDef) -> T:
        pass

    @abstractmethod
    def visit_class_def(self, o: mypy.nodes.ClassDef) -> T:
        pass

    @abstractmethod
    def visit_global_decl(self, o: mypy.nodes.GlobalDecl) -> T:
        pass

    @abstractmethod
    def visit_nonlocal_decl(self, o: mypy.nodes.NonlocalDecl) -> T:
        pass

    @abstractmethod
    def visit_decorator(self, o: mypy.nodes.Decorator) -> T:
        pass

    # Module structure

    @abstractmethod
    def visit_import(self, o: mypy.nodes.Import) -> T:
        pass

    @abstractmethod
    def visit_import_from(self, o: mypy.nodes.ImportFrom) -> T:
        pass

    @abstractmethod
    def visit_import_all(self, o: mypy.nodes.ImportAll) -> T:
        pass

    # Statements

    @abstractmethod
    def visit_block(self, o: mypy.nodes.Block) -> T:
        pass

    @abstractmethod
    def visit_expression_stmt(self, o: mypy.nodes.ExpressionStmt) -> T:
        pass

    @abstractmethod
    def visit_operator_assignment_stmt(self, o: mypy.nodes.OperatorAssignmentStmt) -> T:
        pass

    @abstractmethod
    def visit_while_stmt(self, o: mypy.nodes.WhileStmt) -> T:
        pass

    @abstractmethod
    def visit_return_stmt(self, o: mypy.nodes.ReturnStmt) -> T:
        pass

    @abstractmethod
    def visit_assert_stmt(self, o: mypy.nodes.AssertStmt) -> T:
        pass

    @abstractmethod
    def visit_if_stmt(self, o: mypy.nodes.IfStmt) -> T:
        pass

    @abstractmethod
    def visit_break_stmt(self, o: mypy.nodes.BreakStmt) -> T:
        pass

    @abstractmethod
    def visit_continue_stmt(self, o: mypy.nodes.ContinueStmt) -> T:
        pass

    @abstractmethod
    def visit_pass_stmt(self, o: mypy.nodes.PassStmt) -> T:
        pass

    @abstractmethod
    def visit_raise_stmt(self, o: mypy.nodes.RaiseStmt) -> T:
        pass

    @abstractmethod
    def visit_try_stmt(self, o: mypy.nodes.TryStmt) -> T:
        pass

    @abstractmethod
    def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> T:
        pass

    @abstractmethod
    def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T:
        pass

@trait
@mypyc_attr(allow_interpreted_subclasses=True)
class PatternVisitor(Generic[T]):
    @abstractmethod
    def visit_as_pattern(self, o: mypy.patterns.AsPattern) -> T:
        pass

    @abstractmethod
    def visit_or_pattern(self, o: mypy.patterns.OrPattern) -> T:
        pass

    @abstractmethod
    def visit_value_pattern(self, o: mypy.patterns.ValuePattern) -> T:
        pass

    @abstractmethod
    def visit_singleton_pattern(self, o: mypy.patterns.SingletonPattern) -> T:
        pass

    @abstractmethod
    def visit_sequence_pattern(self, o: mypy.patterns.SequencePattern) -> T:
        pass

    @abstractmethod
    def visit_starred_pattern(self, o: mypy.patterns.StarredPattern) -> T:
        pass

    @abstractmethod
    def visit_mapping_pattern(self, o: mypy.patterns.MappingPattern) -> T:
        pass

    @abstractmethod
    def visit_class_pattern(self, o: mypy.patterns.ClassPattern) -> T:
        pass

@trait
@mypyc_attr(allow_interpreted_subclasses=True)
class NodeVisitor(Generic[T], ExpressionVisitor[T], StatementVisitor[T], PatternVisitor[T]):
    """Empty base class for parse tree node visitors.

    The T type argument specifies the return type of the visit
    methods. As all methods defined here return None by default,
    subclasses do not always need to override all the methods.

    TODO: make the default return value explicit, then turn on
          empty body checking in mypy_self_check.ini.
    """

    # Not in superclasses:

    def visit_mypy_file(self, o: mypy.nodes.MypyFile) -> T:
        pass

    # TODO: We have a visit_var method, but no visit_typeinfo or any
    # other non-Statement SymbolNode (accepting those will raise a
    # runtime error). Maybe this should be resolved in some direction.
    def visit_var(self, o: mypy.nodes.Var) -> T:
        pass

    # Module structure

    def visit_import(self, o: mypy.nodes.Import) -> T:
        pass

    def visit_import_from(self, o: mypy.nodes.ImportFrom) -> T:
        pass

    def visit_import_all(self, o: mypy.nodes.ImportAll) -> T:
        pass

    # Definitions

    def visit_func_def(self, o: mypy.nodes.FuncDef) -> T:
        pass

    def visit_overloaded_func_def(self, o: mypy.nodes.OverloadedFuncDef) -> T:
        pass

    def visit_class_def(self, o: mypy.nodes.ClassDef) -> T:
        pass

    def visit_global_decl(self, o: mypy.nodes.GlobalDecl) -> T:
        pass

    def visit_nonlocal_decl(self, o: mypy.nodes.NonlocalDecl) -> T:
        pass

    def visit_decorator(self, o: mypy.nodes.Decorator) -> T:
        pass

    def visit_type_alias(self, o: mypy.nodes.TypeAlias) -> T:
        pass

    def visit_placeholder_node(self, o: mypy.nodes.PlaceholderNode) -> T:
        pass

    # Statements

    def visit_block(self, o: mypy.nodes.Block) -> T:
        pass

    def visit_expression_stmt(self, o: mypy.nodes.ExpressionStmt) -> T:
        pass

    def visit_assignment_stmt(self, o: mypy.nodes.AssignmentStmt) -> T:
        pass

    def visit_operator_assignment_stmt(self, o: mypy.nodes.OperatorAssignmentStmt) -> T:
        pass

    def visit_while_stmt(self, o: mypy.nodes.WhileStmt) -> T:
        pass

    def visit_for_stmt(self, o: mypy.nodes.ForStmt) -> T:
        pass

    def visit_return_stmt(self, o: mypy.nodes.ReturnStmt) -> T:
        pass

    def visit_assert_stmt(self, o: mypy.nodes.AssertStmt) -> T:
        pass

    def visit_del_stmt(self, o: mypy.nodes.DelStmt) -> T:
        pass

    def visit_if_stmt(self, o: mypy.nodes.IfStmt) -> T:
        pass

    def visit_break_stmt(self, o: mypy.nodes.BreakStmt) -> T:
        pass

    def visit_continue_stmt(self, o: mypy.nodes.ContinueStmt) -> T:
        pass

    def visit_pass_stmt(self, o: mypy.nodes.PassStmt) -> T:
        pass

    def visit_raise_stmt(self, o: mypy.nodes.RaiseStmt) -> T:
        pass

    def visit_try_stmt(self, o: mypy.nodes.TryStmt) -> T:
        pass

    def visit_with_stmt(self, o: mypy.nodes.WithStmt) -> T:
        pass

    def visit_match_stmt(self, o: mypy.nodes.MatchStmt) -> T:
        pass

    def visit_type_alias_stmt(self, o: mypy.nodes.TypeAliasStmt) -> T:
        pass

    # Expressions (default no-op implementation)

    def visit_int_expr(self, o: mypy.nodes.IntExpr) -> T:
        pass

    def visit_str_expr(self, o: mypy.nodes.StrExpr) -> T:
        pass

    def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> T:
        pass

    def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> T:
        pass

    def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> T:
        pass

    def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> T:
        pass

    def visit_star_expr(self, o: mypy.nodes.StarExpr) -> T:
        pass

    def visit_name_expr(self, o: mypy.nodes.NameExpr) -> T:
        pass

    def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> T:
        pass

    def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> T:
        pass

    def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> T:
        pass

    def visit_call_expr(self, o: mypy.nodes.CallExpr) -> T:
        pass

    def visit_op_expr(self, o: mypy.nodes.OpExpr) -> T:
        pass

    def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> T:
        pass

    def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> T:
        pass

    def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> T:
        pass

    def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> T:
        pass

    def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> T:
        pass

    def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> T:
        pass

    def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> T:
        pass

    def visit_list_expr(self, o: mypy.nodes.ListExpr) -> T:
        pass

    def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> T:
        pass

    def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> T:
        pass

    def visit_set_expr(self, o: mypy.nodes.SetExpr) -> T:
        pass

    def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> T:
        pass

    def visit_type_application(self, o: mypy.nodes.TypeApplication) -> T:
        pass

    def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> T:
        pass

    def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> T:
        pass

    def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> T:
        pass

    def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> T:
        pass

    def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> T:
        pass

    def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> T:
        pass

    def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> T:
        pass

    def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> T:
        pass

    def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> T:
        pass

    def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> T:
        pass

    def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> T:
        pass

    def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> T:
        pass

    def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> T:
        pass

    def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> T:
        pass

    def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> T:
        pass

    def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> T:
        pass

    def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> T:
        pass

    def visit_temp_node(self, o: mypy.nodes.TempNode) -> T:
        pass

    # Patterns

    def visit_as_pattern(self, o: mypy.patterns.AsPattern) -> T:
        pass

    def visit_or_pattern(self, o: mypy.patterns.OrPattern) -> T:
        pass

    def visit_value_pattern(self, o: mypy.patterns.ValuePattern) -> T:
        pass

    def visit_singleton_pattern(self, o: mypy.patterns.SingletonPattern) -> T:
        pass

    def visit_sequence_pattern(self, o: mypy.patterns.SequencePattern) -> T:
        pass

    def visit_starred_pattern(self, o: mypy.patterns.StarredPattern) -> T:
        pass

    def visit_mapping_pattern(self, o: mypy.patterns.MappingPattern) -> T:
        pass

    def visit_class_pattern(self, o: mypy.patterns.ClassPattern) -> T:
        pass


import typing
from collections.abc import Iterator, Mapping, Sequence

import attrs
import mypy.nodes
import mypy.types
import mypy.visitor

from puya import log
from puya.errors import InternalError
from puya.parse import SourceLocation
from puya.program_refs import ContractReference
from puyapy.awst_build import constants, pytypes
from puyapy.awst_build.arc4_decorators import get_arc4_abimethod_data
from puyapy.awst_build.base_mypy_visitor import BaseMyPyStatementVisitor
from puyapy.awst_build.context import ASTConversionModuleContext
from puyapy.awst_build.utils import get_decorators_by_fullname
from puyapy.models import (
    ARC4ABIMethodData,
    ContractFragmentBase,
    ContractFragmentMethod,
    ContractFragmentStorage,
)

logger = log.get_logger(__name__)

@attrs.frozen
class _ARC4ClientFragment(ContractFragmentBase):
    id: ContractReference
    _methods: dict[str, ContractFragmentMethod] = attrs.field(factory=dict, init=False)
    mro: Sequence[ContractFragmentBase] = attrs.field(default=(), init=False)
    _symbols: dict[str, pytypes.PyType] = attrs.field(factory=dict, init=False)

    def add_method(self, data: ARC4ABIMethodData, source_location: SourceLocation) -> None:
        method = ContractFragmentMethod(
            member_name=data.member_name,
            metadata=data,
            is_trivial=True,
            source_location=source_location,
        )
        set_result = self._methods.setdefault(method.member_name, method)
        if set_result is method:
            self._symbols[data.member_name] = data.pytype
        else:
            logger.info(
                f"previous definition of {method.member_name} was here",
                location=set_result.source_location,
            )
            logger.error(
                f"redefinition of {method.member_name}",
                location=method.source_location,
            )

    @typing.override
    @property
    def symbols(self) -> Mapping[str, pytypes.PyType | None]:
        return self._symbols

    @typing.override
    def resolve_method(
        self, name: str, *, include_inherited: bool = True
    ) -> ContractFragmentMethod | None:
        return self._methods.get(name)

    @typing.override
    def methods(
        self, *, include_inherited: bool = True, include_overridden: bool = False
    ) -> Iterator[ContractFragmentMethod]:
        yield from self._methods.values()

    @typing.override
    def resolve_storage(
        self, name: str, *, include_inherited: bool = True
    ) -> ContractFragmentStorage | None:
        return None

    @typing.override
    def state(self, *, include_inherited: bool = True) -> Iterator[ContractFragmentStorage]:
        yield from ()

class ARC4ClientASTVisitor(BaseMyPyStatementVisitor[ARC4ABIMethodData | None]):
    @classmethod
    def visit(cls, context: ASTConversionModuleContext, class_def: mypy.nodes.ClassDef) -> None:
        visitor = ARC4ClientASTVisitor(context)
        cref = ContractReference(class_def.info.fullname)
        fragment = _ARC4ClientFragment(id=cref)
        for stmt in class_def.defs.body:
            stmt_loc = context.node_location(stmt)
            with context.log_exceptions(fallback_location=stmt_loc):
                if (abi_method_data := stmt.accept(visitor)) is not None:
                    fragment.add_method(abi_method_data, stmt_loc)
        context.add_contract_fragment(fragment)

    def empty_statement(self, _stmt: mypy.nodes.Statement) -> None:
        return None

    def visit_function(
        self,
        func_def: mypy.nodes.FuncDef,
        decorator: mypy.nodes.Decorator | None,
    ) -> ARC4ABIMethodData | None:
        func_loc = self._location(func_def)
        if decorator is not None:
            dec_by_fullname = get_decorators_by_fullname(self.context, decorator, original=True)
            abimethod_dec = dec_by_fullname.pop(constants.ABIMETHOD_DECORATOR, None)
            for dec_fullname, dec in dec_by_fullname.items():
                logger.error(
                    f'unsupported decorator in ARC4Client: "{dec_fullname}"',
                    location=self._location(dec),
                )
            if abimethod_dec is not None:
                return get_arc4_abimethod_data(self.context, abimethod_dec, func_def)
        logger.error(f"expected an {constants.ABIMETHOD_DECORATOR} decorator", location=func_loc)
        return None

    def visit_block(self, o: mypy.nodes.Block) -> None:
        raise InternalError("shouldn't get here", self._location(o))

    def visit_return_stmt(self, stmt: mypy.nodes.ReturnStmt) -> None:
        self._error("illegal Python syntax, return in class body", location=stmt)

    def visit_class_def(self, cdef: mypy.nodes.ClassDef) -> None:
        self._error("nested classes are not supported", location=cdef)

    def _unsupported_stmt(self, kind: str, stmt: mypy.nodes.Statement) -> None:
        self._error(
            f"{kind} statements are not supported in the class body of an ARC4Client",
            location=stmt,
        )

    def visit_assignment_stmt(self, stmt: mypy.nodes.AssignmentStmt) -> None:
        self._unsupported_stmt("assignment", stmt)

    def visit_operator_assignment_stmt(self, stmt: mypy.nodes.OperatorAssignmentStmt) -> None:
        self._unsupported_stmt("operator assignment", stmt)

    def visit_expression_stmt(self, stmt: mypy.nodes.ExpressionStmt) -> None:
        if isinstance(stmt.expr, mypy.nodes.StrExpr):
            # ignore class docstring, already extracted
            # TODO: should we capture field "docstrings"?
            pass
        else:
            self._unsupported_stmt("expression statement", stmt)

    def visit_if_stmt(self, stmt: mypy.nodes.IfStmt) -> None:
        self._unsupported_stmt("if", stmt)

    def visit_while_stmt(self, stmt: mypy.nodes.WhileStmt) -> None:
        self._unsupported_stmt("while", stmt)

    def visit_for_stmt(self, stmt: mypy.nodes.ForStmt) -> None:
        self._unsupported_stmt("for", stmt)

    def visit_break_stmt(self, stmt: mypy.nodes.BreakStmt) -> None:
        self._unsupported_stmt("break", stmt)

    def visit_continue_stmt(self, stmt: mypy.nodes.ContinueStmt) -> None:
        self._unsupported_stmt("continue", stmt)

    def visit_assert_stmt(self, stmt: mypy.nodes.AssertStmt) -> None:
        self._unsupported_stmt("assert", stmt)

    def visit_del_stmt(self, stmt: mypy.nodes.DelStmt) -> None:
        self._unsupported_stmt("del", stmt)

    def visit_match_stmt(self, stmt: mypy.nodes.MatchStmt) -> None:
        self._unsupported_stmt("match", stmt)

    def visit_type_alias_stmt(self, stmt: mypy.nodes.TypeAliasStmt) -> None:
        self._unsupported_stmt("type", stmt)


import re
import textwrap
from collections.abc import Iterable, Sequence
from pathlib import Path

from puya import (
    arc56_models as arc56,
    log,
)
from puya.avm import OnCompletionAction
from puya.errors import CodeError
from puya.utils import make_path_relative_to_cwd
from puyapy.awst_build import constants
from puyapy.awst_build.arc4_utils import arc4_to_pytype

logger = log.get_logger(__name__)

_AUTO_GENERATED_COMMENT = "# This file is auto-generated, do not modify"
_INDENT = " " * 4
_NON_ALPHA_NUMERIC = re.compile(r"\W+")

def write_arc4_client(contract: arc56.Contract, out_dir: Path) -> None:
    stub_path = out_dir / f"client_{contract.name}.py"
    if _can_overwrite_auto_generated_file(stub_path):
        logger.info(f"writing {make_path_relative_to_cwd(stub_path)}")
        stub_text = _ClientGenerator.generate(contract)
        stub_path.write_text(stub_text)
    else:
        logger.error(
            f"Not outputting {make_path_relative_to_cwd(stub_path)} "
            "since content does not appear to be auto-generated"
        )

def _can_overwrite_auto_generated_file(path: Path) -> bool:
    return not path.exists() or path.read_text().startswith(_AUTO_GENERATED_COMMENT)

class _ClientGenerator:
    def __init__(self, contract: arc56.Contract):
        self.contract = contract
        self.python_methods = set[str]()
        self.struct_to_class = dict[str, str]()
        self.reserved_class_names = set[str]()
        self.reserved_method_names = set[str]()
        self.class_decls = list[str]()

    @classmethod
    def generate(cls, contract: arc56.Contract) -> str:
        return cls(contract)._gen()  # noqa: SLF001

    def _gen(self) -> str:
        # generate class definitions for any referenced structs in methods
        # don't generate from self.contract.structs as it may contain other struct definitions
        client_class = self._unique_class(self.contract.name)
        for method in self.contract.methods:
            for struct in filter(None, (method.returns.struct, *(a.struct for a in method.args))):
                if struct not in self.struct_to_class and (
                    struct_def := self.contract.structs.get(struct)
                ):
                    self._prepare_struct_class(struct, struct_def)
        return "\n".join(
            (
                _AUTO_GENERATED_COMMENT,
                "# flake8: noqa",  # this works for flake8 and ruff
                "# fmt: off",  # disable formatting"
                "import typing",
                "",
                "import algopy",
                "",
                *self.class_decls,
                "",
                f"class {client_class}(algopy.arc4.ARC4Client, typing.Protocol):",
                *_docstring(self.contract.desc),
                *self._gen_methods(),
            )
        )

    def _prepare_struct_class(self, name: str, fields: Sequence[arc56.StructField]) -> str:
        python_name = self._unique_class(name)
        self.struct_to_class[name] = python_name
        lines = [f"class {python_name}(algopy.arc4.Struct):"]
        for field in fields:
            if isinstance(field.type, str):
                typ = self._get_client_type(field.type)
            else:
                # generate anonymous struct type
                anon_struct = f"{name}_{field.name}"
                typ = self._prepare_struct_class(anon_struct, field.type)
            lines.append(_indent(f"{field.name}: {typ}"))
        if self.class_decls:
            self.class_decls.append("")
        self.class_decls.extend(lines)
        return python_name

    def _get_client_type(self, typ: str) -> str:
        # map ABI / AVM type to algopy type
        if typ == arc56.AVMType.uint64:
            return "algopy.UInt64"
        elif typ == arc56.AVMType.bytes:
            return "algopy.Bytes"
        elif struct := self.contract.structs.get(typ):
            try:
                # use existing definition
                return self.struct_to_class[typ]
            except KeyError:
                # generate and return class name
                return self._prepare_struct_class(typ, struct)
        else:
            return str(arc4_to_pytype(typ, None))

    def _unique_class(self, name: str) -> str:
        base_name = name = _get_python_safe_name(name)
        seq = 1
        while name in self.reserved_class_names:
            seq += 1
            name = f"{base_name}{seq}"

        self.reserved_class_names.add(name)
        return name

    def _unique_method(self, name: str) -> str:
        base_name = name = _get_python_safe_name(name)
        seq = 1
        while name in self.reserved_method_names:
            seq += 1
            name = f"{base_name}{seq}"

        self.reserved_method_names.add(name)
        return name

    def _gen_methods(self) -> Iterable[str]:
        if not self.contract.methods:
            yield _indent("pass")
            yield ""
        else:
            for method in self.contract.methods:
                yield self._gen_method(method)

    def _gen_method(self, method: arc56.Method) -> str:
        return_type = self._get_client_type(method.returns.struct or method.returns.type)
        python_method = self._unique_method(method.name)
        return _indent(
            (
                _arc4_method_to_decorator(python_method, method),
                f"def {python_method}(",
                _indent(
                    (
                        "self,",
                        *(self._gen_arg(arg) for arg in method.args),
                    )
                ),
                f") -> {return_type}:" + ("" if method.desc else " ..."),
                *_docstring(method.desc),
                "",
            )
        )

    def _gen_arg(self, arg: arc56.MethodArg) -> str:
        python_type = self._get_client_type(arg.struct or arg.type)
        return f"{arg.name}: {python_type},"

def _docstring(desc: str | None) -> list[str]:
    if desc is None:
        return []
    return _indent(
        [
            '"""',
            *desc.splitlines(),
            '"""',
        ]
    ).splitlines()

def _arc4_method_to_decorator(python_method: str, method: arc56.Method) -> str:
    abimethod_args = dict[str, object]()
    if method.name != python_method:
        abimethod_args["name"] = method.name
    if method.readonly:
        abimethod_args["readonly"] = True
    if not _compatible_actions(method.actions.create, method.actions.call):
        # TODO: support this, once decorators support it
        raise CodeError(
            f"unsupported on completion combination for generating an ARC4 client"
            f" for method: {method.name}"
        )
    actions = sorted(
        {*method.actions.create, *method.actions.call}, key=lambda a: OnCompletionAction[a]
    )
    if set(actions) != {OnCompletionAction.NoOp.name}:
        abimethod_args["allow_actions"] = actions
    if method.actions.create and method.actions.call:
        abimethod_args["create"] = "allow"
    elif method.actions.create:
        abimethod_args["create"] = "require"
    else:
        # disallow is default
        pass
    kwargs = ", ".join(f"{name}={value!r}" for name, value in abimethod_args.items())
    decorator = f"@{constants.ABIMETHOD_DECORATOR_ALIAS}"
    if kwargs:
        decorator += f"({kwargs})"
    return decorator

def _compatible_actions(create: Sequence[str], call: Sequence[str]) -> bool:
    if not create:
        return True
    if not call:
        return True
    # if both collections are present, then they are compatible if everything in
    # create is also in call
    return all(a in call for a in create)

def _indent(lines: Iterable[str] | str) -> str:
    if not isinstance(lines, str):
        lines = "\n".join(lines)
    return textwrap.indent(lines, _INDENT)

def _get_python_safe_name(name: str) -> str:
    return _NON_ALPHA_NUMERIC.sub("_", name)