"""Calculate some properties of classes.

These happen after semantic analysis and before type checking.
"""

from __future__ import annotations

from typing import Final

from mypy.errors import Errors
from mypy.nodes import (
    IMPLICITLY_ABSTRACT,
    IS_ABSTRACT,
    CallExpr,
    Decorator,
    FuncDef,
    Node,
    OverloadedFuncDef,
    PromoteExpr,
    SymbolTable,
    TypeInfo,
    Var,
)
from mypy.options import Options
from mypy.types import MYPYC_NATIVE_INT_NAMES, Instance, ProperType

# Hard coded type promotions (shared between all Python versions).
# These add extra ad-hoc edges to the subtyping relation. For example,
# int is considered a subtype of float, even though there is no
# subclass relationship.
# Note that the bytearray -> bytes promotion is a little unsafe
# as some functions only accept bytes objects. Here convenience
# trumps safety.
TYPE_PROMOTIONS: Final = {
    "builtins.int": "float",
    "builtins.float": "complex",
    "builtins.bytearray": "bytes",
    "builtins.memoryview": "bytes",
}

def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: Errors) -> None:
    """Calculate abstract status of a class.

    Set is_abstract of the type to True if the type has an unimplemented
    abstract attribute.  Also compute a list of abstract attributes.
    Report error is required ABCMeta metaclass is missing.
    """
    typ.is_abstract = False
    typ.abstract_attributes = []
    if typ.typeddict_type:
        return  # TypedDict can't be abstract
    concrete: set[str] = set()
    # List of abstract attributes together with their abstract status
    abstract: list[tuple[str, int]] = []
    abstract_in_this_class: list[str] = []
    if typ.is_newtype:
        # Special case: NewTypes are considered as always non-abstract, so they can be used as:
        #     Config = NewType('Config', Mapping[str, str])
        #     default = Config({'cannot': 'modify'})  # OK
        return
    for base in typ.mro:
        for name, symnode in base.names.items():
            node = symnode.node
            if isinstance(node, OverloadedFuncDef):
                # Unwrap an overloaded function definition. We can just
                # check arbitrarily the first overload item. If the
                # different items have a different abstract status, there
                # should be an error reported elsewhere.
                if node.items:  # can be empty for invalid overloads
                    func: Node | None = node.items[0]
                else:
                    func = None
            else:
                func = node
            if isinstance(func, Decorator):
                func = func.func
            if isinstance(func, FuncDef):
                if (
                    func.abstract_status in (IS_ABSTRACT, IMPLICITLY_ABSTRACT)
                    and name not in concrete
                ):
                    typ.is_abstract = True
                    abstract.append((name, func.abstract_status))
                    if base is typ:
                        abstract_in_this_class.append(name)
            elif isinstance(node, Var):
                if node.is_abstract_var and name not in concrete:
                    typ.is_abstract = True
                    abstract.append((name, IS_ABSTRACT))
                    if base is typ:
                        abstract_in_this_class.append(name)
            concrete.add(name)
    # In stubs, abstract classes need to be explicitly marked because it is too
    # easy to accidentally leave a concrete class abstract by forgetting to
    # implement some methods.
    typ.abstract_attributes = sorted(abstract)
    if is_stub_file:
        if typ.declared_metaclass and typ.declared_metaclass.type.has_base("abc.ABCMeta"):
            return
        if typ.is_protocol:
            return
        if abstract and not abstract_in_this_class:

            def report(message: str, severity: str) -> None:
                errors.report(typ.line, typ.column, message, severity=severity)

            attrs = ", ".join(f'"{attr}"' for attr, _ in sorted(abstract))
            report(f"Class {typ.fullname} has abstract attributes {attrs}", "error")
            report(
                "If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass", "note"
            )
    if typ.is_final and abstract:
        attrs = ", ".join(f'"{attr}"' for attr, _ in sorted(abstract))
        errors.report(
            typ.line, typ.column, f"Final class {typ.fullname} has abstract attributes {attrs}"
        )

def check_protocol_status(info: TypeInfo, errors: Errors) -> None:
    """Check that all classes in MRO of a protocol are protocols"""
    if info.is_protocol:
        for type in info.bases:
            if not type.type.is_protocol and type.type.fullname != "builtins.object":
                errors.report(
                    info.line,
                    info.column,
                    "All bases of a protocol must be protocols",
                    severity="error",
                )

def calculate_class_vars(info: TypeInfo) -> None:
    """Try to infer additional class variables.

    Subclass attribute assignments with no type annotation are assumed
    to be classvar if overriding a declared classvar from the base
    class.

    This must happen after the main semantic analysis pass, since
    this depends on base class bodies having been fully analyzed.
    """
    for name, sym in info.names.items():
        node = sym.node
        if isinstance(node, Var) and node.info and node.is_inferred and not node.is_classvar:
            for base in info.mro[1:]:
                member = base.names.get(name)
                if member is not None and isinstance(member.node, Var) and member.node.is_classvar:
                    node.is_classvar = True

def add_type_promotion(
    info: TypeInfo, module_names: SymbolTable, options: Options, builtin_names: SymbolTable
) -> None:
    """Setup extra, ad-hoc subtyping relationships between classes (promotion).

    This includes things like 'int' being compatible with 'float'.
    """
    defn = info.defn
    promote_targets: list[ProperType] = []
    for decorator in defn.decorators:
        if isinstance(decorator, CallExpr):
            analyzed = decorator.analyzed
            if isinstance(analyzed, PromoteExpr):
                # _promote class decorator (undocumented feature).
                promote_targets.append(analyzed.type)
    if not promote_targets:
        if defn.fullname in TYPE_PROMOTIONS:
            target_sym = module_names.get(TYPE_PROMOTIONS[defn.fullname])
            if defn.fullname == "builtins.bytearray" and options.disable_bytearray_promotion:
                target_sym = None
            elif defn.fullname == "builtins.memoryview" and options.disable_memoryview_promotion:
                target_sym = None
            # With test stubs, the target may not exist.
            if target_sym:
                target_info = target_sym.node
                assert isinstance(target_info, TypeInfo)
                promote_targets.append(Instance(target_info, []))
    # Special case the promotions between 'int' and native integer types.
    # These have promotions going both ways, such as from 'int' to 'i64'
    # and 'i64' to 'int', for convenience.
    if defn.fullname in MYPYC_NATIVE_INT_NAMES:
        int_sym = builtin_names["int"]
        assert isinstance(int_sym.node, TypeInfo)
        int_sym.node._promote.append(Instance(defn.info, []))
        defn.info.alt_promote = Instance(int_sym.node, [])
    if promote_targets:
        defn.info._promote.extend(promote_targets)


"""Semantic analysis of call-based Enum definitions.

This is conceptually part of mypy.semanal (semantic analyzer pass 2).
"""

from __future__ import annotations

from typing import Final, cast

from mypy.nodes import (
    ARG_NAMED,
    ARG_POS,
    MDEF,
    AssignmentStmt,
    CallExpr,
    Context,
    DictExpr,
    EnumCallExpr,
    Expression,
    ListExpr,
    MemberExpr,
    NameExpr,
    RefExpr,
    StrExpr,
    SymbolTableNode,
    TupleExpr,
    TypeInfo,
    Var,
    is_StrExpr_list,
)
from mypy.options import Options
from mypy.semanal_shared import SemanticAnalyzerInterface
from mypy.types import ENUM_REMOVED_PROPS, LiteralType, get_proper_type

# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use
# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes.
ENUM_BASES: Final = frozenset(
    ("enum.Enum", "enum.IntEnum", "enum.Flag", "enum.IntFlag", "enum.StrEnum")
)
ENUM_SPECIAL_PROPS: Final = frozenset(
    (
        "name",
        "value",
        "_name_",
        "_value_",
        *ENUM_REMOVED_PROPS,
        # Also attributes from `object`:
        "__module__",
        "__annotations__",
        "__doc__",
        "__slots__",
        "__dict__",
    )
)

class EnumCallAnalyzer:
    def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None:
        self.options = options
        self.api = api

    def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool:
        """Check if s defines an Enum; if yes, store the definition in symbol table.

        Return True if this looks like an Enum definition (but maybe with errors),
        otherwise return False.
        """
        if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
            return False
        lvalue = s.lvalues[0]
        name = lvalue.name
        enum_call = self.check_enum_call(s.rvalue, name, is_func_scope)
        if enum_call is None:
            return False
        if isinstance(lvalue, MemberExpr):
            self.fail("Enum type as attribute is not supported", lvalue)
            return False
        # Yes, it's a valid Enum definition. Add it to the symbol table.
        self.api.add_symbol(name, enum_call, s)
        return True

    def check_enum_call(
        self, node: Expression, var_name: str, is_func_scope: bool
    ) -> TypeInfo | None:
        """Check if a call defines an Enum.

        Example:

          A = enum.Enum('A', 'foo bar')

        is equivalent to:

          class A(enum.Enum):
              foo = 1
              bar = 2
        """
        if not isinstance(node, CallExpr):
            return None
        call = node
        callee = call.callee
        if not isinstance(callee, RefExpr):
            return None
        fullname = callee.fullname
        if fullname not in ENUM_BASES:
            return None

        new_class_name, items, values, ok = self.parse_enum_call_args(
            call, fullname.split(".")[-1]
        )
        if not ok:
            # Error. Construct dummy return value.
            name = var_name
            if is_func_scope:
                name += "@" + str(call.line)
            info = self.build_enum_call_typeinfo(name, [], fullname, node.line)
        else:
            if new_class_name != var_name:
                msg = f'String argument 1 "{new_class_name}" to {fullname}(...) does not match variable name "{var_name}"'
                self.fail(msg, call)

            name = cast(StrExpr, call.args[0]).value
            if name != var_name or is_func_scope:
                # Give it a unique name derived from the line number.
                name += "@" + str(call.line)
            info = self.build_enum_call_typeinfo(name, items, fullname, call.line)
        # Store generated TypeInfo under both names, see semanal_namedtuple for more details.
        if name != var_name or is_func_scope:
            self.api.add_symbol_skip_local(name, info)
        call.analyzed = EnumCallExpr(info, items, values)
        call.analyzed.set_line(call)
        info.line = node.line
        return info

    def build_enum_call_typeinfo(
        self, name: str, items: list[str], fullname: str, line: int
    ) -> TypeInfo:
        base = self.api.named_type_or_none(fullname)
        assert base is not None
        info = self.api.basic_new_typeinfo(name, base, line)
        info.metaclass_type = info.calculate_metaclass_type()
        info.is_enum = True
        for item in items:
            var = Var(item)
            var.info = info
            var.is_property = True
            var._fullname = f"{info.fullname}.{item}"
            info.names[item] = SymbolTableNode(MDEF, var)
        return info

    def parse_enum_call_args(
        self, call: CallExpr, class_name: str
    ) -> tuple[str, list[str], list[Expression | None], bool]:
        """Parse arguments of an Enum call.

        Return a tuple of fields, values, was there an error.
        """
        args = call.args
        if not all(arg_kind in [ARG_POS, ARG_NAMED] for arg_kind in call.arg_kinds):
            return self.fail_enum_call_arg(f"Unexpected arguments to {class_name}()", call)
        if len(args) < 2:
            return self.fail_enum_call_arg(f"Too few arguments for {class_name}()", call)
        if len(args) > 6:
            return self.fail_enum_call_arg(f"Too many arguments for {class_name}()", call)
        valid_name = [None, "value", "names", "module", "qualname", "type", "start"]
        for arg_name in call.arg_names:
            if arg_name not in valid_name:
                self.fail_enum_call_arg(f'Unexpected keyword argument "{arg_name}"', call)
        value, names = None, None
        for arg_name, arg in zip(call.arg_names, args):
            if arg_name == "value":
                value = arg
            if arg_name == "names":
                names = arg
        if value is None:
            value = args[0]
        if names is None:
            names = args[1]
        if not isinstance(value, StrExpr):
            return self.fail_enum_call_arg(
                f"{class_name}() expects a string literal as the first argument", call
            )
        new_class_name = value.value

        items = []
        values: list[Expression | None] = []
        if isinstance(names, StrExpr):
            fields = names.value
            for field in fields.replace(",", " ").split():
                items.append(field)
        elif isinstance(names, (TupleExpr, ListExpr)):
            seq_items = names.items
            if is_StrExpr_list(seq_items):
                items = [seq_item.value for seq_item in seq_items]
            elif all(
                isinstance(seq_item, (TupleExpr, ListExpr))
                and len(seq_item.items) == 2
                and isinstance(seq_item.items[0], StrExpr)
                for seq_item in seq_items
            ):
                for seq_item in seq_items:
                    assert isinstance(seq_item, (TupleExpr, ListExpr))
                    name, value = seq_item.items
                    assert isinstance(name, StrExpr)
                    items.append(name.value)
                    values.append(value)
            else:
                return self.fail_enum_call_arg(
                    "%s() with tuple or list expects strings or (name, value) pairs" % class_name,
                    call,
                )
        elif isinstance(names, DictExpr):
            for key, value in names.items:
                if not isinstance(key, StrExpr):
                    return self.fail_enum_call_arg(
                        f"{class_name}() with dict literal requires string literals", call
                    )
                items.append(key.value)
                values.append(value)
        elif isinstance(args[1], RefExpr) and isinstance(args[1].node, Var):
            proper_type = get_proper_type(args[1].node.type)
            if (
                proper_type is not None
                and isinstance(proper_type, LiteralType)
                and isinstance(proper_type.value, str)
            ):
                fields = proper_type.value
                for field in fields.replace(",", " ").split():
                    items.append(field)
            elif args[1].node.is_final and isinstance(args[1].node.final_value, str):
                fields = args[1].node.final_value
                for field in fields.replace(",", " ").split():
                    items.append(field)
            else:
                return self.fail_enum_call_arg(
                    "Second argument of %s() must be string, tuple, list or dict literal for mypy to determine Enum members"
                    % class_name,
                    call,
                )
        else:
            # TODO: Allow dict(x=1, y=2) as a substitute for {'x': 1, 'y': 2}?
            return self.fail_enum_call_arg(
                "Second argument of %s() must be string, tuple, list or dict literal for mypy to determine Enum members"
                % class_name,
                call,
            )
        if not items:
            return self.fail_enum_call_arg(f"{class_name}() needs at least one item", call)
        if not values:
            values = [None] * len(items)
        assert len(items) == len(values)
        return new_class_name, items, values, True

    def fail_enum_call_arg(
        self, message: str, context: Context
    ) -> tuple[str, list[str], list[Expression | None], bool]:
        self.fail(message, context)
        return "", [], [], False

    # Helpers

    def fail(self, msg: str, ctx: Context) -> None:
        self.api.fail(msg, ctx)


"""Simple type inference for decorated functions during semantic analysis."""

from __future__ import annotations

from mypy.nodes import ARG_POS, CallExpr, Decorator, Expression, FuncDef, RefExpr, Var
from mypy.semanal_shared import SemanticAnalyzerInterface
from mypy.typeops import function_type
from mypy.types import (
    AnyType,
    CallableType,
    ProperType,
    Type,
    TypeOfAny,
    TypeVarType,
    get_proper_type,
)
from mypy.typevars import has_no_typevars

def infer_decorator_signature_if_simple(
    dec: Decorator, analyzer: SemanticAnalyzerInterface
) -> None:
    """Try to infer the type of the decorated function.

    This lets us resolve additional references to decorated functions
    during type checking. Otherwise the type might not be available
    when we need it, since module top levels can't be deferred.

    This basically uses a simple special-purpose type inference
    engine just for decorators.
    """
    if dec.var.is_property:
        # Decorators are expected to have a callable type (it's a little odd).
        if dec.func.type is None:
            dec.var.type = CallableType(
                [AnyType(TypeOfAny.special_form)],
                [ARG_POS],
                [None],
                AnyType(TypeOfAny.special_form),
                analyzer.named_type("builtins.function"),
                name=dec.var.name,
            )
        elif isinstance(dec.func.type, CallableType):
            dec.var.type = dec.func.type
        return
    decorator_preserves_type = True
    for expr in dec.decorators:
        preserve_type = False
        if isinstance(expr, RefExpr) and isinstance(expr.node, FuncDef):
            if expr.node.type and is_identity_signature(expr.node.type):
                preserve_type = True
        if not preserve_type:
            decorator_preserves_type = False
            break
    if decorator_preserves_type:
        # No non-identity decorators left. We can trivially infer the type
        # of the function here.
        dec.var.type = function_type(dec.func, analyzer.named_type("builtins.function"))
    if dec.decorators:
        return_type = calculate_return_type(dec.decorators[0])
        if return_type and isinstance(return_type, AnyType):
            # The outermost decorator will return Any so we know the type of the
            # decorated function.
            dec.var.type = AnyType(TypeOfAny.from_another_any, source_any=return_type)
        sig = find_fixed_callable_return(dec.decorators[0])
        if sig:
            # The outermost decorator always returns the same kind of function,
            # so we know that this is the type of the decorated function.
            orig_sig = function_type(dec.func, analyzer.named_type("builtins.function"))
            sig.name = orig_sig.items[0].name
            dec.var.type = sig

def is_identity_signature(sig: Type) -> bool:
    """Is type a callable of form T -> T (where T is a type variable)?"""
    sig = get_proper_type(sig)
    if isinstance(sig, CallableType) and sig.arg_kinds == [ARG_POS]:
        if isinstance(sig.arg_types[0], TypeVarType) and isinstance(sig.ret_type, TypeVarType):
            return sig.arg_types[0].id == sig.ret_type.id
    return False

def calculate_return_type(expr: Expression) -> ProperType | None:
    """Return the return type if we can calculate it.

    This only uses information available during semantic analysis so this
    will sometimes return None because of insufficient information (as
    type inference hasn't run yet).
    """
    if isinstance(expr, RefExpr):
        if isinstance(expr.node, FuncDef):
            typ = expr.node.type
            if typ is None:
                # No signature -> default to Any.
                return AnyType(TypeOfAny.unannotated)
            # Explicit Any return?
            if isinstance(typ, CallableType):
                return get_proper_type(typ.ret_type)
            return None
        elif isinstance(expr.node, Var):
            return get_proper_type(expr.node.type)
    elif isinstance(expr, CallExpr):
        return calculate_return_type(expr.callee)
    return None

def find_fixed_callable_return(expr: Expression) -> CallableType | None:
    """Return the return type, if expression refers to a callable that returns a callable.

    But only do this if the return type has no type variables. Return None otherwise.
    This approximates things a lot as this is supposed to be called before type checking
    when full type information is not available yet.
    """
    if isinstance(expr, RefExpr):
        if isinstance(expr.node, FuncDef):
            typ = expr.node.type
            if typ:
                if isinstance(typ, CallableType) and has_no_typevars(typ.ret_type):
                    ret_type = get_proper_type(typ.ret_type)
                    if isinstance(ret_type, CallableType):
                        return ret_type
    elif isinstance(expr, CallExpr):
        t = find_fixed_callable_return(expr.callee)
        if t:
            ret_type = get_proper_type(t.ret_type)
            if isinstance(ret_type, CallableType):
                return ret_type
    return None


"""Top-level logic for the semantic analyzer.

The semantic analyzer binds names, resolves imports, detects various
special constructs that don't have dedicated AST nodes after parse
(such as 'cast' which looks like a call), populates symbol tables, and
performs various simple consistency checks.

Semantic analysis of each SCC (strongly connected component; import
cycle) is performed in one unit. Each module is analyzed as multiple
separate *targets*; the module top level is one target and each function
is a target. Nested functions are not separate targets, however. This is
mostly identical to targets used by mypy daemon (but classes aren't
targets in semantic analysis).

We first analyze each module top level in an SCC. If we encounter some
names that we can't bind because the target of the name may not have
been processed yet, we *defer* the current target for further
processing. Deferred targets will be analyzed additional times until
everything can be bound, or we reach a maximum number of iterations.

We keep track of a set of incomplete namespaces, i.e. namespaces that we
haven't finished populating yet. References to these namespaces cause a
deferral if they can't be satisfied. Initially every module in the SCC
will be incomplete.
"""

from __future__ import annotations

from contextlib import nullcontext
from typing import TYPE_CHECKING, Callable, Final, List, Optional, Tuple, Union
from typing_extensions import TypeAlias as _TypeAlias

import mypy.build
import mypy.state
from mypy.checker import FineGrainedDeferredNode
from mypy.errors import Errors
from mypy.nodes import Decorator, FuncDef, MypyFile, OverloadedFuncDef, TypeInfo, Var
from mypy.options import Options
from mypy.plugin import ClassDefContext
from mypy.plugins import dataclasses as dataclasses_plugin
from mypy.semanal import (
    SemanticAnalyzer,
    apply_semantic_analyzer_patches,
    remove_imported_names_from_symtable,
)
from mypy.semanal_classprop import (
    add_type_promotion,
    calculate_class_abstract_status,
    calculate_class_vars,
    check_protocol_status,
)
from mypy.semanal_infer import infer_decorator_signature_if_simple
from mypy.semanal_shared import find_dataclass_transform_spec
from mypy.semanal_typeargs import TypeArgumentAnalyzer
from mypy.server.aststrip import SavedAttributes
from mypy.util import is_typeshed_file

if TYPE_CHECKING:
    from mypy.build import Graph, State

Patches: _TypeAlias = List[Tuple[int, Callable[[], None]]]

# If we perform this many iterations, raise an exception since we are likely stuck.
MAX_ITERATIONS: Final = 20

# Number of passes over core modules before going on to the rest of the builtin SCC.
CORE_WARMUP: Final = 2
core_modules: Final = [
    "typing",
    "_collections_abc",
    "builtins",
    "abc",
    "collections",
    "collections.abc",
]

def semantic_analysis_for_scc(graph: Graph, scc: list[str], errors: Errors) -> None:
    """Perform semantic analysis for all modules in a SCC (import cycle).

    Assume that reachability analysis has already been performed.

    The scc will be processed roughly in the order the modules are included
    in the list.
    """
    patches: Patches = []
    # Note that functions can't define new module-level attributes
    # using 'global x', since module top levels are fully processed
    # before functions. This limitation is unlikely to go away soon.
    process_top_levels(graph, scc, patches)
    process_functions(graph, scc, patches)
    # We use patch callbacks to fix up things when we expect relatively few
    # callbacks to be required.
    apply_semantic_analyzer_patches(patches)
    # Run class decorator hooks (they requite complete MROs and no placeholders).
    apply_class_plugin_hooks(graph, scc, errors)
    # This pass might need fallbacks calculated above and the results of hooks.
    check_type_arguments(graph, scc, errors)
    calculate_class_properties(graph, scc, errors)
    check_blockers(graph, scc)
    # Clean-up builtins, so that TypeVar etc. are not accessible without importing.
    if "builtins" in scc:
        cleanup_builtin_scc(graph["builtins"])

def cleanup_builtin_scc(state: State) -> None:
    """Remove imported names from builtins namespace.

    This way names imported from typing in builtins.pyi aren't available
    by default (without importing them). We can only do this after processing
    the whole SCC is finished, when the imported names aren't needed for
    processing builtins.pyi itself.
    """
    assert state.tree is not None
    remove_imported_names_from_symtable(state.tree.names, "builtins")

def semantic_analysis_for_targets(
    state: State, nodes: list[FineGrainedDeferredNode], graph: Graph, saved_attrs: SavedAttributes
) -> None:
    """Semantically analyze only selected nodes in a given module.

    This essentially mirrors the logic of semantic_analysis_for_scc()
    except that we process only some targets. This is used in fine grained
    incremental mode, when propagating an update.

    The saved_attrs are implicitly declared instance attributes (attributes
    defined on self) removed by AST stripper that may need to be reintroduced
    here.  They must be added before any methods are analyzed.
    """
    patches: Patches = []
    if any(isinstance(n.node, MypyFile) for n in nodes):
        # Process module top level first (if needed).
        process_top_levels(graph, [state.id], patches)
    restore_saved_attrs(saved_attrs)
    analyzer = state.manager.semantic_analyzer
    for n in nodes:
        if isinstance(n.node, MypyFile):
            # Already done above.
            continue
        process_top_level_function(
            analyzer, state, state.id, n.node.fullname, n.node, n.active_typeinfo, patches
        )
    apply_semantic_analyzer_patches(patches)
    apply_class_plugin_hooks(graph, [state.id], state.manager.errors)
    check_type_arguments_in_targets(nodes, state, state.manager.errors)
    calculate_class_properties(graph, [state.id], state.manager.errors)

def restore_saved_attrs(saved_attrs: SavedAttributes) -> None:
    """Restore instance variables removed during AST strip that haven't been added yet."""
    for (cdef, name), sym in saved_attrs.items():
        info = cdef.info
        existing = info.get(name)
        defined_in_this_class = name in info.names
        assert isinstance(sym.node, Var)
        # This needs to mimic the logic in SemanticAnalyzer.analyze_member_lvalue()
        # regarding the existing variable in class body or in a superclass:
        # If the attribute of self is not defined in superclasses, create a new Var.
        if (
            existing is None
            or
            # (An abstract Var is considered as not defined.)
            (isinstance(existing.node, Var) and existing.node.is_abstract_var)
            or
            # Also an explicit declaration on self creates a new Var unless
            # there is already one defined in the class body.
            sym.node.explicit_self_type
            and not defined_in_this_class
        ):
            info.names[name] = sym

def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None:
    # Process top levels until everything has been bound.

    # Reverse order of the scc so the first modules in the original list will be
    # be processed first. This helps with performance.
    scc = list(reversed(scc))

    # Initialize ASTs and symbol tables.
    for id in scc:
        state = graph[id]
        assert state.tree is not None
        state.manager.semantic_analyzer.prepare_file(state.tree)

    # Initially all namespaces in the SCC are incomplete (well they are empty).
    state.manager.incomplete_namespaces.update(scc)

    worklist = scc.copy()
    # HACK: process core stuff first. This is mostly needed to support defining
    # named tuples in builtin SCC.
    if all(m in worklist for m in core_modules):
        worklist += list(reversed(core_modules)) * CORE_WARMUP
    final_iteration = False
    iteration = 0
    analyzer = state.manager.semantic_analyzer
    analyzer.deferral_debug_context.clear()

    while worklist:
        iteration += 1
        if iteration > MAX_ITERATIONS:
            # Just pick some module inside the current SCC for error context.
            assert state.tree is not None
            with analyzer.file_context(state.tree, state.options):
                analyzer.report_hang()
            break
        if final_iteration:
            # Give up. It's impossible to bind all names.
            state.manager.incomplete_namespaces.clear()
        all_deferred: list[str] = []
        any_progress = False
        while worklist:
            next_id = worklist.pop()
            state = graph[next_id]
            assert state.tree is not None
            deferred, incomplete, progress = semantic_analyze_target(
                next_id, next_id, state, state.tree, None, final_iteration, patches
            )
            all_deferred += deferred
            any_progress = any_progress or progress
            if not incomplete:
                state.manager.incomplete_namespaces.discard(next_id)
        if final_iteration:
            assert not all_deferred, "Must not defer during final iteration"
        # Reverse to process the targets in the same order on every iteration. This avoids
        # processing the same target twice in a row, which is inefficient.
        worklist = list(reversed(all_deferred))
        final_iteration = not any_progress

def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None:
    # Process functions.
    for module in scc:
        tree = graph[module].tree
        assert tree is not None
        analyzer = graph[module].manager.semantic_analyzer
        # In principle, functions can be processed in arbitrary order,
        # but _methods_ must be processed in the order they are defined,
        # because some features (most notably partial types) depend on
        # order of definitions on self.
        #
        # There can be multiple generated methods per line. Use target
        # name as the second sort key to get a repeatable sort order on
        # Python 3.5, which doesn't preserve dictionary order.
        targets = sorted(get_all_leaf_targets(tree), key=lambda x: (x[1].line, x[0]))
        for target, node, active_type in targets:
            assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator))
            process_top_level_function(
                analyzer, graph[module], module, target, node, active_type, patches
            )

def process_top_level_function(
    analyzer: SemanticAnalyzer,
    state: State,
    module: str,
    target: str,
    node: FuncDef | OverloadedFuncDef | Decorator,
    active_type: TypeInfo | None,
    patches: Patches,
) -> None:
    """Analyze single top-level function or method.

    Process the body of the function (including nested functions) again and again,
    until all names have been resolved (or iteration limit reached).
    """
    # We need one more iteration after incomplete is False (e.g. to report errors, if any).
    final_iteration = False
    incomplete = True
    # Start in the incomplete state (no missing names will be reported on first pass).
    # Note that we use module name, since functions don't create qualified names.
    deferred = [module]
    analyzer.deferral_debug_context.clear()
    analyzer.incomplete_namespaces.add(module)
    iteration = 0
    while deferred:
        iteration += 1
        if iteration == MAX_ITERATIONS:
            # Just pick some module inside the current SCC for error context.
            assert state.tree is not None
            with analyzer.file_context(state.tree, state.options):
                analyzer.report_hang()
            break
        if not (deferred or incomplete) or final_iteration:
            # OK, this is one last pass, now missing names will be reported.
            analyzer.incomplete_namespaces.discard(module)
        deferred, incomplete, progress = semantic_analyze_target(
            target, module, state, node, active_type, final_iteration, patches
        )
        if not incomplete:
            state.manager.incomplete_namespaces.discard(module)
        if final_iteration:
            assert not deferred, "Must not defer during final iteration"
        if not progress:
            final_iteration = True

    analyzer.incomplete_namespaces.discard(module)
    # After semantic analysis is done, discard local namespaces
    # to avoid memory hoarding.
    analyzer.saved_locals.clear()

TargetInfo: _TypeAlias = Tuple[
    str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]
]

def get_all_leaf_targets(file: MypyFile) -> list[TargetInfo]:
    """Return all leaf targets in a symbol table (module-level and methods)."""
    result: list[TargetInfo] = []
    for fullname, node, active_type in file.local_definitions():
        if isinstance(node.node, (FuncDef, OverloadedFuncDef, Decorator)):
            result.append((fullname, node.node, active_type))
    return result

def semantic_analyze_target(
    target: str,
    module: str,
    state: State,
    node: MypyFile | FuncDef | OverloadedFuncDef | Decorator,
    active_type: TypeInfo | None,
    final_iteration: bool,
    patches: Patches,
) -> tuple[list[str], bool, bool]:
    """Semantically analyze a single target.

    Return tuple with these items:
    - list of deferred targets
    - was some definition incomplete (need to run another pass)
    - were any new names defined (or placeholders replaced)
    """
    state.manager.processed_targets.append((module, target))
    tree = state.tree
    assert tree is not None
    analyzer = state.manager.semantic_analyzer
    # TODO: Move initialization to somewhere else
    analyzer.global_decls = [set()]
    analyzer.nonlocal_decls = [set()]
    analyzer.globals = tree.names
    analyzer.progress = False
    with state.wrap_context(check_blockers=False):
        refresh_node = node
        if isinstance(refresh_node, Decorator):
            # Decorator expressions will be processed as part of the module top level.
            refresh_node = refresh_node.func
        analyzer.refresh_partial(
            refresh_node,
            patches,
            final_iteration,
            file_node=tree,
            options=state.options,
            active_type=active_type,
        )
        if isinstance(node, Decorator):
            infer_decorator_signature_if_simple(node, analyzer)
    for dep in analyzer.imports:
        state.add_dependency(dep)
        priority = mypy.build.PRI_LOW
        if priority <= state.priorities.get(dep, priority):
            state.priorities[dep] = priority

    # Clear out some stale data to avoid memory leaks and astmerge
    # validity check confusion
    analyzer.statement = None
    del analyzer.cur_mod_node

    if analyzer.deferred:
        return [target], analyzer.incomplete, analyzer.progress
    else:
        return [], analyzer.incomplete, analyzer.progress

def check_type_arguments(graph: Graph, scc: list[str], errors: Errors) -> None:
    for module in scc:
        state = graph[module]
        assert state.tree
        analyzer = TypeArgumentAnalyzer(
            errors,
            state.options,
            state.tree.is_typeshed_file(state.options),
            state.manager.semantic_analyzer.named_type,
        )
        with state.wrap_context():
            with mypy.state.state.strict_optional_set(state.options.strict_optional):
                state.tree.accept(analyzer)

def check_type_arguments_in_targets(
    targets: list[FineGrainedDeferredNode], state: State, errors: Errors
) -> None:
    """Check type arguments against type variable bounds and restrictions.

    This mirrors the logic in check_type_arguments() except that we process only
    some targets. This is used in fine grained incremental mode.
    """
    analyzer = TypeArgumentAnalyzer(
        errors,
        state.options,
        is_typeshed_file(state.options.abs_custom_typeshed_dir, state.path or ""),
        state.manager.semantic_analyzer.named_type,
    )
    with state.wrap_context():
        with mypy.state.state.strict_optional_set(state.options.strict_optional):
            for target in targets:
                func: FuncDef | OverloadedFuncDef | None = None
                if isinstance(target.node, (FuncDef, OverloadedFuncDef)):
                    func = target.node
                saved = (state.id, target.active_typeinfo, func)  # module, class, function
                with errors.scope.saved_scope(saved) if errors.scope else nullcontext():
                    analyzer.recurse_into_functions = func is not None
                    target.node.accept(analyzer)

def apply_class_plugin_hooks(graph: Graph, scc: list[str], errors: Errors) -> None:
    """Apply class plugin hooks within a SCC.

    We run these after to the main semantic analysis so that the hooks
    don't need to deal with incomplete definitions such as placeholder
    types.

    Note that some hooks incorrectly run during the main semantic
    analysis pass, for historical reasons.
    """
    num_passes = 0
    incomplete = True
    # If we encounter a base class that has not been processed, we'll run another
    # pass. This should eventually reach a fixed point.
    while incomplete:
        assert num_passes < 10, "Internal error: too many class plugin hook passes"
        num_passes += 1
        incomplete = False
        for module in scc:
            state = graph[module]
            tree = state.tree
            assert tree
            for _, node, _ in tree.local_definitions():
                if isinstance(node.node, TypeInfo):
                    if not apply_hooks_to_class(
                        state.manager.semantic_analyzer,
                        module,
                        node.node,
                        state.options,
                        tree,
                        errors,
                    ):
                        incomplete = True

def apply_hooks_to_class(
    self: SemanticAnalyzer,
    module: str,
    info: TypeInfo,
    options: Options,
    file_node: MypyFile,
    errors: Errors,
) -> bool:
    # TODO: Move more class-related hooks here?
    defn = info.defn
    ok = True
    for decorator in defn.decorators:
        with self.file_context(file_node, options, info):
            hook = None

            decorator_name = self.get_fullname_for_hook(decorator)
            if decorator_name:
                hook = self.plugin.get_class_decorator_hook_2(decorator_name)
            # Special case: if the decorator is itself decorated with
            # typing.dataclass_transform, apply the hook for the dataclasses plugin
            # TODO: remove special casing here
            if hook is None and find_dataclass_transform_spec(decorator):
                hook = dataclasses_plugin.dataclass_class_maker_callback

            if hook:
                ok = ok and hook(ClassDefContext(defn, decorator, self))

    # Check if the class definition itself triggers a dataclass transform (via a parent class/
    # metaclass)
    spec = find_dataclass_transform_spec(info)
    if spec is not None:
        with self.file_context(file_node, options, info):
            # We can't use the normal hook because reason = defn, and ClassDefContext only accepts
            # an Expression for reason
            ok = ok and dataclasses_plugin.DataclassTransformer(defn, defn, spec, self).transform()

    return ok

def calculate_class_properties(graph: Graph, scc: list[str], errors: Errors) -> None:
    builtins = graph["builtins"].tree
    assert builtins
    for module in scc:
        state = graph[module]
        tree = state.tree
        assert tree
        for _, node, _ in tree.local_definitions():
            if isinstance(node.node, TypeInfo):
                with state.manager.semantic_analyzer.file_context(tree, state.options, node.node):
                    calculate_class_abstract_status(node.node, tree.is_stub, errors)
                    check_protocol_status(node.node, errors)
                    calculate_class_vars(node.node)
                    add_type_promotion(
                        node.node, tree.names, graph[module].options, builtins.names
                    )

def check_blockers(graph: Graph, scc: list[str]) -> None:
    for module in scc:
        graph[module].check_blockers()


"""Semantic analysis of named tuple definitions.

This is conceptually part of mypy.semanal.
"""

from __future__ import annotations

import keyword
from contextlib import contextmanager
from typing import Container, Final, Iterator, List, Mapping, cast

from mypy.errorcodes import ARG_TYPE, ErrorCode
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
from mypy.messages import MessageBuilder
from mypy.nodes import (
    ARG_NAMED_OPT,
    ARG_OPT,
    ARG_POS,
    MDEF,
    Argument,
    AssignmentStmt,
    Block,
    CallExpr,
    ClassDef,
    Context,
    Decorator,
    EllipsisExpr,
    Expression,
    ExpressionStmt,
    FuncBase,
    FuncDef,
    ListExpr,
    NamedTupleExpr,
    NameExpr,
    PassStmt,
    RefExpr,
    Statement,
    StrExpr,
    SymbolTable,
    SymbolTableNode,
    TempNode,
    TupleExpr,
    TypeInfo,
    TypeVarExpr,
    Var,
    is_StrExpr_list,
)
from mypy.options import Options
from mypy.semanal_shared import (
    PRIORITY_FALLBACKS,
    SemanticAnalyzerInterface,
    calculate_tuple_fallback,
    has_placeholder,
    set_callable_name,
)
from mypy.types import (
    TYPED_NAMEDTUPLE_NAMES,
    AnyType,
    CallableType,
    LiteralType,
    TupleType,
    Type,
    TypeOfAny,
    TypeType,
    TypeVarId,
    TypeVarLikeType,
    TypeVarType,
    UnboundType,
    has_type_vars,
)
from mypy.util import get_unique_redefinition_name

# Matches "_prohibited" in typing.py, but adds __annotations__, which works at runtime but can't
# easily be supported in a static checker.
NAMEDTUPLE_PROHIBITED_NAMES: Final = (
    "__new__",
    "__init__",
    "__slots__",
    "__getnewargs__",
    "_fields",
    "_field_defaults",
    "_field_types",
    "_make",
    "_replace",
    "_asdict",
    "_source",
    "__annotations__",
)

NAMEDTUP_CLASS_ERROR: Final = (
    'Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]"'
)

SELF_TVAR_NAME: Final = "_NT"

class NamedTupleAnalyzer:
    def __init__(
        self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder
    ) -> None:
        self.options = options
        self.api = api
        self.msg = msg

    def analyze_namedtuple_classdef(
        self, defn: ClassDef, is_stub_file: bool, is_func_scope: bool
    ) -> tuple[bool, TypeInfo | None]:
        """Analyze if given class definition can be a named tuple definition.

        Return a tuple where first item indicates whether this can possibly be a named tuple,
        and the second item is the corresponding TypeInfo (may be None if not ready and should be
        deferred).
        """
        for base_expr in defn.base_type_exprs:
            if isinstance(base_expr, RefExpr):
                self.api.accept(base_expr)
                if base_expr.fullname in TYPED_NAMEDTUPLE_NAMES:
                    result = self.check_namedtuple_classdef(defn, is_stub_file)
                    if result is None:
                        # This is a valid named tuple, but some types are incomplete.
                        return True, None
                    items, types, default_items, statements = result
                    if is_func_scope and "@" not in defn.name:
                        defn.name += "@" + str(defn.line)
                    existing_info = None
                    if isinstance(defn.analyzed, NamedTupleExpr):
                        existing_info = defn.analyzed.info
                    info = self.build_namedtuple_typeinfo(
                        defn.name, items, types, default_items, defn.line, existing_info
                    )
                    defn.analyzed = NamedTupleExpr(info, is_typed=True)
                    defn.analyzed.line = defn.line
                    defn.analyzed.column = defn.column
                    defn.defs.body = statements
                    # All done: this is a valid named tuple with all types known.
                    return True, info
        # This can't be a valid named tuple.
        return False, None

    def check_namedtuple_classdef(
        self, defn: ClassDef, is_stub_file: bool
    ) -> tuple[list[str], list[Type], dict[str, Expression], list[Statement]] | None:
        """Parse and validate fields in named tuple class definition.

        Return a four tuple:
          * field names
          * field types
          * field default values
          * valid statements
        or None, if any of the types are not ready.
        """
        if len(defn.base_type_exprs) > 1:
            self.fail("NamedTuple should be a single base", defn)
        items: list[str] = []
        types: list[Type] = []
        default_items: dict[str, Expression] = {}
        statements: list[Statement] = []
        for stmt in defn.defs.body:
            statements.append(stmt)
            if not isinstance(stmt, AssignmentStmt):
                # Still allow pass or ... (for empty namedtuples).
                if isinstance(stmt, PassStmt) or (
                    isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr)
                ):
                    continue
                # Also allow methods, including decorated ones.
                if isinstance(stmt, (Decorator, FuncBase)):
                    continue
                # And docstrings.
                if isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr):
                    continue
                statements.pop()
                defn.removed_statements.append(stmt)
                self.fail(NAMEDTUP_CLASS_ERROR, stmt)
            elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr):
                # An assignment, but an invalid one.
                statements.pop()
                defn.removed_statements.append(stmt)
                self.fail(NAMEDTUP_CLASS_ERROR, stmt)
            else:
                # Append name and type in this case...
                name = stmt.lvalues[0].name
                items.append(name)
                if stmt.type is None:
                    types.append(AnyType(TypeOfAny.unannotated))
                else:
                    # We never allow recursive types at function scope. Although it is
                    # possible to support this for named tuples, it is still tricky, and
                    # it would be inconsistent with type aliases.
                    analyzed = self.api.anal_type(
                        stmt.type,
                        allow_placeholder=not self.api.is_func_scope(),
                        prohibit_self_type="NamedTuple item type",
                    )
                    if analyzed is None:
                        # Something is incomplete. We need to defer this named tuple.
                        return None
                    types.append(analyzed)
                # ...despite possible minor failures that allow further analyzis.
                if name.startswith("_"):
                    self.fail(
                        f"NamedTuple field name cannot start with an underscore: {name}", stmt
                    )
                if stmt.type is None or hasattr(stmt, "new_syntax") and not stmt.new_syntax:
                    self.fail(NAMEDTUP_CLASS_ERROR, stmt)
                elif isinstance(stmt.rvalue, TempNode):
                    # x: int assigns rvalue to TempNode(AnyType())
                    if default_items:
                        self.fail(
                            "Non-default NamedTuple fields cannot follow default fields", stmt
                        )
                else:
                    default_items[name] = stmt.rvalue
        if defn.keywords:
            for_function = ' for "__init_subclass__" of "NamedTuple"'
            for key in defn.keywords:
                self.msg.unexpected_keyword_argument_for_function(for_function, key, defn)
        return items, types, default_items, statements

    def check_namedtuple(
        self, node: Expression, var_name: str | None, is_func_scope: bool
    ) -> tuple[str | None, TypeInfo | None, list[TypeVarLikeType]]:
        """Check if a call defines a namedtuple.

        The optional var_name argument is the name of the variable to
        which this is assigned, if any.

        Return a tuple of two items:
          * Internal name of the named tuple (e.g. the name passed as an argument to namedtuple)
            or None if it is not a valid named tuple
          * Corresponding TypeInfo, or None if not ready.

        If the definition is invalid but looks like a namedtuple,
        report errors but return (some) TypeInfo.
        """
        if not isinstance(node, CallExpr):
            return None, None, []
        call = node
        callee = call.callee
        if not isinstance(callee, RefExpr):
            return None, None, []
        fullname = callee.fullname
        if fullname == "collections.namedtuple":
            is_typed = False
        elif fullname in TYPED_NAMEDTUPLE_NAMES:
            is_typed = True
        else:
            return None, None, []
        result = self.parse_namedtuple_args(call, fullname)
        if result:
            items, types, defaults, typename, tvar_defs, ok = result
        else:
            # Error. Construct dummy return value.
            if var_name:
                name = var_name
                if is_func_scope:
                    name += "@" + str(call.line)
            else:
                name = var_name = "namedtuple@" + str(call.line)
            info = self.build_namedtuple_typeinfo(name, [], [], {}, node.line, None)
            self.store_namedtuple_info(info, var_name, call, is_typed)
            if name != var_name or is_func_scope:
                # NOTE: we skip local namespaces since they are not serialized.
                self.api.add_symbol_skip_local(name, info)
            return var_name, info, []
        if not ok:
            # This is a valid named tuple but some types are not ready.
            return typename, None, []

        # We use the variable name as the class name if it exists. If
        # it doesn't, we use the name passed as an argument. We prefer
        # the variable name because it should be unique inside a
        # module, and so we don't need to disambiguate it with a line
        # number.
        if var_name:
            name = var_name
        else:
            name = typename

        if var_name is None or is_func_scope:
            # There are two special cases where need to give it a unique name derived
            # from the line number:
            #   * This is a base class expression, since it often matches the class name:
            #         class NT(NamedTuple('NT', [...])):
            #             ...
            #   * This is a local (function or method level) named tuple, since
            #     two methods of a class can define a named tuple with the same name,
            #     and they will be stored in the same namespace (see below).
            name += "@" + str(call.line)
        if defaults:
            default_items = {
                arg_name: default for arg_name, default in zip(items[-len(defaults) :], defaults)
            }
        else:
            default_items = {}

        existing_info = None
        if isinstance(node.analyzed, NamedTupleExpr):
            existing_info = node.analyzed.info
        info = self.build_namedtuple_typeinfo(
            name, items, types, default_items, node.line, existing_info
        )

        # If var_name is not None (i.e. this is not a base class expression), we always
        # store the generated TypeInfo under var_name in the current scope, so that
        # other definitions can use it.
        if var_name:
            self.store_namedtuple_info(info, var_name, call, is_typed)
        else:
            call.analyzed = NamedTupleExpr(info, is_typed=is_typed)
            call.analyzed.set_line(call)
        # There are three cases where we need to store the generated TypeInfo
        # second time (for the purpose of serialization):
        #   * If there is a name mismatch like One = NamedTuple('Other', [...])
        #     we also store the info under name 'Other@lineno', this is needed
        #     because classes are (de)serialized using their actual fullname, not
        #     the name of l.h.s.
        #   * If this is a method level named tuple. It can leak from the method
        #     via assignment to self attribute and therefore needs to be serialized
        #     (local namespaces are not serialized).
        #   * If it is a base class expression. It was not stored above, since
        #     there is no var_name (but it still needs to be serialized
        #     since it is in MRO of some class).
        if name != var_name or is_func_scope:
            # NOTE: we skip local namespaces since they are not serialized.
            self.api.add_symbol_skip_local(name, info)
        return typename, info, tvar_defs

    def store_namedtuple_info(
        self, info: TypeInfo, name: str, call: CallExpr, is_typed: bool
    ) -> None:
        self.api.add_symbol(name, info, call)
        call.analyzed = NamedTupleExpr(info, is_typed=is_typed)
        call.analyzed.set_line(call)

    def parse_namedtuple_args(
        self, call: CallExpr, fullname: str
    ) -> None | (tuple[list[str], list[Type], list[Expression], str, list[TypeVarLikeType], bool]):
        """Parse a namedtuple() call into data needed to construct a type.

        Returns a 6-tuple:
        - List of argument names
        - List of argument types
        - List of default values
        - First argument of namedtuple
        - All typevars found in the field definition
        - Whether all types are ready.

        Return None if the definition didn't typecheck.
        """
        type_name = "NamedTuple" if fullname in TYPED_NAMEDTUPLE_NAMES else "namedtuple"
        # TODO: Share code with check_argument_count in checkexpr.py?
        args = call.args
        if len(args) < 2:
            self.fail(f'Too few arguments for "{type_name}()"', call)
            return None
        defaults: list[Expression] = []
        rename = False
        if len(args) > 2:
            # Typed namedtuple doesn't support additional arguments.
            if fullname in TYPED_NAMEDTUPLE_NAMES:
                self.fail('Too many arguments for "NamedTuple()"', call)
                return None
            for i, arg_name in enumerate(call.arg_names[2:], 2):
                if arg_name == "defaults":
                    arg = args[i]
                    # We don't care what the values are, as long as the argument is an iterable
                    # and we can count how many defaults there are.
                    if isinstance(arg, (ListExpr, TupleExpr)):
                        defaults = list(arg.items)
                    else:
                        self.fail(
                            "List or tuple literal expected as the defaults argument to "
                            "{}()".format(type_name),
                            arg,
                        )
                elif arg_name == "rename":
                    arg = args[i]
                    if isinstance(arg, NameExpr) and arg.name in ("True", "False"):
                        rename = arg.name == "True"
                    else:
                        self.fail(
                            'Boolean literal expected as the "rename" argument to '
                            f"{type_name}()",
                            arg,
                            code=ARG_TYPE,
                        )
        if call.arg_kinds[:2] != [ARG_POS, ARG_POS]:
            self.fail(f'Unexpected arguments to "{type_name}()"', call)
            return None
        if not isinstance(args[0], StrExpr):
            self.fail(f'"{type_name}()" expects a string literal as the first argument', call)
            return None
        typename = args[0].value
        types: list[Type] = []
        tvar_defs = []
        if not isinstance(args[1], (ListExpr, TupleExpr)):
            if fullname == "collections.namedtuple" and isinstance(args[1], StrExpr):
                str_expr = args[1]
                items = str_expr.value.replace(",", " ").split()
            else:
                self.fail(
                    'List or tuple literal expected as the second argument to "{}()"'.format(
                        type_name
                    ),
                    call,
                )
                return None
        else:
            listexpr = args[1]
            if fullname == "collections.namedtuple":
                # The fields argument contains just names, with implicit Any types.
                if not is_StrExpr_list(listexpr.items):
                    self.fail('String literal expected as "namedtuple()" item', call)
                    return None
                items = [item.value for item in listexpr.items]
            else:
                type_exprs = [
                    t.items[1]
                    for t in listexpr.items
                    if isinstance(t, TupleExpr) and len(t.items) == 2
                ]
                tvar_defs = self.api.get_and_bind_all_tvars(type_exprs)
                # The fields argument contains (name, type) tuples.
                result = self.parse_namedtuple_fields_with_types(listexpr.items, call)
                if result is None:
                    # One of the types is not ready, defer.
                    return None
                items, types, _, ok = result
                if not ok:
                    return [], [], [], typename, [], False
        if not types:
            types = [AnyType(TypeOfAny.unannotated) for _ in items]
        processed_items = []
        seen_names: set[str] = set()
        for i, item in enumerate(items):
            problem = self.check_namedtuple_field_name(item, seen_names)
            if problem is None:
                processed_items.append(item)
                seen_names.add(item)
            else:
                if not rename:
                    self.fail(f'"{type_name}()" {problem}', call)
                # Even if rename=False, we pretend that it is True.
                # At runtime namedtuple creation would throw an error;
                # applying the rename logic means we create a more sensible
                # namedtuple.
                new_name = f"_{i}"
                processed_items.append(new_name)
                seen_names.add(new_name)

        if len(defaults) > len(items):
            self.fail(f'Too many defaults given in call to "{type_name}()"', call)
            defaults = defaults[: len(items)]
        return processed_items, types, defaults, typename, tvar_defs, True

    def parse_namedtuple_fields_with_types(
        self, nodes: list[Expression], context: Context
    ) -> tuple[list[str], list[Type], list[Expression], bool] | None:
        """Parse typed named tuple fields.

        Return (names, types, defaults, whether types are all ready), or None if error occurred.
        """
        items: list[str] = []
        types: list[Type] = []
        for item in nodes:
            if isinstance(item, TupleExpr):
                if len(item.items) != 2:
                    self.fail('Invalid "NamedTuple()" field definition', item)
                    return None
                name, type_node = item.items
                if isinstance(name, StrExpr):
                    items.append(name.value)
                else:
                    self.fail('Invalid "NamedTuple()" field name', item)
                    return None
                try:
                    type = expr_to_unanalyzed_type(type_node, self.options, self.api.is_stub_file)
                except TypeTranslationError:
                    self.fail("Invalid field type", type_node)
                    return None
                # We never allow recursive types at function scope.
                analyzed = self.api.anal_type(
                    type,
                    allow_placeholder=not self.api.is_func_scope(),
                    prohibit_self_type="NamedTuple item type",
                )
                # Workaround #4987 and avoid introducing a bogus UnboundType
                if isinstance(analyzed, UnboundType):
                    analyzed = AnyType(TypeOfAny.from_error)
                # These should be all known, otherwise we would defer in visit_assignment_stmt().
                if analyzed is None:
                    return [], [], [], False
                types.append(analyzed)
            else:
                self.fail('Tuple expected as "NamedTuple()" field', item)
                return None
        return items, types, [], True

    def build_namedtuple_typeinfo(
        self,
        name: str,
        items: list[str],
        types: list[Type],
        default_items: Mapping[str, Expression],
        line: int,
        existing_info: TypeInfo | None,
    ) -> TypeInfo:
        strtype = self.api.named_type("builtins.str")
        implicit_any = AnyType(TypeOfAny.special_form)
        basetuple_type = self.api.named_type("builtins.tuple", [implicit_any])
        dictype = self.api.named_type("builtins.dict", [strtype, implicit_any])
        # Actual signature should return OrderedDict[str, Union[types]]
        ordereddictype = self.api.named_type("builtins.dict", [strtype, implicit_any])
        fallback = self.api.named_type("builtins.tuple", [implicit_any])
        # Note: actual signature should accept an invariant version of Iterable[UnionType[types]].
        # but it can't be expressed. 'new' and 'len' should be callable types.
        iterable_type = self.api.named_type_or_none("typing.Iterable", [implicit_any])
        function_type = self.api.named_type("builtins.function")

        literals: list[Type] = [LiteralType(item, strtype) for item in items]
        match_args_type = TupleType(literals, basetuple_type)

        info = existing_info or self.api.basic_new_typeinfo(name, fallback, line)
        info.is_named_tuple = True
        tuple_base = TupleType(types, fallback)
        if info.special_alias and has_placeholder(info.special_alias.target):
            self.api.process_placeholder(
                None, "NamedTuple item", info, force_progress=tuple_base != info.tuple_type
            )
        info.update_tuple_type(tuple_base)
        info.line = line
        # For use by mypyc.
        info.metadata["namedtuple"] = {"fields": items.copy()}

        # We can't calculate the complete fallback type until after semantic
        # analysis, since otherwise base classes might be incomplete. Postpone a
        # callback function that patches the fallback.
        if not has_placeholder(tuple_base) and not has_type_vars(tuple_base):
            self.api.schedule_patch(
                PRIORITY_FALLBACKS, lambda: calculate_tuple_fallback(tuple_base)
            )

        def add_field(
            var: Var, is_initialized_in_class: bool = False, is_property: bool = False
        ) -> None:
            var.info = info
            var.is_initialized_in_class = is_initialized_in_class
            var.is_property = is_property
            var._fullname = f"{info.fullname}.{var.name}"
            info.names[var.name] = SymbolTableNode(MDEF, var)

        fields = [Var(item, typ) for item, typ in zip(items, types)]
        for var in fields:
            add_field(var, is_property=True)
        # We can't share Vars between fields and method arguments, since they
        # have different full names (the latter are normally used as local variables
        # in functions, so their full names are set to short names when generated methods
        # are analyzed).
        vars = [Var(item, typ) for item, typ in zip(items, types)]

        tuple_of_strings = TupleType([strtype for _ in items], basetuple_type)
        add_field(Var("_fields", tuple_of_strings), is_initialized_in_class=True)
        add_field(Var("_field_types", dictype), is_initialized_in_class=True)
        add_field(Var("_field_defaults", dictype), is_initialized_in_class=True)
        add_field(Var("_source", strtype), is_initialized_in_class=True)
        add_field(Var("__annotations__", ordereddictype), is_initialized_in_class=True)
        add_field(Var("__doc__", strtype), is_initialized_in_class=True)
        if self.options.python_version >= (3, 10):
            add_field(Var("__match_args__", match_args_type), is_initialized_in_class=True)

        assert info.tuple_type is not None  # Set by update_tuple_type() above.
        shared_self_type = TypeVarType(
            name=SELF_TVAR_NAME,
            fullname=f"{info.fullname}.{SELF_TVAR_NAME}",
            # Namespace is patched per-method below.
            id=self.api.tvar_scope.new_unique_func_id(),
            values=[],
            upper_bound=info.tuple_type,
            default=AnyType(TypeOfAny.from_omitted_generics),
        )

        def add_method(
            funcname: str,
            ret: Type | None,  # None means use (patched) self-type
            args: list[Argument],
            is_classmethod: bool = False,
            is_new: bool = False,
        ) -> None:
            fullname = f"{info.fullname}.{funcname}"
            self_type = shared_self_type.copy_modified(
                id=TypeVarId(shared_self_type.id.raw_id, namespace=fullname)
            )
            if ret is None:
                ret = self_type
            if is_classmethod or is_new:
                first = [Argument(Var("_cls"), TypeType.make_normalized(self_type), None, ARG_POS)]
            else:
                first = [Argument(Var("_self"), self_type, None, ARG_POS)]
            args = first + args

            types = [arg.type_annotation for arg in args]
            items = [arg.variable.name for arg in args]
            arg_kinds = [arg.kind for arg in args]
            assert None not in types
            signature = CallableType(cast(List[Type], types), arg_kinds, items, ret, function_type)
            signature.variables = [self_type]
            func = FuncDef(funcname, args, Block([]))
            func.info = info
            func.is_class = is_classmethod
            func.type = set_callable_name(signature, func)
            func._fullname = fullname
            func.line = line
            if is_classmethod:
                v = Var(funcname, func.type)
                v.is_classmethod = True
                v.info = info
                v._fullname = func._fullname
                func.is_decorated = True
                dec = Decorator(func, [NameExpr("classmethod")], v)
                dec.line = line
                sym = SymbolTableNode(MDEF, dec)
            else:
                sym = SymbolTableNode(MDEF, func)
            sym.plugin_generated = True
            info.names[funcname] = sym

        add_method(
            "_replace",
            ret=None,
            args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars],
        )
        if self.options.python_version >= (3, 13):
            add_method(
                "__replace__",
                ret=None,
                args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars],
            )

        def make_init_arg(var: Var) -> Argument:
            default = default_items.get(var.name, None)
            kind = ARG_POS if default is None else ARG_OPT
            return Argument(var, var.type, default, kind)

        add_method("__new__", ret=None, args=[make_init_arg(var) for var in vars], is_new=True)
        add_method("_asdict", args=[], ret=ordereddictype)
        add_method(
            "_make",
            ret=None,
            is_classmethod=True,
            args=[Argument(Var("iterable", iterable_type), iterable_type, None, ARG_POS)],
        )

        self_tvar_expr = TypeVarExpr(
            SELF_TVAR_NAME,
            info.fullname + "." + SELF_TVAR_NAME,
            [],
            info.tuple_type,
            AnyType(TypeOfAny.from_omitted_generics),
        )
        info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr)
        return info

    @contextmanager
    def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]:
        """Preserve the generated body of class-based named tuple and then restore it.

        Temporarily clear the names dict so we don't get errors about duplicate names
        that were already set in build_namedtuple_typeinfo (we already added the tuple
        field names while generating the TypeInfo, and actual duplicates are
        already reported).
        """
        nt_names = named_tuple_info.names
        named_tuple_info.names = SymbolTable()

        yield

        # Make sure we didn't use illegal names, then reset the names in the typeinfo.
        for prohibited in NAMEDTUPLE_PROHIBITED_NAMES:
            if prohibited in named_tuple_info.names:
                if nt_names.get(prohibited) is named_tuple_info.names[prohibited]:
                    continue
                ctx = named_tuple_info.names[prohibited].node
                assert ctx is not None
                self.fail(f'Cannot overwrite NamedTuple attribute "{prohibited}"', ctx)

        # Restore the names in the original symbol table. This ensures that the symbol
        # table contains the field objects created by build_namedtuple_typeinfo. Exclude
        # __doc__, which can legally be overwritten by the class.
        for key, value in nt_names.items():
            if key in named_tuple_info.names:
                if key == "__doc__":
                    continue
                sym = named_tuple_info.names[key]
                if isinstance(sym.node, (FuncBase, Decorator)) and not sym.plugin_generated:
                    # Keep user-defined methods as is.
                    continue
                # Keep existing (user-provided) definitions under mangled names, so they
                # get semantically analyzed.
                r_key = get_unique_redefinition_name(key, named_tuple_info.names)
                named_tuple_info.names[r_key] = sym
            named_tuple_info.names[key] = value

    # Helpers

    def check_namedtuple_field_name(self, field: str, seen_names: Container[str]) -> str | None:
        """Return None for valid fields, a string description for invalid ones."""
        if field in seen_names:
            return f'has duplicate field name "{field}"'
        elif not field.isidentifier():
            return f'field name "{field}" is not a valid identifier'
        elif field.startswith("_"):
            return f'field name "{field}" starts with an underscore'
        elif keyword.iskeyword(field):
            return f'field name "{field}" is a keyword'
        return None

    def fail(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None:
        self.api.fail(msg, ctx, code=code)


"""Semantic analysis of NewType definitions.

This is conceptually part of mypy.semanal (semantic analyzer pass 2).
"""

from __future__ import annotations

from mypy import errorcodes as codes
from mypy.errorcodes import ErrorCode
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
from mypy.messages import MessageBuilder, format_type
from mypy.nodes import (
    ARG_POS,
    MDEF,
    Argument,
    AssignmentStmt,
    Block,
    CallExpr,
    Context,
    FuncDef,
    NameExpr,
    NewTypeExpr,
    PlaceholderNode,
    RefExpr,
    StrExpr,
    SymbolTableNode,
    TypeInfo,
    Var,
)
from mypy.options import Options
from mypy.semanal_shared import SemanticAnalyzerInterface, has_placeholder
from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type
from mypy.types import (
    AnyType,
    CallableType,
    Instance,
    NoneType,
    PlaceholderType,
    TupleType,
    Type,
    TypeOfAny,
    get_proper_type,
)

class NewTypeAnalyzer:
    def __init__(
        self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder
    ) -> None:
        self.options = options
        self.api = api
        self.msg = msg

    def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
        """Check if s declares a NewType; if yes, store it in symbol table.

        Return True if it's a NewType declaration. The current target may be
        deferred as a side effect if the base type is not ready, even if
        the return value is True.

        The logic in this function mostly copies the logic for visit_class_def()
        with a single (non-Generic) base.
        """
        var_name, call = self.analyze_newtype_declaration(s)
        if var_name is None or call is None:
            return False
        name = var_name
        # OK, now we know this is a NewType. But the base type may be not ready yet,
        # add placeholder as we do for ClassDef.

        if self.api.is_func_scope():
            name += "@" + str(s.line)
        fullname = self.api.qualified_name(name)

        if not call.analyzed or isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info:
            # Start from labeling this as a future class, as we do for normal ClassDefs.
            placeholder = PlaceholderNode(fullname, s, s.line, becomes_typeinfo=True)
            self.api.add_symbol(var_name, placeholder, s, can_defer=False)

        old_type, should_defer = self.check_newtype_args(var_name, call, s)
        old_type = get_proper_type(old_type)
        if not isinstance(call.analyzed, NewTypeExpr):
            call.analyzed = NewTypeExpr(var_name, old_type, line=call.line, column=call.column)
        else:
            call.analyzed.old_type = old_type
        if old_type is None:
            if should_defer:
                # Base type is not ready.
                self.api.defer()
                return True

        # Create the corresponding class definition if the aliased type is subtypeable
        assert isinstance(call.analyzed, NewTypeExpr)
        if isinstance(old_type, TupleType):
            newtype_class_info = self.build_newtype_typeinfo(
                name, old_type, old_type.partial_fallback, s.line, call.analyzed.info
            )
            newtype_class_info.update_tuple_type(old_type)
        elif isinstance(old_type, Instance):
            if old_type.type.is_protocol:
                self.fail("NewType cannot be used with protocol classes", s)
            newtype_class_info = self.build_newtype_typeinfo(
                name, old_type, old_type, s.line, call.analyzed.info
            )
        else:
            if old_type is not None:
                message = "Argument 2 to NewType(...) must be subclassable (got {})"
                self.fail(
                    message.format(format_type(old_type, self.options)),
                    s,
                    code=codes.VALID_NEWTYPE,
                )
            # Otherwise the error was already reported.
            old_type = AnyType(TypeOfAny.from_error)
            object_type = self.api.named_type("builtins.object")
            newtype_class_info = self.build_newtype_typeinfo(
                name, old_type, object_type, s.line, call.analyzed.info
            )
            newtype_class_info.fallback_to_any = True

        check_for_explicit_any(
            old_type, self.options, self.api.is_typeshed_stub_file, self.msg, context=s
        )

        if self.options.disallow_any_unimported and has_any_from_unimported_type(old_type):
            self.msg.unimported_type_becomes_any("Argument 2 to NewType(...)", old_type, s)

        # If so, add it to the symbol table.
        assert isinstance(call.analyzed, NewTypeExpr)
        # As we do for normal classes, create the TypeInfo only once, then just
        # update base classes on next iterations (to get rid of placeholders there).
        if not call.analyzed.info:
            call.analyzed.info = newtype_class_info
        else:
            call.analyzed.info.bases = newtype_class_info.bases
        self.api.add_symbol(var_name, call.analyzed.info, s)
        if self.api.is_func_scope():
            self.api.add_symbol_skip_local(name, call.analyzed.info)
        newtype_class_info.line = s.line
        return True

    def analyze_newtype_declaration(self, s: AssignmentStmt) -> tuple[str | None, CallExpr | None]:
        """Return the NewType call expression if `s` is a newtype declaration or None otherwise."""
        name, call = None, None
        if (
            len(s.lvalues) == 1
            and isinstance(s.lvalues[0], NameExpr)
            and isinstance(s.rvalue, CallExpr)
            and isinstance(s.rvalue.callee, RefExpr)
            and (s.rvalue.callee.fullname in ("typing.NewType", "typing_extensions.NewType"))
        ):
            name = s.lvalues[0].name

            if s.type:
                self.fail("Cannot declare the type of a NewType declaration", s)

            names = self.api.current_symbol_table()
            existing = names.get(name)
            # Give a better error message than generic "Name already defined".
            if (
                existing
                and not isinstance(existing.node, PlaceholderNode)
                and not s.rvalue.analyzed
            ):
                self.fail(f'Cannot redefine "{name}" as a NewType', s)

            # This dummy NewTypeExpr marks the call as sufficiently analyzed; it will be
            # overwritten later with a fully complete NewTypeExpr if there are no other
            # errors with the NewType() call.
            call = s.rvalue

        return name, call

    def check_newtype_args(
        self, name: str, call: CallExpr, context: Context
    ) -> tuple[Type | None, bool]:
        """Ananlyze base type in NewType call.

        Return a tuple (type, should defer).
        """
        has_failed = False
        args, arg_kinds = call.args, call.arg_kinds
        if len(args) != 2 or arg_kinds[0] != ARG_POS or arg_kinds[1] != ARG_POS:
            self.fail("NewType(...) expects exactly two positional arguments", context)
            return None, False

        # Check first argument
        if not isinstance(args[0], StrExpr):
            self.fail("Argument 1 to NewType(...) must be a string literal", context)
            has_failed = True
        elif args[0].value != name:
            msg = 'String argument 1 "{}" to NewType(...) does not match variable name "{}"'
            self.fail(msg.format(args[0].value, name), context)
            has_failed = True

        # Check second argument
        msg = "Argument 2 to NewType(...) must be a valid type"
        try:
            unanalyzed_type = expr_to_unanalyzed_type(args[1], self.options, self.api.is_stub_file)
        except TypeTranslationError:
            self.fail(msg, context)
            return None, False

        # We want to use our custom error message (see above), so we suppress
        # the default error message for invalid types here.
        old_type = get_proper_type(
            self.api.anal_type(
                unanalyzed_type,
                report_invalid_types=False,
                allow_placeholder=not self.api.is_func_scope(),
            )
        )
        should_defer = False
        if isinstance(old_type, PlaceholderType):
            old_type = None
        if old_type is None:
            should_defer = True

        # The caller of this function assumes that if we return a Type, it's always
        # a valid one. So, we translate AnyTypes created from errors into None.
        if isinstance(old_type, AnyType) and old_type.is_from_error:
            self.fail(msg, context)
            return None, False

        return None if has_failed else old_type, should_defer

    def build_newtype_typeinfo(
        self,
        name: str,
        old_type: Type,
        base_type: Instance,
        line: int,
        existing_info: TypeInfo | None,
    ) -> TypeInfo:
        info = existing_info or self.api.basic_new_typeinfo(name, base_type, line)
        info.bases = [base_type]  # Update in case there were nested placeholders.
        info.is_newtype = True

        # Add __init__ method
        args = [
            Argument(Var("self"), NoneType(), None, ARG_POS),
            self.make_argument("item", old_type),
        ]
        signature = CallableType(
            arg_types=[Instance(info, []), old_type],
            arg_kinds=[arg.kind for arg in args],
            arg_names=["self", "item"],
            ret_type=NoneType(),
            fallback=self.api.named_type("builtins.function"),
            name=name,
        )
        init_func = FuncDef("__init__", args, Block([]), typ=signature)
        init_func.info = info
        init_func._fullname = info.fullname + ".__init__"
        if not existing_info:
            updated = True
        else:
            previous_sym = info.names["__init__"].node
            assert isinstance(previous_sym, FuncDef)
            updated = old_type != previous_sym.arguments[1].variable.type
        info.names["__init__"] = SymbolTableNode(MDEF, init_func)

        if has_placeholder(old_type):
            self.api.process_placeholder(None, "NewType base", info, force_progress=updated)
        return info

    # Helpers

    def make_argument(self, name: str, type: Type) -> Argument:
        return Argument(Var(name), type, None, ARG_POS)

    def fail(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None:
        self.api.fail(msg, ctx, code=code)


"""Block/import reachability analysis."""

from __future__ import annotations

from mypy.nodes import (
    AssertStmt,
    AssignmentStmt,
    Block,
    ClassDef,
    ExpressionStmt,
    ForStmt,
    FuncDef,
    IfStmt,
    Import,
    ImportAll,
    ImportFrom,
    MatchStmt,
    MypyFile,
    ReturnStmt,
)
from mypy.options import Options
from mypy.reachability import (
    assert_will_always_fail,
    infer_reachability_of_if_statement,
    infer_reachability_of_match_statement,
)
from mypy.traverser import TraverserVisitor

class SemanticAnalyzerPreAnalysis(TraverserVisitor):
    """Analyze reachability of blocks and imports and other local things.

    This runs before semantic analysis, so names have not been bound. Imports are
    also not resolved yet, so we can only access the current module.

    This determines static reachability of blocks and imports due to version and
    platform checks, among others.

    The main entry point is 'visit_file'.

    Reachability of imports needs to be determined very early in the build since
    this affects which modules will ultimately be processed.

    Consider this example:

      import sys

      def do_stuff() -> None:
          if sys.version_info >= (3, 10):
              import xyz  # Only available in Python 3.10+
              xyz.whatever()
          ...

    The block containing 'import xyz' is unreachable in Python 3 mode. The import
    shouldn't be processed in Python 3 mode, even if the module happens to exist.
    """

    def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None:
        self.platform = options.platform
        self.cur_mod_id = mod_id
        self.cur_mod_node = file
        self.options = options
        self.is_global_scope = True
        self.skipped_lines: set[int] = set()

        for i, defn in enumerate(file.defs):
            defn.accept(self)
            if isinstance(defn, AssertStmt) and assert_will_always_fail(defn, options):
                # We've encountered an assert that's always false,
                # e.g. assert sys.platform == 'lol'.  Truncate the
                # list of statements.  This mutates file.defs too.
                if i < len(file.defs) - 1:
                    next_def, last = file.defs[i + 1], file.defs[-1]
                    if last.end_line is not None:
                        # We are on a Python version recent enough to support end lines.
                        self.skipped_lines |= set(range(next_def.line, last.end_line + 1))
                del file.defs[i + 1 :]
                break
        file.skipped_lines = self.skipped_lines

    def visit_func_def(self, node: FuncDef) -> None:
        old_global_scope = self.is_global_scope
        self.is_global_scope = False
        super().visit_func_def(node)
        self.is_global_scope = old_global_scope
        file_node = self.cur_mod_node
        if (
            self.is_global_scope
            and file_node.is_stub
            and node.name == "__getattr__"
            and file_node.is_package_init_file()
        ):
            # __init__.pyi with __getattr__ means that any submodules are assumed
            # to exist, even if there is no stub. Note that we can't verify that the
            # return type is compatible, since we haven't bound types yet.
            file_node.is_partial_stub_package = True

    def visit_class_def(self, node: ClassDef) -> None:
        old_global_scope = self.is_global_scope
        self.is_global_scope = False
        super().visit_class_def(node)
        self.is_global_scope = old_global_scope

    def visit_import_from(self, node: ImportFrom) -> None:
        node.is_top_level = self.is_global_scope
        super().visit_import_from(node)

    def visit_import_all(self, node: ImportAll) -> None:
        node.is_top_level = self.is_global_scope
        super().visit_import_all(node)

    def visit_import(self, node: Import) -> None:
        node.is_top_level = self.is_global_scope
        super().visit_import(node)

    def visit_if_stmt(self, s: IfStmt) -> None:
        infer_reachability_of_if_statement(s, self.options)
        for expr in s.expr:
            expr.accept(self)
        for node in s.body:
            node.accept(self)
        if s.else_body:
            s.else_body.accept(self)

    def visit_block(self, b: Block) -> None:
        if b.is_unreachable:
            if b.end_line is not None:
                # We are on a Python version recent enough to support end lines.
                self.skipped_lines |= set(range(b.line, b.end_line + 1))
            return
        super().visit_block(b)

    def visit_match_stmt(self, s: MatchStmt) -> None:
        infer_reachability_of_match_statement(s, self.options)
        for guard in s.guards:
            if guard is not None:
                guard.accept(self)
        for body in s.bodies:
            body.accept(self)

    # The remaining methods are an optimization: don't visit nested expressions
    # of common statements, since they can have no effect.

    def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
        pass

    def visit_expression_stmt(self, s: ExpressionStmt) -> None:
        pass

    def visit_return_stmt(self, s: ReturnStmt) -> None:
        pass

    def visit_for_stmt(self, s: ForStmt) -> None:
        s.body.accept(self)
        if s.else_body is not None:
            s.else_body.accept(self)