use std::cell::{OnceCell, RefCell};
use std::rc::Rc;

use compact_str::CompactString;
use itertools::Itertools;
use ruff_db::files::File;
use ruff_db::parsed::ParsedModuleRef;
use ruff_db::source::source_text;
use ruff_python_ast::helpers::is_dotted_name;
use ruff_python_ast::name::Name;
use ruff_python_ast::{
    self as ast, AnyNodeRef, ArgOrKeyword, ArgumentsSourceOrder, ExprContext, HasNodeIndex,
    PythonVersion,
};
use ruff_python_stdlib::builtins::version_builtin_was_added;
use ruff_python_stdlib::typing::as_pep_585_generic;
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use strum::IntoEnumIterator;
use ty_module_resolver::{ModuleName, resolve_module};
use ty_python_core::ast_ids::HasScopedUseId;
use ty_python_core::statement::StatementInner;

use super::{
    DeferredAndUndecorated, DefinitionInference, DefinitionInferenceExtra, DefinitionTypes,
    ExpressionInference, ExpressionInferenceExtra, FrozenMap, FrozenSet, FrozenValueMap,
    FunctionDecoratorInference, InferenceRegion, OtherDefinitionInferenceExtra, ScopeInference,
    ScopeInferenceExtra, infer_deferred_types, infer_definition_types, infer_expression_types,
    infer_same_file_expression_type, infer_unpack_types,
};
use crate::diagnostic::format_enumeration;
use crate::place::{
    ConsideredDefinitions, DefinedPlace, Definedness, LookupError, Place, PlaceAndQualifiers,
    RequiresExplicitReExport, TypeOrigin, builtins_module_scope, builtins_symbol,
    class_body_implicit_symbol, explicit_global_symbol, loop_header_reachability,
    module_type_implicit_global_declaration, module_type_implicit_global_symbol, place_by_id,
    place_from_bindings_with_reachability_cache, place_from_declarations_with_reachability_cache,
    typing_extensions_symbol,
};
use crate::reachability::{ReachabilityEvaluationCache, evaluate_reachability_with_cache};
use crate::types::add_inferred_python_version_hint_to_diagnostic;
use crate::types::attribute_write::{AssignmentAttributeMembers, assignment_attribute_members};
use crate::types::call::bind::MatchingOverloadIndex;
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
use crate::types::callable::{CallableFunctionProvenance, CallableTypeKind};
use crate::types::class::{ClassLiteral, CodeGeneratorKind, MethodDecorator};
use crate::types::constraints::{ConstraintSetBuilder, PathBounds, Solutions};
use crate::types::context::InferContext;
use crate::types::diagnostic::{
    self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CYCLIC_TYPE_ALIAS_DEFINITION,
    GeneratorMismatchKind, INEFFECTIVE_FINAL, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT,
    INVALID_DECLARATION, INVALID_ENUM_MEMBER_ANNOTATION, INVALID_LEGACY_TYPE_VARIABLE,
    INVALID_NEWTYPE, INVALID_PARAMSPEC, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM,
    INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_BOUND, INVALID_TYPE_VARIABLE_CONSTRAINTS,
    POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_SUBMODULE, UNDEFINED_REVEAL,
    UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR,
    UNUSED_AWAITABLE, hint_if_stdlib_attribute_exists_on_other_versions,
    report_attempted_protocol_instantiation, report_bad_dunder_delattr_call,
    report_bad_dunder_delete_call, report_call_to_abstract_method,
    report_cannot_pop_required_field_on_typed_dict, report_invalid_assignment,
    report_invalid_class_match_pattern, report_invalid_exception_caught,
    report_invalid_exception_cause, report_invalid_exception_raised,
    report_invalid_exception_tuple_caught, report_invalid_generator_yield_type,
    report_invalid_key_on_typed_dict, report_invalid_match_args_type,
    report_invalid_type_checking_constant,
    report_match_pattern_against_non_runtime_checkable_protocol,
    report_match_pattern_against_typed_dict, report_mismatched_type_name,
    report_possibly_missing_attribute, report_possibly_unresolved_reference,
    report_too_many_positional_patterns_for_class_pattern, report_unsupported_augmented_assignment,
    report_unsupported_comparison,
};
use crate::types::enums::{enum_ignored_names, is_enum_class_by_inheritance};
use crate::types::function::{
    FunctionDecorators, FunctionType, KnownFunction, report_revealed_type,
    same_module_uncached_raw_signature,
};
use crate::types::generics::{
    GenericContext, InferableTypeVars, Specialization, SpecializationBuilder, bind_typevar,
    enclosing_binding_contexts,
};
use crate::types::infer::builder::named_tuple::NamedTupleKind;
use crate::types::infer::builder::paramspec_validation::validate_paramspec_components;
use crate::types::infer::builder::typed_dict::TypedDictConstructorForm;
use crate::types::infer::{
    StatementInference, StatementInferenceInner, StatementInferenceInnerExtra, TypeAndRange,
    TypeExpressionFlags, infer_statement_types, nearest_enclosing_class,
    nearest_enclosing_function, original_class_type,
};
use crate::types::match_pattern::{ClassPatternPositionalResult, class_pattern_positional_result};
use crate::types::narrow::NarrowingEvaluatorExtension;
use crate::types::narrow::pattern_success_types;
use crate::types::newtype::NewType;
use crate::types::set_theoretic::RecursivelyDefined;
use crate::types::signatures::{CallableSignature, ReturnCallableTypeVarScope};
use crate::types::special_form::TypeQualifier;
use crate::types::subclass_of::SubclassOfInner;
use crate::types::tuple::promotion::TupleSizePromotionConstraints;
use crate::types::tuple::{Tuple, TupleLength, TupleSpecBuilder, TupleType};
use crate::types::type_alias::{ManualPEP695TypeAliasType, PEP695TypeAliasType};
use crate::types::typed_dict::{TypedDictAssignmentKind, TypedDictKeyAssignment};
use crate::types::typevar::{BoundTypeVarIdentity, TypeVarConstraints, TypeVarIdentity};
use crate::types::unpacker::UnpackResult;
use crate::types::{
    BoundTypeVarInstance, CallDunderError, CallableBinding, CallableType, CallableTypes, ClassType,
    DynamicType, InferenceFlags, InternedConstraintSet, InternedType, IntersectionBuilder,
    IntersectionType, KnownClass, KnownInstanceType, KnownUnion, LiteralValueTypeKind,
    MemberLookupPolicy, ParamSpecAttrKind, Parameter, Parameters, SentinelInstance, Signature,
    SpecialFormType, SubclassOfType, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
    TypeQualifiers, TypeVarBoundOrConstraints, TypeVarKind, TypeVarVariance, TypedDictModule,
    TypedDictType, UnionAccumulator, UnionBuilder, UnionType, any_over_type, binding_type,
    extract_fixed_length_iterable_element_types, infer_complete_scope_types, infer_scope_types,
    is_discarded_dict_key_assignment, todo_type,
};
use crate::{AnalysisSettings, Db, FxIndexSet, Program};
use ty_python_core::ast_ids::ScopedUseId;
use ty_python_core::definition::{
    AnnotatedAssignmentDefinitionKind, AssignmentDefinitionKind, ComprehensionDefinitionKind,
    Definition, DefinitionKind, DefinitionNodeKey, DefinitionState, ExceptHandlerDefinitionKind,
    ForStmtDefinitionKind, LambdaParameterDefinitionNodeKind, LoopHeaderDefinitionKind,
    NestedBindingsDefinitionKind, ParameterDefinitionNodeKind, TargetKind, WithItemDefinitionKind,
};
use ty_python_core::expression::{Expression, ExpressionKind};
use ty_python_core::narrowing_constraints::ConstraintKey;
use ty_python_core::node_key::NodeKey;
use ty_python_core::place::{PlaceExpr, PlaceExprRef};
use ty_python_core::predicate::PatternPredicate;
use ty_python_core::scope::{FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId, ScopeKind};
use ty_python_core::symbol::{ScopedSymbolId, Symbol};
use ty_python_core::{
    ApplicableConstraints, EnclosingSnapshotResult, EvaluationMode, SemanticIndex, Truthiness,
    place_table, unpack::UnpackPosition,
};
use ty_python_core::{ExpressionNodeKey, Statement};

mod annotation_expression;
mod attribute_assignment;
mod binary_expressions;
mod class;
mod dict;
mod dynamic_class;
mod enum_call;
mod final_attribute;
mod function;
mod imports;
mod named_tuple;
mod new_class;
mod paramspec_validation;
mod post_inference;
mod subscript;
mod type_call;
mod type_expression;
mod type_form;
mod typed_dict;
mod typevar;

use super::comparisons::{self, BinaryComparisonVisitor};

/// A helper to track if we already know that declared and inferred types are the same.
#[derive(Debug, Clone, PartialEq, Eq)]
enum DeclaredAndInferredType<'db> {
    /// We know that both the declared and inferred types are the same.
    AreTheSame(TypeAndQualifiers<'db>),
    /// Declared and inferred types might be different, we need to check assignability.
    MightBeDifferent {
        declared_ty: TypeAndQualifiers<'db>,
        inferred_ty: Type<'db>,
    },
}

impl<'db> DeclaredAndInferredType<'db> {
    fn are_the_same_type(ty: Type<'db>) -> Self {
        Self::AreTheSame(TypeAndQualifiers::new(
            ty,
            TypeOrigin::Inferred,
            TypeQualifiers::empty(),
        ))
    }
}

fn should_preserve_inferred_binding_type(ty: Type<'_>) -> bool {
    // Dataclass field specifiers carry metadata in the inferred RHS type; replacing it with the
    // declared field type would lose settings like `init=False`.
    matches!(ty, Type::KnownInstance(KnownInstanceType::Field(_)))
}

/// We currently store one dataclass field-specifiers inline, because that covers standard
/// dataclasses. attrs uses 2 specifiers, pydantic and strawberry use 3 specifiers. SQLAlchemy
/// uses 7 field specifiers. We could probably store more inline if this turns out to be a
/// performance problem. For now, we optimize for memory usage.
const NUM_FIELD_SPECIFIERS_INLINE: usize = 1;

/// Builder to infer all types in a region.
///
/// A builder is used by creating it with [`new()`](TypeInferenceBuilder::new), and then calling
/// [`finish_expression()`](TypeInferenceBuilder::finish_expression), [`finish_definition()`](TypeInferenceBuilder::finish_definition), or [`finish_scope()`](TypeInferenceBuilder::finish_scope) on it, which returns
/// type inference result.
///
/// There are a few different kinds of methods in the type inference builder, and the naming
/// distinctions are a bit subtle.
///
/// The `finish` methods call [`infer_region`](TypeInferenceBuilder::infer_region), which delegates
/// to one of [`infer_region_scope`](TypeInferenceBuilder::infer_region_scope),
/// [`infer_region_definition`](TypeInferenceBuilder::infer_region_definition),
/// [`infer_region_function_decorators`](TypeInferenceBuilder::infer_region_function_decorators),
/// [`infer_region_deferred`](TypeInferenceBuilder::infer_region_deferred), or
/// [`infer_region_expression`](TypeInferenceBuilder::infer_region_expression), depending which
/// kind of [`InferenceRegion`] we are inferring types for.
///
/// Scope inference starts with the scope body, walking all statements and expressions and
/// recording the types of each expression in the inference result. Most of the methods
/// here (with names like `infer_*_statement` or `infer_*_expression` or some other node kind) take
/// a single AST node and are called as part of this AST visit.
///
/// When the visit encounters a node which creates a [`Definition`], we look up the definition in
/// the semantic index and call the [`infer_definition_types()`] query on it, which creates another
/// [`TypeInferenceBuilder`] just for that definition, and we merge the returned inference result
/// into the one we are currently building for the entire scope. Using the query in this way
/// ensures that if we first infer types for some scattered definitions in a scope, and later for
/// the entire scope, we don't re-infer any types, we reuse the cached inference for those
/// definitions and their sub-expressions.
///
/// Functions with a name like `infer_*_definition` take both a node and a [`Definition`], and are
/// called by [`infer_region_definition`](TypeInferenceBuilder::infer_region_definition).
///
/// So for example we have both
/// [`infer_function_definition_statement`](TypeInferenceBuilder::infer_function_definition_statement),
/// which takes just the function AST node, and
/// [`infer_function_definition`](TypeInferenceBuilder::infer_function_definition), which takes
/// both the node and the [`Definition`] id. The former is called as part of walking the AST, and
/// it just looks up the [`Definition`] for that function in the semantic index and calls
/// [`infer_definition_types()`] on it, which will create a new [`TypeInferenceBuilder`] with
/// [`InferenceRegion::Definition`], and in that builder
/// [`infer_region_definition`](TypeInferenceBuilder::infer_region_definition) will call
/// [`infer_function_definition`](TypeInferenceBuilder::infer_function_definition) to actually
/// infer a type for the definition.
///
/// Similarly, when we encounter a standalone-inferable expression (right-hand side of an
/// assignment, type narrowing guard), we use the [`infer_expression_types()`] query to ensure we
/// don't infer its types more than once.
pub(super) struct TypeInferenceBuilder<'db, 'ast> {
    context: InferContext<'db, 'ast>,

    index: &'db SemanticIndex<'db>,
    region: InferenceRegion<'db>,

    /// The types of every expression in this region.
    expressions: FxHashMap<ExpressionNodeKey, Type<'db>>,

    /// An expression cache shared across builders during multi-inference.
    expression_cache: Option<Rc<RefCell<ExpressionCache<'db>>>>,

    /// Reachability evaluations reused while inferring this region.
    ///
    /// Most inference regions never evaluate reachability, so allocate the cache lazily. Speculative
    /// builders share an initialized cache with their parent so repeated place lookups performed
    /// during multi-inference can reuse predicate truthiness computed by the parent builder.
    reachability_cache: OnceCell<Rc<ReachabilityEvaluationCache<'db>>>,

    /// Type qualifiers (`Required`, `NotRequired`, etc.) for annotation expressions.
    /// Only populated for expressions that have non-empty qualifiers.
    qualifiers: FxHashMap<ExpressionNodeKey, TypeQualifiers>,

    /// Metadata for type expressions.
    /// Only populated for expressions that have non-empty flags.
    type_expression_flags: FxHashMap<ExpressionNodeKey, TypeExpressionFlags>,

    /// The constraints on any collection initializers that are accessed in this region.
    //
    // TODO: Store projected constraint sets directly here instead of specialized receiver types.
    // Bound-method calls on unconstrained collection initializers can introduce method-local typevars
    // (for example, `list.sort` constrains `T@list` using `SupportsRichComparisonT@sort`). A
    // principled representation would store an owned constraint set over the collection initializer's
    // generic context and existentially quantify away the method-local typevars, so combining
    // `xs.append("x")` with `xs.sort()` yields `str ≤ T ≤ SupportsRichComparison` instead of
    // leaking `SupportsRichComparisonT@sort` into the inferred list element type.
    collection_use_constraints: FxHashMap<Definition<'db>, FxIndexSet<Type<'db>>>,

    /// Expressions that are string annotations
    string_annotations: FxHashSet<ExpressionNodeKey>,

    /// Expected types for expression nodes tracked for IDE completion.
    expected_types: FxHashMap<ExpressionNodeKey, Type<'db>>,

    /// The scope this region is part of.
    scope: ScopeId<'db>,

    // bindings, declarations, and deferred can only exist in definition, or scope contexts.
    /// The types of every binding in this region.
    ///
    /// The list should only contain one entry per binding at most.
    bindings: VecMap<Definition<'db>, Type<'db>>,

    /// The types and type qualifiers of every valid declaration in this region.
    ///
    /// The list should only contain one entry per declaration at most.
    declarations: VecMap<Definition<'db>, TypeAndQualifiers<'db>>,

    /// The definitions with deferred sub-parts.
    ///
    /// The list should only contain one entry per definition.
    deferred: VecSet<Definition<'db>>,

    /// The returned types and their corresponding ranges of the region, if it is a function body.
    return_types_and_ranges: Vec<TypeAndRange<'db>>,

    /// A set of functions that have been defined **and** called in this region.
    ///
    /// This is a set because the same function could be called multiple times in the same region.
    /// This is mainly used in [`post_inference::overloaded_function::check_overloaded_function`] to
    /// check an overloaded function that is shadowed by a function with the same name in this
    /// scope but has been called before. For example:
    ///
    /// ```py
    /// from typing import overload
    ///
    /// @overload
    /// def foo() -> None: ...
    /// @overload
    /// def foo(x: int) -> int: ...
    /// def foo(x: int | None) -> int | None: return x
    ///
    /// foo()  # An overloaded function that was defined in this scope have been called
    ///
    /// def foo(x: int) -> int:
    ///     return x
    /// ```
    ///
    /// To keep the calculation deterministic, we use an `FxIndexSet` whose order is determined by the sequence of insertion calls.
    called_functions: FxIndexSet<FunctionType<'db>>,

    /// Whether we are in a context that binds unbound typevars.
    typevar_binding_context: Option<Definition<'db>>,

    /// The deferred state of inferring types of certain expressions within the region.
    ///
    /// This is different from [`InferenceRegion::Deferred`] which works on the entire definition
    /// while this is relevant for specific expressions within the region itself and is updated
    /// during the inference process.
    ///
    /// For example, when inferring the types of an annotated assignment, the type of an annotation
    /// expression could be deferred if the file has `from __future__ import annotations` import or
    /// is a stub file but we're still in a non-deferred region.
    deferred_state: DeferredExpressionState,

    /// For decorated function or class definitions, the type before applying decorators.
    undecorated_type: Option<Type<'db>>,

    /// The fallback type for missing expressions/bindings/declarations or recursive type inference.
    cycle_recovery: Option<Type<'db>>,

    /// If the inference region refers to a definition, whether synthesized dictionary-key
    /// assignments derived from its right-hand side should be discarded.
    discards_dict_key_assignments: bool,

    /// A list of `dataclass_transform` field specifiers that are "active" (when inferring
    /// the right hand side of an annotated assignment in a class that is a dataclass).
    dataclass_field_specifiers: SmallVec<[Type<'db>; NUM_FIELD_SPECIFIERS_INLINE]>,
}

/// An expression cache shared across builders during multi-inference.
type ExpressionCache<'db> = FxHashMap<(ExpressionNodeKey, TypeContext<'db>), Type<'db>>;

impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
    /// How big a string do we build before bailing?
    ///
    /// This is a fairly arbitrary number. It should be *far* more than enough
    /// for most use cases, but we can reevaluate it later if useful.
    pub(super) const MAX_STRING_LITERAL_SIZE: usize = 4096;

    /// Creates a new builder for inferring types in a region.
    pub(super) fn new(
        db: &'db dyn Db,
        region: InferenceRegion<'db>,
        index: &'db SemanticIndex<'db>,
        module: &'ast ParsedModuleRef,
    ) -> Self {
        let scope = region.scope(db);
        Self {
            context: InferContext::new(db, scope, module),
            index,
            region,
            scope,
            return_types_and_ranges: vec![],
            called_functions: FxIndexSet::default(),
            deferred_state: DeferredExpressionState::None,
            expressions: FxHashMap::default(),
            expression_cache: None,
            reachability_cache: OnceCell::new(),
            qualifiers: FxHashMap::default(),
            type_expression_flags: FxHashMap::default(),
            collection_use_constraints: FxHashMap::default(),
            string_annotations: FxHashSet::default(),
            expected_types: FxHashMap::default(),
            bindings: VecMap::default(),
            declarations: VecMap::default(),
            typevar_binding_context: None,
            deferred: VecSet::default(),
            undecorated_type: None,
            cycle_recovery: None,
            discards_dict_key_assignments: false,
            dataclass_field_specifiers: SmallVec::new(),
        }
    }

    fn reachability_cache(&self) -> &ReachabilityEvaluationCache<'db> {
        self.reachability_cache
            .get_or_init(|| {
                let scope = self.scope();
                let reachability_constraints = self
                    .index
                    .use_def_map(scope.file_scope_id(self.db()))
                    .reachability_constraints();
                Rc::new(ReachabilityEvaluationCache::new(
                    scope,
                    reachability_constraints,
                ))
            })
            .as_ref()
    }

    fn discard_dict_key_assignments_for(&mut self, definition: Definition<'db>) {
        if matches!(self.region, InferenceRegion::Definition(d) if d == definition) {
            self.discards_dict_key_assignments = true;
        }
    }

    fn fallback_type(&self) -> Option<Type<'db>> {
        self.cycle_recovery
    }

    pub(super) fn recursive_type_expression_definition(&self) -> Option<Definition<'db>> {
        self.typevar_binding_context.or(match self.region {
            InferenceRegion::Definition(definition) | InferenceRegion::Deferred(definition) => {
                Some(definition)
            }
            InferenceRegion::Statement(_)
            | InferenceRegion::Expression(_, _)
            | InferenceRegion::FunctionDecorators(_)
            | InferenceRegion::Scope(_, _) => None,
        })
    }

    fn extend_cycle_recovery(&mut self, other: Option<Type<'db>>) {
        if let Some(other) = other {
            match self.cycle_recovery {
                Some(existing) => {
                    self.cycle_recovery =
                        Some(UnionType::from_two_elements(self.db(), existing, other));
                }
                None => {
                    self.cycle_recovery = Some(other);
                }
            }
        }
    }

    fn extend_definition(
        &mut self,
        definition: Definition<'db>,
        inference: &DefinitionInference<'db>,
    ) {
        #[cfg(debug_assertions)]
        assert_eq!(self.scope, inference.scope);

        self.expressions
            .extend(inference.expressions.iter().copied());
        self.declarations.extend(inference.declarations(definition));

        if !matches!(self.region, InferenceRegion::Scope(..)) {
            self.bindings.extend(inference.bindings(definition));
        }

        if let Some(extra) = &inference.extra {
            match extra.as_ref() {
                DefinitionInferenceExtra::Qualifiers(qualifiers) => {
                    self.qualifiers.extend(qualifiers.iter().copied());
                }
                DefinitionInferenceExtra::Deferred(deferred) => {
                    self.deferred.extend(deferred.iter().copied());
                }
                DefinitionInferenceExtra::Diagnostics(diagnostics) => {
                    self.context.extend(diagnostics);
                }
                DefinitionInferenceExtra::DeferredAndUndecorated(extra) => {
                    self.deferred.extend(extra.deferred.iter().copied());
                }
                DefinitionInferenceExtra::CalledFunctions(called_functions) => {
                    self.called_functions
                        .extend(called_functions.iter().copied());
                }
                DefinitionInferenceExtra::ExpectedTypes(expected_types) => {
                    self.expected_types.extend(expected_types.iter().copied());
                }
                DefinitionInferenceExtra::StringAnnotations(string_annotations) => {
                    self.string_annotations
                        .extend(string_annotations.iter().copied());
                }
                DefinitionInferenceExtra::Undecorated(_)
                | DefinitionInferenceExtra::DiscardsDictKeyAssignments => {}
                DefinitionInferenceExtra::Other(extra) => {
                    self.called_functions
                        .extend(extra.called_functions.iter().copied());
                    self.extend_cycle_recovery(extra.cycle_recovery);
                    self.context.extend(&extra.diagnostics);
                    self.deferred.extend(extra.deferred.iter().copied());
                    self.string_annotations
                        .extend(extra.string_annotations.iter().copied());
                    self.expected_types
                        .extend(extra.expected_types.iter().copied());
                    self.qualifiers.extend(extra.qualifiers.iter().copied());
                    self.type_expression_flags
                        .extend(extra.type_expression_flags.iter().copied());

                    #[expect(
                        clippy::iter_over_hash_type,
                        reason = "constraints for distinct collection definitions are merged independently"
                    )]
                    for (collection_def, constraints) in &extra.collection_use_constraints {
                        self.collection_use_constraints
                            .entry(*collection_def)
                            .and_modify(|this| this.extend(constraints))
                            .or_insert(constraints.clone());
                    }
                }
            }
        }
    }

    fn extend_statement(&mut self, inference: &StatementInference<'db>) {
        let inference = match inference {
            StatementInference::Other(inference) => inference,
            StatementInference::Expression(inference) => return self.extend_expression(inference),
            StatementInference::Definition(definition, inference) => {
                return self.extend_definition(*definition, inference);
            }
        };

        #[cfg(debug_assertions)]
        assert_eq!(self.scope, inference.scope);

        self.expressions
            .extend(inference.expressions.iter().copied());
        self.declarations.extend(inference.declarations());

        if !matches!(self.region, InferenceRegion::Scope(..)) {
            self.bindings.extend(inference.bindings());
        }

        if let Some(extra) = &inference.extra {
            self.called_functions
                .extend(extra.called_functions.iter().copied());
            self.return_types_and_ranges
                .extend(extra.return_types_and_ranges.iter().copied());
            self.extend_cycle_recovery(extra.cycle_recovery);
            self.context.extend(&extra.diagnostics);
            self.deferred.extend(extra.deferred.iter().copied());
            self.string_annotations
                .extend(extra.string_annotations.iter().copied());
            self.expected_types
                .extend(extra.expected_types.iter().copied());
            self.qualifiers.extend(extra.qualifiers.iter().copied());
            self.type_expression_flags
                .extend(extra.type_expression_flags.iter().copied());
        }
    }

    fn extend_expression(&mut self, inference: &ExpressionInference<'db>) {
        #[cfg(debug_assertions)]
        assert_eq!(self.scope, inference.scope);

        self.extend_expression_unchecked(inference);
    }

    fn extend_expression_unchecked(&mut self, inference: &ExpressionInference<'db>) {
        self.expressions
            .extend(inference.expressions.iter().copied());

        if let Some(extra) = &inference.extra {
            self.context.extend(&extra.diagnostics);
            self.extend_cycle_recovery(extra.cycle_recovery);
            self.string_annotations
                .extend(extra.string_annotations.iter().copied());
            self.expected_types
                .extend(extra.expected_types.iter().copied());
            self.type_expression_flags
                .extend(extra.type_expression_flags.iter().copied());

            #[expect(
                clippy::iter_over_hash_type,
                reason = "constraints for distinct collection definitions are merged independently"
            )]
            for (collection_def, constraints) in &extra.collection_use_constraints {
                self.collection_use_constraints
                    .entry(*collection_def)
                    .and_modify(|this| this.extend(constraints))
                    .or_insert(constraints.clone());
            }

            if !matches!(self.region, InferenceRegion::Scope(..)) {
                self.bindings.extend(extra.bindings.iter().copied());
            }
        }
    }

    fn extend_scope(&mut self, inference: &ScopeInference<'db>) {
        self.expressions.extend(inference.expressions.iter());

        if let Some(extra) = &inference.extra {
            self.context.extend(&extra.diagnostics);
            self.extend_cycle_recovery(extra.cycle_recovery);
            self.string_annotations
                .extend(extra.string_annotations.iter().copied());
            self.expected_types
                .extend(extra.expected_types.iter().copied());
            self.type_expression_flags
                .extend(extra.type_expression_flags.iter().copied());

            #[expect(
                clippy::iter_over_hash_type,
                reason = "constraints for distinct collection definitions are merged independently"
            )]
            for (collection_def, constraints) in &extra.collection_use_constraints {
                self.collection_use_constraints
                    .entry(*collection_def)
                    .and_modify(|this| this.extend(constraints))
                    .or_insert(constraints.clone());
            }
        }
    }

    fn file(&self) -> File {
        self.context.file()
    }

    fn module(&self) -> &'ast ParsedModuleRef {
        self.context.module()
    }

    fn db(&self) -> &'db dyn Db {
        self.context.db()
    }

    fn scope(&self) -> ScopeId<'db> {
        self.scope
    }

    /// Returns call bindings annotated with the call site's enclosing binding contexts.
    ///
    /// Call binding uses this as an optimization hint to avoid freshening generic callable
    /// signatures when the callable's generic context cannot collide with a containing scope.
    fn bindings_for_call(&self, callable_type: Type<'db>) -> Bindings<'db> {
        let db = self.db();
        callable_type
            .bindings(db)
            .with_enclosing_binding_contexts(enclosing_binding_contexts(
                self.index,
                self.scope().file_scope_id(db),
            ))
    }

    fn settings(&self) -> &AnalysisSettings {
        self.db().analysis_settings(self.file())
    }

    fn is_in_type_checking_block(&self, scope: ScopeId<'db>, node: impl Ranged) -> bool {
        self.index
            .is_in_type_checking_block(scope.file_scope_id(self.db()), node.range())
    }

    /// If the current scope is a class body scope of a dataclass-like class, populate
    /// `self.dataclass_field_specifiers` with the field specifiers from the class's
    /// `dataclass_params` or `dataclass_transform` parameters. This is needed so that
    /// calls to field-specifier functions are recognized during type inference of the
    /// right-hand side of annotated assignments.
    fn setup_dataclass_field_specifiers(&mut self) {
        fn field_specifiers<'db>(
            db: &'db dyn Db,
            index: &'db SemanticIndex<'db>,
            scope: ScopeId<'db>,
        ) -> Option<SmallVec<[Type<'db>; NUM_FIELD_SPECIFIERS_INLINE]>> {
            let enclosing_scope = index.scope(scope.file_scope_id(db));
            let class_node = enclosing_scope.node().as_class()?;
            let class_definition = index.expect_single_definition(class_node);
            let class_literal = original_class_type(db, class_definition)?.as_static()?;

            class_literal
                .dataclass_params(db)
                .map(|params| SmallVec::from(params.field_specifiers(db)))
                .or_else(|| {
                    Some(SmallVec::from(
                        CodeGeneratorKind::from_class(db, class_literal.into())?
                            .field_specifiers(db)?,
                    ))
                })
        }

        if let Some(specifiers) = field_specifiers(self.db(), self.index, self.scope()) {
            self.dataclass_field_specifiers = specifiers;
        }
    }

    /// Setup a shared expression cache for multi-inference.
    ///
    /// Returns `false` if the expression cache was already initialized.
    fn setup_expression_cache(&mut self) -> bool {
        if self.expression_cache.is_some() {
            false
        } else {
            self.expression_cache = Some(Rc::new(RefCell::new(FxHashMap::default())));
            true
        }
    }

    fn teardown_expression_cache(&mut self) {
        self.expression_cache = None;
    }

    /// Are we currently inferring types in file with deferred types?
    /// This is true for stub files, for files with `__future__.annotations`, and
    /// by default for all source files in Python 3.14 and later.
    fn defer_annotations(&self) -> bool {
        self.index.has_future_annotations()
            || self.in_stub()
            || Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY314
    }

    /// Are we currently in a context where name resolution should be deferred
    /// (`__future__.annotations`, stub file, or stringified annotation)?
    fn is_deferred(&self) -> bool {
        self.deferred_state.is_deferred()
    }

    /// Return the node key of the given AST node, or the key of the outermost enclosing string
    /// literal, if the node originates from inside a stringified annotation.
    fn enclosing_node_key(&self, node: AnyNodeRef<'_>) -> NodeKey {
        match self.deferred_state {
            DeferredExpressionState::InStringAnnotation(enclosing_node_key) => enclosing_node_key,
            _ => NodeKey::from_node(node),
        }
    }

    fn in_stub(&self) -> bool {
        self.context.in_stub()
    }

    fn in_string_annotation(&self) -> bool {
        self.deferred_state.in_string_annotation()
    }

    /// Returns `true` if `expr` is a call to a known diagnostic function
    /// (e.g., `reveal_type` or `assert_type`) whose return value should not
    /// trigger the `unused-awaitable` lint.
    fn is_known_function_call(&self, expr: &ast::Expr) -> bool {
        let ast::Expr::Call(call) = expr else {
            return false;
        };
        matches!(
            self.expression_type(&call.func),
            Type::FunctionLiteral(f)
                if matches!(
                    f.known(self.db()),
                    Some(KnownFunction::RevealType | KnownFunction::AssertType)
                )
        )
    }

    /// Get the already-inferred type of an expression node, or Unknown.
    fn expression_type(&self, expr: &ast::Expr) -> Type<'db> {
        self.try_expression_type(expr).unwrap_or_else(Type::unknown)
    }

    fn try_expression_type(&self, expr: &ast::Expr) -> Option<Type<'db>> {
        self.expressions
            .get(&expr.into())
            .copied()
            .or(self.fallback_type())
    }

    /// Return an already-inferred type for `expr`, or infer it with `tcx` if needed.
    ///
    /// This is used in places where an expression may already have been inferred earlier with a
    /// more specific type context, and re-inferring it would be redundant or would duplicate
    /// diagnostics.
    fn get_or_infer_expression(&mut self, expr: &ast::Expr, tcx: TypeContext<'db>) -> Type<'db> {
        self.try_expression_type(expr)
            .unwrap_or_else(|| self.infer_expression(expr, tcx))
    }

    /// Store qualifiers for an annotation expression.
    fn store_qualifiers(&mut self, expr: &ast::Expr, qualifiers: TypeQualifiers) {
        if !qualifiers.is_empty() {
            self.qualifiers.insert(expr.into(), qualifiers);
        }
    }

    /// Store metadata for a type expression.
    fn store_type_expression_flags(
        &mut self,
        expr: impl Into<ExpressionNodeKey>,
        flags: TypeExpressionFlags,
    ) {
        if flags.is_empty() {
            return;
        }

        self.type_expression_flags
            .entry(expr.into())
            .or_default()
            .insert(flags);
    }

    /// Get metadata for a type expression from the current inference result.
    fn type_expression_flags(&self, expr: impl Into<ExpressionNodeKey>) -> TypeExpressionFlags {
        self.type_expression_flags
            .get(&expr.into())
            .copied()
            .unwrap_or_default()
    }

    /// Get the type of an expression from any scope in the same file.
    ///
    /// If the expression is in the current scope, and we are inferring the entire scope, just look
    /// up the expression in our own results, otherwise call [`infer_scope_types()`] for the scope
    /// of the expression.
    ///
    /// ## Panics
    ///
    /// If the expression is in the current scope but we haven't yet inferred a type for it.
    ///
    /// Can cause query cycles if the expression is from a different scope and type inference is
    /// already in progress for that scope (further up the stack).
    fn file_expression_type(&self, expression: &ast::Expr) -> Type<'db> {
        let file_scope = self.index.expression_scope_id(expression);
        let expr_scope = file_scope.to_scope_id(self.db(), self.file());
        match self.region {
            InferenceRegion::Scope(scope, _) if scope == expr_scope => {
                self.expression_type(expression)
            }
            _ => infer_complete_scope_types(self.db(), expr_scope).expression_type(expression),
        }
    }

    /// Get metadata for a type expression from any scope in the same file.
    fn file_type_expression_flags(&self, expression: &ast::Expr) -> TypeExpressionFlags {
        let file_scope = self.index.expression_scope_id(expression);
        let expr_scope = file_scope.to_scope_id(self.db(), self.file());
        match self.region {
            InferenceRegion::Scope(scope, _) if scope == expr_scope => {
                self.type_expression_flags(expression)
            }
            _ => {
                infer_complete_scope_types(self.db(), expr_scope).type_expression_flags(expression)
            }
        }
    }

    /// Infers types in the given [`InferenceRegion`].
    fn infer_region(&mut self) {
        match self.region {
            InferenceRegion::Statement(statement) => self.infer_region_statement(statement),
            InferenceRegion::Scope(scope, tcx) => self.infer_region_scope(scope, tcx),
            InferenceRegion::Definition(definition) => self.infer_region_definition(definition),
            InferenceRegion::FunctionDecorators(definition) => {
                self.infer_region_function_decorators(definition);
            }
            InferenceRegion::Deferred(definition) => self.infer_region_deferred(definition),
            InferenceRegion::Expression(expression, tcx) => {
                self.infer_region_expression(expression, tcx);
            }
        }
    }

    fn infer_region_scope(&mut self, scope: ScopeId<'db>, tcx: TypeContext<'db>) {
        let node = scope.node(self.db());
        match node {
            NodeWithScopeKind::Module => {
                self.infer_module(self.module().syntax());
            }
            NodeWithScopeKind::Function(function) => {
                self.infer_function_body(function.node(self.module()));
            }
            NodeWithScopeKind::Lambda(lambda) => {
                self.infer_lambda_body(lambda.node(self.module()), tcx);
            }
            NodeWithScopeKind::Class(class) => self.infer_class_body(class.node(self.module())),
            NodeWithScopeKind::ClassTypeParameters(class) => {
                self.infer_class_type_params(class.node(self.module()));
            }
            NodeWithScopeKind::FunctionTypeParameters(function) => {
                self.infer_function_type_params(function.node(self.module()));
            }
            NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => {
                self.infer_type_alias_type_params(type_alias.node(self.module()));
            }
            NodeWithScopeKind::TypeAlias(type_alias) => {
                self.infer_type_alias(type_alias.node(self.module()));
            }
            NodeWithScopeKind::ListComprehension(comprehension) => {
                self.infer_list_comprehension_expression_scope(
                    comprehension.node(self.module()),
                    tcx,
                );
            }
            NodeWithScopeKind::SetComprehension(comprehension) => {
                self.infer_set_comprehension_expression_scope(
                    comprehension.node(self.module()),
                    tcx,
                );
            }
            NodeWithScopeKind::DictComprehension(comprehension) => {
                self.infer_dict_comprehension_expression_scope(
                    comprehension.node(self.module()),
                    tcx,
                );
            }
            NodeWithScopeKind::GeneratorExpression(generator) => {
                self.infer_generator_expression_scope(generator.node(self.module()), tcx);
            }
        }

        // Infer deferred types for all definitions.
        let deferred_definitions: Vec<_> = std::mem::take(&mut self.deferred).into_iter().collect();
        for definition in &deferred_definitions {
            self.extend_definition(*definition, infer_deferred_types(self.db(), *definition));
        }

        assert!(
            self.deferred.is_empty(),
            "Inferring deferred types should not add more deferred definitions"
        );

        if self.db().should_check_file(self.file()) {
            let mut seen_overloaded_places = FxHashSet::default();
            let mut seen_public_functions = FxHashSet::default();

            for (&definition, ty_and_quals) in &self.declarations {
                let ty = ty_and_quals.inner_type();
                match definition.kind(self.db()) {
                    DefinitionKind::Function(function) => {
                        post_inference::function::check_function_definition(
                            &self.context,
                            definition,
                            &|expr| self.file_expression_type(expr),
                        );
                        post_inference::overloaded_function::check_overloaded_function(
                            &self.context,
                            ty,
                            definition,
                            self.scope.scope(self.db()).node(),
                            self.index,
                            &mut seen_overloaded_places,
                            &mut seen_public_functions,
                        );
                        post_inference::typeguard::check_type_guard_definition(
                            &self.context,
                            ty,
                            function.node(self.module()),
                        );
                    }
                    DefinitionKind::Class(class_node) => {
                        let original_ty = match self.region {
                            InferenceRegion::Definition(current) if current == definition => {
                                self.undecorated_type
                            }
                            _ => original_class_type(self.db(), definition).map(Type::ClassLiteral),
                        };
                        let ty = original_ty.unwrap_or(ty);
                        post_inference::static_class::check_static_class_definitions(
                            &self.context,
                            ty,
                            class_node.node(self.module()),
                            self.index,
                            &|expr| self.file_expression_type(expr),
                        );
                    }
                    DefinitionKind::AnnotatedAssignment(assignment) => {
                        if let Some(diagnostics) =
                            post_inference::pep_613_alias::check_pep_613_alias(
                                assignment, definition, self,
                            )
                        {
                            self.context.extend(&diagnostics);
                        }
                    }
                    _ => {}
                }
            }

            for definition in &deferred_definitions {
                post_inference::dynamic_class::check_dynamic_class_definition(
                    &self.context,
                    *definition,
                );
            }

            for function in &self.called_functions {
                post_inference::overloaded_function::check_overloaded_function(
                    &self.context,
                    Type::FunctionLiteral(*function),
                    function.definition(self.db()),
                    self.scope.scope(self.db()).node(),
                    self.index,
                    &mut seen_overloaded_places,
                    &mut seen_public_functions,
                );
            }

            post_inference::final_variable::check_final_without_value(&self.context, self.index);
        }
    }

    fn infer_region_statement(&mut self, statement: StatementInner<'db>) {
        self.infer_statement(statement.node_ref(self.db()).node(self.module()));
    }

    fn infer_region_definition(&mut self, definition: Definition<'db>) {
        match definition.kind(self.db()) {
            DefinitionKind::Function(function) => {
                self.infer_function_definition(function.node(self.module()), definition);
            }
            DefinitionKind::Class(class) => {
                self.infer_class_definition(class.node(self.module()), definition);
            }
            DefinitionKind::TypeAlias(type_alias) => {
                self.infer_type_alias_definition(type_alias.node(self.module()), definition);
            }
            DefinitionKind::Import(import) => {
                self.infer_import_definition(import.alias(self.module()), definition);
            }
            DefinitionKind::ImportFrom(import_from) => {
                self.infer_import_from_definition(
                    import_from.import(self.module()),
                    import_from.alias(self.module()),
                    definition,
                );
            }
            DefinitionKind::ImportFromSubmodule(import_from) => {
                self.infer_import_from_submodule_definition(
                    import_from.import(self.module()),
                    definition,
                );
            }
            DefinitionKind::StarImport(import) => {
                self.infer_import_from_definition(
                    import.import(self.module()),
                    import.alias(self.module()),
                    definition,
                );
            }
            DefinitionKind::Assignment(assignment) => {
                self.infer_assignment_definition(assignment, definition);
            }
            DefinitionKind::AnnotatedAssignment(annotated_assignment) => {
                self.infer_annotated_assignment_definition(annotated_assignment, definition);
            }
            DefinitionKind::AugmentedAssignment(augmented_assignment) => {
                self.infer_augment_assignment_definition(
                    augmented_assignment.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::DictKeyAssignment(dict_key_assignment) => {
                self.infer_dict_key_assignment_definition(
                    dict_key_assignment.key(self.module()),
                    dict_key_assignment.value(self.module()),
                    dict_key_assignment.assignment(),
                    definition,
                );
            }
            DefinitionKind::For(for_statement_definition) => {
                self.infer_for_statement_definition(for_statement_definition, definition);
            }
            DefinitionKind::NamedExpression(named_expression) => {
                self.infer_named_expression_definition(
                    named_expression.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::Comprehension(comprehension) => {
                self.infer_comprehension_definition(comprehension, definition);
            }
            DefinitionKind::Parameter(
                ParameterDefinitionNodeKind::VariadicPositionalParameter(parameter),
            ) => {
                self.infer_variadic_positional_parameter_definition(
                    parameter.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::Parameter(ParameterDefinitionNodeKind::VariadicKeywordParameter(
                parameter,
            )) => {
                self.infer_variadic_keyword_parameter_definition(
                    parameter.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::Parameter(ParameterDefinitionNodeKind::Parameter(
                parameter_with_default,
            )) => {
                self.infer_parameter_definition(
                    parameter_with_default.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::LambdaParameter(LambdaParameterDefinitionNodeKind {
                index,
                lambda,
                parameter: ParameterDefinitionNodeKind::VariadicPositionalParameter(parameter),
            }) => {
                self.infer_variadic_positional_lambda_parameter_definition(
                    *index,
                    parameter.node(self.module()),
                    lambda.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::LambdaParameter(LambdaParameterDefinitionNodeKind {
                parameter: ParameterDefinitionNodeKind::VariadicKeywordParameter(parameter),
                ..
            }) => {
                self.infer_variadic_keyword_lambda_parameter_definition(
                    parameter.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::LambdaParameter(LambdaParameterDefinitionNodeKind {
                index,
                lambda,
                parameter: ParameterDefinitionNodeKind::Parameter(parameter_with_default),
            }) => {
                self.infer_lambda_parameter_definition(
                    *index,
                    parameter_with_default.node(self.module()),
                    lambda.node(self.module()),
                    definition,
                );
            }
            DefinitionKind::WithItem(with_item_definition) => {
                self.infer_with_item_definition(with_item_definition, definition);
            }
            DefinitionKind::MatchPattern(match_pattern) => {
                self.infer_match_pattern_definition(
                    match_pattern.pattern(self.module()),
                    match_pattern.predicate(),
                    definition,
                );
            }
            DefinitionKind::ExceptHandler(except_handler_definition) => {
                self.infer_except_handler_definition(except_handler_definition, definition);
            }
            DefinitionKind::TypeVar(node) => {
                self.infer_typevar_definition(node.node(self.module()), definition);
            }
            DefinitionKind::ParamSpec(node) => {
                self.infer_paramspec_definition(node.node(self.module()), definition);
            }
            DefinitionKind::TypeVarTuple(node) => {
                self.infer_typevartuple_definition(node.node(self.module()), definition);
            }
            DefinitionKind::LoopHeader(loop_header) => {
                self.infer_loop_header_definition(loop_header, definition);
            }
            DefinitionKind::NestedBindings(nested_bindings) => {
                self.infer_nested_bindings_definition(nested_bindings, definition);
            }
        }
    }

    fn infer_region_function_decorators(&mut self, definition: Definition<'db>) {
        let DefinitionKind::Function(function) = definition.kind(self.db()) else {
            return;
        };

        for decorator in &function.node(self.module()).decorator_list {
            let decorator_type = self.infer_decorator(decorator);
            if let Type::FunctionLiteral(function) = decorator_type
                && let Some(KnownFunction::NoTypeCheck) = function.known(self.db())
            {
                // Match `infer_function_definition`: suppress diagnostics that follow
                // `@no_type_check`, including later decorators.
                self.context.inference_flags |= InferenceFlags::IN_NO_TYPE_CHECK;
            }
        }
    }

    fn infer_region_deferred(&mut self, definition: Definition<'db>) {
        // N.B. We don't defer the types for an annotated assignment here because it is done in
        // the same definition query. It utilizes the deferred expression state instead.
        //
        // This is because for partially stringified annotations like `a: tuple[int, "ForwardRef"]`,
        // we need to defer the types of non-stringified expressions like `tuple` and `int` in the
        // definition query while the stringified expression `"ForwardRef"` would need to deferred
        // to use end-of-scope semantics. This would require custom and possibly a complex
        // implementation to allow this "split" to happen.

        match definition.kind(self.db()) {
            DefinitionKind::Function(function) => {
                self.infer_function_deferred(definition, function.node(self.module()));
            }
            DefinitionKind::Class(class) => {
                self.infer_class_deferred(definition, class.node(self.module()));
            }
            DefinitionKind::TypeVar(typevar) => {
                self.infer_typevar_deferred(typevar.node(self.module()));
            }
            DefinitionKind::ParamSpec(paramspec) => {
                self.infer_paramspec_deferred(paramspec.node(self.module()));
            }
            DefinitionKind::Assignment(assignment) => {
                self.infer_assignment_deferred(
                    assignment.target(self.module()),
                    assignment.value(self.module()),
                );
            }
            _ => {}
        }
    }

    fn infer_region_expression(&mut self, expression: Expression<'db>, tcx: TypeContext<'db>) {
        self.setup_dataclass_field_specifiers();

        match expression.kind(self.db()) {
            ExpressionKind::Normal => {
                self.infer_expression_impl(expression.node_ref(self.db()).node(self.module()), tcx);
            }
            ExpressionKind::TypeExpression => {
                self.infer_type_expression(expression.node_ref(self.db()).node(self.module()));
            }
        }
    }

    /// Add a binding for the given definition.
    ///
    /// Returns the result of the `infer_value_ty` closure, which is called with the declared type
    /// as type context.
    fn add_binding<'a>(
        &mut self,
        node: AnyNodeRef<'a>,
        binding: Definition<'db>,
    ) -> AddBinding<'db, 'a> {
        let db = self.db();
        debug_assert!(
            binding
                .kind(db)
                .category(self.context.in_stub(), self.module())
                .is_binding()
        );

        let db = self.db();
        let file_scope_id = binding.file_scope(db);
        let place_table = self.index.place_table(file_scope_id);
        let use_def = self.index.use_def_map(file_scope_id);

        let place_id = binding.place(self.db());
        let place = place_table.place(place_id);

        let (declarations, is_local) = if let Some(symbol) = place_id.as_symbol()
            && let Some((owner_scope, owner_symbol)) =
                self.forwarded_assignment_owner(file_scope_id, symbol)
        {
            (
                self.index
                    .use_def_map(owner_scope)
                    .end_of_scope_symbol_declarations(owner_symbol),
                false,
            )
        } else {
            (use_def.declarations_at_binding(binding), true)
        };

        let (mut place_and_quals, conflicting) = place_from_declarations_with_reachability_cache(
            self.db(),
            declarations,
            self.reachability_cache(),
        )
        .into_place_and_conflicting_declarations();

        if let Some(conflicting) = conflicting {
            // TODO point out the conflicting declarations in the diagnostic?
            let place = place_table.place(binding.place(db));
            if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) {
                builder.into_diagnostic(format_args!(
                    "Conflicting declared types for `{place}`: {}",
                    format_enumeration(conflicting.iter().map(|ty| ty.display(db)))
                ));
            }
        }

        // Fall back to implicit module globals for (possibly) unbound names
        if !place_and_quals.place.is_definitely_bound()
            && let PlaceExprRef::Symbol(symbol) = place
        {
            let symbol_id = place_id.expect_symbol();

            if self.skip_non_global_scopes(file_scope_id, symbol_id)
                || self.scope.file_scope_id(self.db()).is_global()
            {
                place_and_quals = place_and_quals.or_fall_back_to(self.db(), || {
                    module_type_implicit_global_declaration(self.db(), symbol.name())
                });
            }
        }

        let PlaceAndQualifiers {
            place: resolved_place,
            qualifiers,
        } = place_and_quals;

        let declared_ty = if resolved_place.is_undefined() && !place.is_symbol() {
            self.fallback_member_declared_type(node)
        } else {
            None
        }
        .or_else(|| resolved_place.ignore_possibly_undefined());

        AddBinding {
            declared_ty,
            binding,
            node,
            qualifiers,
            is_local,
        }
    }

    /// For a member binding without a live place declaration, obtain its declared type from
    /// normal attribute or subscript lookup on its receiver.
    fn fallback_member_declared_type(&mut self, node: AnyNodeRef<'_>) -> Option<Type<'db>> {
        let db = self.db();

        if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node {
            let value_type = self.infer_maybe_standalone_expression(value, TypeContext::default());
            if let Place::Defined(DefinedPlace {
                ty,
                definedness: Definedness::AlwaysDefined,
                ..
            }) = value_type.member(db, attr).place
            {
                // TODO: also consider qualifiers on the attribute
                Some(ty)
            } else {
                None
            }
        } else if let AnyNodeRef::ExprSubscript(
            subscript @ ast::ExprSubscript {
                value, slice, ctx, ..
            },
        ) = node
        {
            let value_ty = self.infer_expression(value, TypeContext::default());
            let slice_ty = self.infer_expression(slice, TypeContext::default());
            Some(self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx))
        } else {
            None
        }
    }

    /// Returns the owner of an assignment redirected by `global` or `nonlocal`.
    ///
    /// `global` assignments target the module symbol, while `nonlocal` assignments target the
    /// closest owning function-like scope. Local assignments and forwarding declarations whose
    /// owner cannot be resolved return `None`.
    ///
    /// ```python
    /// x = 0
    ///
    /// def outer():
    ///     y = 0
    ///
    ///     def inner():
    ///         global x
    ///         nonlocal y
    ///         x = 1  # owned by the module scope
    ///         y = 1  # owned by `outer`
    /// ```
    fn forwarded_assignment_owner(
        &self,
        scope: FileScopeId,
        symbol: ScopedSymbolId,
    ) -> Option<(FileScopeId, ScopedSymbolId)> {
        let scoped_symbol = self.index.place_table(scope).symbol(symbol);

        if scope.is_global() || scoped_symbol.is_local() {
            return None;
        }

        if scoped_symbol.is_global() {
            let global_scope = FileScopeId::global();
            // If this variable appears in a `global` declaration but has no explicit binding in
            // the global scope, return `None` so the caller can fall back to the local scope.
            return self
                .index
                .place_table(global_scope)
                .symbol_id(scoped_symbol.name())
                .map(|symbol| (global_scope, symbol));
        }

        debug_assert!(scoped_symbol.is_nonlocal());

        // Walk up parent scopes looking for the enclosing scope that defines this name.
        // `ancestor_scopes` includes the current scope, so skip that one.
        for (enclosing_scope, enclosing) in self.index.ancestor_scopes(scope).skip(1) {
            // Ignore class scopes and the global scope.
            if !enclosing.kind().is_function_like() {
                continue;
            }
            let place_table = self.index.place_table(enclosing_scope);
            let Some(enclosing_symbol) = place_table.symbol_id(scoped_symbol.name()) else {
                // This ancestor scope doesn't have a binding. Keep going.
                continue;
            };
            let symbol = place_table.symbol(enclosing_symbol);
            if symbol.is_global() {
                // The variable is `global` in this ancestor scope. This breaks the `nonlocal`
                // chain, and it's a syntax error in `infer_nonlocal_statement`. Ignore that here
                // and bail out of this loop.
                break;
            }
            if !symbol.is_local() {
                // The variable is either explicitly `nonlocal` or just a free read in this
                // ancestor scope. Keep going.
                continue;
            }

            // We found the closest definition. Note that (as in `infer_place_load`) this does not
            // need to be a binding. It could be just a declaration, e.g. `x: int`.
            return Some((enclosing_scope, enclosing_symbol));
        }

        // If no ancestor owns the name, return `None` so the caller can fall back to the local
        // scope. This will also be reported as a syntax error in `infer_nonlocal_statement`.
        None
    }

    /// Returns `true` if `symbol_id` should be looked up in the global scope, skipping intervening
    /// local scopes.
    fn skip_non_global_scopes(
        &self,
        file_scope_id: FileScopeId,
        symbol_id: ScopedSymbolId,
    ) -> bool {
        !file_scope_id.is_global()
            && self
                .index
                .symbol_is_global_in_scope(symbol_id, file_scope_id)
    }

    fn add_declaration(
        &mut self,
        node: AnyNodeRef,
        declaration: Definition<'db>,
        ty: TypeAndQualifiers<'db>,
    ) {
        debug_assert!(
            declaration
                .kind(self.db())
                .category(self.context.in_stub(), self.module())
                .is_declaration()
        );
        let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
        let prior_bindings = use_def.bindings_at_definition(declaration);
        // unbound_ty is Never because for this check we don't care about unbound
        let inferred_ty = place_from_bindings_with_reachability_cache(
            self.db(),
            prior_bindings,
            self.reachability_cache(),
        )
        .place
        .with_qualifiers(TypeQualifiers::empty())
        .or_fall_back_to(self.db(), || {
            // Fallback to bindings declared on `types.ModuleType` if it's a global symbol
            let scope = self.scope().file_scope_id(self.db());
            let place = self
                .index
                .place_table(scope)
                .place(declaration.place(self.db()));

            if let PlaceExprRef::Symbol(symbol) = &place
                && scope.is_global()
            {
                module_type_implicit_global_symbol(self.db(), self.file(), symbol.name())
            } else {
                Place::Undefined.into()
            }
        })
        .place
        .ignore_possibly_undefined()
        .unwrap_or(Type::Never);
        let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_type()) {
            ty
        } else {
            if let Some(builder) = self.context.report_lint(&INVALID_DECLARATION, node) {
                builder.into_diagnostic(format_args!(
                    "Cannot declare type `{}` for inferred type `{}`",
                    ty.inner_type().display(self.db()),
                    inferred_ty.display(self.db())
                ));
            }
            TypeAndQualifiers::declared(Type::unknown())
        };
        self.declarations.insert(declaration, ty);
    }

    fn add_declaration_with_binding(
        &mut self,
        node: AnyNodeRef,
        definition: Definition<'db>,
        declared_and_inferred_ty: &DeclaredAndInferredType<'db>,
    ) {
        debug_assert!(
            definition
                .kind(self.db())
                .category(self.context.in_stub(), self.module())
                .is_binding()
        );
        debug_assert!(
            definition
                .kind(self.db())
                .category(self.context.in_stub(), self.module())
                .is_declaration()
        );

        let (declared_ty, inferred_ty) = match *declared_and_inferred_ty {
            DeclaredAndInferredType::AreTheSame(type_and_qualifiers) => {
                (type_and_qualifiers, type_and_qualifiers.inner_type())
            }
            DeclaredAndInferredType::MightBeDifferent {
                declared_ty,
                inferred_ty,
            } => {
                let file_scope_id = self.scope().file_scope_id(self.db());
                if file_scope_id.is_global() {
                    let place_table = self.index.place_table(file_scope_id);
                    let place = place_table.place(definition.place(self.db()));
                    let file = self.file();
                    if let Some(module_type_implicit_declaration) = place
                        .as_symbol()
                        .map(|symbol| {
                            module_type_implicit_global_symbol(self.db(), file, symbol.name())
                        })
                        .and_then(|place| place.place.ignore_possibly_undefined())
                    {
                        let declared_type = declared_ty.inner_type();
                        if !declared_type
                            .is_assignable_to(self.db(), module_type_implicit_declaration)
                        {
                            if let Some(builder) =
                                self.context.report_lint(&INVALID_DECLARATION, node)
                            {
                                let mut diagnostic = builder.into_diagnostic(format_args!(
                                    "Cannot shadow implicit global attribute `{place}` with declaration of type `{}`",
                                    declared_type.display(self.db())
                                ));
                                diagnostic.info(format_args!("The global symbol `{}` must always have a type assignable to `{}`",
                                    place,
                                    module_type_implicit_declaration.display(self.db())
                                ));
                            }
                        }
                    }
                }
                let declared_type = declared_ty.inner_type();
                if inferred_ty.is_assignable_to(self.db(), declared_type) {
                    if !should_preserve_inferred_binding_type(inferred_ty)
                        // TODO We currently can't distinguish here between "no declared type" and
                        // "declared types is `Unknown` (e.g. due to a bad annotation, missing
                        // import, etc.)". Ideally we would still prefer `Unknown` declared type,
                        // but use inferred type if there is no declared type.
                        && !matches!(declared_type, Type::Dynamic(DynamicType::Unknown))
                        && declared_type.is_assignable_to(self.db(), inferred_ty)
                    {
                        (declared_ty, declared_type)
                    } else {
                        (declared_ty, inferred_ty)
                    }
                } else {
                    self.discard_dict_key_assignments_for(definition);
                    report_invalid_assignment(
                        &self.context,
                        node,
                        definition,
                        declared_type,
                        inferred_ty,
                    );

                    // if the assignment is invalid, fall back to assuming the annotation is correct
                    (declared_ty, declared_type)
                }
            }
        };

        self.declarations.insert(definition, declared_ty);
        self.bindings.insert(definition, inferred_ty);
    }

    fn add_unknown_declaration_with_binding(
        &mut self,
        node: AnyNodeRef,
        definition: Definition<'db>,
    ) {
        self.add_declaration_with_binding(
            node,
            definition,
            &DeclaredAndInferredType::are_the_same_type(Type::unknown()),
        );
    }

    fn record_return_type(&mut self, ty: Type<'db>, range: TextRange) {
        self.return_types_and_ranges
            .push(TypeAndRange { ty, range });
    }

    fn infer_module(&mut self, module: &ast::ModModule) {
        self.infer_body(&module.body);
    }

    fn infer_type_alias_type_params(&mut self, type_alias: &ast::StmtTypeAlias) {
        let type_params = type_alias
            .type_params
            .as_ref()
            .expect("type alias type params scope without type params");

        let binding_context = self.index.expect_single_definition(type_alias);
        let previous_typevar_binding_context =
            self.typevar_binding_context.replace(binding_context);
        self.infer_type_parameters(type_params);
        self.typevar_binding_context = previous_typevar_binding_context;
    }

    fn infer_type_alias(&mut self, type_alias: &ast::StmtTypeAlias) {
        let previous_check_unbound_typevars = self
            .context
            .inference_flags
            .replace(InferenceFlags::CHECK_UNBOUND_TYPEVARS, true);
        self.context.inference_flags |= InferenceFlags::IN_TYPE_ALIAS;
        let value_ty = self.infer_type_expression(&type_alias.value);
        self.context
            .inference_flags
            .remove(InferenceFlags::IN_TYPE_ALIAS);
        self.context.inference_flags.set(
            InferenceFlags::CHECK_UNBOUND_TYPEVARS,
            previous_check_unbound_typevars,
        );

        // A type alias where a value type points to itself, i.e. the expanded type is `Divergent` is meaningless
        // (but a type alias that expands to something like `list[Divergent]` may be a valid recursive type alias)
        // and would lead to infinite recursion. Therefore, such type aliases should not be exposed.
        // ```python
        // type Itself = Itself  # error: "Cyclic definition of `Itself`"
        // type A = B  # error: "Cyclic definition of `A`"
        // type B = A  # error: "Cyclic definition of `B`"
        // type G[T] = G[T]  # error: "Cyclic definition of `G`"
        // type RecursiveList[T] = list[T | RecursiveList[T]]  # OK
        // type RecursiveList2[T] = list[RecursiveList2[T]]  # It's not possible to create an element of this, but it's not an error for now
        // type IntOr = int | IntOr  # It's redundant, but OK for now
        // type IntOrStr = int | StrOrInt  # It's redundant, but OK
        // type StrOrInt = str | IntOrStr  # It's redundant, but OK
        // ```
        let expanded = value_ty.expand_eagerly(self.db());
        if expanded.is_divergent() {
            if let Some(builder) = self
                .context
                .report_lint(&CYCLIC_TYPE_ALIAS_DEFINITION, type_alias)
            {
                builder.into_diagnostic(format_args!(
                    "Cyclic definition of `{}`",
                    &type_alias.name.as_name_expr().unwrap().id,
                ));
            }
            // Replace with `Divergent`.
            self.expressions
                .insert(type_alias.value.as_ref().into(), expanded);
        }
    }

    /// If the current scope is a method inside an enclosing class,
    /// return `Some(class)` where `class` represents the enclosing class.
    ///
    /// If the current scope is not a method inside an enclosing class,
    /// return `None`.
    ///
    /// Note that this method will only return `Some` if the immediate parent scope
    /// is a class scope OR the immediate parent scope is an annotation scope
    /// and the grandparent scope is a class scope. This means it has different
    /// behaviour to the [`super::nearest_enclosing_class`] function.
    fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
        let current_scope_id = self.scope().file_scope_id(self.db());
        let class_definition = self.index.class_definition_of_method(current_scope_id)?;
        original_class_type(self.db(), class_definition)
            .map(|class_literal| class_literal.default_specialization(self.db()))
    }

    /// Report an undeclared protocol attribute written through a method receiver.
    ///
    /// The instance or class receiver may be referenced directly, from an eager nested scope, or
    /// through a capture in a nested function.
    fn report_undeclared_protocol_attribute(&self, target: &ast::ExprAttribute) {
        let db = self.db();
        let Some(receiver) = target.value.as_name_expr() else {
            return;
        };
        let Some(method_scope_id) = self.receiver_method_scope(receiver) else {
            return;
        };
        let Some(protocol) = self
            .index
            .class_definition_of_method(method_scope_id)
            .and_then(|definition| original_class_type(db, definition))
            .map(|class| class.default_specialization(db))
            .and_then(|class| class.into_protocol_class(db))
        else {
            return;
        };
        if protocol.interface(db).includes_member(db, target.attr.id())
            || protocol.has_member_declaration(db, target.attr.id())
        {
            return;
        }

        diagnostic::report_undeclared_protocol_attribute(&self.context, target, protocol);
    }

    /// Returns the implicit `__class__` cell in the current direct method body or
    /// lazy scope defined directly in a class body.
    fn dunder_class_cell_type(&self) -> Option<ClassLiteral<'db>> {
        let current_scope_id = self.scope().file_scope_id(self.db());
        let class_definition =
            if let Some(definition) = self.index.class_definition_of_method(current_scope_id) {
                definition
            } else {
                let current_scope = self.index.scope(current_scope_id);
                if !matches!(
                    current_scope.node(),
                    NodeWithScopeKind::Lambda(_) | NodeWithScopeKind::GeneratorExpression(_)
                ) {
                    return None;
                }
                let class = self
                    .index
                    .parent_scope(current_scope_id)?
                    .node()
                    .as_class()?;
                self.index.expect_single_definition(class)
            };
        original_class_type(self.db(), class_definition)
    }

    /// If the current scope is a (non-lambda) function, return that function's AST node.
    ///
    /// If the current scope is not a function (or it is a lambda function), return `None`.
    fn current_function_definition(&self) -> Option<&ast::StmtFunctionDef> {
        let current_scope_id = self.scope().file_scope_id(self.db());
        let current_scope = self.index.scope(current_scope_id);
        if !current_scope.kind().is_non_lambda_function() {
            return None;
        }
        current_scope
            .node()
            .as_function()
            .map(|node_ref| node_ref.node(self.module()))
    }

    fn function_type(&self, function: &ast::StmtFunctionDef) -> Option<FunctionType<'db>> {
        let definition = self.index.expect_single_definition(function);
        infer_definition_types(self.db(), definition).function_type(definition)
    }

    fn current_function_type(&self) -> Option<FunctionType<'db>> {
        self.function_type(self.current_function_definition()?)
    }

    fn function_decorator_types<'a>(
        &'a self,
        function: &'a ast::StmtFunctionDef,
    ) -> impl Iterator<Item = Type<'db>> + 'a {
        let definition = self.index.expect_single_definition(function);

        let definition_types = infer_definition_types(self.db(), definition);

        function
            .decorator_list
            .iter()
            .map(move |decorator| definition_types.expression_type(&decorator.expression))
    }

    /// Returns `true` if the current scope is the function body scope of a function overload (that
    /// is, the stub declaration decorated with `@overload`, not the implementation), or an
    /// abstract method (decorated with `@abstractmethod`.)
    fn in_function_overload_or_abstractmethod(&self) -> bool {
        let Some(function) = self.current_function_definition() else {
            return false;
        };

        self.function_decorator_types(function)
            .any(|decorator_type| {
                match decorator_type {
                    Type::FunctionLiteral(function) => matches!(
                        function.known(self.db()),
                        Some(KnownFunction::Overload | KnownFunction::AbstractMethod)
                    ),
                    Type::Never => {
                        // In unreachable code, we infer `Never` for decorators like `typing.overload`.
                        // Return `true` here to avoid false positive `invalid-return-type` lints for
                        // `@overload`ed functions without a body in unreachable code.
                        true
                    }
                    Type::Divergent(_) => true,
                    _ => false,
                }
            })
    }

    fn infer_body(&mut self, suite: &[ast::Stmt]) {
        for statement in suite {
            self.infer_maybe_standalone_statement(statement);

            if let ast::Stmt::Expr(ast::StmtExpr {
                range: _,
                node_index: _,
                value,
            }) = statement
            {
                let ty = self.expression_type(value);
                if ty.is_awaitable(self.db()) && !self.is_known_function_call(value) {
                    if let Some(builder) =
                        self.context.report_lint(&UNUSED_AWAITABLE, value.as_ref())
                    {
                        builder.into_diagnostic(format_args!(
                            "Object of type `{}` is not awaited",
                            ty.display(self.db()),
                        ));
                    }
                }
            }
        }
    }

    fn infer_statement(&mut self, statement: &ast::Stmt) {
        match statement {
            ast::Stmt::FunctionDef(function) => self.infer_function_definition_statement(function),
            ast::Stmt::ClassDef(class) => self.infer_class_definition_statement(class),
            ast::Stmt::Expr(ast::StmtExpr {
                range: _,
                node_index: _,
                value,
            }) => {
                // If this is a call expression, we would have added an `IsNonTerminalCall`
                // constraint, meaning this will be a standalone expression.
                self.infer_maybe_standalone_expression(value, TypeContext::default());
            }
            ast::Stmt::If(if_statement) => self.infer_if_statement(if_statement),
            ast::Stmt::Try(try_statement) => self.infer_try_statement(try_statement),
            ast::Stmt::With(with_statement) => self.infer_with_statement(with_statement),
            ast::Stmt::Match(match_statement) => self.infer_match_statement(match_statement),
            ast::Stmt::Assign(assign) => self.infer_assignment_statement(assign),
            ast::Stmt::AnnAssign(assign) => self.infer_annotated_assignment_statement(assign),
            ast::Stmt::AugAssign(aug_assign) => {
                self.infer_augmented_assignment_statement(aug_assign);
            }
            ast::Stmt::TypeAlias(type_statement) => self.infer_type_alias_statement(type_statement),
            ast::Stmt::For(for_statement) => self.infer_for_statement(for_statement),
            ast::Stmt::While(while_statement) => self.infer_while_statement(while_statement),
            ast::Stmt::Import(import) => self.infer_import_statement(import),
            ast::Stmt::ImportFrom(import) => self.infer_import_from_statement(import),
            ast::Stmt::Assert(assert_statement) => self.infer_assert_statement(assert_statement),
            ast::Stmt::Raise(raise) => self.infer_raise_statement(raise),
            ast::Stmt::Return(ret) => self.infer_return_statement(ret),
            ast::Stmt::Delete(delete) => self.infer_delete_statement(delete),
            ast::Stmt::Global(global) => self.infer_global_statement(global),
            ast::Stmt::Nonlocal(_)
            | ast::Stmt::Break(_)
            | ast::Stmt::Continue(_)
            | ast::Stmt::Pass(_)
            | ast::Stmt::IpyEscapeCommand(_) => {
                // No-op
            }
        }
    }

    fn infer_definition(&mut self, node: impl Into<DefinitionNodeKey> + std::fmt::Debug + Copy) {
        let definition = self.index.expect_single_definition(node);
        let result = infer_definition_types(self.db(), definition);
        self.extend_definition(definition, result);
    }

    fn infer_type_alias_definition(
        &mut self,
        type_alias: &ast::StmtTypeAlias,
        definition: Definition<'db>,
    ) {
        // Check that no type parameter with a default follows a TypeVarTuple
        // in the type alias's PEP 695 type parameter list.
        if let Some(type_params) = type_alias.type_params.as_deref() {
            post_inference::type_param_validation::check_no_default_after_typevar_tuple_pep695(
                &self.context,
                type_params,
            );
        }

        let rhs_scope = self
            .index
            .node_scope(NodeWithScopeRef::TypeAlias(type_alias))
            .to_scope_id(self.db(), self.file());

        let type_alias_ty = Type::KnownInstance(KnownInstanceType::TypeAliasType(
            TypeAliasType::PEP695(PEP695TypeAliasType::new(
                self.db(),
                &type_alias.name.as_name_expr().unwrap().id,
                rhs_scope,
                None,
            )),
        ));

        self.store_expression_type(&type_alias.name, type_alias_ty);

        self.add_declaration_with_binding(
            type_alias.into(),
            definition,
            &DeclaredAndInferredType::are_the_same_type(type_alias_ty),
        );
    }

    fn infer_if_statement(&mut self, if_statement: &ast::StmtIf) {
        let ast::StmtIf {
            range: _,
            node_index: _,
            test,
            body,
            elif_else_clauses,
        } = if_statement;

        let test_ty = self.infer_standalone_expression(test, TypeContext::default());

        if let Err(err) = test_ty.try_bool(self.db()) {
            err.report_diagnostic(&self.context, &**test);
        }

        self.infer_body(body);

        for clause in elif_else_clauses {
            let ast::ElifElseClause {
                range: _,
                node_index: _,
                test,
                body,
            } = clause;

            if let Some(test) = &test {
                let test_ty = self.infer_standalone_expression(test, TypeContext::default());

                if let Err(err) = test_ty.try_bool(self.db()) {
                    err.report_diagnostic(&self.context, test);
                }
            }

            self.infer_body(body);
        }
    }

    fn infer_try_statement(&mut self, try_statement: &ast::StmtTry) {
        let ast::StmtTry {
            range: _,
            node_index: _,
            body,
            handlers,
            orelse,
            finalbody,
            is_star: _,
        } = try_statement;

        self.infer_body(body);

        for handler in handlers {
            let ast::ExceptHandler::ExceptHandler(handler) = handler;
            let ast::ExceptHandlerExceptHandler {
                type_: handled_exceptions,
                name: symbol_name,
                body,
                range: _,
                node_index: _,
            } = handler;

            // If `symbol_name` is `Some()` and `handled_exceptions` is `None`,
            // it's invalid syntax (something like `except as e:`).
            // However, it's obvious that the user *wanted* `e` to be bound here,
            // so we'll have created a definition in the semantic-index stage anyway.
            if symbol_name.is_some() {
                self.infer_definition(handler);
            } else {
                self.infer_exception(handled_exceptions.as_deref(), try_statement.is_star);
            }

            self.infer_body(body);
        }

        self.infer_body(orelse);
        self.infer_body(finalbody);
    }

    fn infer_with_statement(&mut self, with_statement: &ast::StmtWith) {
        let ast::StmtWith {
            range: _,
            node_index: _,
            is_async,
            items,
            body,
        } = with_statement;
        for item in items {
            let target = item.optional_vars.as_deref();
            if let Some(target) = target {
                self.infer_target(target, &item.context_expr, &|builder, tcx| {
                    // TODO: `infer_with_statement_definition` reports a diagnostic if `ctx_manager_ty` isn't a context manager
                    //  but only if the target is a name. We should report a diagnostic here if the target isn't a name:
                    //  `with not_context_manager as a.x: ...
                    builder
                        .infer_standalone_expression(&item.context_expr, tcx)
                        .enter(builder.db())
                });
            } else {
                // Call into the context expression inference to validate that it evaluates
                // to a valid context manager.
                let context_expression_ty =
                    self.infer_expression(&item.context_expr, TypeContext::default());
                self.infer_context_expression(&item.context_expr, context_expression_ty, *is_async);
                self.infer_optional_expression(target, TypeContext::default());
            }
        }

        self.infer_body(body);
    }

    fn infer_with_item_definition(
        &mut self,
        with_item: &WithItemDefinitionKind<'db>,
        definition: Definition<'db>,
    ) {
        let context_expr = with_item.context_expr(self.module());
        let target = with_item.target(self.module());

        let target_ty = match with_item.target_kind() {
            TargetKind::Sequence(unpack_position, unpack) => {
                let unpacked = infer_unpack_types(self.db(), unpack);
                if unpack_position == UnpackPosition::First {
                    self.context.extend(unpacked.diagnostics());
                }
                unpacked.expression_type(target)
            }
            TargetKind::Single => {
                let context_expr_ty =
                    self.infer_standalone_expression(context_expr, TypeContext::default());
                self.infer_context_expression(context_expr, context_expr_ty, with_item.is_async())
            }
        };

        self.store_expression_type(target, target_ty);
        self.add_binding(target.into(), definition)
            .insert(self, target_ty);
    }

    /// Infers the type of a context expression (`with expr`) and returns the target's type
    ///
    /// Returns [`Type::unknown`] if the context expression doesn't implement the context manager protocol.
    ///
    /// ## Terminology
    /// See [PEP343](https://peps.python.org/pep-0343/#standard-terminology).
    fn infer_context_expression(
        &mut self,
        context_expression: &ast::Expr,
        context_expression_type: Type<'db>,
        is_async: bool,
    ) -> Type<'db> {
        let eval_mode = if is_async {
            EvaluationMode::Async
        } else {
            EvaluationMode::Sync
        };

        context_expression_type
            .try_enter_with_mode(self.db(), eval_mode)
            .unwrap_or_else(|err| {
                err.report_diagnostic(
                    &self.context,
                    context_expression_type,
                    context_expression.into(),
                );
                err.fallback_enter_type(self.db())
            })
    }

    fn infer_exception(&mut self, node: Option<&ast::Expr>, is_star: bool) -> Type<'db> {
        // If there is no handled exception, it's invalid syntax;
        // a diagnostic will have already been emitted
        let node_ty = node.map_or(Type::unknown(), |ty| {
            self.infer_expression(ty, TypeContext::default())
        });
        let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db());

        // If it's an `except*` handler, this won't actually be the type of the bound symbol;
        // it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
        let symbol_ty = if let Some(tuple_spec) = node_ty.tuple_instance_spec(self.db()) {
            let mut builder = UnionBuilder::new(self.db());
            let mut invalid_elements = vec![];

            for (index, element) in tuple_spec.all_elements().iter().enumerate() {
                builder = builder.add(
                    if element.is_assignable_to(self.db(), type_base_exception) {
                        element.to_instance(self.db()).expect(
                            "`Type::to_instance()` should always return `Some()` \
                                if called on a type assignable to `type[BaseException]`",
                        )
                    } else {
                        invalid_elements.push((index, element));
                        Type::unknown()
                    },
                );
            }

            if !invalid_elements.is_empty()
                && let Some(node) = node
            {
                if let ast::Expr::Tuple(tuple) = node
                    && !tuple.iter().any(ast::Expr::is_starred_expr)
                    && Some(tuple.len()) == tuple_spec.len().into_fixed_length()
                {
                    let invalid_elements = invalid_elements
                        .iter()
                        .map(|(index, ty)| (&tuple.elts[*index], **ty));

                    report_invalid_exception_tuple_caught(
                        &self.context,
                        tuple,
                        node_ty,
                        invalid_elements,
                    );
                } else {
                    report_invalid_exception_caught(&self.context, node, node_ty);
                }
            }

            builder.build()
        } else if let Some(symbol_ty) =
            self.exception_handler_symbol_ty_from_valid_ty(node_ty, type_base_exception)
        {
            symbol_ty
        } else if node_ty.is_assignable_to(
            self.db(),
            UnionType::from_two_elements(
                self.db(),
                type_base_exception,
                Type::homogeneous_tuple(self.db(), type_base_exception),
            ),
        ) {
            // TODO: Handle valid handler expressions that are opaque to the structural helper
            // above, for example a type variable bounded by the full class-or-tuple union.
            KnownClass::BaseException.to_instance(self.db())
        } else {
            if let Some(node) = node {
                report_invalid_exception_caught(&self.context, node, node_ty);
            }
            Type::unknown()
        };

        if is_star {
            let class = if symbol_ty
                .is_subtype_of(self.db(), KnownClass::Exception.to_instance(self.db()))
            {
                KnownClass::ExceptionGroup
            } else {
                KnownClass::BaseExceptionGroup
            };
            class.to_specialized_instance(self.db(), &[symbol_ty])
        } else {
            symbol_ty
        }
    }

    fn exception_handler_symbol_ty_from_valid_ty(
        &self,
        ty: Type<'db>,
        type_base_exception: Type<'db>,
    ) -> Option<Type<'db>> {
        if let Some(tuple_spec) = ty.tuple_instance_spec(self.db()) {
            // `except (ValueError, TypeError) as e:`
            UnionType::try_from_elements(
                self.db(),
                tuple_spec.all_elements().iter().map(|element| {
                    if element.is_assignable_to(self.db(), type_base_exception) {
                        Some(element.to_instance(self.db()).expect(
                            "`Type::to_instance()` should always return `Some()` \
                                if called on a type assignable to `type[BaseException]`",
                        ))
                    } else {
                        None
                    }
                }),
            )
        } else if ty.is_assignable_to(self.db(), type_base_exception) {
            // `except ValueError as e:`
            Some(ty.to_instance(self.db()).expect(
                "`Type::to_instance()` should always return `Some()` \
                    if called on a type assignable to `type[BaseException]`",
            ))
        } else if ty.is_assignable_to(
            self.db(),
            Type::homogeneous_tuple(self.db(), type_base_exception),
        ) {
            // `except exception_types as e:`, where
            // `exception_types: tuple[type[ValueError], ...]`
            Some(
                ty.tuple_instance_spec(self.db())
                    .and_then(|spec| {
                        let specialization = spec
                            .homogeneous_element_type(self.db())
                            .to_instance(self.db());

                        debug_assert!(specialization.is_some_and(|specialization_type| {
                            specialization_type.is_assignable_to(
                                self.db(),
                                KnownClass::BaseException.to_instance(self.db()),
                            )
                        }));

                        specialization
                    })
                    .unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db())),
            )
        } else if let Type::Union(union) = ty {
            // `except exception_types as e:`, where
            // `exception_types: type[ValueError] | tuple[type[ValueError], ...]`
            union.try_map(self.db(), |element| {
                self.exception_handler_symbol_ty_from_valid_ty(*element, type_base_exception)
            })
        } else {
            None
        }
    }

    fn infer_except_handler_definition(
        &mut self,
        except_handler_definition: &ExceptHandlerDefinitionKind,
        definition: Definition<'db>,
    ) {
        let symbol_ty = self.infer_exception(
            except_handler_definition.handled_exceptions(self.module()),
            except_handler_definition.is_star(),
        );

        self.add_binding(
            except_handler_definition.node(self.module()).into(),
            definition,
        )
        .insert(self, symbol_ty);
    }

    /// Infer the type for a loop header definition.
    ///
    /// The loop header sees all the bindings that originate in the loop and are visible at a
    /// loop-back edge (either the end of the loop body or a `continue` statement). See `struct
    /// LoopHeader` in the semantic index for more on how all this fits together.
    fn infer_loop_header_definition(
        &mut self,
        loop_header_kind: &LoopHeaderDefinitionKind,
        definition: Definition<'db>,
    ) {
        // This cutoff was chosen by benchmarking real isort to keep loop analysis
        // overhead minimal while preserving diagnostics.
        const MAX_EXACT_LOOP_HEADER_REACHABILITY_NODES: usize = 4096;

        let db = self.db();
        let loop_header = loop_header_reachability(db, definition);
        let use_def = self
            .index
            .use_def_map(self.scope().file_scope_id(self.db()));

        // Loop-header types are an approximation point for loop fixpoint analysis. Inferring the
        // exact union of every visible loop-back binding can recursively force inference of large
        // boolean expressions and explode on real-world loops.
        if use_def.reachability_constraints().used_interiors().len()
            > MAX_EXACT_LOOP_HEADER_REACHABILITY_NODES
        {
            self.bindings.insert(definition, Type::unknown());
            return;
        }

        let place = loop_header_kind.place();

        let mut union = UnionBuilder::new(db).recursively_defined(RecursivelyDefined::Yes);

        for reachable_binding in &loop_header.reachable_bindings {
            let binding_ty = binding_type(db, reachable_binding.definition);
            let narrowed_ty = use_def
                .narrowing_evaluator(reachable_binding.narrowing_constraint)
                .narrow(db, binding_ty, place);

            union.add_in_place(narrowed_ty);
        }

        self.bindings.insert(definition, union.build());
    }

    fn infer_nested_bindings_definition(
        &mut self,
        nested_bindings_kind: &NestedBindingsDefinitionKind,
        definition: Definition<'db>,
    ) {
        const MAX_EXACT_NESTED_BINDING_REACHABILITY_NODES: usize = 2048;

        let db = self.db();
        let scope = definition.scope(db);
        let scope_id = scope.file_scope_id(db);
        let symbol_id = definition
            .place(db)
            .as_symbol()
            .expect("nested bindings definition should be a symbol");
        let symbol = self.index.place_table(scope_id).symbol(symbol_id);

        // At the point where a nested bindings definition is first synthesized, we don't
        // necessarily know whether the current scope will see global or nonlocal bindings.
        // Consider this example:
        //
        //     def outer():
        //         def inner1():
        //             global x
        //             x = 1
        //         def inner2():
        //             nonlocal x
        //             x = 2
        //         # Nested bindings of both kinds are potentially visible here, but we can't
        //         # actually use both kinds in the same scope. If `print(x)` comes next, we should
        //         # only see 2. But if `global x; print(x)` comes next, we should only see 1.
        //         ...
        //
        // By the time we get here in type inference, though, we can ask the semantic index whether
        // the symbol resolves to the global scope or not. (For free variables, this currently
        // requires walking ancestor scopes.) If so, we see any nested `global` bindings that were
        // recorded. If not, we see any nested `nonlocal` ones.
        //
        // Note that if `x` is a free variable in this scope, then this synthetic binding will not
        // shadow `UNBOUND`, and `infer_place_load` will walk to the defining scope and see all the
        // nested bindings from there via the symbol's public type. However, we still want to
        // respect locally visible nested bindings in that case, because there might be narrowing
        // constraints that apply to the public type but not these nested bindings.
        let this_scope_sees_global_bindings = self
            .index
            .symbol_resolves_to_global_scope(symbol_id, scope_id);

        // If a function body binds `x`, it's interested in nested `nonlocal` bindings of `x` too,
        // because those resolve to the same variable. But if a *class* body binds `x`, it does
        // *not* want to consider nested bindings of `x`, because those do *not* resolve to the
        // same variable.
        let this_scope_sees_nonlocal_bindings = !(this_scope_sees_global_bindings
            || (scope.scope(db).kind().is_class() && symbol.is_local()));

        let mut visible_nested_declarations = nested_bindings_kind
            .nested_declarations
            .iter()
            .filter(|declaration| {
                if declaration.is_global() {
                    this_scope_sees_global_bindings
                } else {
                    this_scope_sees_nonlocal_bindings
                }
            })
            .peekable();
        if visible_nested_declarations.peek().is_some()
            && self
                .index
                .use_def_map(scope_id)
                .reachability_constraints()
                .used_interiors()
                .len()
                > MAX_EXACT_NESTED_BINDING_REACHABILITY_NODES
        {
            // As with loop header definitions above, use a reachability cutoff to avoid excessive
            // perf costs in complicated projects like `isort`.
            self.bindings.insert(definition, Type::unknown());
            return;
        }

        let mut union = UnionBuilder::new(db).recursively_defined(RecursivelyDefined::Yes);
        for declaration in visible_nested_declarations {
            assert!(
                declaration.is_bound,
                "nested declarations without bindings shouldn't be recorded here",
            );
            let nested_place_table = self.index.place_table(declaration.file_scope_id);
            let nested_symbol_id = nested_place_table
                .symbol_id(&nested_bindings_kind.name)
                .unwrap();
            let use_def = self.index.use_def_map(declaration.file_scope_id);
            let Some(ty) = place_from_bindings_with_reachability_cache(
                db,
                use_def.reachable_bindings(nested_symbol_id.into()),
                self.reachability_cache(),
            )
            .place
            .raw_type() else {
                continue;
            };
            union.add_in_place(ty);
        }
        self.bindings.insert(definition, union.build());
    }

    fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) {
        let ast::StmtMatch {
            range: _,
            node_index: _,
            subject,
            cases,
        } = match_statement;

        self.infer_standalone_expression(subject, TypeContext::default());

        for case in cases {
            let ast::MatchCase {
                range: _,
                node_index: _,
                body,
                pattern,
                guard,
            } = case;
            self.infer_match_pattern(pattern);

            if let Some(guard) = guard.as_deref() {
                let guard_ty = self.infer_standalone_expression(guard, TypeContext::default());

                if let Err(err) = guard_ty.try_bool(self.db()) {
                    err.report_diagnostic(&self.context, guard);
                }
            }

            self.infer_body(body);
        }
    }

    fn infer_match_pattern_definition(
        &mut self,
        pattern: &'ast ast::Pattern,
        predicate: PatternPredicate<'db>,
        definition: Definition<'db>,
    ) {
        let ty =
            pattern_success_types(self.db(), predicate).binding_type(definition.place(self.db()));
        self.add_binding(pattern.into(), definition)
            .insert(self, ty);
    }

    fn validate_class_pattern(&mut self, pattern: &ast::PatternMatchClass, cls_ty: Type<'db>) {
        if let Type::SpecialForm(SpecialFormType::CollectionsAbcCallable) = cls_ty {
            if let Some(first_excess_pattern) = pattern.arguments.patterns.first() {
                report_too_many_positional_patterns_for_class_pattern(
                    &self.context,
                    first_excess_pattern,
                    0,
                    pattern.arguments.patterns.len(),
                    "collections.abc.Callable",
                );
            }
            return;
        }

        if let Type::ClassLiteral(class) = cls_ty {
            if class.is_typed_dict(self.db()) {
                report_match_pattern_against_typed_dict(&self.context, &*pattern.cls, class);
                return;
            }
            if let Some(protocol_class) = class.into_protocol_class(self.db())
                && !protocol_class.is_runtime_checkable(self.db())
            {
                report_match_pattern_against_non_runtime_checkable_protocol(
                    &self.context,
                    &*pattern.cls,
                    protocol_class,
                );
                return;
            }

            let positional_patterns = &pattern.arguments.patterns;
            if let [first_positional_pattern, ..] = positional_patterns.as_slice()
                && let Some(result) = class_pattern_positional_result(self.db(), class)
            {
                match result {
                    ClassPatternPositionalResult::Limit(limit) => {
                        if let Some(first_excess_pattern) = positional_patterns.get(limit) {
                            report_too_many_positional_patterns_for_class_pattern(
                                &self.context,
                                first_excess_pattern,
                                limit,
                                positional_patterns.len(),
                                cls_ty.display(self.db()),
                            );
                        }
                    }
                    ClassPatternPositionalResult::InvalidType(match_args_ty) => {
                        report_invalid_match_args_type(
                            &self.context,
                            first_positional_pattern,
                            match_args_ty,
                            cls_ty,
                        );
                    }
                }
            }
        } else if !cls_ty.is_assignable_to(self.db(), KnownClass::Type.to_instance(self.db())) {
            report_invalid_class_match_pattern(&self.context, &*pattern.cls, cls_ty);
        }
    }

    fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
        // We need to create a standalone expression for each arm of a match statement, since they
        // can introduce constraints on the match subject. (Or more accurately, for the match arm's
        // pattern, since its the pattern that introduces any constraints, not the body.) Ideally,
        // that standalone expression would wrap the match arm's pattern as a whole. But a
        // standalone expression can currently only wrap an ast::Expr, which patterns are not. So,
        // we need to choose an Expr that can “stand in” for the pattern, which we can wrap in a
        // standalone expression.
        //
        // The structural pattern is stored separately on `PatternPredicate`, so analyses that need
        // the complete pattern can inspect its arguments without making them standalone
        // expressions.
        //
        // This function is only called for the top-level pattern of a match arm, and is
        // responsible for inferring the standalone expression for each supported pattern type. It
        // then hands off to `infer_nested_match_pattern` for any subexpressions and subpatterns,
        // where we do NOT have any additional standalone expressions to infer through.
        //
        match pattern {
            ast::Pattern::MatchValue(match_value) => {
                self.infer_standalone_expression(&match_value.value, TypeContext::default());
            }
            ast::Pattern::MatchClass(match_class) => {
                let ast::PatternMatchClass {
                    range: _,
                    node_index: _,
                    cls,
                    arguments,
                } = match_class;
                for pattern in &arguments.patterns {
                    self.infer_nested_match_pattern(pattern);
                }
                for keyword in &arguments.keywords {
                    self.infer_nested_match_pattern(&keyword.pattern);
                }
                let cls_ty = self.infer_standalone_expression(cls, TypeContext::default());
                self.validate_class_pattern(match_class, cls_ty);
            }
            ast::Pattern::MatchOr(match_or) => {
                for pattern in &match_or.patterns {
                    self.infer_match_pattern(pattern);
                }
            }
            _ => {
                self.infer_nested_match_pattern(pattern);
            }
        }
    }

    fn infer_nested_match_pattern(&mut self, pattern: &ast::Pattern) {
        match pattern {
            ast::Pattern::MatchValue(match_value) => {
                self.infer_maybe_standalone_expression(&match_value.value, TypeContext::default());
            }
            ast::Pattern::MatchSequence(match_sequence) => {
                for pattern in &match_sequence.patterns {
                    self.infer_nested_match_pattern(pattern);
                }
            }
            ast::Pattern::MatchMapping(match_mapping) => {
                let ast::PatternMatchMapping {
                    range: _,
                    node_index: _,
                    keys,
                    patterns,
                    rest,
                } = match_mapping;
                for key in keys {
                    self.infer_maybe_standalone_expression(key, TypeContext::default());
                }
                for pattern in patterns {
                    self.infer_nested_match_pattern(pattern);
                }
                if let Some(rest) = rest {
                    self.infer_definition(rest);
                }
            }
            ast::Pattern::MatchClass(match_class) => {
                let ast::PatternMatchClass {
                    range: _,
                    node_index: _,
                    cls,
                    arguments,
                } = match_class;
                for pattern in &arguments.patterns {
                    self.infer_nested_match_pattern(pattern);
                }
                for keyword in &arguments.keywords {
                    self.infer_nested_match_pattern(&keyword.pattern);
                }
                let cls_ty = self.infer_maybe_standalone_expression(cls, TypeContext::default());
                self.validate_class_pattern(match_class, cls_ty);
            }
            ast::Pattern::MatchAs(match_as) => {
                if let Some(pattern) = &match_as.pattern {
                    self.infer_nested_match_pattern(pattern);
                }
                if let Some(name) = &match_as.name {
                    self.infer_definition(name);
                }
            }
            ast::Pattern::MatchOr(match_or) => {
                for pattern in &match_or.patterns {
                    self.infer_nested_match_pattern(pattern);
                }
            }
            ast::Pattern::MatchStar(match_star) => {
                if let Some(name) = &match_star.name {
                    self.infer_definition(name);
                }
            }
            ast::Pattern::MatchSingleton(_) => {}
        }
    }

    fn infer_assignment_statement(&mut self, assignment: &ast::StmtAssign) {
        let ast::StmtAssign {
            range: _,
            node_index: _,
            targets,
            value,
        } = assignment;

        for target in targets {
            if let Some(unpack) = self.index.try_unpack(target) {
                // Infer the standalone expression here to include its diagnostics in this region.
                self.infer_standalone_expression(value, TypeContext::default());

                let unpacked = infer_unpack_types(self.db(), unpack);
                self.context.extend(unpacked.diagnostics());
                self.infer_unpacked_assignment_target(target, value, unpacked);
            } else {
                self.infer_target(target, value, &|builder, tcx| {
                    builder.infer_standalone_expression(value, tcx)
                });
            }
        }
    }

    fn infer_unpacked_assignment_target(
        &mut self,
        target: &ast::Expr,
        value: &ast::Expr,
        unpacked: &UnpackResult<'db>,
    ) {
        match target {
            ast::Expr::Starred(ast::ExprStarred { value: target, .. }) => {
                self.infer_unpacked_assignment_target(target, value, unpacked);
            }
            ast::Expr::List(ast::ExprList { elts, .. })
            | ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
                for target in elts {
                    self.infer_unpacked_assignment_target(target, value, unpacked);
                }
            }
            _ => {
                let assigned_ty = unpacked.expression_type(target);
                self.infer_target_impl(target, value, Some(&|_, _| assigned_ty));
            }
        }
    }

    /// Infer the (definition) types involved in a `target` expression.
    ///
    /// This is used for assignment statements, for statements, etc. with a single or multiple
    /// targets (unpacking). If `target` is an attribute expression, we check that the assignment
    /// is valid. For 'target's that are definitions, this check happens elsewhere.
    ///
    /// The `infer_value_expr` function is used to infer the type of the `value` expression which
    /// are not `Name` expressions. The returned type is the one that is eventually assigned to the
    /// `target`.
    fn infer_target(
        &mut self,
        target: &ast::Expr,
        value: &ast::Expr,
        infer_value_expr: &dyn Fn(&mut Self, TypeContext<'db>) -> Type<'db>,
    ) {
        match target {
            ast::Expr::Name(_) => {
                self.infer_target_impl(target, value, None);
            }

            _ => self.infer_target_impl(target, value, Some(&infer_value_expr)),
        }
    }

    /// Returns `true` if `property_ty` is a property whose deleter returns `Never`/`NoReturn`
    /// when called for deletion on `object_ty`.
    fn property_deleter_returns_never(&self, property_ty: Type<'db>, object_ty: Type<'db>) -> bool {
        let db = self.db();
        property_ty.as_property_instance().is_some_and(|property| {
            property.deleter(db).is_some_and(|deleter| {
                match deleter.try_call(db, &CallArguments::positional([object_ty])) {
                    Ok(result) => result.return_type(db).is_never(),
                    Err(err) => err.return_type(db).is_never(),
                }
            })
        })
    }

    fn validate_attribute_deletion(
        &mut self,
        target: &ast::ExprAttribute,
        object_ty: Type<'db>,
        attribute: &str,
        emit_diagnostics: bool,
    ) -> bool {
        let db = self.db();

        match object_ty {
            Type::Union(union) => {
                for element_ty in union.elements(db) {
                    if !self.validate_attribute_deletion(
                        target,
                        *element_ty,
                        attribute,
                        emit_diagnostics,
                    ) {
                        return false;
                    }
                }
                true
            }

            Type::Intersection(intersection) => {
                if intersection.positive(db).iter().any(|element_ty| {
                    self.validate_attribute_deletion(target, *element_ty, attribute, false)
                }) {
                    true
                } else {
                    if emit_diagnostics && let Some(element_ty) = intersection.positive(db).first()
                    {
                        self.validate_attribute_deletion(target, *element_ty, attribute, true);
                    }
                    false
                }
            }

            Type::EnumComplement(complement) => self.validate_attribute_deletion(
                target,
                complement.remaining_literal_union(db),
                attribute,
                emit_diagnostics,
            ),

            // Type aliases need their own arm so aliased unions and intersections reuse the
            // specialized handling above. `NewType` instances don't: dunder lookup and attribute
            // fallback already delegate through the concrete base type when needed.
            Type::TypeAlias(alias) => self.validate_attribute_deletion(
                target,
                alias.value_type(db),
                attribute,
                emit_diagnostics,
            ),

            Type::NominalInstance(..)
            | Type::ProtocolInstance(_)
            | Type::LiteralValue(..)
            | Type::SpecialForm(..)
            | Type::ClassLiteral(..)
            | Type::GenericAlias(..)
            | Type::SubclassOf(..)
            | Type::KnownInstance(..)
            | Type::PropertyInstance(..)
            | Type::FunctionLiteral(..)
            | Type::Callable(..)
            | Type::BoundMethod(_)
            | Type::KnownBoundMethod(_)
            | Type::WrapperDescriptor(_)
            | Type::DataclassDecorator(_)
            | Type::DataclassTransformer(_)
            | Type::TypeVar(..)
            | Type::AlwaysTruthy
            | Type::AlwaysFalsy
            | Type::TypeIs(_)
            | Type::TypeGuard(_)
            | Type::TypeForm(_)
            | Type::TypedDict(_)
            | Type::NewTypeInstance(_) => {
                let delattr_dunder_call_result = object_ty.try_call_dunder_with_policy(
                    db,
                    "__delattr__",
                    &mut CallArguments::positional([Type::string_literal(db, attribute)]),
                    TypeContext::default(),
                    MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
                );

                let returns_never = match &delattr_dunder_call_result {
                    Ok(result) => result.return_type(db).is_never(),
                    Err(err) => err.return_type(db).is_some_and(|ty| ty.is_never()),
                };
                if returns_never {
                    if emit_diagnostics
                        && let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target)
                    {
                        builder.into_diagnostic(format_args!(
                            "Cannot delete attribute `{attribute}` on type `{}` \
                             whose `__delattr__` method returns `Never`/`NoReturn`",
                            object_ty.display(db),
                        ));
                    }
                    return false;
                }

                match delattr_dunder_call_result {
                    Ok(_) | Err(CallDunderError::PossiblyUnbound { .. }) => {
                        if self.validate_final_attribute_deletion(
                            target,
                            object_ty,
                            attribute,
                            emit_diagnostics,
                        ) {
                            return false;
                        }
                        return true;
                    }
                    Err(CallDunderError::CallError(kind, _bindings, _)) => {
                        if emit_diagnostics {
                            report_bad_dunder_delattr_call(
                                &self.context,
                                attribute,
                                object_ty,
                                target,
                                kind == CallErrorKind::BindingError,
                            );
                        }
                        return false;
                    }
                    Err(CallDunderError::MethodNotAvailable) => {}
                }

                if self.validate_final_attribute_deletion(
                    target,
                    object_ty,
                    attribute,
                    emit_diagnostics,
                ) {
                    return false;
                }

                if let Some(PlaceAndQualifiers {
                    place:
                        Place::Defined(DefinedPlace {
                            ty: attr_ty,
                            definedness: Definedness::AlwaysDefined,
                            ..
                        }),
                    ..
                }) = assignment_attribute_members(db, object_ty, attribute)
                    .and_then(AssignmentAttributeMembers::type_member)
                {
                    let attr_ty = attr_ty.bind_self_typevars(db, object_ty);
                    let delete_dunder_call_result = attr_ty.try_call_dunder(
                        db,
                        "__delete__",
                        CallArguments::positional([object_ty]),
                        TypeContext::default(),
                    );

                    if self.property_deleter_returns_never(attr_ty, object_ty) {
                        if emit_diagnostics
                            && let Some(builder) =
                                self.context.report_lint(&INVALID_ASSIGNMENT, target)
                        {
                            builder.into_diagnostic(format_args!(
                                "Cannot delete attribute `{attribute}` on type `{}` \
                                 whose `__delete__` method returns `Never`/`NoReturn`",
                                object_ty.display(db),
                            ));
                        }
                        return false;
                    }

                    match delete_dunder_call_result {
                        Ok(_) | Err(CallDunderError::PossiblyUnbound { .. }) => return true,
                        Err(CallDunderError::CallError(kind, bindings, _)) => {
                            if emit_diagnostics {
                                let failure = CallError(kind, bindings);
                                report_bad_dunder_delete_call(
                                    &self.context,
                                    &failure,
                                    attribute,
                                    object_ty,
                                    target,
                                );
                            }
                            return false;
                        }
                        Err(CallDunderError::MethodNotAvailable) => {}
                    }
                }

                true
            }

            Type::Dynamic(..)
            | Type::Divergent(_)
            | Type::Never
            | Type::ModuleLiteral(..)
            | Type::BoundSuper(..) => true,
        }
    }

    #[expect(clippy::type_complexity)]
    fn infer_target_impl(
        &mut self,
        target: &ast::Expr,
        value: &ast::Expr,
        infer_assigned_ty: Option<&dyn Fn(&mut Self, TypeContext<'db>) -> Type<'db>>,
    ) {
        match target {
            ast::Expr::Name(name) => {
                if let Some(infer_assigned_ty) = infer_assigned_ty {
                    infer_assigned_ty(self, TypeContext::default());
                }

                self.infer_definition(name);
            }
            ast::Expr::Starred(ast::ExprStarred {
                value: starred_value,
                ..
            }) => {
                self.infer_target_impl(starred_value, value, infer_assigned_ty);
            }
            ast::Expr::List(ast::ExprList { elts, .. })
            | ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
                let assigned_ty = infer_assigned_ty.map(|f| f(self, TypeContext::default()));

                if let Some(tuple_spec) =
                    assigned_ty.and_then(|ty| ty.tuple_instance_spec(self.db()))
                {
                    let assigned_tys = tuple_spec.all_elements().to_vec();

                    for (i, element) in elts.iter().enumerate() {
                        match assigned_tys.get(i).copied() {
                            None => self.infer_target_impl(element, value, None),
                            Some(ty) => self.infer_target_impl(element, value, Some(&|_, _| ty)),
                        }
                    }
                } else {
                    for element in elts {
                        self.infer_target_impl(element, value, None);
                    }
                }
            }
            ast::Expr::Attribute(
                attr_expr @ ast::ExprAttribute {
                    value: object,
                    ctx: ExprContext::Store,
                    attr,
                    ..
                },
            ) => {
                let object_ty = self.infer_expression(object, TypeContext::default());
                self.report_undeclared_protocol_attribute(attr_expr);

                if let Some(infer_assigned_ty) = infer_assigned_ty {
                    let infer_assigned_ty = &mut |builder: &mut Self, tcx| {
                        let assigned_ty = infer_assigned_ty(builder, tcx);
                        builder.store_expression_type(target, assigned_ty);
                        assigned_ty
                    };

                    self.validate_attribute_assignment(
                        attr_expr,
                        object_ty,
                        attr.id(),
                        infer_assigned_ty,
                        true,
                    );
                }
            }
            ast::Expr::Subscript(subscript_expr) => {
                if let Some(infer_assigned_ty) = infer_assigned_ty {
                    let infer_assigned_ty = &mut |builder: &mut Self, tcx| {
                        let assigned_ty = infer_assigned_ty(builder, tcx);
                        builder.store_expression_type(target, assigned_ty);
                        assigned_ty
                    };

                    self.validate_subscript_assignment(subscript_expr, value, infer_assigned_ty);
                }
            }

            // TODO: Remove this once we handle all possible assignment targets.
            _ => {
                if let Some(infer_assigned_ty) = infer_assigned_ty {
                    infer_assigned_ty(self, TypeContext::default());
                }

                self.infer_expression(target, TypeContext::default());
            }
        }
    }

    fn infer_assignment_definition(
        &mut self,
        assignment: &AssignmentDefinitionKind<'db>,
        definition: Definition<'db>,
    ) {
        let target = assignment.target(self.module());

        let add = self.add_binding(target.into(), definition);
        let target_ty =
            self.infer_assignment_definition_impl(assignment, definition, add.type_context());
        self.store_expression_type(target, target_ty);
        add.insert(self, target_ty);
    }

    fn stub_placeholder_binding_type(&self, value: &ast::Expr) -> Option<Type<'db>> {
        if self.in_stub() && value.is_ellipsis_literal_expr() {
            Some(Type::unknown())
        } else {
            None
        }
    }

    fn infer_assignment_definition_impl(
        &mut self,
        assignment: &AssignmentDefinitionKind<'db>,
        definition: Definition<'db>,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let value = assignment.value(self.module());
        let target = assignment.target(self.module());

        let mut target_ty = match assignment.unpack() {
            Some(unpack) => {
                // The assignment statement owns unpacking diagnostics so that targets without a
                // name definition are still checked, and each diagnostic is reported only once.
                let unpacked = infer_unpack_types(self.db(), unpack);
                unpacked.expression_type(target)
            }
            None => {
                // This could be an implicit type alias (OptionalList = list[T] | None). Use the definition
                // of `OptionalList` as the binding context while inferring the RHS (`list[T] | None`), in
                // order to bind `T` to `OptionalList`.
                let previous_typevar_binding_context =
                    self.typevar_binding_context.replace(definition);

                let value_ty = if let Some(standalone_expression) = self.index.try_expression(value)
                {
                    self.infer_standalone_expression_impl(value, standalone_expression, tcx)
                } else if let ast::Expr::Call(call_expr) = value {
                    // If the RHS is not a standalone expression, this is a simple assignment
                    // (single target, no unpackings). That means it's a valid syntactic form
                    // for a legacy TypeVar creation; check for that.
                    let callable_type = self.infer_maybe_standalone_expression(
                        call_expr.func.as_ref(),
                        TypeContext::default(),
                    );

                    let ty = if let Some(namedtuple_kind) =
                        NamedTupleKind::from_type(self.db(), callable_type)
                    {
                        self.infer_namedtuple_call_expression(
                            call_expr,
                            Some(definition),
                            namedtuple_kind,
                        )
                    } else if let Some(typed_dict_module) =
                        TypedDictModule::from_type(self.db(), callable_type)
                    {
                        self.infer_typeddict_call_expression(
                            call_expr,
                            Some(definition),
                            typed_dict_module,
                        )
                    } else if let Some(function) = callable_type.as_function_literal()
                        && function.is_known(self.db(), KnownFunction::NewClass)
                    {
                        self.infer_new_class_call(call_expr, Some(definition))
                    } else if let Some(base_class) =
                        enum_call::enum_functional_call_base(self.db(), callable_type)
                        && let Some(ty) =
                            self.infer_enum_call_expression(call_expr, Some(definition), base_class)
                    {
                        ty
                    } else {
                        match callable_type
                            .as_class_literal()
                            .and_then(|cls| cls.known(self.db()))
                        {
                            Some(
                                typevar_class @ (KnownClass::TypeVar
                                | KnownClass::ExtensionsTypeVar),
                            ) => self.infer_legacy_typevar(
                                target,
                                call_expr,
                                definition,
                                typevar_class,
                            ),
                            Some(
                                paramspec_class @ (KnownClass::ParamSpec
                                | KnownClass::ExtensionsParamSpec),
                            ) => self.infer_legacy_paramspec(
                                target,
                                call_expr,
                                definition,
                                paramspec_class,
                            ),
                            Some(KnownClass::NewType) => {
                                self.infer_newtype_expression(target, call_expr, definition)
                            }
                            Some(KnownClass::Type) => {
                                // Try to extract the dynamic class with definition.
                                // This returns `None` if it's not a three-arg call to `type()`,
                                // signalling that we must fall back to normal call inference.
                                self.infer_builtins_type_call(call_expr, Some(definition))
                            }
                            Some(KnownClass::TypeAliasType) => {
                                self.infer_typealiastype_call(target, call_expr, definition)
                            }
                            Some(KnownClass::Sentinel) => self
                                .infer_sentinel_expression(target, call_expr, definition)
                                .unwrap_or_else(|| {
                                    self.infer_call_expression_impl(call_expr, callable_type, tcx)
                                }),
                            Some(_) | None => {
                                self.infer_call_expression_impl(call_expr, callable_type, tcx)
                            }
                        }
                    };

                    self.store_expression_type(value, ty);
                    ty
                } else {
                    self.infer_expression(value, tcx)
                };

                self.typevar_binding_context = previous_typevar_binding_context;

                // `TYPE_CHECKING` is a special variable that should only be assigned `False`
                // at runtime, but is always considered `True` in type checking.
                // See mdtest/known_constants.md#user-defined-type_checking for details.
                if target.as_name_expr().map(|name| name.id.as_str()) == Some("TYPE_CHECKING") {
                    if !matches!(
                        value.as_boolean_literal_expr(),
                        Some(ast::ExprBooleanLiteral { value: false, .. })
                    ) {
                        report_invalid_type_checking_constant(&self.context, target.into());
                    }
                    Type::bool_literal(true)
                } else {
                    self.stub_placeholder_binding_type(value)
                        .unwrap_or(value_ty)
                }
            }
        };

        if let Some(special_form) = target.as_name_expr().and_then(|name| {
            SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name.id)
        }) {
            target_ty = Type::SpecialForm(special_form);
        }

        target_ty
    }

    fn infer_newtype_expression(
        &mut self,
        target: &ast::Expr,
        call_expr: &ast::ExprCall,
        definition: Definition<'db>,
    ) -> Type<'db> {
        fn error<'db>(
            context: &InferContext<'db, '_>,
            message: impl std::fmt::Display,
            node: impl Ranged,
        ) -> Type<'db> {
            if let Some(builder) = context.report_lint(&INVALID_NEWTYPE, node) {
                builder.into_diagnostic(message);
            }
            Type::unknown()
        }

        let db = self.db();
        let arguments = &call_expr.arguments;

        if !arguments.keywords.is_empty() {
            return error(
                &self.context,
                "Keyword arguments are not supported in `NewType` creation",
                call_expr,
            );
        }

        if let Some(starred) = arguments.args.iter().find(|arg| arg.is_starred_expr()) {
            return error(
                &self.context,
                "Starred arguments are not supported in `NewType` creation",
                starred,
            );
        }

        if arguments.args.len() != 2 {
            return error(
                &self.context,
                format!(
                    "Wrong number of arguments in `NewType` creation: expected 2, found {}",
                    arguments.args.len()
                ),
                call_expr,
            );
        }

        let name_param_ty = self.infer_expression(&arguments.args[0], TypeContext::default());

        let Some(name) = name_param_ty.as_string_literal().map(|name| name.value(db)) else {
            return error(
                &self.context,
                "The first argument to `NewType` must be a string literal",
                call_expr,
            );
        };

        let ast::Expr::Name(ast::ExprName {
            id: target_name, ..
        }) = target
        else {
            return error(
                &self.context,
                "A `NewType` definition must be a simple variable assignment",
                target,
            );
        };

        if name != target_name {
            report_mismatched_type_name(
                &self.context,
                &arguments.args[0],
                "NewType",
                target_name,
                Some(name),
                name_param_ty,
            );
        }

        // Inference of `tp` must be deferred, to avoid cycles.
        self.deferred.insert(definition);

        Type::KnownInstance(KnownInstanceType::NewType(NewType::new(
            db,
            ast::name::Name::from(name),
            definition,
            None,
        )))
    }

    fn infer_sentinel_expression(
        &mut self,
        target: &ast::Expr,
        call_expr: &ast::ExprCall,
        definition: Definition<'db>,
    ) -> Option<Type<'db>> {
        if !self.sentinel_definition_scope_is_supported() {
            return None;
        }

        let ast::Expr::Name(ast::ExprName {
            id: target_name, ..
        }) = target
        else {
            return None;
        };

        let ast::Arguments {
            args,
            keywords,
            range: _,
            node_index: _,
        } = &call_expr.arguments;

        if args.iter().any(ast::Expr::is_starred_expr) {
            return None;
        }

        let (name_arg, mut repr_arg) = match &**args {
            [name_arg] => (name_arg, None),
            [name_arg, repr_arg] => (name_arg, Some(repr_arg)),
            _ => return None,
        };

        for keyword in keywords {
            let Some(keyword_name) = &keyword.arg else {
                return None;
            };

            if keyword_name.as_str() != "repr" || repr_arg.is_some() {
                return None;
            }

            repr_arg = Some(&keyword.value);
        }

        if !matches!(name_arg, ast::Expr::StringLiteral(_)) {
            return None;
        }

        let Some(repr_arg) = repr_arg else {
            return Some(Type::KnownInstance(KnownInstanceType::Sentinel(
                SentinelInstance::new(self.db(), target_name, definition),
            )));
        };

        if !matches!(repr_arg, ast::Expr::StringLiteral(_)) && !repr_arg.is_none_literal_expr() {
            return None;
        }

        Some(Type::KnownInstance(KnownInstanceType::Sentinel(
            SentinelInstance::new(self.db(), target_name, definition),
        )))
    }

    fn sentinel_definition_scope_is_supported(&self) -> bool {
        let db = self.db();
        let mut scope_id = self.scope.file_scope_id(db);

        loop {
            let scope = self.index.scope(scope_id);
            match scope.node().scope_kind() {
                ScopeKind::Module => return true,
                ScopeKind::Class => {}
                ScopeKind::Function
                | ScopeKind::Lambda
                | ScopeKind::Comprehension
                | ScopeKind::TypeAlias
                | ScopeKind::TypeParams => return false,
            }

            let Some(parent) = scope.parent() else {
                return false;
            };
            scope_id = parent;
        }
    }

    fn infer_assignment_deferred(&mut self, target: &ast::Expr, value: &'ast ast::Expr) {
        // Infer deferred bounds/constraints/defaults of a legacy TypeVar / ParamSpec / NewType,
        // and field types for functional TypedDict.
        let ast::Expr::Call(ast::ExprCall {
            func, arguments, ..
        }) = value
        else {
            return;
        };
        let func_ty = self
            .try_expression_type(func)
            .unwrap_or_else(|| self.infer_expression(func, TypeContext::default()));
        if func_ty == Type::SpecialForm(SpecialFormType::NamedTuple) {
            // Only the `fields` argument is deferred for `NamedTuple`;
            // other arguments are inferred eagerly.
            self.infer_typing_namedtuple_fields(&arguments.args[1]);
            return;
        }
        let known_class = func_ty
            .as_class_literal()
            .and_then(|cls| cls.known(self.db()));
        match (known_class, self.region) {
            (Some(KnownClass::NewType), _) => {
                self.infer_newtype_assignment_deferred(arguments);
                return;
            }
            (Some(KnownClass::TypeAliasType), InferenceRegion::Deferred(definition)) => {
                self.infer_typealiastype_assignment_deferred(definition, arguments);
                return;
            }
            (Some(KnownClass::Type), InferenceRegion::Deferred(definition)) => {
                self.infer_builtins_type_deferred(definition, value);
                return;
            }
            _ => {}
        }
        if TypedDictModule::from_type(self.db(), func_ty).is_some() {
            self.infer_functional_typeddict_deferred(arguments);
            return;
        }
        if let InferenceRegion::Deferred(definition) = self.region
            && let Some(function) = func_ty.as_function_literal()
            && function.is_known(self.db(), KnownFunction::NewClass)
        {
            self.infer_new_class_deferred(definition, value);
            return;
        }
        let mut constraint_tys = Vec::new();
        for arg in arguments.args.iter().skip(1) {
            let constraint = self.infer_type_expression(arg);
            constraint_tys.push(constraint);

            if constraint.has_typevar_or_typevar_instance(self.db())
                && let Some(builder) = self
                    .context
                    .report_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS, arg)
            {
                builder.into_diagnostic("TypeVar constraint cannot be generic");
            }
        }
        let mut bound_or_constraints = if !constraint_tys.is_empty() {
            Some(TypeVarBoundOrConstraints::Constraints(
                TypeVarConstraints::new(self.db(), constraint_tys.into_boxed_slice()),
            ))
        } else {
            None
        };
        if let Some(bound) = arguments.find_keyword("bound") {
            let bound_type = self.infer_type_expression(&bound.value);
            bound_or_constraints = Some(TypeVarBoundOrConstraints::UpperBound(bound_type));

            if bound_type.has_typevar_or_typevar_instance(self.db())
                && let Some(builder) = self
                    .context
                    .report_lint(&INVALID_TYPE_VARIABLE_BOUND, bound)
            {
                builder.into_diagnostic("TypeVar upper bound cannot be generic");
            }
        }
        if let Some(default) = arguments.find_keyword("default") {
            if matches!(
                known_class,
                Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec)
            ) {
                // Pass `None` for the name: the outer-scope typevar check inside
                // `infer_paramspec_default` is only relevant for PEP 695 type parameter
                // scopes. Legacy ParamSpec definitions live at module/class-body scope,
                // so the check would be a no-op here. Out-of-scope defaults for legacy
                // typevars are instead validated by `check_legacy_typevar_defaults`
                // (for functions) and `report_invalid_typevar_default_reference`
                // (for classes).
                self.infer_paramspec_default(&default.value, None);
            } else {
                let default_ty = self.infer_type_expression(&default.value);
                let bound_or_constraints_node = arguments
                    .find_keyword("bound")
                    .map(|kw| BoundOrConstraintsNodes::Bound(&kw.value))
                    .or_else(|| {
                        if arguments.args.len() < 3 {
                            return None;
                        }
                        Some(BoundOrConstraintsNodes::Constraints(&arguments.args[1..]))
                    });
                self.validate_typevar_default(
                    target.as_name_expr().map(|name| &*name.id),
                    bound_or_constraints,
                    default_ty,
                    &default.value,
                    bound_or_constraints_node,
                );
            }
        }
    }

    // Infer the deferred base type of a NewType.
    fn infer_newtype_assignment_deferred(&mut self, arguments: &ast::Arguments) {
        let inferred = self.infer_type_expression(&arguments.args[1]);

        if inferred.has_typevar_or_typevar_instance(self.db()) {
            if let Some(builder) = self
                .context
                .report_lint(&INVALID_NEWTYPE, &arguments.args[1])
            {
                let mut diag = builder.into_diagnostic("invalid base for `typing.NewType`");
                diag.set_primary_message("A `NewType` base cannot be generic");
            }
            return;
        }

        match inferred {
            Type::NewTypeInstance(_) | Type::NominalInstance(_) => return,
            // There are exactly two union types allowed as bases for NewType: `int | float` and
            // `int | float | complex`. These are allowed because that's what `float` and `complex`
            // expand into in type position. We don't currently ask whether the union was implicit
            // or explicit, so the explicit version is also allowed.
            Type::Union(union_ty) => {
                if let Some(KnownUnion::Float | KnownUnion::Complex) = union_ty.known(self.db()) {
                    return;
                }
            }
            // `Unknown` is likely to be the result of an unresolved import or a typo, which will
            // already get a diagnostic, so don't pile on an extra diagnostic here.
            Type::Dynamic(DynamicType::Unknown) => return,
            _ => {}
        }
        if let Some(builder) = self
            .context
            .report_lint(&INVALID_NEWTYPE, &arguments.args[1])
        {
            let mut diag = builder.into_diagnostic("invalid base for `typing.NewType`");
            diag.set_primary_message(format!("type `{}`", inferred.display(self.db())));
            if matches!(inferred, Type::ProtocolInstance(_)) {
                diag.info("The base of a `NewType` is not allowed to be a protocol class.");
            } else if matches!(inferred, Type::TypedDict(_)) {
                diag.info("The base of a `NewType` is not allowed to be a `TypedDict`.");
            } else {
                diag.info("The base of a `NewType` must be a class type or another `NewType`.");
            }
        }
    }

    /// Infer a `TypeAliasType("Name", value)` call in a simple assignment context.
    ///
    /// Follows the same pattern as [`Self::infer_newtype_expression`]: validates the
    /// arguments, constructs a [`ManualPEP695TypeAliasType`], and defers inference of
    /// the value argument.
    fn infer_typealiastype_call(
        &mut self,
        target: &ast::Expr,
        call_expr: &ast::ExprCall,
        definition: Definition<'db>,
    ) -> Type<'db> {
        fn error<'db>(
            context: &InferContext<'db, '_>,
            message: impl std::fmt::Display,
            node: impl Ranged,
        ) -> Type<'db> {
            if let Some(builder) = context.report_lint(&INVALID_TYPE_ALIAS_TYPE, node) {
                builder.into_diagnostic(message);
            }
            Type::unknown()
        }

        let db = self.db();
        let arguments = &call_expr.arguments;

        if let Some(starred) = arguments.args.iter().find(|arg| arg.is_starred_expr()) {
            return error(
                &self.context,
                "Starred arguments are not supported in `TypeAliasType` creation",
                starred,
            );
        }

        if arguments.args.len() != 2 {
            return error(
                &self.context,
                format_args!(
                    "Wrong number of arguments in `TypeAliasType` creation: expected 2, found {}",
                    arguments.args.len()
                ),
                call_expr,
            );
        }

        let name_param_ty = self.infer_expression(&arguments.args[0], TypeContext::default());

        let Some(name) = name_param_ty.as_string_literal().map(|name| name.value(db)) else {
            return error(
                &self.context,
                "The first argument to `TypeAliasType` must be a string literal",
                &arguments.args[0],
            );
        };

        let ast::Expr::Name(ast::ExprName {
            id: target_name, ..
        }) = target
        else {
            return error(
                &self.context,
                "A `TypeAliasType` definition must be a simple variable assignment",
                target,
            );
        };

        if name != target_name {
            report_mismatched_type_name(
                &self.context,
                &arguments.args[0],
                "TypeAliasType",
                target_name,
                Some(name),
                name_param_ty,
            );
        }

        // Inference of the value argument must be deferred, to avoid cycles.
        self.deferred.insert(definition);

        Type::KnownInstance(KnownInstanceType::TypeAliasType(
            TypeAliasType::ManualPEP695(ManualPEP695TypeAliasType::new(
                db,
                ast::name::Name::new(name),
                definition,
            )),
        ))
    }

    /// Infer the deferred value type of a `TypeAliasType`.
    fn infer_typealiastype_assignment_deferred(
        &mut self,
        definition: Definition<'db>,
        arguments: &ast::Arguments,
    ) {
        // Match the binding context used by eager assignment inference so legacy type variables
        // in the alias value are bound to the alias definition.
        let previous_context = self.typevar_binding_context.replace(definition);

        self.infer_type_expression(&arguments.args[1]);
        // Infer keyword arguments (e.g. `type_params`) so their types are stored.
        for keyword in &arguments.keywords {
            self.infer_expression(&keyword.value, TypeContext::default());
        }

        self.typevar_binding_context = previous_context;
    }

    fn is_valid_receiver_annotation_target(&self, target: &ast::Expr) -> bool {
        target
            .as_attribute_expr()
            .is_some_and(|target| self.is_receiver_attribute_annotation_target(target))
    }

    fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) {
        if assignment.target.is_name_expr() {
            self.infer_definition(assignment);
        } else {
            // Non-name assignment targets are inferred as ordinary expressions, not definitions.
            let ast::StmtAnnAssign {
                range: _,
                node_index: _,
                annotation,
                value,
                target,
                simple: _,
            } = assignment;
            let annotated = self.infer_annotation_expression(
                annotation,
                DeferredExpressionState::from(self.defer_annotations()),
            );

            if !annotated.qualifiers.is_empty() {
                for qualifier in TypeQualifier::iter() {
                    if !qualifier.is_valid_for_non_name_targets()
                        && annotated
                            .qualifiers
                            .contains(TypeQualifiers::from(qualifier))
                        && let Some(builder) = self
                            .context
                            .report_lint(&INVALID_TYPE_FORM, annotation.as_ref())
                    {
                        builder.into_diagnostic(format_args!(
                            "`{name}` annotations are not allowed for non-name targets",
                            name = qualifier.name()
                        ));
                    }
                }
            }

            // P.args and P.kwargs are only valid as annotations on *args and **kwargs.
            if let Type::TypeVar(typevar) = annotated.inner_type()
                && typevar.is_paramspec(self.db())
                && let Some(attr) = typevar.paramspec_attr(self.db())
            {
                let name = typevar.name(self.db());
                let (attr_name, variadic) = match attr {
                    ParamSpecAttrKind::Args => ("args", "*args"),
                    ParamSpecAttrKind::Kwargs => ("kwargs", "**kwargs"),
                };
                if let Some(builder) = self
                    .context
                    .report_lint(&INVALID_PARAMSPEC, annotation.as_ref())
                {
                    builder.into_diagnostic(format_args!(
                        "`{name}.{attr_name}` is only valid for annotating `{variadic}` function parameters",
                    ));
                }
            } else if let ast::Expr::Attribute(attr_expr) = annotation.as_ref()
                && matches!(attr_expr.attr.as_str(), "args" | "kwargs")
            {
                let value_ty = self.expression_type(&attr_expr.value);
                if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = value_ty
                    && typevar.is_paramspec(self.db())
                {
                    let name = typevar.name(self.db());
                    let attr_name = &attr_expr.attr;
                    let variadic = if attr_name == "args" {
                        "*args"
                    } else {
                        "**kwargs"
                    };
                    if let Some(builder) = self
                        .context
                        .report_lint(&INVALID_PARAMSPEC, annotation.as_ref())
                    {
                        builder.into_diagnostic(format_args!(
                            "`{name}.{attr_name}` is only valid for annotating `{variadic}` function parameters",
                        ));
                    }
                }
            }

            // Disallow annotations on non-name targets unless they are valid receivers (e.g.
            // `self.x: int` or `cls.x: int`).
            if !self.is_valid_receiver_annotation_target(target) {
                let message = match target.as_ref() {
                    ast::Expr::Attribute(_) => {
                        "Type annotations are not allowed on this attribute expression"
                    }
                    ast::Expr::Subscript(_) => {
                        "Type annotations are not allowed on subscripted expressions"
                    }
                    _ => {
                        // For parser-recovered invalid targets, the syntax diagnostic is
                        // sufficient.
                        if let Some(value) = value {
                            self.infer_maybe_standalone_expression(value, TypeContext::default());
                        }
                        self.infer_expression(target, TypeContext::default());
                        return;
                    }
                };

                // For syntactically valid non-name targets, reject the annotation and validate
                // any accompanying assignment.
                if let Some(builder) = self
                    .context
                    .report_lint(&INVALID_TYPE_FORM, annotation.as_ref())
                {
                    builder.into_diagnostic(message);
                }

                if let Some(value) = value {
                    self.infer_target(target, value, &|builder, tcx| {
                        builder.infer_maybe_standalone_expression(value, tcx)
                    });
                } else {
                    self.infer_expression(target, TypeContext::default());
                }
                return;
            }

            let value_ty = value.as_ref().map(|value| {
                self.infer_maybe_standalone_expression(
                    value,
                    TypeContext::new(Some(annotated.inner_type())),
                )
            });

            // If we have an annotated assignment like `self.attr: int = 1`, we still need to
            // do type inference on the `self.attr` target to get types for all sub-expressions.
            self.infer_expression(target, TypeContext::default());
            if let ast::Expr::Attribute(target) = target.as_ref() {
                self.report_undeclared_protocol_attribute(target);
            }

            // For annotated assignments like `self.x: Final[int] = 1`, the `Final` qualifier
            // comes from the annotation itself, so we can check it directly rather than
            // looking up qualifiers from the object type (as `validate_final_attribute_assignment`
            // does for augmented assignments).
            if value.is_some()
                && annotated.qualifiers.contains(TypeQualifiers::FINAL)
                && let ast::Expr::Attribute(attr_expr) = target.as_ref()
            {
                let object_ty = self.expression_type(&attr_expr.value);
                self.invalid_assignment_to_final_attribute(
                    object_ty,
                    attr_expr,
                    attr_expr.attr.id(),
                    annotated.qualifiers,
                );
            }

            // But here we explicitly overwrite the type for the overall `self.attr` node.
            // We do not use `store_expression_type` here, because it checks that no type
            // has been stored for the expression before. When there's a value, use the
            // inferred type (matching the name-target definition path); otherwise fall
            // back to the annotated type. If the value is not assignable to the declared
            // type, report an error and fall back to the annotated type.
            let target_ty = if let Some(value_ty) = value_ty {
                let declared_ty = annotated.inner_type();
                if value_ty.is_assignable_to(self.db(), declared_ty) {
                    value_ty
                } else {
                    if let Some(builder) = self
                        .context
                        .report_lint(&INVALID_ASSIGNMENT, value.as_deref().unwrap())
                    {
                        let mut diag = builder.into_diagnostic(format_args!(
                            "Object of type `{}` is not assignable to `{}`",
                            value_ty.display(self.db()),
                            declared_ty.display(self.db()),
                        ));
                        diag.annotate(
                            self.context
                                .secondary(annotation.as_ref())
                                .message("Declared type"),
                        );
                        diag.set_primary_message(format_args!(
                            "Incompatible value of type `{}`",
                            value_ty.display(self.db()),
                        ));
                    }
                    declared_ty
                }
            } else {
                annotated.inner_type()
            };
            self.expressions.insert((&**target).into(), target_ty);
        }
    }

    /// Infer the types in an annotated assignment definition.
    fn infer_annotated_assignment_definition(
        &mut self,
        assignment: &'db AnnotatedAssignmentDefinitionKind,
        definition: Definition<'db>,
    ) {
        let target = assignment.target(self.module());
        let value = assignment.value(self.module());

        if !target.is_name_expr() && !self.is_valid_receiver_annotation_target(target) {
            // Omit this definition from `self.declarations`; declaration lookup treats an absent
            // inferred declaration as rejected.
            if !definition
                .kind(self.db())
                .category(self.in_stub(), self.module())
                .is_binding()
            {
                return;
            }

            let node = target.into();
            let add = AddBinding {
                declared_ty: self.fallback_member_declared_type(node),
                binding: definition,
                node,
                qualifiers: TypeQualifiers::empty(),
                is_local: true,
            };
            let target_ty = if let Some(value) = value {
                // Infer the value as an ordinary assignment without using the rejected annotation
                // as its declared type.
                let value_ty = self.infer_maybe_standalone_expression(value, add.type_context());
                self.stub_placeholder_binding_type(value)
                    .unwrap_or(value_ty)
            } else {
                // Annotation-only definitions are bindings in stubs.
                add.declared_ty.unwrap_or(Type::unknown())
            };
            self.store_expression_type(target, target_ty);
            add.insert(self, target_ty);

            return;
        }

        let annotation = assignment.annotation(self.module());
        // Pydantic supports field specifiers in annotations via `Annotated[T, Field(...)]`.
        self.setup_dataclass_field_specifiers();
        let mut declared = self.infer_annotation_expression_allow_pep_613(
            annotation,
            DeferredExpressionState::from(self.defer_annotations()),
        );
        self.dataclass_field_specifiers.clear();

        // P.args and P.kwargs are only valid as annotations on *args and **kwargs,
        // not as variable annotations. Check both resolved type and AST form.
        if let Type::TypeVar(typevar) = declared.inner_type()
            && typevar.is_paramspec(self.db())
            && let Some(attr) = typevar.paramspec_attr(self.db())
        {
            let name = typevar.name(self.db());
            let (attr_name, variadic) = match attr {
                ParamSpecAttrKind::Args => ("args", "*args"),
                ParamSpecAttrKind::Kwargs => ("kwargs", "**kwargs"),
            };
            if let Some(builder) = self.context.report_lint(&INVALID_PARAMSPEC, annotation) {
                builder.into_diagnostic(format_args!(
                    "`{name}.{attr_name}` is only valid for annotating `{variadic}` function parameters",
                ));
            }
        } else if let ast::Expr::Attribute(attr_expr) = annotation
            && matches!(attr_expr.attr.as_str(), "args" | "kwargs")
        {
            // Also check the AST form for cases where P isn't bound (e.g., class body
            // annotations). In this case, the type might not resolve to a TypeVar.
            let value_ty = self.expression_type(&attr_expr.value);
            if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = value_ty
                && typevar.is_paramspec(self.db())
            {
                let name = typevar.name(self.db());
                let attr_name = &attr_expr.attr;
                let variadic = if attr_name == "args" {
                    "*args"
                } else {
                    "**kwargs"
                };
                if let Some(builder) = self.context.report_lint(&INVALID_PARAMSPEC, annotation) {
                    builder.into_diagnostic(format_args!(
                        "`{name}.{attr_name}` is only valid for annotating `{variadic}` function parameters",
                    ));
                }
            }
        }

        let is_pep_613_type_alias = declared.inner_type().is_typealias_special_form();

        if !declared.qualifiers.is_empty() {
            for qualifier in TypeQualifier::iter() {
                if !declared
                    .qualifiers
                    .contains(TypeQualifiers::from(qualifier))
                {
                    continue;
                }
                let current_scope_id = self.scope().file_scope_id(self.db());

                if self.index.scope(current_scope_id).kind() != ScopeKind::Class {
                    match qualifier {
                        TypeQualifier::Final => {}
                        TypeQualifier::ClassVar => {
                            if let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                            {
                                builder
                                    .into_diagnostic("`ClassVar` is only allowed in class bodies");
                            }
                        }
                        TypeQualifier::InitVar => {
                            if let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                            {
                                builder.into_diagnostic(
                                    "`InitVar` is only allowed in dataclass fields",
                                );
                            }
                        }
                        TypeQualifier::NotRequired
                        | TypeQualifier::ReadOnly
                        | TypeQualifier::Required => {
                            if let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                            {
                                builder.into_diagnostic(format_args!(
                                    "`{name}` is only allowed in TypedDict fields",
                                    name = qualifier.name()
                                ));
                            }
                        }
                    }

                    continue;
                }

                let nearest_enclosing_class =
                    nearest_enclosing_class(self.db(), self.index, self.scope());
                let class_kind = nearest_enclosing_class.and_then(|class| {
                    CodeGeneratorKind::from_class(self.db(), ClassLiteral::Static(class))
                });

                match class_kind {
                    Some(CodeGeneratorKind::TypedDict) => {
                        if !qualifier.is_valid_in_typeddict_field()
                            && let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                        {
                            builder.into_diagnostic(format_args!(
                                "`{name}` is not allowed in TypedDict fields",
                                name = qualifier.name()
                            ));
                        }
                    }
                    Some(
                        class_kind @ (CodeGeneratorKind::DataclassLike(_)
                        | CodeGeneratorKind::Pydantic(_)),
                    ) => match qualifier {
                        TypeQualifier::NotRequired
                        | TypeQualifier::ReadOnly
                        | TypeQualifier::Required => {
                            let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                            else {
                                continue;
                            };
                            let field_kind = class_kind.name();
                            builder.into_diagnostic(format_args!(
                                "`{name}` is not allowed in {field_kind} fields",
                                name = qualifier.name(),
                            ));
                        }
                        TypeQualifier::ClassVar | TypeQualifier::Final | TypeQualifier::InitVar => {
                        }
                    },
                    Some(CodeGeneratorKind::NamedTuple) | None => match qualifier {
                        TypeQualifier::NotRequired
                        | TypeQualifier::Required
                        | TypeQualifier::ReadOnly => {
                            let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                            else {
                                continue;
                            };
                            builder.into_diagnostic(format_args!(
                                "`{name}` is only allowed in TypedDict fields",
                                name = qualifier.name()
                            ));
                        }
                        TypeQualifier::InitVar => {
                            let Some(builder) =
                                self.context.report_lint(&INVALID_TYPE_FORM, annotation)
                            else {
                                continue;
                            };
                            builder
                                .into_diagnostic("`InitVar` is only allowed in dataclass fields");
                        }
                        TypeQualifier::ClassVar | TypeQualifier::Final => {}
                    },
                }
            }
        }

        if target
            .as_name_expr()
            .is_some_and(|name| &name.id == "TYPE_CHECKING")
        {
            if !KnownClass::Bool
                .to_instance(self.db())
                .is_assignable_to(self.db(), declared.inner_type())
            {
                // annotation not assignable from `bool` is an error
                report_invalid_type_checking_constant(&self.context, target.into());
            } else if self.in_stub()
                && value
                    .as_ref()
                    .is_none_or(|value| value.is_ellipsis_literal_expr())
            {
                // stub file assigning nothing or `...` is fine
            } else if !matches!(
                value
                    .as_ref()
                    .and_then(|value| value.as_boolean_literal_expr()),
                Some(ast::ExprBooleanLiteral { value: false, .. })
            ) {
                // otherwise, assigning something other than `False` is an error
                report_invalid_type_checking_constant(&self.context, target.into());
            }
            declared.inner = Type::bool_literal(true);
        }

        // Handle various singletons.
        if let Some(name_expr) = target.as_name_expr()
            && let Some(special_form) =
                SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name_expr.id)
        {
            declared.inner = Type::SpecialForm(special_form);
        }

        // If the target of an assignment is not one of the place expressions we support,
        // then they are not definitions, so we can only be here if the target is in a form supported as a place expression.
        // In this case, we can simply store types in `target` below, instead of calling `infer_expression` (which would return `Never`).
        debug_assert!(PlaceExpr::try_from_expr(target).is_some());

        if let Some(value) = value {
            self.setup_dataclass_field_specifiers();

            // We defer the r.h.s. of PEP-613 `TypeAlias` assignments in stub files.
            let previous_deferred_state = self.deferred_state;

            if is_pep_613_type_alias {
                self.context.inference_flags |= InferenceFlags::IN_PEP_613_ALIAS_FIRST_PASS;
                if self.in_stub() {
                    self.deferred_state = DeferredExpressionState::Deferred;
                }
            }

            // This might be a PEP-613 type alias (`OptionalList: TypeAlias = list[T] | None`). Use
            // the definition of `OptionalList` as the binding context while inferring the
            // RHS (`list[T] | None`), in order to bind `T` to `OptionalList`.
            let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);

            let inferred_ty = self.infer_maybe_standalone_expression(
                value,
                TypeContext::new(Some(declared.inner_type())),
            );
            let inferred_ty = if is_pep_613_type_alias && target.is_name_expr() {
                // The post-inference pass emits the diagnostic, but this first-pass value is
                // retained as the alias binding.
                match inferred_ty {
                    Type::SpecialForm(SpecialFormType::TypingSelf) => {
                        self.expressions.insert(value.into(), Type::unknown());
                        Type::unknown()
                    }
                    Type::KnownInstance(KnownInstanceType::LiteralStringAlias(ty))
                        if ty.inner(self.db()).contains_self(self.db()) =>
                    {
                        Type::KnownInstance(KnownInstanceType::LiteralStringAlias(
                            InternedType::new(self.db(), Type::unknown()),
                        ))
                    }
                    _ => inferred_ty,
                }
            } else {
                inferred_ty
            };

            self.typevar_binding_context = previous_typevar_binding_context;
            self.deferred_state = previous_deferred_state;
            self.dataclass_field_specifiers.clear();
            self.context
                .inference_flags
                .remove(InferenceFlags::IN_PEP_613_ALIAS_FIRST_PASS);

            let inferred_ty = if target
                .as_name_expr()
                .is_some_and(|name| &name.id == "TYPE_CHECKING")
            {
                Type::bool_literal(true)
            } else if self.in_stub() && value.is_ellipsis_literal_expr() {
                declared.inner_type()
            } else {
                inferred_ty
            };

            if is_pep_613_type_alias {
                let inferred_ty =
                    if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = inferred_ty {
                        let identity = TypeVarIdentity::new(
                            self.db(),
                            typevar.identity(self.db()).name(self.db()),
                            typevar.identity(self.db()).definition(self.db()),
                            TypeVarKind::Pep613Alias,
                        );
                        Type::KnownInstance(KnownInstanceType::TypeVar(
                            typevar.with_identity(self.db(), identity),
                        ))
                    } else {
                        inferred_ty
                    };
                self.add_declaration_with_binding(
                    target.into(),
                    definition,
                    &DeclaredAndInferredType::AreTheSame(TypeAndQualifiers::declared(inferred_ty)),
                );
            } else {
                // Check for annotated enum members. The typing spec states that enum
                // members should not have explicit type annotations.
                if let Some(name_expr) = target.as_name_expr()
                    && !name_expr.id.starts_with("__")
                    && !matches!(name_expr.id.as_str(), "_ignore_" | "_value_" | "_name_")
                    // Not bare Final (bare Final is allowed on enum members)
                    && !(declared.qualifiers.contains(TypeQualifiers::FINAL)
                        && matches!(declared.inner_type(), Type::Dynamic(DynamicType::Unknown)))
                    // Value type would be an enum member at runtime (exclude callables,
                    // which are never members)
                    && !inferred_ty.is_subtype_of(
                        self.db(),
                        Type::Callable(CallableType::unknown(self.db()))
                            .top_materialization(self.db()),
                    )
                {
                    let current_scope_id = self.scope().file_scope_id(self.db());
                    let current_scope = self.index.scope(current_scope_id);
                    if current_scope.kind() == ScopeKind::Class
                        && let Some(class) =
                            nearest_enclosing_class(self.db(), self.index, self.scope())
                        && is_enum_class_by_inheritance(self.db(), class)
                        && !enum_ignored_names(self.db(), self.scope()).contains(&name_expr.id)
                        && let Some(builder) = self
                            .context
                            .report_lint(&INVALID_ENUM_MEMBER_ANNOTATION, annotation)
                    {
                        let mut diag = builder.into_diagnostic(format_args!(
                            "Type annotation on enum member `{}` is not allowed",
                            &name_expr.id
                        ));
                        diag.info(
                            "See: https://typing.python.org/en/latest/spec/enums.html#enum-members",
                        );
                    }
                }

                self.add_declaration_with_binding(
                    target.into(),
                    definition,
                    &DeclaredAndInferredType::MightBeDifferent {
                        declared_ty: declared,
                        inferred_ty,
                    },
                );
            }

            self.store_expression_type(target, inferred_ty);
        } else {
            if is_pep_613_type_alias {
                if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, annotation) {
                    builder.into_diagnostic(
                        "`TypeAlias` must be assigned a value in annotated assignments",
                    );
                }
                declared.inner = Type::unknown();
            }
            if self.in_stub() {
                self.add_declaration_with_binding(
                    target.into(),
                    definition,
                    &DeclaredAndInferredType::AreTheSame(declared),
                );
            } else {
                self.add_declaration(target.into(), definition, declared);
            }

            self.store_expression_type(target, declared.inner_type());
        }
    }

    fn infer_augmented_assignment_statement(&mut self, assignment: &ast::StmtAugAssign) {
        if assignment.target.is_name_expr() {
            self.infer_definition(assignment);
        } else {
            // Non-name assignment targets are inferred as ordinary expressions, not definitions.
            self.infer_augment_assignment(assignment);

            if let ast::Expr::Attribute(attr_expr) = assignment.target.as_ref() {
                let object_ty = self.expression_type(&attr_expr.value);
                self.report_undeclared_protocol_attribute(attr_expr);
                self.validate_final_attribute_assignment(attr_expr, object_ty, attr_expr.attr.id());
            }
        }
    }

    fn infer_augmented_op(
        &mut self,
        assignment: &ast::StmtAugAssign,
        target_type: Type<'db>,
        value_expr: &ast::Expr,
        infer_value_ty: &mut dyn FnMut(&mut Self, TypeContext<'db>) -> Type<'db>,
    ) -> Type<'db> {
        // If the target defines, e.g., `__iadd__`, infer the augmented assignment as a call to that
        // dunder.
        let op = assignment.op;
        let db = self.db();

        // Fall back to non-augmented binary operator inference.
        let binary_return_ty = |builder: &mut Self, value_ty| {
            builder
                .infer_binary_expression_type(assignment.into(), false, target_type, value_ty, op)
                .unwrap_or_else(|| {
                    report_unsupported_augmented_assignment(
                        &builder.context,
                        assignment,
                        target_type,
                        value_ty,
                    );
                    Type::unknown()
                })
        };

        match target_type {
            Type::Union(union) => {
                let mut infer_value_ty = MultiInferenceGuard::new(infer_value_ty);

                // Perform loud inference without type context, as there may be multiple
                // equally applicable type contexts for each union member.
                infer_value_ty.infer_loud(self, TypeContext::default());

                union.map(db, |&elem_type| {
                    self.infer_augmented_op(
                        assignment,
                        elem_type,
                        value_expr,
                        &mut |builder, tcx| infer_value_ty.infer_silent(builder, tcx),
                    )
                })
            }

            _ => {
                if let Some(typed_dict_update_ty) = self
                    .try_infer_typed_dict_pep_584_augmented_assignment(
                        assignment,
                        target_type,
                        value_expr,
                        infer_value_ty,
                    )
                {
                    return typed_dict_update_ty;
                }

                let ast_arguments = [ArgOrKeyword::Arg(value_expr)];
                let mut call_arguments = CallArguments::positional([Type::unknown()]);

                let call = self.infer_and_try_call_dunder(
                    db,
                    target_type,
                    op.in_place_dunder(),
                    ArgumentsIter::synthesized(&ast_arguments),
                    &mut call_arguments,
                    &mut |builder, (_, _, tcx)| infer_value_ty(builder, tcx),
                    TypeContext::default(),
                );

                match call {
                    Ok(outcome) => outcome.return_type(db),
                    Err(CallDunderError::MethodNotAvailable) => {
                        let value_ty = infer_value_ty(self, TypeContext::default());
                        binary_return_ty(self, value_ty)
                    }
                    Err(CallDunderError::PossiblyUnbound {
                        bindings: outcome, ..
                    }) => {
                        let value_ty = outcome.type_for_argument(&call_arguments, 0);
                        UnionType::from_two_elements(
                            db,
                            outcome.return_type(db),
                            binary_return_ty(self, value_ty),
                        )
                    }
                    Err(CallDunderError::CallError(_, bindings, _)) => {
                        let value_ty = bindings.type_for_argument(&call_arguments, 0);
                        report_unsupported_augmented_assignment(
                            &self.context,
                            assignment,
                            target_type,
                            value_ty,
                        );
                        bindings.return_type(db)
                    }
                }
            }
        }
    }

    fn infer_augment_assignment_definition(
        &mut self,
        assignment: &'ast ast::StmtAugAssign,
        definition: Definition<'db>,
    ) {
        let target_ty = self.infer_augment_assignment(assignment);
        self.add_binding(assignment.into(), definition)
            .insert(self, target_ty);
    }

    fn infer_augment_assignment(&mut self, assignment: &ast::StmtAugAssign) -> Type<'db> {
        let ast::StmtAugAssign {
            range: _,
            node_index: _,
            target,
            op: _,
            value,
        } = assignment;

        // Resolve the target type, assuming a load context.
        let target_type = match &**target {
            ast::Expr::Name(name) => {
                let previous_value = self.infer_name_load(name);
                self.store_expression_type(target, previous_value);
                previous_value
            }
            ast::Expr::Attribute(attr) => {
                let previous_value = self.infer_attribute_load(attr);
                self.store_expression_type(target, previous_value);
                previous_value
            }
            ast::Expr::Subscript(subscript) => {
                let previous_value = self.infer_subscript_load(subscript);
                self.store_expression_type(target, previous_value);
                previous_value
            }
            _ => self.infer_expression(target, TypeContext::default()),
        };

        self.infer_augmented_op(assignment, target_type, value, &mut |builder, tcx| {
            builder.infer_expression(value, tcx)
        })
    }

    fn infer_dict_key_assignment_definition(
        &mut self,
        key: &'ast ast::Expr,
        value: &'ast ast::Expr,
        assignment: Definition<'db>,
        definition: Definition<'db>,
    ) {
        let value_ty = infer_definition_types(self.db(), assignment).expression_type(value);
        self.add_binding(key.into(), definition)
            .insert(self, value_ty);
    }

    fn infer_type_alias_statement(&mut self, node: &ast::StmtTypeAlias) {
        self.infer_definition(node);
    }

    fn fixed_length_iterable_element_type(
        &self,
        iterable: &ast::Expr,
        expression_type: impl FnMut(&ast::Expr) -> Type<'db>,
    ) -> Option<Type<'db>> {
        let element_types =
            extract_fixed_length_iterable_element_types(self.db(), iterable, expression_type)?;

        if element_types.is_empty() {
            None
        } else {
            Some(UnionType::from_elements(
                self.db(),
                element_types.iter().copied(),
            ))
        }
    }

    fn infer_for_statement(&mut self, for_statement: &ast::StmtFor) {
        let ast::StmtFor {
            range: _,
            node_index: _,
            target,
            iter,
            body,
            orelse,
            is_async,
        } = for_statement;

        self.infer_target(target, iter, &|builder, tcx| {
            // TODO: `infer_for_statement_definition` reports a diagnostic if `iter_ty` isn't iterable
            //  but only if the target is a name. We should report a diagnostic here if the target isn't a name:
            //  `for a.x in not_iterable: ...
            let iterable_type = builder.infer_standalone_expression(iter, tcx);
            if !*is_async
                && let Some(element_type) = builder
                    .fixed_length_iterable_element_type(iter, |expr| builder.expression_type(expr))
            {
                element_type
            } else {
                iterable_type
                    .iterate(builder.db())
                    .homogeneous_element_type(builder.db())
            }
        });

        self.infer_body(body);
        self.infer_body(orelse);
    }

    fn infer_for_statement_definition(
        &mut self,
        for_stmt: &ForStmtDefinitionKind<'db>,
        definition: Definition<'db>,
    ) {
        let iterable = for_stmt.iterable(self.module());
        let target = for_stmt.target(self.module());

        let loop_var_value_type = match for_stmt.target_kind() {
            TargetKind::Sequence(unpack_position, unpack) => {
                let unpacked = infer_unpack_types(self.db(), unpack);
                if unpack_position == UnpackPosition::First {
                    self.context.extend(unpacked.diagnostics());
                }

                unpacked.expression_type(target)
            }
            TargetKind::Single => {
                let iterable_type =
                    self.infer_standalone_expression(iterable, TypeContext::default());

                if !for_stmt.is_async()
                    && let Some(element_type) = self
                        .fixed_length_iterable_element_type(iterable, |expr| {
                            self.expression_type(expr)
                        })
                {
                    element_type
                } else {
                    iterable_type
                        .try_iterate_with_mode(
                            self.db(),
                            EvaluationMode::from_is_async(for_stmt.is_async()),
                        )
                        .map(|tuple| tuple.homogeneous_element_type(self.db()))
                        .unwrap_or_else(|err| {
                            err.report_diagnostic(&self.context, iterable_type, iterable.into());
                            err.fallback_element_type(self.db())
                        })
                }
            }
        };

        self.store_expression_type(target, loop_var_value_type);
        self.add_binding(target.into(), definition)
            .insert(self, loop_var_value_type);
    }

    fn infer_while_statement(&mut self, while_statement: &ast::StmtWhile) {
        let ast::StmtWhile {
            range: _,
            node_index: _,
            test,
            body,
            orelse,
        } = while_statement;

        let test_ty = self.infer_standalone_expression(test, TypeContext::default());

        if let Err(err) = test_ty.try_bool(self.db()) {
            err.report_diagnostic(&self.context, &**test);
        }

        self.infer_body(body);
        self.infer_body(orelse);
    }

    fn infer_assert_statement(&mut self, assert: &ast::StmtAssert) {
        let ast::StmtAssert {
            range: _,
            node_index: _,
            test,
            msg,
        } = assert;

        let test_ty = self.infer_standalone_expression(test, TypeContext::default());

        if let Err(err) = test_ty.try_bool(self.db()) {
            err.report_diagnostic(&self.context, &**test);
        }

        self.infer_optional_expression(msg.as_deref(), TypeContext::default());
    }

    fn infer_raise_statement(&mut self, raise: &ast::StmtRaise) {
        let ast::StmtRaise {
            range: _,
            node_index: _,
            exc,
            cause,
        } = raise;

        let base_exception_type = KnownClass::BaseException.to_subclass_of(self.db());
        let base_exception_instance = KnownClass::BaseException.to_instance(self.db());

        let can_be_raised =
            UnionType::from_two_elements(self.db(), base_exception_type, base_exception_instance);
        let can_be_exception_cause =
            UnionType::from_two_elements(self.db(), can_be_raised, Type::none(self.db()));

        if let Some(raised) = exc {
            let raised_type = self.infer_expression(raised, TypeContext::default());

            if !raised_type.is_assignable_to(self.db(), can_be_raised) {
                report_invalid_exception_raised(&self.context, raised, raised_type);
            }
        }

        if let Some(cause) = cause {
            let cause_type = self.infer_expression(cause, TypeContext::default());

            if !cause_type.is_assignable_to(self.db(), can_be_exception_cause) {
                report_invalid_exception_cause(&self.context, cause, cause_type);
            }
        }
    }

    fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
        let tcx = if ret.value.is_some() {
            nearest_enclosing_function(self.db(), self.index, self.scope())
                .map(|func| {
                    // When inferring expressions within a function body,
                    // the expected type passed should be the "raw" type,
                    // i.e. type variables in the return type are non-inferable,
                    // and the return types of async functions are not wrapped in `CoroutineType[...]`.
                    let return_ty = same_module_uncached_raw_signature(
                        self.db(),
                        func,
                        ReturnCallableTypeVarScope::Lexical,
                    )
                    .return_ty;

                    // For generator functions, the declared return type is e.g.
                    // `Generator[YieldType, SendType, ReturnType]`. The type context
                    // for a `return` statement should be the `ReturnType` type parameter
                    let file_scope_id = self.scope().file_scope_id(self.db());
                    let context_ty = if file_scope_id.is_generator_function(self.index) {
                        return_ty
                            .generator_return_type(self.db())
                            .unwrap_or(return_ty)
                    } else {
                        return_ty
                    };

                    TypeContext::new(Some(context_ty))
                })
                .unwrap_or_default()
        } else {
            TypeContext::default()
        };
        if let Some(ty) = self.infer_optional_expression(ret.value.as_deref(), tcx) {
            let range = ret
                .value
                .as_ref()
                .map_or(ret.range(), |value| value.range());
            self.record_return_type(ty, range);
        } else {
            self.record_return_type(Type::none(self.db()), ret.range());
        }
    }

    fn infer_delete_statement(&mut self, delete: &ast::StmtDelete) {
        let ast::StmtDelete {
            range: _,
            node_index: _,
            targets,
        } = delete;
        for target in targets {
            self.infer_expression(target, TypeContext::default());
        }
    }

    fn infer_global_statement(&mut self, global: &ast::StmtGlobal) {
        // CPython allows examples like this, where a global variable is never explicitly defined
        // in the global scope:
        //
        // ```py
        // def f():
        //     global x
        //     x = 1
        // def g():
        //     print(x)
        // ```
        //
        // However, allowing this pattern would make it hard for us to guarantee
        // accurate analysis about the types and boundness of global-scope symbols,
        // so we require the variable to be explicitly defined (either bound or declared)
        // in the global scope.
        let ast::StmtGlobal {
            node_index: _,
            range: _,
            names,
        } = global;
        let global_place_table = self.index.place_table(FileScopeId::global());
        for name in names {
            if let Some(symbol_id) = global_place_table.symbol_id(name) {
                let symbol = global_place_table.symbol(symbol_id);
                if symbol.is_bound() || symbol.is_declared() {
                    // This name is explicitly defined in the global scope (not just in function
                    // bodies that mark it `global`).
                    continue;
                }
            }
            if !module_type_implicit_global_symbol(self.db(), self.file(), name)
                .place
                .is_undefined()
            {
                // This name is an implicit global like `__file__` (but not a built-in like `int`).
                continue;
            }
            // This variable isn't explicitly defined in the global scope, nor is it an
            // implicit global from `types.ModuleType`, so we consider this `global` statement invalid.
            let Some(builder) = self.context.report_lint(&UNRESOLVED_GLOBAL, name) else {
                return;
            };
            let mut diag =
                builder.into_diagnostic(format_args!("Invalid global declaration of `{name}`"));
            diag.set_primary_message(format_args!(
                "`{name}` has no declarations or bindings in the global scope"
            ));
            diag.info("This limits ty's ability to make accurate inferences about the boundness and types of global-scope symbols");
            diag.info(format_args!(
                "Consider adding a declaration to the global scope, e.g. `{name}: int`"
            ));
        }
    }

    fn module_type_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
        resolve_module(self.db(), self.file(), module_name)
            .map(|module| Type::module_literal(self.db(), self.file(), module))
    }

    fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> {
        let ast::Decorator {
            range: _,
            node_index: _,
            expression,
        } = decorator;

        self.infer_expression(expression, TypeContext::default())
    }

    /// Apply a decorator to a function or class type and return the resulting type.
    ///
    /// Constructor semantics for class-like decorators are handled by `Type::bindings`, so we
    /// can always use `try_call` here.
    fn apply_decorator(
        &mut self,
        decorator_ty: Type<'db>,
        decorated_ty: Type<'db>,
        decorator_node: &ast::Decorator,
    ) -> Type<'db> {
        fn propagate_callable_kind<'d>(
            db: &'d dyn Db,
            ty: Type<'d>,
            kind: CallableTypeKind,
            provenance: CallableFunctionProvenance,
        ) -> Option<Type<'d>> {
            match ty {
                Type::Callable(callable) => Some(Type::Callable(CallableType::new(
                    db,
                    callable.signatures(db),
                    kind,
                    provenance,
                ))),
                Type::Union(union) => union.try_map(db, |element| {
                    propagate_callable_kind(db, *element, kind, provenance)
                }),
                Type::TypeAlias(alias) => {
                    propagate_callable_kind(db, alias.value_type(db), kind, provenance)
                }
                // Intersections are currently not handled here because that would require
                // the decorator to be explicitly annotated as returning an intersection.
                Type::Intersection(_) | Type::EnumComplement(_) => None,
                // All other types cannot have a callable kind propagated to them.
                Type::Dynamic(_)
                | Type::Divergent(_)
                | Type::Never
                | Type::FunctionLiteral(_)
                | Type::BoundMethod(_)
                | Type::KnownBoundMethod(_)
                | Type::WrapperDescriptor(_)
                | Type::DataclassDecorator(_)
                | Type::DataclassTransformer(_)
                | Type::ModuleLiteral(_)
                | Type::ClassLiteral(_)
                | Type::GenericAlias(_)
                | Type::SubclassOf(_)
                | Type::NominalInstance(_)
                | Type::ProtocolInstance(_)
                | Type::SpecialForm(_)
                | Type::KnownInstance(_)
                | Type::PropertyInstance(_)
                | Type::AlwaysTruthy
                | Type::AlwaysFalsy
                | Type::LiteralValue(_)
                | Type::TypeVar(_)
                | Type::BoundSuper(_)
                | Type::TypeIs(_)
                | Type::TypeGuard(_)
                | Type::TypeForm(_)
                | Type::TypedDict(_)
                | Type::NewTypeInstance(_) => None,
            }
        }

        fn callable_paramspec_and_return_typevar<'db>(
            db: &'db dyn Db,
            ty: Type<'db>,
        ) -> Option<(BoundTypeVarInstance<'db>, BoundTypeVarInstance<'db>)> {
            let callable = ty.resolve_type_alias(db).as_callable()?;
            if callable.kind(db) != CallableTypeKind::Regular {
                return None;
            }
            let [signature] = callable.signatures(db).overloads.as_slice() else {
                return None;
            };
            let paramspec = signature.parameters().as_paramspec()?;
            Some((paramspec, signature.return_ty.as_typevar()?))
        }

        fn is_transparent_callable_decorator<'d>(
            db: &'d dyn Db,
            decorator_ty: Type<'d>,
            decorated_ty: Type<'d>,
        ) -> bool {
            if !matches!(decorated_ty, Type::FunctionLiteral(_) | Type::Callable(_)) {
                return false;
            }
            let Some(decorator_callable) = decorator_ty
                .try_upcast_to_callable(db)
                .and_then(CallableTypes::exactly_one)
            else {
                return false;
            };
            let decorator_signatures = decorator_callable.signatures(db);
            let [decorator_signature] = decorator_signatures.overloads.as_slice() else {
                return false;
            };
            let [parameter] = decorator_signature.parameters().as_slice() else {
                return false;
            };

            let Some((parameter_callable_paramspec, parameter_callable_return_typevar)) =
                callable_paramspec_and_return_typevar(db, parameter.annotated_type())
            else {
                return false;
            };
            let Some((return_callable_paramspec, return_callable_typevar)) =
                callable_paramspec_and_return_typevar(db, decorator_signature.return_ty)
            else {
                return false;
            };
            parameter_callable_paramspec.is_same_typevar_as(db, return_callable_paramspec)
                && parameter_callable_return_typevar.is_same_typevar_as(db, return_callable_typevar)
        }

        // For FunctionLiteral, get the kind directly without computing the full signature.
        // This avoids a query cycle when the function has default parameter values, since
        // computing the signature requires evaluating those defaults which may trigger
        // deferred inference.
        let propagatable_kind = match decorated_ty {
            Type::FunctionLiteral(func) => Some((
                func.callable_type_kind(self.db()),
                CallableFunctionProvenance::from_function_return_annotation(
                    func.has_explicit_return_annotation(self.db()),
                ),
            )),
            _ => decorated_ty
                .try_upcast_to_callable(self.db())
                .and_then(CallableTypes::exactly_one)
                .and_then(|callable| match callable.kind(self.db()) {
                    kind @ (CallableTypeKind::FunctionLike
                    | CallableTypeKind::StaticMethodLike
                    | CallableTypeKind::ClassMethodLike) => {
                        Some((kind, callable.provenance(self.db())))
                    }
                    _ => None,
                }),
        };

        let call_arguments = CallArguments::positional([decorated_ty]);
        let mut decorator_call_succeeded = true;
        let return_ty = decorator_ty
            .try_call(self.db(), &call_arguments)
            .map(|bindings| bindings.return_type(self.db()))
            .unwrap_or_else(|CallError(_, bindings)| {
                decorator_call_succeeded = false;
                bindings.report_diagnostics(&self.context, decorator_node.into());
                bindings.return_type(self.db())
            });

        // TODO: Remove this special case once the new constraint solver can preserve
        // per-overload ParamSpec/return correlations for `Callable[P, R] -> Callable[P, R]`.
        if decorator_call_succeeded
            && is_transparent_callable_decorator(self.db(), decorator_ty, decorated_ty)
            && let Some(callable) = decorated_ty
                .try_upcast_to_callable(self.db())
                .and_then(CallableTypes::exactly_one)
        {
            return Type::Callable(callable);
        }

        // When a method on a class is decorated with a function that returns a
        // `Callable`, assume that the returned callable is also function-like (or
        // classmethod-like or staticmethod-like). See "Decorating a method with
        // a `Callable`-typed decorator" in `callables_as_descriptors.md` for the
        // extended explanation.
        propagatable_kind
            .and_then(|(kind, provenance)| {
                propagate_callable_kind(self.db(), return_ty, kind, provenance)
            })
            .unwrap_or(return_ty)
    }

    #[expect(clippy::too_many_arguments)]
    fn infer_and_try_call_dunder(
        &mut self,
        db: &'db dyn Db,
        object: Type<'db>,
        name: &str,
        ast_arguments: ArgumentsIter<'_>,
        argument_types: &mut CallArguments<'_, 'db>,
        infer_argument_ty: &mut dyn FnMut(&mut Self, ArgExpr<'db, '_>) -> Type<'db>,
        call_expression_tcx: TypeContext<'db>,
    ) -> Result<Bindings<'db>, CallDunderError<'db>> {
        match object
            .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NO_INSTANCE_FALLBACK)
            .place
        {
            Place::Defined(DefinedPlace {
                ty: dunder_callable,
                definedness: boundness,
                provenance,
                ..
            }) => {
                let mut bindings = self
                    .bindings_for_call(dunder_callable)
                    .match_parameters(db, argument_types);

                if let Err(call_error) = self.infer_and_check_argument_types(
                    ast_arguments,
                    argument_types,
                    infer_argument_ty,
                    &mut bindings,
                    call_expression_tcx,
                ) {
                    return Err(CallDunderError::CallError(
                        call_error,
                        Box::new(bindings),
                        provenance,
                    ));
                }

                if boundness == Definedness::PossiblyUndefined {
                    return Err(CallDunderError::PossiblyUnbound {
                        bindings: Box::new(bindings),
                        unbound_on: None,
                    });
                }
                Ok(bindings)
            }
            Place::Undefined => Err(CallDunderError::MethodNotAvailable),
        }
    }

    fn infer_and_check_argument_types(
        &mut self,
        ast_arguments: ArgumentsIter<'_>,
        argument_types: &mut CallArguments<'_, 'db>,
        infer_argument_ty: &mut dyn FnMut(&mut Self, ArgExpr<'db, '_>) -> Type<'db>,
        bindings: &mut Bindings<'db>,
        call_expression_tcx: TypeContext<'db>,
    ) -> Result<(), CallErrorKind> {
        let db = self.db();
        let constraints = ConstraintSetBuilder::new();

        let has_generic_context = bindings
            .iter_flat()
            .flat_map(CallableBinding::overloads)
            .any(|overload| overload.signature.generic_context.is_some());

        // If the type context is a union, attempt to narrow to a specific element.
        let narrow_targets = call_expression_tcx
            .narrow_targets(db)
            // We only need to attempt narrowing on generic calls, otherwise the type
            // context has no effect.
            .filter(|_| has_generic_context)
            .unwrap_or_default();

        let mut try_narrow = |narrowed_ty: Type<'db>| {
            // Short-circuit if there is no overload with a matching return type.
            if !bindings.satisfies(|overload| {
                let inferable = overload
                    .signature
                    .generic_context
                    .map(|generic_context| generic_context.inferable_typevars(db))
                    .unwrap_or(InferableTypeVars::None);

                !overload
                    .return_ty
                    .when_assignable_to(db, narrowed_ty, &constraints, inferable)
                    .is_never_satisfied(db)
            }) {
                return None;
            }

            let narrowed_tcx = TypeContext::new(Some(narrowed_ty));

            let mut speculative_bindings = bindings.clone();
            let mut speculative_builder = self.speculate();

            // Attempt to infer the argument types using the narrowed type context.
            speculative_builder.infer_all_argument_types(
                ast_arguments.clone(),
                argument_types,
                infer_argument_ty,
                bindings,
                narrowed_tcx,
            );

            // Ensure the argument types match their annotated types.
            if speculative_bindings
                .check_types_impl(
                    db,
                    &constraints,
                    argument_types,
                    narrowed_tcx,
                    &self.dataclass_field_specifiers,
                )
                .is_err()
            {
                return None;
            }

            // Ensure the inferred return type is assignable to the narrowed declared type.
            //
            // TODO: Checking assignability against the full declared type could help avoid
            // cases where the constraint solver is not smart enough to solve complex unions.
            // We should see revisit this after the new constraint solver is implemented.
            if !speculative_bindings
                .return_type(db)
                .is_assignable_to(db, narrowed_ty)
            {
                return None;
            }

            // Successfully narrowed to an element of the union.
            self.extend(speculative_builder);
            Some(bindings.check_types_impl(
                db,
                &constraints,
                argument_types,
                narrowed_tcx,
                &self.dataclass_field_specifiers,
            ))
        };

        // Prefer the declared type of generic classes or callables when narrowing.
        //
        // Splitting up this loop is not necessary for correctness, but leads to a slight
        // performance improvement.
        for narrowed_ty in narrow_targets
            .iter()
            .filter(|ty| ty.may_prefer_declared_type(db))
        {
            if let Some(result) = try_narrow(*narrowed_ty) {
                return result;
            }
        }
        for narrowed_ty in narrow_targets
            .iter()
            .filter(|ty| !ty.may_prefer_declared_type(db))
        {
            if let Some(result) = try_narrow(*narrowed_ty) {
                return result;
            }
        }

        // Infer against the entire union as a fallback.
        //
        // TODO: We could also attempt an inference without type context, but this
        // leads to similar performance issues.
        self.infer_all_argument_types(
            ast_arguments,
            argument_types,
            infer_argument_ty,
            bindings,
            call_expression_tcx,
        );

        bindings.check_types_impl(
            db,
            &constraints,
            argument_types,
            call_expression_tcx,
            &self.dataclass_field_specifiers,
        )
    }

    /// Infer the argument types for all bindings.
    ///
    /// Note that this method may infer the type of a given argument expression multiple times with
    /// distinct type context. The provided `MultiInferenceState` can be used to dictate multi-inference
    /// behavior.
    fn infer_all_argument_types<'bindings>(
        &mut self,
        ast_arguments: ArgumentsIter<'_>,
        arguments_types: &mut CallArguments<'_, 'db>,
        infer_argument_ty: &mut dyn FnMut(&mut Self, ArgExpr<'db, '_>) -> Type<'db>,
        bindings: &'bindings Bindings<'db>,
        call_expression_tcx: TypeContext<'db>,
    ) {
        fn add_overloads_from_binding<'a, 'db>(
            overloads_with_binding: &mut Vec<(&'a Binding<'db>, &'a CallableBinding<'db>)>,
            binding: &'a CallableBinding<'db>,
        ) {
            match binding.matching_overload_index() {
                MatchingOverloadIndex::Single(_) | MatchingOverloadIndex::Multiple(_) => {
                    overloads_with_binding.extend(
                        binding
                            .matching_overloads()
                            .map(|(_, overload)| (overload, binding)),
                    );
                }

                // If there is a single overload that does not match, we still infer the argument
                // types for better diagnostics.
                MatchingOverloadIndex::None => {
                    if let [overload] = binding.overloads() {
                        overloads_with_binding.push((overload, binding));
                    }
                }
            }
        }

        let db = self.db();
        let constraints = ConstraintSetBuilder::new();

        let mut overloads_with_binding: Vec<(&Binding<'db>, &CallableBinding<'db>)> = Vec::new();

        bindings.visit_type_context_callables(&mut |binding| {
            add_overloads_from_binding(&mut overloads_with_binding, binding);
        });

        // A keyword argument matched to `**P.kwargs` can appear before the keyword argument that
        // binds `P`, e.g. `wrapper(TagSet=[...], func=put_object)`. Seed those binder argument
        // types first so the normal ParamSpec context path below is not source-order dependent.
        for (argument_index, ast_argument) in ast_arguments.clone().enumerate() {
            if ast_argument.is_variadic() {
                continue;
            }
            let ast_argument = ast_argument.value();

            let mut inferred_declared_types = FxHashSet::default();
            for declared_type in overloads_with_binding
                .iter()
                .filter_map(|(overload, binding)| {
                    overload.paramspec_binder_parameter_type(db, binding, argument_index)
                })
            {
                if !inferred_declared_types.insert(declared_type) {
                    continue;
                }

                let mut speculative_builder = self.speculate_without_diagnostics();
                let inferred_ty = infer_argument_ty(
                    &mut speculative_builder,
                    (
                        argument_index,
                        ast_argument,
                        TypeContext::new(Some(declared_type)),
                    ),
                );
                arguments_types.insert_type(argument_index, declared_type, inferred_ty);
            }
        }

        for (argument_index, ast_argument) in ast_arguments.enumerate() {
            // Splatted arguments are inferred before parameter matching to
            // determine their length.
            //
            // TODO: Re-infer splatted arguments with their type context.
            if ast_argument.is_variadic() {
                continue;
            }
            let ast_argument = ast_argument.value();

            let parameter_tcx = |overload: &'bindings Binding<'db>,
                                 binding: &CallableBinding<'db>| {
                overload.argument_type_context(
                    db,
                    &constraints,
                    binding,
                    arguments_types,
                    argument_index,
                    call_expression_tcx,
                )
            };

            // If there is only a single binding and overload, we can infer the argument directly with
            // the unique parameter type annotation.
            if let Ok((overload, binding)) = overloads_with_binding.iter().exactly_one() {
                let parameter_context = parameter_tcx(overload, binding);

                if let Some(parameter_context) = parameter_context {
                    let inferred_ty = infer_argument_ty(
                        self,
                        (
                            argument_index,
                            ast_argument,
                            parameter_context.type_context(),
                        ),
                    );
                    parameter_context.insert_inferred_type(
                        arguments_types,
                        argument_index,
                        inferred_ty,
                    );
                } else {
                    let inferred_ty = infer_argument_ty(
                        self,
                        (argument_index, ast_argument, TypeContext::default()),
                    );
                    arguments_types.insert_type(
                        argument_index,
                        TypeContext::default(),
                        inferred_ty,
                    );
                }
            } else {
                // Infer the type of each argument once with each distinct parameter type as type context.
                let parameter_contexts: Vec<_> = overloads_with_binding
                    .iter()
                    .filter_map(|(overload, binding)| parameter_tcx(overload, binding))
                    .collect();

                // We perform inference once without any type context, emitting any diagnostics that are unrelated
                // to bidirectional type inference.
                let default_ty =
                    infer_argument_ty(self, (argument_index, ast_argument, TypeContext::default()));
                arguments_types.insert_type(argument_index, TypeContext::default(), default_ty);

                let mut inferred_by_cache_key = FxHashMap::default();

                // Cache expressions inferred across speculative inference attempts.
                //
                // This is important to avoid exponential blowup for deeply nested generic calls,
                // as inner expressions are repeatedly inferred with the same type context.
                let teardown = self.setup_expression_cache();

                for parameter_context in parameter_contexts {
                    let inference_cache_key = parameter_context.inference_cache_key();
                    if let Some(inferred_ty) =
                        inferred_by_cache_key.get(&inference_cache_key).copied()
                    {
                        // Even when the inference cache key is identical, this overload may later
                        // look up the inferred type through a different original `ParamSpec`
                        // annotation, so insert through its own context.
                        parameter_context.insert_inferred_type(
                            arguments_types,
                            argument_index,
                            inferred_ty,
                        );
                        continue;
                    }

                    // We use a speculative builder to silence any diagnostics emitted during multi-inference, as the
                    // type context is only used as a hint to infer a more assignable argument type, and should not lead
                    // to diagnostics for non-matching overloads.
                    let mut speculative_builder = self.speculate_without_diagnostics();
                    let inferred_ty = infer_argument_ty(
                        &mut speculative_builder,
                        (
                            argument_index,
                            ast_argument,
                            parameter_context.type_context(),
                        ),
                    );

                    inferred_by_cache_key.insert(inference_cache_key, inferred_ty);
                    self.union_expected_types(&speculative_builder.expected_types);
                    parameter_context.insert_inferred_type(
                        arguments_types,
                        argument_index,
                        inferred_ty,
                    );
                }

                if teardown {
                    self.teardown_expression_cache();
                }
            }
        }
    }

    fn infer_maybe_standalone_statement(&mut self, statement: &ast::Stmt) {
        if let Some(standalone_statement) = self.index.try_statement(statement) {
            self.infer_standalone_statement_impl(standalone_statement);
        } else {
            self.infer_statement(statement);
        }
    }

    fn infer_standalone_statement_impl(&mut self, standalone_statement: Statement<'db>) {
        let types = infer_statement_types(self.db(), standalone_statement);
        self.extend_statement(&types);
    }

    fn infer_optional_expression(
        &mut self,
        expression: Option<&ast::Expr>,
        tcx: TypeContext<'db>,
    ) -> Option<Type<'db>> {
        expression.map(|expr| self.infer_expression(expr, tcx))
    }

    #[track_caller]
    fn infer_expression(&mut self, expression: &ast::Expr, tcx: TypeContext<'db>) -> Type<'db> {
        debug_assert!(
            !self.index.is_standalone_expression(expression),
            "Calling `self.infer_expression` on a standalone-expression is not allowed because it can lead to double-inference. Use `self.infer_standalone_expression` instead."
        );

        self.infer_expression_impl(expression, tcx)
    }

    fn infer_expression_with_state(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
        state: DeferredExpressionState,
    ) -> Type<'db> {
        let previous_deferred_state = std::mem::replace(&mut self.deferred_state, state);
        let ty = self.infer_expression(expression, tcx);
        self.deferred_state = previous_deferred_state;
        ty
    }

    fn infer_maybe_standalone_expression(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        if let Some(standalone_expression) = self.index.try_expression(expression) {
            self.infer_standalone_expression_impl(expression, standalone_expression, tcx)
        } else {
            self.infer_expression(expression, tcx)
        }
    }

    fn infer_expression_with_collection_literal_peer_context(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
        peer_ty: Option<Type<'db>>,
    ) -> Type<'db> {
        self.infer_with_collection_literal_peer_context(expression, tcx, peer_ty, |builder, tcx| {
            builder.infer_expression(expression, tcx)
        })
    }

    fn infer_maybe_standalone_expression_with_collection_literal_peer_context(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
        peer_ty: Option<Type<'db>>,
    ) -> Type<'db> {
        self.infer_with_collection_literal_peer_context(expression, tcx, peer_ty, |builder, tcx| {
            builder.infer_maybe_standalone_expression(expression, tcx)
        })
    }

    fn infer_with_collection_literal_peer_context(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
        peer_ty: Option<Type<'db>>,
        mut infer_expression: impl FnMut(&mut Self, TypeContext<'db>) -> Type<'db>,
    ) -> Type<'db> {
        let peer_tcx = if allows_collection_literal_peer_context(tcx)
            && is_collection_literal(expression)
            && let Some(peer_ty) = peer_ty
        {
            TypeContext::new(Some(peer_ty))
        } else {
            return infer_expression(self, tcx);
        };

        let mut speculative_builder = self.speculate();
        let ty = infer_expression(&mut speculative_builder, peer_tcx);

        // Peer context is only an inference hint. If it introduces diagnostics, discard it and
        // infer normally so that only diagnostics intrinsic to the expression are reported.
        if speculative_builder.context.has_diagnostics() {
            infer_expression(self, tcx)
        } else {
            self.extend(speculative_builder);
            ty
        }
    }

    #[track_caller]
    fn infer_standalone_expression(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let standalone_expression = self.index.expression(expression);
        self.infer_standalone_expression_impl(expression, standalone_expression, tcx)
    }

    fn infer_standalone_expression_impl(
        &mut self,
        expression: &ast::Expr,
        standalone_expression: Expression<'db>,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        // `infer_expression_types` cache inference results for a given expression and type
        // context. For expressions that are not directly affected by type context, we defer
        // applying the type context until after the Salsa query runs, allowing us to reuse
        // the memoized query result during multi-inference.
        let inference_tcx = if can_defer_type_context(expression) {
            TypeContext::default()
        } else {
            tcx
        };

        let types = infer_expression_types(self.db(), standalone_expression, inference_tcx);
        self.extend_expression(types);

        // Instead of calling `self.expression_type(expr)` after extending here, we get
        // the result from `types` directly because we might be in cycle recovery where
        // `types.cycle_fallback_type` is `Some(fallback_ty)`, which we can retrieve by
        // using `expression_type` on `types`:
        let ty = types.expression_type(expression);
        if inference_tcx == tcx {
            ty
        } else {
            let ty = self.apply_type_context(expression, ty, tcx);
            self.expressions.insert(expression.into(), ty);
            ty
        }
    }

    /// Infer the type of an expression.
    fn infer_expression_impl(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        if let Some(ty) = self.expression_cache.as_ref().and_then(|expression_cache| {
            expression_cache
                .borrow()
                .get(&(expression.into(), tcx))
                .copied()
        }) {
            self.store_expression_type(expression, ty);
            return ty;
        }

        if let Some(target) = tcx.annotation
            && let Some(ty) = self.infer_type_form_contextual_expression(expression, target)
        {
            self.store_expression_type(expression, ty);

            if let Some(expression_cache) = &self.expression_cache {
                expression_cache
                    .borrow_mut()
                    .insert((expression.into(), tcx), ty);
            }

            return ty;
        }

        self.infer_value_expression_impl(expression, tcx)
    }

    /// Infer an expression without implicitly treating this root as a `TypeForm`.
    ///
    /// Child expressions still use their normal contextual inference, so the
    /// expression's existing bidirectional behavior is preserved.
    fn infer_value_expression_impl(
        &mut self,
        expression: &ast::Expr,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let mut ty = match expression {
            ast::Expr::NoneLiteral(ast::ExprNoneLiteral {
                range: _,
                node_index: _,
            }) => Type::none(self.db()),
            ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal),
            ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
            ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal, tcx),
            ast::Expr::BytesLiteral(bytes_literal) => {
                self.infer_bytes_literal_expression(bytes_literal)
            }
            ast::Expr::FString(fstring) => self.infer_fstring_expression(fstring),
            ast::Expr::TString(tstring) => self.infer_tstring_expression(tstring),
            ast::Expr::EllipsisLiteral(literal) => self.infer_ellipsis_literal_expression(literal),
            ast::Expr::Tuple(tuple) => self.infer_tuple_expression(tuple, tcx),
            ast::Expr::List(list) => self.infer_list_expression(list, tcx),
            ast::Expr::Set(set) => self.infer_set_expression(set, tcx),
            ast::Expr::Dict(dict) => self.infer_dict_expression(dict, tcx),
            ast::Expr::Generator(generator) => self.infer_generator_expression(generator, tcx),
            ast::Expr::ListComp(listcomp) => {
                self.infer_list_comprehension_expression(listcomp, tcx)
            }
            ast::Expr::DictComp(dictcomp) => {
                self.infer_dict_comprehension_expression(dictcomp, tcx)
            }
            ast::Expr::SetComp(setcomp) => self.infer_set_comprehension_expression(setcomp, tcx),
            ast::Expr::Name(name) => self.infer_name_expression(name),
            ast::Expr::Attribute(attribute) => self.infer_attribute_expression(attribute),
            ast::Expr::UnaryOp(unary_op) => self.infer_unary_expression(unary_op),
            ast::Expr::BinOp(binary) => self.infer_binary_expression(binary, tcx),
            ast::Expr::BoolOp(bool_op) => self.infer_boolean_expression(bool_op, tcx),
            ast::Expr::Compare(compare) => self.infer_compare_expression(compare),
            ast::Expr::Subscript(subscript) => self.infer_subscript_expression(subscript),
            ast::Expr::Slice(slice) => self.infer_slice_expression(slice),
            ast::Expr::If(if_expression) => self.infer_if_expression(if_expression, tcx),
            ast::Expr::Lambda(lambda_expression) => {
                self.infer_lambda_expression(lambda_expression, tcx)
            }
            ast::Expr::Call(call_expression) => self.infer_call_expression(call_expression, tcx),
            ast::Expr::Starred(starred) => self.infer_starred_expression(starred, tcx),
            ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression),
            ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from),
            ast::Expr::Await(await_expression) => {
                self.infer_await_expression(await_expression, tcx)
            }
            ast::Expr::Named(named) => self.infer_named_expression(named),
            ast::Expr::IpyEscapeCommand(_) => {
                todo_type!("Ipy escape command support")
            }
        };

        ty = self.apply_type_context(expression, ty, tcx);
        self.store_expression_type(expression, ty);

        if let Some(expression_cache) = &self.expression_cache {
            expression_cache
                .borrow_mut()
                .insert((expression.into(), tcx), ty);
        }

        ty
    }

    /// Applies the provided type context to an already inferred type.
    fn apply_type_context(
        &mut self,
        expression: &ast::Expr,
        mut ty: Type<'db>,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        if matches!(expression, ast::Expr::Name(_) | ast::Expr::Attribute(_))
            && let Some(target) = tcx.annotation
        {
            ty = self.specialize_generic_class_from_context(ty, target);
        }

        // Avoid promoting explicitly annotated literal values.
        if let Type::LiteralValue(literal) = ty
            && let Some(tcx) = tcx.annotation
            && let literal_tcx @ (Type::Union(_) | Type::LiteralValue(_)) = tcx
                .resolve_type_alias(self.db())
                .filter_union(self.db(), |ty| ty.as_literal_value().is_some())
            && ty.is_assignable_to(self.db(), literal_tcx)
        {
            ty = Type::LiteralValue(literal.to_unpromotable());
        }

        if let Some(tcx) = tcx.annotation
            && let Some(collection_def) = self.index.unannotated_collection_initializer(expression)
        {
            self.collection_use_constraints
                .entry(collection_def)
                .or_default()
                .insert(tcx);
        }

        ty
    }

    /// Specialize a bare generic class value based on type context.
    ///
    /// Currently supports only Callable type contexts.
    ///
    /// This lets `list` in a `Callable[[], list[str]]` context be treated as `list[str]`.
    fn specialize_generic_class_from_context(&self, ty: Type<'db>, target: Type<'db>) -> Type<'db> {
        // TODO: The constraint-set assignability rules should already be
        // able to determine that `list` (coerced into a callable) is assignable
        // to `Callable[[], list[str]]` when `_T@list = str`. However, when
        // comparing callables, if either is generic, we existentially quantify
        // away its typevars, transforming `∃ _T@list . _T@list = str` into
        // `always`. If we _didn't_ perform that quantification, we would have
        // the information we need to choose an appropriate specialization of
        // `list` given the type context, and we wouldn't have to duplicate all
        // of the logic below.
        let Type::ClassLiteral(class) = ty else {
            return ty;
        };
        let db = self.db();
        let exactly_one_callable = |union: UnionType<'db>| {
            union
                .elements(db)
                .iter()
                .filter_map(|element| element.resolve_type_alias(db).as_callable())
                .exactly_one()
                .ok()
        };
        let Some(target_callable) = (match target {
            Type::Callable(callable) => Some(callable),
            Type::Union(union) => exactly_one_callable(union),
            Type::TypeAlias(_) => match target.resolve_type_alias(db) {
                Type::Callable(callable) => Some(callable),
                Type::Union(union) => exactly_one_callable(union),
                _ => None,
            },
            _ => None,
        }) else {
            return ty;
        };
        // Callables made entirely of dynamic types provide no constraints for specializing the
        // class. The same is true for a parameterless context whose return type is an
        // unspecialized variable from an enclosing generic call.
        if target_callable.signatures(db).iter().all(|signature| {
            let parameters = signature.parameters().as_slice();
            (signature.return_ty.is_dynamic()
                && parameters
                    .iter()
                    .all(|parameter| parameter.annotated_type().is_dynamic()))
                || (parameters.is_empty()
                    && matches!(
                        signature.return_ty,
                        Type::Dynamic(DynamicType::UnspecializedTypeVar)
                    ))
        }) {
            return ty;
        }
        let Some(class_generic_context) = class.generic_context(db) else {
            return ty;
        };
        let Some(source_callable) = ty.try_upcast_to_callable(db) else {
            return ty;
        };
        // The callable relation existentially solves variables bound by each signature. Keep
        // method-local constructor variables scoped there, but expose class variables to this
        // outer contextual-specialization solve.
        let source_callable = source_callable.map(|callable| {
            let signatures = CallableSignature::from_overloads(
                callable.signatures(db).overloads.iter().map(|signature| {
                    let signature_generic_context = signature.generic_context.and_then(|context| {
                        let mut variables = context
                            .variables(db)
                            .filter(|typevar| {
                                !class_generic_context.contains(db, typevar.identity(db))
                            })
                            .peekable();
                        variables
                            .peek()
                            .is_some()
                            .then(|| GenericContext::from_typevar_instances(db, variables))
                    });
                    Signature::new_generic(
                        signature_generic_context,
                        signature.parameters().clone(),
                        signature.return_ty,
                    )
                    .with_definition(signature.definition())
                }),
            );
            CallableType::new(db, signatures, callable.kind(db), callable.provenance(db))
        });
        let inferable = class_generic_context.inferable_typevars(db);
        let constraints = ConstraintSetBuilder::new();
        let path_bounds = source_callable
            .into_type(db)
            .assignable_solutions_with_inferable(db, Type::Callable(target_callable), inferable);
        let Solutions::Constrained(solutions) = path_bounds.solve(db, &constraints) else {
            return ty;
        };

        let mut type_context_mappings: FxHashMap<BoundTypeVarIdentity<'db>, UnionAccumulator<'db>> =
            FxHashMap::default();
        for solution in solutions {
            for binding in solution {
                let inferred_ty = binding
                    .solution
                    .filter_union(db, |ty| !ty.has_unspecialized_type_var(db));
                if inferred_ty.has_unspecialized_type_var(db) {
                    continue;
                }

                type_context_mappings
                    .entry(binding.bound_typevar.identity(db))
                    .and_modify(|existing| existing.add(db, inferred_ty))
                    .or_insert_with(|| UnionAccumulator::new(inferred_ty));
            }
        }

        if type_context_mappings.is_empty() {
            return ty;
        }

        let type_context_mappings: FxHashMap<BoundTypeVarIdentity<'db>, Type<'db>> =
            type_context_mappings
                .into_iter()
                .map(|(identity, accumulator)| (identity, accumulator.into_type(db)))
                .collect();
        let specialized = Type::from(class.apply_specialization(db, |generic_context| {
            generic_context.specialize_recursive(
                db,
                generic_context
                    .variables(db)
                    .map(|typevar| type_context_mappings.get(&typevar.identity(db)).copied()),
            )
        }));
        if specialized.is_assignable_to(db, Type::Callable(target_callable)) {
            specialized
        } else {
            ty
        }
    }

    #[track_caller]
    fn store_expression_type(&mut self, expression: &ast::Expr, ty: Type<'db>) {
        let previous = self.expressions.insert(expression.into(), ty);
        assert_eq!(previous, None);
    }

    fn store_maybe_expected_type(
        &mut self,
        expression: impl Into<ExpressionNodeKey>,
        ty: Type<'db>,
    ) {
        if !self.has_string_literal_completion_candidates(ty) {
            return;
        }

        self.store_expected_type(expression, ty);
    }

    fn store_expected_type(&mut self, expression: impl Into<ExpressionNodeKey>, ty: Type<'db>) {
        self.expected_types.insert(expression.into(), ty);
    }

    fn has_string_literal_completion_candidates(&self, ty: Type<'db>) -> bool {
        match ty {
            Type::LiteralValue(literal) => literal.as_string().is_some(),
            Type::Union(union) => union
                .elements(self.db())
                .iter()
                .any(|ty| self.has_string_literal_completion_candidates(*ty)),
            Type::Intersection(intersection) => intersection
                .iter_positive(self.db())
                .any(|ty| self.has_string_literal_completion_candidates(ty)),
            Type::TypeAlias(_) => true,
            _ => false,
        }
    }

    fn union_expected_types(&mut self, expected_types: &FxHashMap<ExpressionNodeKey, Type<'db>>) {
        let db = self.db();
        #[expect(
            clippy::iter_over_hash_type,
            reason = "expected types for distinct expressions are unioned independently"
        )]
        for (expression, ty) in expected_types {
            self.expected_types
                .entry(*expression)
                .and_modify(|existing| *existing = UnionType::from_two_elements(db, *existing, *ty))
                .or_insert(*ty);
        }
    }

    fn infer_number_literal_expression(&self, literal: &ast::ExprNumberLiteral) -> Type<'db> {
        let ast::ExprNumberLiteral {
            range: _,
            node_index: _,
            value,
        } = literal;
        let db = self.db();

        match value {
            ast::Number::Int(n) => n
                .as_i64()
                .map(Type::int_literal)
                .unwrap_or_else(|| KnownClass::Int.to_instance(db)),
            ast::Number::Float(_) => KnownClass::Float.to_instance(db),
            ast::Number::Complex { .. } => KnownClass::Complex.to_instance(db),
        }
    }

    #[expect(clippy::unused_self)]
    fn infer_boolean_literal_expression(&self, literal: &ast::ExprBooleanLiteral) -> Type<'db> {
        let ast::ExprBooleanLiteral {
            range: _,
            node_index: _,
            value,
        } = literal;

        Type::bool_literal(*value)
    }

    fn infer_string_literal_expression(
        &mut self,
        literal: &ast::ExprStringLiteral,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        if let Some(expected) = tcx.annotation {
            self.store_maybe_expected_type(ast::ExprRef::from(literal), expected);
        }

        if tcx.is_typealias() {
            let aliased_type = self.infer_string_type_expression(literal);
            return Type::KnownInstance(KnownInstanceType::LiteralStringAlias(InternedType::new(
                self.db(),
                aliased_type,
            )));
        }
        if literal.value.len() <= Self::MAX_STRING_LITERAL_SIZE {
            Type::string_literal(self.db(), literal.value.to_str())
        } else {
            Type::literal_string()
        }
    }

    fn infer_bytes_literal_expression(&mut self, literal: &ast::ExprBytesLiteral) -> Type<'db> {
        // TODO: ignoring r/R prefixes for now, should normalize bytes values
        let bytes: Vec<u8> = literal.value.bytes().collect();
        Type::bytes_literal(self.db(), &bytes)
    }

    fn infer_fstring_expression(&mut self, fstring: &ast::ExprFString) -> Type<'db> {
        let ast::ExprFString {
            range: _,
            node_index: _,
            value,
        } = fstring;

        let mut collector = StringPartsCollector::new();
        for part in value {
            // Make sure we iter through every parts to infer all sub-expressions. The `collector`
            // struct ensures we don't allocate unnecessary strings.
            match part {
                ast::FStringPart::Literal(literal) => {
                    collector.push_str(&literal.value);
                }
                ast::FStringPart::FString(fstring) => {
                    for element in &fstring.elements {
                        match element {
                            ast::InterpolatedStringElement::Interpolation(expression) => {
                                let ast::InterpolatedElement {
                                    range: _,
                                    node_index: _,
                                    expression,
                                    debug_text,
                                    conversion,
                                    format_spec,
                                } = expression;
                                let ty = self.infer_expression(expression, TypeContext::default());

                                if let Some(format_spec) = format_spec {
                                    for element in format_spec.elements.interpolations() {
                                        self.infer_expression(
                                            &element.expression,
                                            TypeContext::default(),
                                        );
                                    }
                                }

                                // TODO: handle format specifiers by calling a method
                                // (`Type::format`?) that handles the `__format__` method.
                                // Conversion flags should be handled before calling `__format__`.
                                // https://docs.python.org/3/library/string.html#format-string-syntax
                                if debug_text.is_some()
                                    || !conversion.is_none()
                                    || format_spec.is_some()
                                {
                                    collector.add_non_literal_string_expression();
                                } else {
                                    let str_ty = ty.str(self.db());
                                    if let Some(literal) = str_ty.as_string_literal() {
                                        collector.push_str(literal.value(self.db()));
                                    } else if str_ty
                                        .is_subtype_of(self.db(), Type::literal_string())
                                    {
                                        collector.add_literal_string_expression();
                                    } else {
                                        collector.add_non_literal_string_expression();
                                    }
                                }
                            }
                            ast::InterpolatedStringElement::Literal(literal) => {
                                collector.push_str(&literal.value);
                            }
                        }
                    }
                }
            }
        }
        collector.string_type(self.db())
    }

    fn infer_tstring_expression(&mut self, tstring: &ast::ExprTString) -> Type<'db> {
        let ast::ExprTString { value, .. } = tstring;
        for tstring in value {
            for element in &tstring.elements {
                match element {
                    ast::InterpolatedStringElement::Interpolation(
                        tstring_interpolation_element,
                    ) => {
                        let ast::InterpolatedElement {
                            expression,
                            format_spec,
                            ..
                        } = tstring_interpolation_element;
                        self.infer_expression(expression, TypeContext::default());
                        if let Some(format_spec) = format_spec {
                            for element in format_spec.elements.interpolations() {
                                self.infer_expression(&element.expression, TypeContext::default());
                            }
                        }
                    }
                    ast::InterpolatedStringElement::Literal(_) => {}
                }
            }
        }
        KnownClass::Template.to_instance(self.db())
    }

    fn infer_ellipsis_literal_expression(
        &mut self,
        _literal: &ast::ExprEllipsisLiteral,
    ) -> Type<'db> {
        KnownClass::EllipsisType.to_instance(self.db())
    }

    fn infer_tuple_expression(
        &mut self,
        tuple: &ast::ExprTuple,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        /// If a tuple literal has more elements than this constant,
        /// we promote `Literal` types when inferring the elements of the tuple.
        /// This provides a huge speedup on files that have very large unannotated tuple literals.
        const MAX_TUPLE_LENGTH_FOR_UNANNOTATED_LITERAL_INFERENCE: usize = 64;

        let ast::ExprTuple {
            range: _,
            node_index: _,
            elts,
            ctx: _,
            parenthesized: _,
        } = tuple;

        // Remove any union elements of the annotation that are unrelated to the tuple type.
        let tcx = tcx.map(|annotation| {
            let inferable = KnownClass::Tuple
                .try_to_class_literal(self.db())
                .and_then(|class| class.generic_context(self.db()))
                .map(|generic_context| generic_context.inferable_typevars(self.db()))
                .unwrap_or(InferableTypeVars::None);
            annotation.filter_disjoint_elements(
                self.db(),
                Type::homogeneous_tuple(self.db(), Type::unknown()),
                inferable,
            )
        });

        let mut is_homogeneous_tuple_annotation = false;

        let annotated_tuple = tcx
            .known_specialization(self.db(), KnownClass::Tuple)
            .and_then(|specialization| {
                let spec = specialization
                    .tuple(self.db())
                    .expect("the specialization of `KnownClass::Tuple` must have a tuple spec");

                if let Tuple::Variable(tuple) = spec
                    && tuple.prefix_elements().is_empty()
                    && tuple.suffix_elements().is_empty()
                {
                    is_homogeneous_tuple_annotation = true;
                }

                spec.resize(self.db(), TupleLength::Fixed(elts.len())).ok()
            });

        // TODO: this is a simplification for now.
        //
        // It might be possible to use the type context where the annotation is not a pure-homogeneous
        // tuple and the actual tuple has starred elements in it. It seems complex to reason about,
        // though, and unlikely to come up much.
        let can_use_type_context =
            is_homogeneous_tuple_annotation || elts.iter().all(|elt| !elt.is_starred_expr());

        let mut annotated_elt_tys = annotated_tuple
            .as_ref()
            .map(Tuple::all_elements)
            .unwrap_or_default()
            .iter()
            .copied();

        let db = self.db();

        let mut infer_element = |elt: &ast::Expr| {
            let annotated_elt_ty = annotated_elt_tys.by_ref().next();
            let ctx = if can_use_type_context {
                let expected = if elt.is_starred_expr() {
                    let expected_element = annotated_elt_ty.unwrap_or_else(Type::object);
                    Some(KnownClass::Iterable.to_specialized_instance(db, &[expected_element]))
                } else {
                    annotated_elt_ty
                };
                TypeContext::new(expected)
            } else {
                TypeContext::default()
            };
            if tuple.len() > MAX_TUPLE_LENGTH_FOR_UNANNOTATED_LITERAL_INFERENCE {
                // Promote literals for very large unannotated tuples,
                // to avoid pathological performance issues
                self.infer_expression(elt, ctx).promote(db)
            } else {
                self.infer_expression(elt, ctx)
            }
        };

        let mut builder = TupleSpecBuilder::with_capacity(elts.len());

        for element in elts {
            if let ast::Expr::Starred(starred) = element {
                let element_type = infer_element(element);
                // Fine to use `iterate` rather than `try_iterate` here:
                // errors from iterating over something not iterable will have been
                // emitted in the `infer_element` call above.
                let mut spec = element_type.iterate(db).into_owned();

                let known_length = match &*starred.value {
                    ast::Expr::List(ast::ExprList { elts, .. })
                    | ast::Expr::Set(ast::ExprSet { elts, .. }) => elts
                        .iter()
                        .all(|elt| !elt.is_starred_expr())
                        .then_some(elts.len()),
                    ast::Expr::Dict(ast::ExprDict { items, .. }) => items
                        .iter()
                        .all(|item| item.key.is_some())
                        .then_some(items.len()),
                    _ => None,
                };

                if let Some(known_length) = known_length {
                    spec = spec
                        .resize(db, TupleLength::Fixed(known_length))
                        .unwrap_or(spec);
                }

                builder = builder.concat(db, &spec);
            } else {
                builder.push(infer_element(element));
            }
        }

        Type::tuple(TupleType::new(db, &builder.build()))
    }

    fn infer_list_expression(&mut self, list: &ast::ExprList, tcx: TypeContext<'db>) -> Type<'db> {
        let ast::ExprList {
            range: _,
            node_index: _,
            elts,
            ctx: _,
        } = list;

        let elts = elts.iter().map(|elt| [Some(elt)]).collect_vec();
        let mut infer_elt_ty =
            |builder: &mut Self, (_, elt, tcx)| builder.infer_expression(elt, tcx);

        self.infer_collection_literal(
            KnownClass::List,
            Some(list.into()),
            &elts,
            &mut infer_elt_ty,
            tcx,
        )
        .unwrap_or_else(|| KnownClass::List.to_specialized_instance(self.db(), &[Type::unknown()]))
    }

    fn infer_set_expression(&mut self, set: &ast::ExprSet, tcx: TypeContext<'db>) -> Type<'db> {
        let ast::ExprSet {
            range: _,
            node_index: _,
            elts,
        } = set;

        let elts = elts.iter().map(|elt| [Some(elt)]).collect_vec();
        let fallback_tcx = self.incomplete_typed_dict_key_context(set, tcx);
        let mut infer_elt_ty = |builder: &mut Self, arg: ArgExpr<'db, '_>| {
            let (_, elt, elt_tcx) = arg;
            builder.infer_set_element(elt, elt_tcx, fallback_tcx)
        };

        self.infer_collection_literal(
            KnownClass::Set,
            Some(set.into()),
            &elts,
            &mut infer_elt_ty,
            tcx,
        )
        .unwrap_or_else(|| KnownClass::Set.to_specialized_instance(self.db(), &[Type::unknown()]))
    }

    /// Infers a set element, optionally with a fallback context for an incomplete `TypedDict` key.
    ///
    /// When normal set element context is available, semantic inference keeps that context. If a
    /// `TypedDict` key fallback is also available, it is only added to the stored expected type used
    /// by IDE string-literal completions.
    fn infer_set_element(
        &mut self,
        elt: &ast::Expr,
        elt_tcx: TypeContext<'db>,
        fallback_tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let inference_tcx = if elt_tcx.annotation.is_some() {
            elt_tcx
        } else {
            fallback_tcx
        };
        let inferred_ty = self.infer_expression(elt, inference_tcx);

        // `expected_types` is IDE completion metadata. If normal set inference already has a
        // string-literal context, preserve that semantic context for inference while also offering
        // the transient `TypedDict` key fallback as a completion candidate.
        if let (Some(elt_ty), Some(fallback_ty)) = (elt_tcx.annotation, fallback_tcx.annotation) {
            self.store_expected_type(
                elt,
                UnionType::from_two_elements(self.db(), elt_ty, fallback_ty),
            );
        }

        inferred_ty
    }

    /// Returns a fallback type context for completing a `TypedDict` key while editing.
    ///
    /// While editing `{"key": value}` as a `TypedDict` literal, `{"key"}` parses as a set
    /// until the colon is typed. This preserves key completions in that transient state.
    fn incomplete_typed_dict_key_context(
        &self,
        set: &ast::ExprSet,
        tcx: TypeContext<'db>,
    ) -> TypeContext<'db> {
        let [elt] = set.elts.as_slice() else {
            return TypeContext::default();
        };

        if !elt.is_string_literal_expr() {
            return TypeContext::default();
        }

        TypeContext::new(
            tcx.annotation
                .and_then(|annotation| self.typed_dict_key_expected_type(annotation)),
        )
    }

    fn infer_dict_expression(&mut self, dict: &ast::ExprDict, tcx: TypeContext<'db>) -> Type<'db> {
        let ast::ExprDict {
            range: _,
            node_index: _,
            items,
        } = dict;

        let mut item_types = FxHashMap::default();

        // Validate `TypedDict` dictionary literal assignments.
        if let Some(annotation) = tcx
            .annotation
            .map(|annotation| annotation.resolve_type_alias(self.db()))
        {
            if let Some(typed_dict) = annotation.as_typed_dict() {
                // If there is a single typed dict annotation, infer against it directly.
                if let Some(ty) =
                    self.infer_typed_dict_expression(dict, typed_dict, &mut item_types)
                {
                    return ty;
                }
            } else if let Type::Union(union) = annotation {
                let union_elements = union.elements(self.db());
                let mut typed_dicts = Vec::new();
                let mut has_dict_compatible_fallback = false;

                for element in union_elements {
                    let element = element.resolve_type_alias(self.db());

                    if let Some(typed_dict) = element.as_typed_dict() {
                        typed_dicts.push(typed_dict);
                    } else if !has_dict_compatible_fallback {
                        // Suppress `TypedDict` diagnostics only if this literal is assignable to
                        // the non-`TypedDict` arm of the union.
                        let mut speculative_builder = self.speculate_without_diagnostics();
                        has_dict_compatible_fallback = speculative_builder
                            .infer_dict_expression(dict, TypeContext::new(Some(element)))
                            .is_assignable_to(self.db(), element);
                    }
                }

                if let [typed_dict] = typed_dicts.as_slice()
                    && !has_dict_compatible_fallback
                {
                    if let Some(ty) =
                        self.infer_typed_dict_expression(dict, *typed_dict, &mut item_types)
                    {
                        return ty;
                    }
                } else if !typed_dicts.is_empty() {
                    // Infer all expressions with diagnostics enabled before starting
                    // multi-inference. This preserves the general expression types even if we later
                    // fall back to a non-`TypedDict` arm of the union.
                    for item in items {
                        if let Some(key) = item.key.as_ref() {
                            let key_ty = self.infer_expression(key, TypeContext::default());
                            item_types.insert(key.node_index().load(), key_ty);
                        }

                        let value_ty = self.infer_expression(&item.value, TypeContext::default());
                        item_types.insert(item.value.node_index().load(), value_ty);
                    }

                    let mut narrowed_tys = Vec::new();
                    let mut item_types = FxHashMap::default();
                    // Reuse nested expressions that receive the same field context across candidates.
                    let teardown = self.setup_expression_cache();
                    for typed_dict in typed_dicts {
                        // Suppress diagnostics for discarded candidates. A mixed union like
                        // `TypedDict | dict[str, Any]` should remain quiet when the dict arm accepts
                        // the literal.
                        if let Some(inferred_ty) = self
                            .speculate_without_diagnostics()
                            .infer_typed_dict_expression(dict, typed_dict, &mut item_types)
                        {
                            narrowed_tys.push(inferred_ty);
                        }

                        item_types.clear();
                    }
                    if teardown {
                        self.teardown_expression_cache();
                    }

                    // Successfully narrowed to a subset of typed dicts.
                    if !narrowed_tys.is_empty() {
                        return UnionType::from_elements(self.db(), narrowed_tys);
                    }
                }
            }
        }

        let items = items
            .iter()
            .map(|item| [item.key.as_ref(), Some(&item.value)])
            .collect_vec();

        // Avoid inferring the items multiple times if we already attempted to infer the
        // dictionary literal as a `TypedDict`. This also allows us to infer using the
        // type context of the expected `TypedDict` field.
        let mut infer_elt_ty = |builder: &mut Self, (_, elt, tcx): ArgExpr<'db, '_>| {
            item_types
                .get(&elt.node_index().load())
                .copied()
                .or_else(|| builder.try_expression_type(elt))
                .unwrap_or_else(|| builder.infer_expression(elt, tcx))
        };

        self.infer_collection_literal(
            KnownClass::Dict,
            Some(dict.into()),
            &items,
            &mut infer_elt_ty,
            tcx,
        )
        .unwrap_or_else(|| {
            KnownClass::Dict.to_specialized_instance(self.db(), &[Type::unknown(), Type::unknown()])
        })
    }

    // Infer the type of a collection literal expression.
    fn infer_collection_literal<'expr, const N: usize>(
        &mut self,
        collection_class: KnownClass,
        collection_expr: Option<ast::ExprRef<'_>>,
        elts: &[[Option<&'expr ast::Expr>; N]],
        infer_elt_expression: &mut dyn FnMut(&mut Self, ArgExpr<'db, 'expr>) -> Type<'db>,
        tcx: TypeContext<'db>,
    ) -> Option<Type<'db>> {
        let db = self.db();

        let mut try_narrow = |narrowed_ty| {
            let mut speculative_builder = self.speculate();

            // Attempt to infer the collection literal using the narrowed type context.
            let inferred_ty = speculative_builder.infer_collection_literal_impl(
                collection_class,
                collection_expr,
                elts,
                infer_elt_expression,
                TypeContext::new(Some(narrowed_ty)),
            )?;

            // Ensure the inferred return type is assignable to the narrowed declared type.
            if !inferred_ty.is_assignable_to(db, narrowed_ty) {
                return None;
            }

            // Successfully narrowed to an element of the union.
            self.extend(speculative_builder);
            Some(inferred_ty)
        };

        // If the type context is a union, attempt to narrow to a specific element.
        for narrowed_ty in tcx
            .narrow_targets(db)
            .as_deref()
            .into_iter()
            .flatten()
            .filter(|ty| ty.class_specialization(db).is_some())
        {
            if let Some(result) = try_narrow(*narrowed_ty) {
                return Some(result);
            }
        }

        self.infer_collection_literal_impl(
            collection_class,
            collection_expr,
            elts,
            infer_elt_expression,
            tcx,
        )
    }

    // Infer the type of a collection literal expression.
    fn infer_collection_literal_impl<'expr, const N: usize>(
        &mut self,
        collection_class: KnownClass,
        collection_expr: Option<ast::ExprRef<'_>>,
        elts: &[[Option<&'expr ast::Expr>; N]],
        infer_elt_expression: &mut dyn FnMut(&mut Self, ArgExpr<'db, 'expr>) -> Type<'db>,
        tcx: TypeContext<'db>,
    ) -> Option<Type<'db>> {
        // Extract the type variable `T` from `list[T]` in typeshed.
        let elt_tys = |collection_class: KnownClass| {
            let collection_alias = collection_class
                .try_to_class_literal(self.db())?
                .identity_specialization(self.db())
                .into_generic_alias()?;

            let generic_context = collection_alias
                .specialization(self.db())
                .generic_context(self.db());

            Some((
                collection_alias,
                generic_context,
                generic_context.variables(self.db()),
            ))
        };

        let Some((collection_alias, generic_context, elt_tys)) = elt_tys(collection_class) else {
            // Infer the element types without type context, and fallback to `Unknown` for
            // custom typesheds.
            for (i, elt) in elts.iter().flatten().flatten().enumerate() {
                infer_elt_expression(self, (i, elt, TypeContext::default()));
            }

            return None;
        };

        let constraints = ConstraintSetBuilder::new();
        let inferable = generic_context.inferable_typevars(self.db());
        let identity_instance = Type::instance(self.db(), ClassType::Generic(collection_alias));
        let mut builder = SpecializationBuilder::new(self.db(), &constraints, inferable);

        // Remove any union elements of that are unrelated to the collection type.
        //
        // For example, we only want the `list[int]` from `annotation: list[int] | None` if
        // `collection_ty` is `list`.
        let tcx = tcx.map(|annotation| {
            let collection_ty = collection_class.to_instance(self.db());
            annotation.filter_disjoint_elements(self.db(), collection_ty, inferable)
        });

        // Collect type constraints from the declared element types.
        //
        // We use a forward assignability check (`identity_instance ≤ tcx`) to infer what each
        // typevar maps to in the type context. For example, if the type context is `list[int]` and
        // `collection_instance` is `list[T]`, the check produces `T = int`.
        let (elt_tcx_constraints, elt_tcx_variance) = {
            let mut elt_tcx_constraints: FxHashMap<
                BoundTypeVarIdentity<'db>,
                UnionAccumulator<'db>,
            > = FxHashMap::default();
            let mut elt_tcx_variance: FxHashMap<BoundTypeVarIdentity<'_>, TypeVarVariance> =
                FxHashMap::default();

            if let Some(tcx) = tcx.annotation.map(|tcx| tcx.resolve_type_alias(self.db()))
                && matches!(tcx, Type::NominalInstance(_))
                && let Some(specialization) = tcx.known_specialization(self.db(), collection_class)
                && specialization.generic_context(self.db()) == generic_context
                && generic_context.variables(self.db()).all(|typevar| {
                    !typevar.is_paramspec(self.db())
                        && typevar
                            .typevar(self.db())
                            .bound_or_constraints(self.db())
                            .is_none()
                })
            {
                // For an instance of the collection class itself, the identity specialization
                // maps directly to the contextual specialization. Avoid constructing and solving
                // a general assignability constraint set for this common case.
                for (typevar, inferred_ty) in generic_context
                    .variables(self.db())
                    .zip(specialization.types(self.db()))
                {
                    let inferred_ty = inferred_ty
                        .filter_union(self.db(), |ty| {
                            !ty.as_typevar()
                                .is_some_and(|tv| tv.is_inferable(self.db(), inferable))
                        })
                        .filter_union(self.db(), |ty| !ty.has_unspecialized_type_var(self.db()));
                    if inferred_ty.has_unspecialized_type_var(self.db()) {
                        continue;
                    }

                    let identity = typevar.identity(self.db());
                    elt_tcx_constraints.insert(identity, UnionAccumulator::new(inferred_ty));
                    elt_tcx_variance.insert(identity, typevar.variance(self.db()));
                }
            } else if let Some(tcx) = tcx.annotation
                && tcx.class_specialization(self.db()).is_some()
            {
                let db = self.db();

                let path_bounds =
                    identity_instance.assignable_solutions_with_inferable(db, tcx, inferable);
                let solutions = path_bounds.solve_with(|variance, path_bound| {
                    let identity = path_bound.bound_typevar.identity(db);
                    elt_tcx_variance
                        .entry(identity)
                        .and_modify(|current| *current = current.join(variance))
                        .or_insert(variance);
                    PathBounds::default_solve(db, &constraints, path_bound)
                });

                match solutions {
                    // If the type context is not compatible with the collection type (e.g., a
                    // `list` literal where a `tuple` is expected), the assignability check
                    // produces an unsatisfiable result. In that case, we simply proceed without
                    // type context constraints rather than aborting the entire collection literal
                    // inference.
                    Solutions::Unsatisfiable | Solutions::Unconstrained => {}
                    Solutions::Constrained(solutions) => {
                        for solution in &solutions {
                            for binding in solution {
                                // The SequentMap's transitivity reasoning can inject
                                // cross-typevar references into the solution bounds.
                                // For example, `_KT ≤ str ∧ str ≤ _VT` derives `_KT ≤ _VT`,
                                // which adds `_KT` to `_VT`'s lower bound. Remove inferable
                                // typevars from the same generic context, since they represent
                                // cross-typevar relationships that are resolved independently.
                                let inferred_ty = builder
                                    .remove_inferable_typevar_artifacts_from_solution(
                                        binding.bound_typevar,
                                        binding.solution,
                                    );

                                // Avoid inferring a preferred type based on partially specialized
                                // type context from an outer generic call. If the type context is
                                // a union, we try to keep any concrete elements.
                                let inferred_ty = inferred_ty
                                    .filter_union(db, |ty| !ty.has_unspecialized_type_var(db));
                                if inferred_ty.has_unspecialized_type_var(db) {
                                    continue;
                                }

                                let identity = binding.bound_typevar.identity(db);
                                elt_tcx_constraints
                                    .entry(identity)
                                    .and_modify(|existing| existing.add(db, inferred_ty))
                                    .or_insert_with(|| UnionAccumulator::new(inferred_ty));
                            }
                        }

                        // Remove variance entries for typevars whose solutions were filtered out
                        // (e.g., due to unspecialized typevars). Variance should only be tracked
                        // for typevars with actual type context constraints.
                        elt_tcx_variance
                            .retain(|identity, _| elt_tcx_constraints.contains_key(identity));
                    }
                }
            }

            let db = self.db();
            let elt_tcx_constraints: FxHashMap<BoundTypeVarIdentity<'db>, Type<'db>> =
                elt_tcx_constraints
                    .into_iter()
                    .map(|(identity, accumulator)| (identity, accumulator.into_type(db)))
                    .collect();

            (elt_tcx_constraints, elt_tcx_variance)
        };

        // Dictionary unpacking always contributes constraints on the inferred key and value types,
        // even when the unpacked mapping is assignable to the context. Keep it on the general path
        // so gradual types such as `Any` are preserved.
        let has_dict_unpack = collection_class == KnownClass::Dict
            && elts
                .iter()
                .any(|elts| matches!(elts.as_slice(), [None, Some(_)]));

        let mut pre_inferred_elt_tys = None;

        // Avoid projecting and solving a constraint set when contextual inference has already
        // provided the complete specialization and every literal element is compatible with it.
        if !has_dict_unpack
            && tcx.annotation.is_some()
            && let Some(specialization) = generic_context
                .variables(self.db())
                .map(|typevar| {
                    let identity = typevar.identity(self.db());
                    // Keep this parallel with the slow path below: a covariant context provides
                    // only an upper bound, which does not determine the specialization for an empty
                    // literal. A contravariant context provides a lower bound, for which inference
                    // selects the narrowest valid solution.
                    if elt_tcx_variance
                        .get(&identity)
                        .is_some_and(|variance| variance.is_covariant())
                    {
                        return None;
                    }
                    elt_tcx_constraints.get(&identity).copied()
                })
                .collect::<Option<Vec<_>>>()
        {
            // The slow path below adds the contextual specialization as an invariant mapping,
            // then discards every element constraint that is already assignable to its context.
            // Infer the elements once here and retain their types so that a failed fast-path check
            // does not recursively re-infer nested collection literals on the slow path.
            let mut inferred_elts = Vec::with_capacity(elts.len());
            let mut compatible = true;

            for elts in elts {
                let mut inferred_elt_tys = [None; N];
                for (i, elt, elt_tcx) in itertools::izip!(0.., elts, specialization.iter().copied())
                {
                    let Some(elt) = elt else { continue };
                    let elt_tcx = if elt.is_starred_expr() && collection_class != KnownClass::Dict {
                        Type::homogeneous_tuple(self.db(), elt_tcx)
                    } else {
                        elt_tcx
                    };
                    let inferred_elt_ty =
                        infer_elt_expression(self, (i, elt, TypeContext::new(Some(elt_tcx))));
                    inferred_elt_tys[i] = Some(inferred_elt_ty);

                    if !inferred_elt_ty.is_assignable_to(self.db(), elt_tcx) {
                        compatible = false;
                    }
                }
                inferred_elts.push(inferred_elt_tys);
            }

            if compatible {
                let class_type = collection_alias.origin(self.db()).apply_specialization(
                    self.db(),
                    |generic_context| {
                        generic_context
                            .specialize_recursive(self.db(), specialization.into_iter().map(Some))
                    },
                );
                return Type::from(class_type).to_instance(self.db());
            }

            pre_inferred_elt_tys = Some(inferred_elts);
        }

        // Create a set of constraints to infer a precise type for `T`.
        let mut tuple_size_promotion_constraints = TupleSizePromotionConstraints::default();

        for elt_ty in elt_tys.clone() {
            let elt_ty_identity = elt_ty.identity(self.db());
            let elt_tcx = elt_tcx_constraints
                // The annotated type acts as a constraint for `T`.
                //
                // Note that we infer the annotated type _before_ the elements, to more closely match
                // the order of any unions as written in the type annotation.
                .get(&elt_ty_identity)
                .copied();

            if elt_tcx.is_some_and(|elt_tcx| !elt_tcx.is_dynamic()) {
                // Record type annotations that provide concrete shape information in order to
                // disqualify this typevar from tuple size promotion.
                tuple_size_promotion_constraints.record_declared_type(elt_ty_identity);
            }

            // Avoid unnecessarily widening the return type based on a covariant
            // type parameter from the type context.
            //
            // Note that we also avoid unioning  the inferred type with `Unknown` in this
            // case, which is only necessary for invariant collections.
            if elt_tcx_variance
                .get(&elt_ty_identity)
                .is_some_and(|variance| variance.is_covariant())
            {
                continue;
            }

            // If there is no applicable context for this element type variable, we infer from the
            // literal elements directly. This violates the gradual guarantee (we don't know that
            // our inference is compatible with subsequent additions to the collection), but it
            // matches the behavior of other type checkers and is usually the desired behavior.
            if let Some(elt_tcx) = elt_tcx {
                builder.add_type_mapping(elt_ty, elt_tcx, TypeVarVariance::Invariant);
            }
        }

        if tcx.annotation.is_none()
            && let Some(collection_expr) = collection_expr
            && let InferenceRegion::Expression(current_expr, _) = self.region
            && current_expr.node_ref(self.db()).index() == *collection_expr.node_index()
            && let Some(assignment) = current_expr.assigned_to(self.db())
            && let Ok(collection_def) =
                DefinitionNodeKey::from_assignment(assignment.node(self.module())).exactly_one()
            && let Some(collection_def) = self.index.try_definition(collection_def)
        {
            // For unannotated collection literals, collect any constraints created by later uses
            // of this definition in the scope.
            for (statement, use_expression) in
                self.index.constraining_collection_uses(collection_def)
            {
                let statement_use_types = infer_statement_types(self.db(), statement);

                if let Some(divergent) = statement_use_types
                    .expression_type(use_expression)
                    .as_divergent()
                {
                    // Infer `collection[Divergent]` for the initial cycle result.
                    let divergent_instance = collection_alias
                        .origin(self.db())
                        .apply_specialization(self.db(), |generic_context| {
                            generic_context
                                .repeat_specialization(self.db(), Type::Divergent(divergent))
                        });

                    builder
                        .infer(
                            identity_instance,
                            Type::instance(self.db(), divergent_instance),
                        )
                        .ok()?;
                } else if let Some(constraints) =
                    statement_use_types.collection_use_constraints(collection_def)
                {
                    for constraint in constraints {
                        if constraint.has_unspecialized_type_var(self.db()) {
                            continue;
                        }

                        builder.infer(identity_instance, *constraint).ok()?;
                    }
                }
            }
        }

        for (elts_index, elts) in elts.iter().enumerate() {
            // An unpacking expression for a dictionary.
            if let &[None, Some(value_expr)] = elts.as_slice() {
                let unpack_ty = infer_elt_expression(self, (1, value_expr, tcx));

                let Some((unpacked_key_ty, unpacked_value_ty)) =
                    unpack_ty.unpack_keys_and_items(self.db())
                else {
                    if let Some(builder) =
                        self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_expr)
                    {
                        let mut diag = builder
                            .into_diagnostic("Argument expression after ** must be a mapping type");

                        diag.set_primary_message(format_args!(
                            "Found `{}`",
                            unpack_ty.display(self.db())
                        ));
                    }

                    continue;
                };

                let mut elt_tys = elt_tys.clone();
                if let Some((key_ty, value_ty)) = elt_tys.next_tuple() {
                    tuple_size_promotion_constraints.record_unpromotable_type(
                        self.db(),
                        key_ty.identity(self.db()),
                        unpacked_key_ty.promote(self.db()),
                    );
                    tuple_size_promotion_constraints.record_unpromotable_type(
                        self.db(),
                        value_ty.identity(self.db()),
                        unpacked_value_ty.promote(self.db()),
                    );

                    builder.infer(Type::TypeVar(key_ty), unpacked_key_ty).ok()?;

                    builder
                        .infer(Type::TypeVar(value_ty), unpacked_value_ty)
                        .ok()?;
                }

                continue;
            }

            // The inferred type of each element acts as an additional constraint on `T`.
            for (i, elt, elt_ty) in itertools::izip!(0.., elts, elt_tys.clone()) {
                let Some(elt) = elt else { continue };

                // Note that unlike when preferring the declared type, we use covariant type
                // assignments from the type context to potentially _narrow_ the inferred type,
                // by avoiding promotion.
                let elt_ty_identity = elt_ty.identity(self.db());

                // If the element is a starred expression, we want to apply the type context to each element
                // in the unpacked expression (which we will store as a tuple when inferring it). We
                // therefore wrap the type context in an `tuple[T, ...]` specialization.
                let elt_tcx = elt_tcx_constraints
                    .get(&elt_ty_identity)
                    .copied()
                    .map(|tcx| {
                        if elt.is_starred_expr() && collection_class != KnownClass::Dict {
                            Type::homogeneous_tuple(self.db(), tcx)
                        } else {
                            tcx
                        }
                    });

                let inferred_elt_ty = pre_inferred_elt_tys
                    .as_ref()
                    .and_then(|inferred_elts| inferred_elts[elts_index][i])
                    .unwrap_or_else(|| {
                        infer_elt_expression(self, (i, elt, TypeContext::new(elt_tcx)))
                    });

                // Simplify the inference based on a non-covariant declared type.
                if let Some(elt_tcx) =
                    elt_tcx.filter(|_| !elt_tcx_variance[&elt_ty_identity].is_covariant())
                    && inferred_elt_ty.is_assignable_to(self.db(), elt_tcx)
                {
                    continue;
                }

                // We promote element literal types in invariant position by default, unless they were
                // inferred with an explicit literal annotation.
                let inferred_elt_ty = inferred_elt_ty.promote(self.db());

                let inferred_type_for_typevar = if elt.is_starred_expr() {
                    inferred_elt_ty
                        .iterate(self.db())
                        .homogeneous_element_type(self.db())
                } else {
                    inferred_elt_ty
                };

                tuple_size_promotion_constraints.record_inferred_expression_type(
                    self.db(),
                    elt_ty_identity,
                    elt,
                    inferred_type_for_typevar,
                );

                builder
                    .infer(Type::TypeVar(elt_ty), inferred_type_for_typevar)
                    .ok()?;
            }
        }

        let class_type = collection_alias
            .origin(self.db())
            .apply_specialization(self.db(), |_| {
                builder.build_with(generic_context, |current_typevar, bounds| {
                    let lower = bounds?.lower?;

                    let lower = if tcx.annotation.is_none() {
                        // Constraints learned from later collection uses should follow the same
                        // promotion policy as literal elements: promote element literal types in
                        // invariant position unless an explicit annotation made them unpromotable.
                        lower.promote(self.db())
                    } else {
                        lower
                    };

                    let lower = if tuple_size_promotion_constraints
                        .allow(current_typevar.identity(self.db()))
                    {
                        lower.promote_tuple_size_in_union(self.db())
                    } else {
                        lower
                    };

                    let lower = if elt_tcx_constraints.is_empty() {
                        lower
                            // Promote singleton types to `T | Unknown` in inferred type parameters,
                            // so that e.g. `[None]` is inferred as `list[None | Unknown]`.
                            .promote_singletons_recursively(self.db())
                    } else {
                        lower
                    };

                    Some(lower)
                })
            });

        Type::from(class_type).to_instance(self.db())
    }

    /// Infer the type of the `iter` expression of the first comprehension.
    fn infer_first_comprehension_iter(&mut self, comprehensions: &[ast::Comprehension]) {
        let mut comprehensions_iter = comprehensions.iter();
        let Some(first_comprehension) = comprehensions_iter.next() else {
            unreachable!("Comprehension must contain at least one generator");
        };
        self.infer_maybe_standalone_expression(&first_comprehension.iter, TypeContext::default());
    }

    /// Derive the type context for a generator expression's yielded element from the expected type
    /// of the generator expression itself.
    ///
    /// We model the generator expression as a synthetic `GeneratorType[T, None, None]` or
    /// `AsyncGeneratorType[T, None]`, then ask constraint-set assignability to solve for `T` against
    /// the expected annotation. The solved `T` becomes the type context for the expression being
    /// yielded, so normal assignability handles protocols and unions like `Iterable[int] | None`
    /// without adding target-specific special cases here.
    fn generator_yield_type_context(
        &self,
        tcx: TypeContext<'db>,
        evaluation_mode: EvaluationMode,
    ) -> TypeContext<'db> {
        let Some(annotation) = tcx.annotation else {
            return TypeContext::default();
        };

        let db = self.db();
        let yield_typevar = BoundTypeVarInstance::synthetic(
            db,
            Name::new_static("_GeneratorYieldT"),
            TypeVarVariance::Covariant,
        );
        let yield_ty = Type::TypeVar(yield_typevar);
        let none = Type::none(db);
        let generator_ty = if evaluation_mode.is_async() {
            KnownClass::AsyncGeneratorType.to_specialized_instance(db, &[yield_ty, none])
        } else {
            KnownClass::GeneratorType.to_specialized_instance(db, &[yield_ty, none, none])
        };

        let generic_context = GenericContext::from_typevar_instances(db, [yield_typevar]);
        let path_bounds = generator_ty.assignable_solutions_with_inferable(
            db,
            annotation,
            generic_context.inferable_typevars(db),
        );
        let constraints = ConstraintSetBuilder::new();
        let Solutions::Constrained(solutions) = path_bounds.solve(db, &constraints) else {
            return TypeContext::default();
        };

        let mut yield_tcx: Option<UnionAccumulator<'db>> = None;
        for solution in solutions {
            for binding in solution {
                if binding.bound_typevar != yield_typevar {
                    continue;
                }
                match &mut yield_tcx {
                    Some(accumulator) => accumulator.add(db, binding.solution),
                    None => yield_tcx = Some(UnionAccumulator::new(binding.solution)),
                }
            }
        }

        TypeContext::new(yield_tcx.map(|accumulator| accumulator.into_type(db)))
    }

    fn infer_generator_expression(
        &mut self,
        generator: &ast::ExprGenerator,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprGenerator {
            range: _,
            node_index: _,
            elt,
            generators,
            parenthesized: _,
        } = generator;

        self.infer_first_comprehension_iter(generators);

        let Some(scope_id) = self
            .index
            .try_node_scope(NodeWithScopeRef::GeneratorExpression(generator))
        else {
            return Type::unknown();
        };
        let evaluation_mode =
            EvaluationMode::from_is_async(scope_id.is_async_comprehension(self.index));
        let yield_tcx = self.generator_yield_type_context(tcx, evaluation_mode);
        let scope = scope_id.to_scope_id(self.db(), self.file());
        let inference = infer_scope_types(self.db(), scope, yield_tcx);
        self.extend_scope(inference);
        let yield_type = self.comprehension_element_type(elt, inference);

        if evaluation_mode.is_async() {
            KnownClass::AsyncGeneratorType
                .to_specialized_instance(self.db(), &[yield_type, Type::none(self.db())])
        } else {
            KnownClass::GeneratorType.to_specialized_instance(
                self.db(),
                &[yield_type, Type::none(self.db()), Type::none(self.db())],
            )
        }
    }

    fn comprehension_element_type(
        &self,
        element: &ast::Expr,
        inference: &ScopeInference<'db>,
    ) -> Type<'db> {
        let element_type = inference.expression_type(element);
        if element.is_starred_expr() {
            element_type
                .iterate(self.db())
                .homogeneous_element_type(self.db())
        } else {
            element_type
        }
    }

    /// Return a specialization of the collection class (list, dict, set) based on the type context and the inferred
    /// element / key-value types from the comprehension expression.
    fn infer_comprehension_specialization<const N: usize>(
        &mut self,
        collection_class: KnownClass,
        collection_expr: ast::ExprRef<'_>,
        elements: [Option<&ast::Expr>; N],
        inference: &ScopeInference<'db>,
        tcx: TypeContext<'db>,
    ) -> Option<Type<'db>> {
        let mut infer_element_ty =
            |_builder: &mut Self, (_, elt, _)| inference.expression_type(elt);

        self.infer_collection_literal(
            collection_class,
            Some(collection_expr),
            &[elements],
            &mut infer_element_ty,
            tcx,
        )
    }

    fn infer_list_comprehension_expression(
        &mut self,
        listcomp: &ast::ExprListComp,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprListComp {
            range: _,
            node_index: _,
            elt,
            generators,
        } = listcomp;

        self.infer_first_comprehension_iter(generators);

        let Some(scope_id) = self
            .index
            .try_node_scope(NodeWithScopeRef::ListComprehension(listcomp))
        else {
            return Type::unknown();
        };
        let scope = scope_id.to_scope_id(self.db(), self.file());
        let inference = infer_scope_types(self.db(), scope, tcx);
        self.extend_scope(inference);

        self.infer_comprehension_specialization(
            KnownClass::List,
            listcomp.into(),
            [Some(elt)],
            inference,
            tcx,
        )
        .unwrap_or_else(|| KnownClass::List.to_specialized_instance(self.db(), &[Type::unknown()]))
    }

    fn infer_set_comprehension_expression(
        &mut self,
        setcomp: &ast::ExprSetComp,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprSetComp {
            range: _,
            node_index: _,
            elt,
            generators,
        } = setcomp;

        self.infer_first_comprehension_iter(generators);

        let Some(scope_id) = self
            .index
            .try_node_scope(NodeWithScopeRef::SetComprehension(setcomp))
        else {
            return Type::unknown();
        };
        let scope = scope_id.to_scope_id(self.db(), self.file());
        let inference = infer_scope_types(self.db(), scope, tcx);
        self.extend_scope(inference);

        self.infer_comprehension_specialization(
            KnownClass::Set,
            setcomp.into(),
            [Some(elt)],
            inference,
            tcx,
        )
        .unwrap_or_else(|| KnownClass::Set.to_specialized_instance(self.db(), &[Type::unknown()]))
    }

    fn infer_dict_comprehension_expression(
        &mut self,
        dictcomp: &ast::ExprDictComp,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprDictComp {
            range: _,
            node_index: _,
            key,
            value,
            generators,
        } = dictcomp;

        self.infer_first_comprehension_iter(generators);

        let Some(scope_id) = self
            .index
            .try_node_scope(NodeWithScopeRef::DictComprehension(dictcomp))
        else {
            return Type::unknown();
        };
        let scope = scope_id.to_scope_id(self.db(), self.file());
        let inference = infer_scope_types(self.db(), scope, tcx);
        self.extend_scope(inference);

        self.infer_comprehension_specialization(
            KnownClass::Dict,
            dictcomp.into(),
            [key.as_deref(), Some(value)],
            inference,
            tcx,
        )
        .unwrap_or_else(|| {
            KnownClass::Dict.to_specialized_instance(self.db(), &[Type::unknown(), Type::unknown()])
        })
    }

    fn infer_generator_expression_scope(
        &mut self,
        generator: &ast::ExprGenerator,
        tcx: TypeContext<'db>,
    ) {
        let ast::ExprGenerator {
            range: _,
            node_index: _,
            elt,
            generators,
            parenthesized: _,
        } = generator;

        let elt_tcx = if elt.is_starred_expr() {
            tcx.map(|yield_ty| KnownClass::Iterable.to_specialized_instance(self.db(), &[yield_ty]))
        } else {
            tcx
        };
        self.infer_expression(elt, elt_tcx);
        self.infer_comprehensions(generators);
    }

    fn infer_list_comprehension_expression_scope(
        &mut self,
        listcomp: &ast::ExprListComp,
        tcx: TypeContext<'db>,
    ) {
        let ast::ExprListComp {
            range: _,
            node_index: _,
            elt,
            generators,
        } = listcomp;

        // Infer the element type using the outer type context.
        let elts = [[Some(elt.as_ref())]];
        let mut infer_elt_ty =
            |builder: &mut Self, (_, elt, tcx)| builder.infer_expression(elt, tcx);

        self.infer_collection_literal(
            KnownClass::List,
            Some(listcomp.into()),
            &elts,
            &mut infer_elt_ty,
            tcx,
        );

        self.infer_comprehensions(generators);
    }

    fn infer_set_comprehension_expression_scope(
        &mut self,
        setcomp: &ast::ExprSetComp,
        tcx: TypeContext<'db>,
    ) {
        let ast::ExprSetComp {
            range: _,
            node_index: _,
            elt,
            generators,
        } = setcomp;

        // Infer the element type using the outer type context.
        let elts = [[Some(elt.as_ref())]];
        let mut infer_elt_ty =
            |builder: &mut Self, (_, elt, tcx)| builder.infer_expression(elt, tcx);

        self.infer_collection_literal(
            KnownClass::Set,
            Some(setcomp.into()),
            &elts,
            &mut infer_elt_ty,
            tcx,
        );

        self.infer_comprehensions(generators);
    }

    fn infer_dict_comprehension_expression_scope(
        &mut self,
        dictcomp: &ast::ExprDictComp,
        tcx: TypeContext<'db>,
    ) {
        let ast::ExprDictComp {
            range: _,
            node_index: _,
            key,
            value,
            generators,
        } = dictcomp;

        if key.is_some() {
            // Infer the key and value types using the outer type context.
            let elts = [[key.as_deref(), Some(value.as_ref())]];
            let mut infer_elt_ty =
                |builder: &mut Self, (_, elt, tcx)| builder.infer_expression(elt, tcx);

            self.infer_collection_literal(
                KnownClass::Dict,
                Some(dictcomp.into()),
                &elts,
                &mut infer_elt_ty,
                tcx,
            );
        } else {
            // Dict-unpack comprehensions are typed by the outer expression inference. Inferring
            // them through the collection-literal helper here would report the same invalid
            // mapping diagnostic twice.
            self.infer_expression(value, TypeContext::default());
        }

        self.infer_comprehensions(generators);
    }

    fn infer_comprehensions(&mut self, comprehensions: &[ast::Comprehension]) {
        let mut comprehensions_iter = comprehensions.iter();
        let Some(first_comprehension) = comprehensions_iter.next() else {
            unreachable!("Comprehension must contain at least one generator");
        };
        self.infer_comprehension(first_comprehension, true);
        for comprehension in comprehensions_iter {
            self.infer_comprehension(comprehension, false);
        }
    }

    fn infer_comprehension(&mut self, comprehension: &ast::Comprehension, is_first: bool) {
        let ast::Comprehension {
            range: _,
            node_index: _,
            target,
            iter,
            ifs,
            is_async: _,
        } = comprehension;

        self.infer_target(target, iter, &|builder, tcx| {
            // TODO: `infer_comprehension_definition` reports a diagnostic if `iter_ty` isn't iterable
            //  but only if the target is a name. We should report a diagnostic here if the target isn't a name:
            //  `[... for a.x in not_iterable]
            if is_first {
                infer_same_file_expression_type(builder.db(), builder.index.expression(iter), tcx)
            } else {
                builder.infer_maybe_standalone_expression(iter, tcx)
            }
            .iterate(builder.db())
            .homogeneous_element_type(builder.db())
        });

        for expr in ifs {
            self.infer_maybe_standalone_expression(expr, TypeContext::default());
        }
    }

    fn infer_comprehension_definition(
        &mut self,
        comprehension: &ComprehensionDefinitionKind<'db>,
        definition: Definition<'db>,
    ) {
        let iterable = comprehension.iterable(self.module());
        let target = comprehension.target(self.module());

        let mut infer_iterable_type = || {
            let expression = self.index.expression(iterable);
            let result = infer_expression_types(self.db(), expression, TypeContext::default());
            let iterable_type = result.expression_type(iterable);
            let element_type = if comprehension.is_async() {
                None
            } else {
                self.fixed_length_iterable_element_type(iterable, |expr| {
                    result.expression_type(expr)
                })
            };

            // Two things are different if it's the first comprehension:
            // (1) We must lookup the `ScopedExpressionId` of the iterable expression in the outer scope,
            //     because that's the scope we visit it in in the semantic index builder
            // (2) We must *not* call `self.extend()` on the result of the type inference,
            //     because `ScopedExpressionId`s are only meaningful within their own scope, so
            //     we'd add types for random wrong expressions in the current scope
            if !(comprehension.is_first() && target.is_name_expr()) {
                self.extend_expression_unchecked(result);
            }

            (iterable_type, element_type)
        };

        let target_type = match comprehension.target_kind() {
            TargetKind::Sequence(unpack_position, unpack) => {
                let unpacked = infer_unpack_types(self.db(), unpack);
                if unpack_position == UnpackPosition::First {
                    self.context.extend(unpacked.diagnostics());
                }

                unpacked.expression_type(target)
            }
            TargetKind::Single => {
                let (iterable_type, element_type) = infer_iterable_type();

                if let Some(element_type) = element_type {
                    element_type
                } else {
                    iterable_type
                        .try_iterate_with_mode(
                            self.db(),
                            EvaluationMode::from_is_async(comprehension.is_async()),
                        )
                        .map(|tuple| tuple.homogeneous_element_type(self.db()))
                        .unwrap_or_else(|err| {
                            err.report_diagnostic(&self.context, iterable_type, iterable.into());
                            err.fallback_element_type(self.db())
                        })
                }
            }
        };

        self.expressions.insert(target.into(), target_type);
        self.add_binding(target.into(), definition)
            .insert(self, target_type);
    }

    fn infer_named_expression(&mut self, named: &ast::ExprNamed) -> Type<'db> {
        // See https://peps.python.org/pep-0572/#differences-between-assignment-expressions-and-assignment-statements
        if named.target.is_name_expr() {
            let definition = self.index.expect_single_definition(named);
            let result = infer_definition_types(self.db(), definition);
            self.extend_definition(definition, result);
            result.binding_type(definition)
        } else {
            // For syntactically invalid targets, we still need to run type inference:
            self.infer_expression(&named.target, TypeContext::default());
            self.infer_expression(&named.value, TypeContext::default());
            Type::unknown()
        }
    }

    fn infer_named_expression_definition(
        &mut self,
        named: &'ast ast::ExprNamed,
        definition: Definition<'db>,
    ) -> Type<'db> {
        let ast::ExprNamed {
            range: _,
            node_index: _,
            target,
            value,
        } = named;

        let add = self.add_binding(named.target.as_ref().into(), definition);

        let ty = self.infer_expression(value, add.type_context());
        self.store_expression_type(target, ty);
        add.insert(self, ty)
    }

    fn infer_if_expression(
        &mut self,
        if_expression: &ast::ExprIf,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprIf {
            range: _,
            node_index: _,
            test,
            body,
            orelse,
        } = if_expression;

        let test_ty = self.infer_maybe_standalone_expression(test, TypeContext::default());
        let (body_ty, orelse_ty) =
            if allows_collection_literal_peer_context(tcx) && is_collection_literal(body) {
                // Infer the peer branch first so the body can use its type as context.
                let orelse_ty = self.infer_expression(orelse, tcx);
                let body_ty = self.infer_expression_with_collection_literal_peer_context(
                    body,
                    tcx,
                    Some(orelse_ty),
                );
                (body_ty, orelse_ty)
            } else {
                let body_ty = self.infer_expression(body, tcx);
                let orelse_ty = self.infer_expression_with_collection_literal_peer_context(
                    orelse,
                    tcx,
                    Some(body_ty),
                );
                (body_ty, orelse_ty)
            };

        match test_ty.try_bool(self.db()).unwrap_or_else(|err| {
            err.report_diagnostic(&self.context, &**test);
            err.fallback_truthiness()
        }) {
            Truthiness::AlwaysTrue => body_ty,
            Truthiness::AlwaysFalse => orelse_ty,
            Truthiness::Ambiguous => UnionType::from_two_elements(self.db(), body_ty, orelse_ty),
        }
    }

    fn infer_lambda_body(&mut self, lambda_expression: &ast::ExprLambda, tcx: TypeContext<'db>) {
        self.infer_expression(&lambda_expression.body, tcx);
    }

    fn infer_lambda_expression(
        &mut self,
        lambda_expression: &ast::ExprLambda,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprLambda {
            range: _,
            node_index: _,
            parameters,
            body: _,
        } = lambda_expression;

        // In stub files, default values may reference names that are defined later in the file.
        let in_stub = self.in_stub();
        let previous_deferred_state = std::mem::replace(&mut self.deferred_state, in_stub.into());

        let callable_tcx = if let Some(tcx) = tcx.annotation
            // TODO: We could perform multi-inference here if there are multiple `Callable` annotations
            // in the union/intersection.
            && let Some(callable) = tcx
                .filter_union(self.db(), Type::is_callable_type)
                .as_callable()
        {
            match callable.signatures(self.db()).overloads.as_slice() {
                [signature] => Some(signature),
                // TODO: We could similarly perform multi-inference here if there are multiple overloads.
                _ => None,
            }
        } else {
            None
        };

        // Extract the annotated parameter types.
        //
        // Note that `Callable` annotations are only valid for positional parameters.
        let mut parameter_types = match callable_tcx {
            None => [].iter(),
            Some(signature) => signature.parameters().into_iter(),
        }
        .map(Parameter::annotated_type);

        let parameters = if let Some(parameters) = parameters {
            let positional_only = parameters
                .posonlyargs
                .iter()
                .map(|param| {
                    let parameter = Parameter::positional_only(Some(param.name().id.clone()))
                        .with_optional_default_type(param.default().map(|default_expr| {
                            self.infer_expression(default_expr, TypeContext::default())
                                .replace_parameter_defaults(self.db())
                        }));

                    if let Some(annotated_type) = parameter_types.next() {
                        parameter.with_annotated_type(annotated_type)
                    } else {
                        parameter
                    }
                })
                .collect::<Vec<_>>();
            let positional_or_keyword = parameters
                .args
                .iter()
                .map(|param| {
                    let parameter = Parameter::positional_or_keyword(param.name().id.clone())
                        .with_optional_default_type(param.default().map(|default_expr| {
                            self.infer_expression(default_expr, TypeContext::default())
                                .replace_parameter_defaults(self.db())
                        }));

                    if let Some(annotated_type) = parameter_types.next() {
                        parameter.with_annotated_type(annotated_type)
                    } else {
                        parameter
                    }
                })
                .collect::<Vec<_>>();
            let variadic = parameters
                .vararg
                .as_ref()
                .map(|param| Parameter::variadic(param.name().id.clone()));
            let keyword_only = parameters
                .kwonlyargs
                .iter()
                .map(|param| {
                    Parameter::keyword_only(param.name().id.clone()).with_optional_default_type(
                        param.default().map(|default_expr| {
                            self.infer_expression(default_expr, TypeContext::default())
                                .replace_parameter_defaults(self.db())
                        }),
                    )
                })
                .collect::<Vec<_>>();
            let keyword_variadic = parameters
                .kwarg
                .as_ref()
                .map(|param| Parameter::keyword_variadic(param.name().id.clone()));

            let parameters = positional_only
                .into_iter()
                .chain(positional_or_keyword)
                .chain(variadic)
                .chain(keyword_only)
                .chain(keyword_variadic);

            Parameters::from_annotation(self.db(), parameters)
        } else {
            Parameters::empty()
        };

        self.deferred_state = previous_deferred_state;

        let Some(scope_id) = self
            .index
            .try_node_scope(NodeWithScopeRef::Lambda(lambda_expression))
        else {
            return Type::unknown();
        };

        let scope = scope_id.to_scope_id(self.db(), self.file());

        // If we have a direct `Callable` type context, we can infer the body with the annotated
        // return type as type context.
        let return_tcx = if let Some(signature) = callable_tcx {
            match signature.return_ty {
                Type::Dynamic(DynamicType::Unknown) => TypeContext::new(None),
                _ => TypeContext::new(Some(signature.return_ty)),
            }
        } else {
            // TODO: Useful inference of a lambda's return type will require a different approach,
            // which does the inference of the body expression based on arguments at each call site,
            // rather than eagerly computing a return type without knowing the argument types.
            TypeContext::new(None)
        };

        let inference = infer_scope_types(self.db(), scope, return_tcx);
        self.extend_scope(inference);

        let return_ty = inference.expression_type(lambda_expression.body.as_ref());
        Type::Callable(CallableType::new(
            self.db(),
            CallableSignature::single(Signature::new(parameters, return_ty)),
            CallableTypeKind::FunctionLike,
            CallableFunctionProvenance::ImplicitReturn,
        ))
    }

    /// Attempt to narrow a splatted dictionary argument based on the narrowed types of individual
    /// keys, if any.
    ///
    /// Returns the intersection between the dictionary type and a synthesized typed dict of any narrowed
    /// keys, or `None` otherwise.
    fn try_narrow_dict_kwargs(
        &self,
        argument_type: Type<'db>,
        argument: &'ast ast::ArgOrKeyword,
    ) -> Option<Type<'db>> {
        let db = self.db();
        let file_scope_id = self.scope().file_scope_id(db);
        let use_def = self.index.use_def_map(file_scope_id);

        let keyword = argument.as_variadic()?;

        if !argument_type
            .as_nominal_instance()?
            .has_known_class(db, KnownClass::Dict)
        {
            return None;
        }

        let definition_key = |definition: Definition<'_>| {
            let key = match definition.kind(db) {
                DefinitionKind::DictKeyAssignment(assignment) => assignment.key(self.module()),
                DefinitionKind::Assignment(assignment) => {
                    &assignment.target(self.module()).as_subscript_expr()?.slice
                }
                DefinitionKind::AnnotatedAssignment(assignment) => {
                    &assignment.target(self.module()).as_subscript_expr()?.slice
                }
                _ => return None,
            };

            Some(key.as_string_literal_expr()?.value.to_str())
        };

        // Collect the types of each distinct key.
        let mut elements: Vec<(&str, Type<'db>)> = Vec::new();

        for bindings in use_def.multi_bindings_at_use(keyword.scoped_use_id(db, self.file())) {
            let place = place_from_bindings_with_reachability_cache(
                db,
                bindings.clone(),
                self.reachability_cache(),
            );
            let Some(key) = place.first_definition.and_then(definition_key) else {
                continue;
            };

            if let Place::Defined(DefinedPlace {
                ty: field_ty,
                definedness: Definedness::AlwaysDefined,
                ..
            }) = place.place
            {
                elements.push((key, field_ty));
            }
        }

        if elements.is_empty() {
            return None;
        }

        // Synthesize overloads for `__getitem__` based on known dictionary elements.
        let getitem_overloads = elements.into_iter().map(|(name, ty)| {
            Signature::new(
                Parameters::standard([
                    Parameter::positional_only(Some(Name::new_static("self"))),
                    Parameter::positional_or_keyword(Name::new_static("key"))
                        .with_annotated_type(Type::string_literal(db, name)),
                ]),
                ty,
            )
        });

        let getitem_protocol = Type::protocol_with_methods(
            db,
            [(
                "__getitem__",
                CallableType::new(
                    db,
                    CallableSignature::from_overloads(getitem_overloads),
                    CallableTypeKind::FunctionLike,
                    CallableFunctionProvenance::None,
                ),
            )],
        );

        // Note that we return an intersection to preserve the original dictionary type,
        // as it may contain keys that were not explicitly assigned to.
        Some(IntersectionType::from_elements(
            db,
            [argument_type, getitem_protocol],
        ))
    }

    /// Infer the variadic argument types needed for call binding and emit the shared diagnostics
    /// for invalid `*args` and `**kwargs` inputs.
    fn prepare_call_arguments<'a>(
        &mut self,
        arguments: &'a ast::Arguments,
    ) -> CallArguments<'a, 'db> {
        let call_arguments =
            CallArguments::from_arguments(arguments, |arg_or_keyword, splatted_value| {
                let ty = self.get_or_infer_expression(splatted_value, TypeContext::default());
                if let ast::ArgOrKeyword::Arg(argument) = arg_or_keyword
                    && argument.is_starred_expr()
                {
                    self.store_expression_type(argument, ty);
                } else if let Some(ty) = self.try_narrow_dict_kwargs(ty, arg_or_keyword) {
                    return ty;
                }

                ty
            });

        for arg in &arguments.args {
            if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = arg {
                let iterable_type = self.expression_type(value);
                if let Err(err) = iterable_type.try_iterate(self.db()) {
                    err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
                }
            }
        }

        for keyword in arguments
            .keywords
            .iter()
            .filter(|keyword| keyword.arg.is_none())
        {
            let mapping_type = self.expression_type(&keyword.value);

            if mapping_type.as_paramspec_typevar(self.db()).is_some()
                || mapping_type.unpack_keys_and_items(self.db()).is_some()
            {
                continue;
            }

            let Some(builder) = self
                .context
                .report_lint(&INVALID_ARGUMENT_TYPE, &keyword.value)
            else {
                continue;
            };

            builder
                .into_diagnostic("Argument expression after ** must be a mapping type")
                .set_primary_message(format_args!("Found `{}`", mapping_type.display(self.db())));
        }

        call_arguments
    }

    // TODO: This should not be needed once we use constraint sets to track the usages of each
    // container literal across a scope.
    // https://github.com/astral-sh/ty/issues/3507
    fn collection_use_constraint_from_specialization(
        &self,
        identity_instance: Type<'db>,
        receiver_generic_context: Option<GenericContext<'db>>,
        call_specialization: Specialization<'db>,
    ) -> Option<Type<'db>> {
        let constraint = identity_instance.apply_specialization(self.db(), call_specialization);
        let Some(receiver_generic_context) = receiver_generic_context else {
            return Some(constraint);
        };

        // Method-local typevars describe requirements imposed by the method, not concrete element
        // types learned for the collection. Until collection-use constraints are represented as
        // projected constraint sets, avoid leaking those method-local typevars into the inferred
        // collection literal type.
        if any_over_type(self.db(), constraint, false, |ty| {
            ty.as_typevar().is_some_and(|typevar| {
                !receiver_generic_context.contains(self.db(), typevar.identity(self.db()))
            })
        }) {
            return None;
        }

        Some(constraint)
    }

    fn infer_call_expression(
        &mut self,
        call_expression: &ast::ExprCall,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let callable_type =
            self.infer_maybe_standalone_expression(&call_expression.func, TypeContext::default());

        self.infer_call_expression_impl(call_expression, callable_type, tcx)
    }

    fn infer_empty_list_or_set_constructor(
        &mut self,
        collection_class: KnownClass,
        call_expression: &ast::ExprCall,
        tcx: TypeContext<'db>,
    ) -> Option<Type<'db>> {
        let elements: [[Option<&ast::Expr>; 1]; 0] = [];
        let mut infer_element_ty = |_: &mut Self, _| Type::unknown();

        self.infer_collection_literal(
            collection_class,
            Some(call_expression.into()),
            &elements,
            &mut infer_element_ty,
            tcx,
        )
    }

    /// Infers a truthiness-refined `range` instance for literal built-in `range(...)` calls.
    ///
    /// The refinement only records whether the constructed range is statically non-empty. Dynamic
    /// arguments, keyword arguments, starred arguments, shadowed `range` callables, and invalid
    /// literal forms fall back to the ordinary `range` instance.
    ///
    /// This uses the argument types inferred by normal call binding; it does not re-infer
    /// arguments just to compute the refinement.
    ///
    /// ```python
    /// range(3)        # known non-empty
    /// range(3, 0, -1) # known non-empty
    /// range(n)        # ordinary range
    /// ```
    fn infer_builtin_range_instance_type(
        &self,
        callable_type: Type<'db>,
        arguments: &ast::Arguments,
        call_arguments: &CallArguments<'_, 'db>,
    ) -> Option<Type<'db>> {
        let Type::ClassLiteral(class) = callable_type else {
            return None;
        };
        if !class.is_known(self.db(), KnownClass::Range)
            || !arguments.keywords.is_empty()
            || arguments.args.iter().any(ast::Expr::is_starred_expr)
        {
            return None;
        }

        let int_literal = |argument_index: usize| {
            call_arguments
                .argument_types(argument_index)?
                .get_default()?
                .as_int_literal()
        };

        let is_non_empty = match arguments.args.len() {
            1 => int_literal(0)? > 0,
            2 => int_literal(0)? < int_literal(1)?,
            3 => {
                let start = int_literal(0)?;
                let stop = int_literal(1)?;
                let step = int_literal(2)?;

                match step.cmp(&0) {
                    std::cmp::Ordering::Greater => start < stop,
                    std::cmp::Ordering::Less => start > stop,
                    std::cmp::Ordering::Equal => return None,
                }
            }
            _ => return None,
        };

        Some(Type::KnownInstance(KnownInstanceType::Range {
            is_non_empty,
        }))
    }

    fn infer_call_expression_impl(
        &mut self,
        call_expression: &ast::ExprCall,
        callable_type: Type<'db>,
        call_expression_tcx: TypeContext<'db>,
    ) -> Type<'db> {
        fn report_missing_implicit_constructor_call<'db>(
            context: &InferContext<'db, '_>,
            db: &'db dyn Db,
            callable_type: Type<'db>,
            call_expression: &ast::ExprCall,
            bindings: &Bindings<'db>,
        ) {
            if bindings.has_implicit_dunder_new_is_possibly_unbound() {
                if let Some(builder) =
                    context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, call_expression)
                {
                    builder.into_diagnostic(format_args!(
                        "Method `__new__` on type `{}` may be missing.",
                        callable_type.display(db),
                    ));
                }
            }

            if bindings.has_implicit_dunder_init_is_possibly_unbound() {
                if let Some(builder) =
                    context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, call_expression)
                {
                    builder.into_diagnostic(format_args!(
                        "Method `__init__` on type `{}` may be missing.",
                        callable_type.display(db),
                    ));
                }
            }
        }

        let ast::ExprCall {
            range: _,
            node_index: _,
            func,
            arguments,
        } = call_expression;

        // Semantic indexing recognizes only bare empty constructor calls. Confirm that the name
        // still resolves to the corresponding builtin before using later collection constraints.
        let collection_initializer_class = if arguments.is_empty()
            && self
                .index
                .try_expression(call_expression)
                .and_then(|expression| expression.assigned_to(self.db()))
                .is_some()
            && let Some(name) = func.as_name_expr()
            && let Some(known_class) = callable_type
                .as_class_literal()
                .and_then(|class| class.known(self.db()))
            && matches!(
                (name.id.as_str(), known_class),
                ("list", KnownClass::List) | ("set", KnownClass::Set) | ("dict", KnownClass::Dict)
            ) {
            Some(known_class)
        } else {
            None
        };

        if callable_type
            .as_class_literal()
            .is_some_and(|class_literal| class_literal.is_known(self.db(), KnownClass::Dict))
            && let Some(ty) = self.infer_keyword_only_dict_call(
                func,
                arguments,
                (collection_initializer_class == Some(KnownClass::Dict))
                    .then_some(call_expression.into()),
                call_expression_tcx,
            )
        {
            return ty;
        }

        // Handle 3-argument `type(name, bases, dict)`.
        if let Type::ClassLiteral(class) = callable_type
            && class.is_known(self.db(), KnownClass::Type)
        {
            return self.infer_builtins_type_call(call_expression, None);
        }

        // Handle `types.new_class(name, bases, ...)`.
        if let Some(function) = callable_type.as_function_literal()
            && function.is_known(self.db(), KnownFunction::NewClass)
        {
            return self.infer_new_class_call(call_expression, None);
        }

        // Handle `typing.NamedTuple(typename, fields)` and `collections.namedtuple(typename, field_names)`.
        if let Some(namedtuple_kind) = NamedTupleKind::from_type(self.db(), callable_type) {
            return self.infer_namedtuple_call_expression(call_expression, None, namedtuple_kind);
        }

        // Handle `Enum(name, members)`.
        if let Some(base_class) = enum_call::enum_functional_call_base(self.db(), callable_type)
            && let Some(ty) = self.infer_enum_call_expression(call_expression, None, base_class)
        {
            return ty;
        }

        if let Some(typed_dict_module) = TypedDictModule::from_type(self.db(), callable_type) {
            return self.infer_typeddict_call_expression(call_expression, None, typed_dict_module);
        }

        if callable_type == Type::SpecialForm(SpecialFormType::TypeForm) {
            return self.infer_type_form_call_expression(call_expression);
        }

        if callable_type.is_notimplemented(self.db()) {
            if let Some(builder) = self
                .context
                .report_lint(&CALL_NON_CALLABLE, call_expression)
            {
                let mut diagnostic = builder.into_diagnostic("`NotImplemented` is not callable");
                diagnostic.annotate(
                    self.context
                        .secondary(&**func)
                        .message("Did you mean `NotImplementedError`?"),
                );
                diagnostic.set_concise_message(
                    "`NotImplemented` is not callable - did you mean `NotImplementedError`?",
                );
            }
            return Type::unknown();
        }

        let class = match callable_type {
            Type::ClassLiteral(class) => Some(ClassType::NonGeneric(class)),
            Type::GenericAlias(generic) => Some(ClassType::Generic(generic)),
            Type::SubclassOf(subclass) => subclass.subclass_of().into_class(self.db()),
            _ => None,
        };

        // Prepare `TypedDict` constructor calls before variadic argument setup so field-directed
        // value inference becomes canonical before `**kwargs` expressions are inferred.
        let has_prepared_typed_dict_constructor = class
            .filter(|class| class.is_typed_dict(self.db()))
            .map(|class| {
                let typed_dict = TypedDictType::new(class);
                let form = TypedDictConstructorForm::from_arguments(arguments);
                self.prepare_typed_dict_constructor(
                    typed_dict,
                    form,
                    arguments,
                    func.as_ref().into(),
                );
            })
            .is_some();

        // We don't call `Type::try_call`, because we want to perform type inference on the
        // arguments after matching them to parameters, but before checking that the argument types
        // are assignable to any parameter annotations.
        let mut call_arguments = self.prepare_call_arguments(arguments);

        // Special handling for `TypedDict` method calls
        if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
            let value_type = self.expression_type(value);
            let method_name = attr.id.as_str();

            if let Type::TypedDict(typed_dict_ty) = value_type
                && matches!(method_name, "get" | "pop" | "setdefault")
                && !arguments.args.is_empty()

                // Validate the key argument for `TypedDict` methods
                && let Some(first_arg) = arguments.args.first()
                && let Some(key) = (match first_arg {
                    ast::Expr::StringLiteral(ast::ExprStringLiteral {
                        value: key_literal,
                        ..
                    }) => Some(key_literal.to_str()),
                    _ => self
                        .speculate_without_diagnostics()
                        .get_or_infer_expression(first_arg, TypeContext::default())
                        .as_string_literal()
                        .map(|key_literal| key_literal.value(self.db())),
                })
            {
                let items = typed_dict_ty.items(self.db());
                let is_declared = items.contains_key(key);

                if let Some(field) = typed_dict_ty.item(self.db(), key) {
                    // Key exists - check if it's a `pop()` on a required field
                    if is_declared && method_name == "pop" && field.is_required() {
                        report_cannot_pop_required_field_on_typed_dict(
                            &self.context,
                            first_arg.into(),
                            Type::TypedDict(typed_dict_ty),
                            key,
                        );
                        return Type::unknown();
                    }

                    if !is_declared
                        && method_name == "get"
                        && arguments.keywords.is_empty()
                        && matches!(arguments.args.len(), 1 | 2)
                    {
                        let default_ty = if let Some(default) = arguments.args.get(1) {
                            self.get_or_infer_expression(
                                default,
                                TypeContext::new(Some(field.declared_ty)),
                            )
                        } else {
                            Type::none(self.db())
                        };
                        return UnionType::from_two_elements(
                            self.db(),
                            field.declared_ty,
                            default_ty,
                        );
                    }

                    if !is_declared && field.is_read_only() {
                        let mutation = match method_name {
                            "pop"
                                if arguments.keywords.is_empty()
                                    && matches!(arguments.args.len(), 1 | 2) =>
                            {
                                Some(("pop", "from"))
                            }
                            "setdefault"
                                if arguments.keywords.is_empty() && arguments.args.len() == 2 =>
                            {
                                Some(("set default for", "on"))
                            }
                            _ => None,
                        };
                        if let Some((action, preposition)) = mutation {
                            if let Some(builder) =
                                self.context.report_lint(&INVALID_ARGUMENT_TYPE, first_arg)
                            {
                                builder.into_diagnostic(format_args!(
                                    "Cannot {action} read-only extra item \"{key}\" {preposition} TypedDict `{}`",
                                    Type::TypedDict(typed_dict_ty).display(self.db()),
                                ));
                            }
                            return Type::unknown();
                        }
                    }

                    // Unknown literal keys are concrete extra items, so mutating operations can
                    // use their extra-items type even when arbitrary `str` keys are unsafe.
                    if !is_declared && !field.is_read_only() {
                        match method_name {
                            "pop"
                                if arguments.keywords.is_empty()
                                    && matches!(arguments.args.len(), 1 | 2) =>
                            {
                                return arguments.args.get(1).map_or(
                                    field.declared_ty,
                                    |default| {
                                        UnionType::from_two_elements(
                                            self.db(),
                                            field.declared_ty,
                                            self.get_or_infer_expression(
                                                default,
                                                TypeContext::new(Some(field.declared_ty)),
                                            ),
                                        )
                                    },
                                );
                            }
                            "setdefault"
                                if arguments.keywords.is_empty() && arguments.args.len() == 2 =>
                            {
                                let default = &arguments.args[1];
                                let default_ty = self.get_or_infer_expression(
                                    default,
                                    TypeContext::new(Some(field.declared_ty)),
                                );
                                TypedDictKeyAssignment {
                                    context: &self.context,
                                    typed_dict: typed_dict_ty,
                                    full_object_ty: None,
                                    key,
                                    value_ty: default_ty,
                                    typed_dict_node: value.as_ref().into(),
                                    key_node: first_arg.into(),
                                    value_node: default.into(),
                                    assignment_kind: TypedDictAssignmentKind::Constructor,
                                    emit_diagnostic: true,
                                }
                                .validate();
                                return field.declared_ty;
                            }
                            _ => {}
                        }
                    }
                } else if method_name != "get" {
                    // Key not found, report error with suggestion and return early
                    let key_ty = Type::string_literal(self.db(), key);
                    report_invalid_key_on_typed_dict(
                        &self.context,
                        first_arg.into(),
                        first_arg.into(),
                        Type::TypedDict(typed_dict_ty),
                        None,
                        key_ty,
                        items,
                    );
                    // Return `Unknown` to prevent the overload system from generating its own error
                    return Type::unknown();
                }
            }
        }

        if let Type::FunctionLiteral(function) = callable_type {
            // Make sure that the `function.definition` is only called when the function is defined
            // in the same file as the one we're currently inferring the types for. This is because
            // the `definition` method accesses the semantic index, which could create a
            // cross-module AST dependency.
            if function.file(self.db()) == self.file()
                && function.definition(self.db()).scope(self.db()) == self.scope()
            {
                self.called_functions.insert(function);
            }

            // Warn when `final()` is called as a function (not a decorator).
            // Type checkers cannot interpret this usage and will not prevent subclassing.
            if function.is_known(self.db(), KnownFunction::Final) {
                if let Some(builder) = self
                    .context
                    .report_lint(&INEFFECTIVE_FINAL, call_expression)
                {
                    let mut diagnostic = builder.into_diagnostic(
                        "Type checkers will not prevent subclassing when `final()` is called as a function",
                    );
                    diagnostic.info("Use `@final` as a decorator on a class or method instead");
                }
            }
        }

        // Check for unsound calls to abstract classmethods/staticmethods on class objects
        match callable_type {
            Type::BoundMethod(bound_method) => {
                let function = bound_method.function(self.db());
                if let Some(class) = bound_method
                    .self_instance(self.db())
                    .to_class_type(self.db())
                {
                    if function.is_classmethod(self.db())
                        && function.as_abstract_method(self.db(), class).is_some()
                        && function.has_trivial_body(self.db())
                    {
                        report_call_to_abstract_method(
                            &self.context,
                            call_expression,
                            function,
                            "classmethod",
                        );
                    }
                }
            }
            Type::FunctionLiteral(function) if function.is_staticmethod(self.db()) => {
                if let ast::Expr::Attribute(ast::ExprAttribute { value, .. }) = func.as_ref() {
                    let value_type = self.expression_type(value);
                    if let Some(class) = value_type.to_class_type(self.db()) {
                        if function.as_abstract_method(self.db(), class).is_some()
                            && function.has_trivial_body(self.db())
                        {
                            report_call_to_abstract_method(
                                &self.context,
                                call_expression,
                                function,
                                "staticmethod",
                            );
                        }
                    }
                }
            }
            _ => {}
        }

        if let Some(class) = class {
            // It might look odd here that we emit an error for class-literals and generic aliases but not
            // `type[]` types. But it's deliberate! The typing spec explicitly mandates that `type[]` types
            // can be called even though class-literals cannot. This is because even though a protocol class
            // `SomeProtocol` is always an abstract class, `type[SomeProtocol]` can be a concrete subclass of
            // that protocol -- and indeed, according to the spec, type checkers must disallow abstract
            // subclasses of the protocol to be passed to parameters that accept `type[SomeProtocol]`.
            // <https://typing.python.org/en/latest/spec/protocol.html#type-and-class-objects-vs-protocols>.
            if !callable_type.is_subclass_of()
                && let Some(protocol) = class.into_protocol_class(self.db())
            {
                report_attempted_protocol_instantiation(&self.context, call_expression, protocol);
            }

            // Inference of correctly-placed `TypeVar`, `ParamSpec`, `NewType`, and
            // `TypeAliasType` definitions is done in `infer_legacy_typevar`,
            // `infer_paramspec`, `infer_newtype_expression`, and
            // `infer_typealiastype_call`, and doesn't use the full call-binding
            // machinery. If we reach here, it means that someone is trying to
            // instantiate one of these in an invalid context.
            match class.known(self.db()) {
                Some(KnownClass::TypeVar | KnownClass::ExtensionsTypeVar) => {
                    if let Some(builder) = self
                        .context
                        .report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)
                    {
                        builder.into_diagnostic(
                            "A `TypeVar` definition must be a simple variable assignment",
                        );
                    }
                }
                Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec) => {
                    if let Some(builder) = self
                        .context
                        .report_lint(&INVALID_PARAMSPEC, call_expression)
                    {
                        builder.into_diagnostic(
                            "A `ParamSpec` definition must be a simple variable assignment",
                        );
                    }
                }
                Some(KnownClass::NewType) => {
                    if let Some(builder) =
                        self.context.report_lint(&INVALID_NEWTYPE, call_expression)
                    {
                        builder.into_diagnostic(
                            "A `NewType` definition must be a simple variable assignment",
                        );
                    }
                }
                Some(KnownClass::TypeAliasType) => {
                    if let Some(builder) = self
                        .context
                        .report_lint(&INVALID_TYPE_ALIAS_TYPE, call_expression)
                    {
                        builder.into_diagnostic(
                            "A `TypeAliasType` definition must be a simple variable assignment",
                        );
                    }
                }
                _ => {}
            }
        }

        let mut bindings = self
            .bindings_for_call(callable_type)
            .match_parameters(self.db(), &call_arguments);

        report_missing_implicit_constructor_call(
            &self.context,
            self.db(),
            callable_type,
            call_expression,
            &bindings,
        );

        let bindings_result = self.infer_and_check_argument_types(
            ArgumentsIter::from_ast(arguments),
            &mut call_arguments,
            &mut |builder, (_, expr, tcx)| {
                if has_prepared_typed_dict_constructor {
                    builder.get_or_infer_expression(expr, tcx)
                } else {
                    builder.infer_expression(expr, tcx)
                }
            },
            &mut bindings,
            call_expression_tcx,
        );

        let mut bindings = match bindings_result {
            Ok(()) => bindings,
            Err(_) => {
                bindings.report_diagnostics(&self.context, call_expression.into());
                return bindings.return_type(self.db());
            }
        };

        for binding in bindings.iter_flat_mut() {
            let binding_type = binding.callable_type;
            for (_, overload) in binding.matching_overloads_mut() {
                match binding_type {
                    Type::FunctionLiteral(function_literal) => {
                        if let Some(known_function) = function_literal.known(self.db()) {
                            known_function.check_call(
                                &self.context,
                                overload,
                                &call_arguments,
                                call_expression,
                                self.file(),
                            );
                        }
                    }
                    Type::ClassLiteral(class) => {
                        if let Some(known_class) = class.known(self.db()) {
                            known_class.check_call(
                                &self.context,
                                self.index,
                                overload,
                                call_expression,
                            );
                        }
                    }
                    Type::Never => {
                        // In unreachable sections of code, we infer `Never` for symbols that were
                        // defined outside the unreachable part. We still want to emit revealed-type
                        // diagnostics in these sections, so check on the name of the callable here
                        // and assume that it's actually `typing.reveal_type`.
                        let is_reveal_type = match func.as_ref() {
                            ast::Expr::Name(name) => name.id == "reveal_type",
                            ast::Expr::Attribute(attr) => {
                                attr.attr.id == "reveal_type" && is_dotted_name(func)
                            }
                            _ => false,
                        };
                        if is_reveal_type && let Some(first_arg) = arguments.args.first() {
                            let revealed_ty = self.expression_type(first_arg);
                            report_revealed_type(&self.context, revealed_ty, first_arg);
                        }
                    }
                    _ => {}
                }
            }
        }

        // Record the constraints for the receiver of a bound method call, if the receiver is an
        // unannotated collection initializer.
        if let ast::Expr::Attribute(attribute @ ast::ExprAttribute { value, .. }) = func.as_ref() {
            let value_type = self.expression_type(value);

            if let Some(collection_def) = self.index.unannotated_collection_initializer(value)
                && let Some((collection_literal, _)) = value_type.class_specialization(self.db())
            {
                let identity_instance = Type::instance(
                    self.db(),
                    collection_literal.identity_specialization(self.db()),
                );
                let collection_generic_context = collection_literal.generic_context(self.db());

                let mut identity_bindings = self
                    .infer_attribute_load_impl(attribute, identity_instance)
                    .bindings(self.db())
                    .match_parameters(self.db(), &call_arguments)
                    // Perform inference against the type variables on the receiver's generic context.
                    .with_generic_context(self.db(), collection_generic_context);

                let call_result = self
                    .speculate_without_diagnostics()
                    .infer_and_check_argument_types(
                        ArgumentsIter::from_ast(arguments),
                        &mut call_arguments,
                        // TODO: The argument types have already been inferred and stored in `call_arguments`.
                        // However, `value` would have been inferred to a be a collection with `Divergent`
                        // element types, meaning the type context for a given argument, by which the inferred
                        // type is keyed, may not be the same as the type context we get here. It is not immediately
                        // clear how to retrieve those types, and so we just re-infer the argument expressions
                        // for simplicity.
                        &mut |builder, (_, expr, tcx)| builder.infer_expression(expr, tcx),
                        &mut identity_bindings,
                        call_expression_tcx,
                    );

                if call_result.is_ok() {
                    let db = self.db();
                    for call_specialization in identity_bindings
                        .iter_flat()
                        .flat_map(CallableBinding::matching_overloads)
                        .filter_map(|(_, identity_overload)| identity_overload.specialization(db))
                    {
                        // Record the constraints on the receiver's generic context formed by
                        // the arguments to this bound method call.
                        let Some(constraints) = self.collection_use_constraint_from_specialization(
                            identity_instance,
                            collection_generic_context,
                            call_specialization,
                        ) else {
                            continue;
                        };

                        self.collection_use_constraints
                            .entry(collection_def)
                            .or_default()
                            .insert(constraints);
                    }
                }
            }
        }

        // `range(...)` always constructs a `range`, but with literal arguments we can preserve
        // whether that range is statically non-empty on the constructed instance itself.
        if let Some(instance_ty) =
            self.infer_builtin_range_instance_type(callable_type, arguments, &call_arguments)
        {
            bindings = bindings.with_constructed_instance_type(self.db(), instance_ty);
        }

        let db = self.db();
        let scope = self.scope();
        let return_ty = bindings.return_type(db);
        let return_ty = match collection_initializer_class {
            Some(collection_class @ (KnownClass::List | KnownClass::Set))
                if return_ty
                    .class_specialization(db)
                    .is_some_and(|(class, _)| class.is_known(db, collection_class)) =>
            {
                self.infer_empty_list_or_set_constructor(
                    collection_class,
                    call_expression,
                    call_expression_tcx,
                )
                .unwrap_or(return_ty)
            }
            _ => return_ty,
        };

        let find_narrowed_place = |argument_index: usize| match arguments.args.get(argument_index) {
            None => {
                // This branch looks extraneous, especially in the face of `missing-arguments`.
                // However, that lint won't be able to catch this:
                //
                // ```python
                // def f(v: object = object()) -> TypeIs[int]: ...
                //
                // if f(): ...
                // ```
                //
                // TODO: Will this report things that is actually fine?
                if let Some(builder) = self
                    .context
                    .report_lint(&INVALID_TYPE_GUARD_CALL, arguments)
                {
                    builder.into_diagnostic("Type guard call does not have a target");
                }
                None
            }
            Some(expr) => match PlaceExpr::try_from_expr(expr) {
                Some(place_expr) => place_table(db, scope).place_id(&place_expr),
                None => None,
            },
        };

        let narrowed_argument_index = || {
            bindings
                .single_element()
                .and_then(|binding| {
                    binding
                        .signature_type
                        .as_function_literal()
                        .or_else(|| binding.callable_type.as_function_literal())
                        .map(|function| {
                            usize::from(
                                function.has_implicit_receiver(db) && binding.bound_type.is_none(),
                            )
                        })
                })
                .unwrap_or(0)
        };

        match return_ty {
            Type::TypeIs(type_is) => match find_narrowed_place(narrowed_argument_index()) {
                Some(place) => type_is.bind(db, scope, place),
                None => return_ty,
            },
            Type::TypeGuard(type_guard) => match find_narrowed_place(narrowed_argument_index()) {
                Some(place) => type_guard.bind(db, scope, place),
                None => return_ty,
            },
            _ => return_ty,
        }
    }

    fn infer_starred_expression(
        &mut self,
        starred: &ast::ExprStarred,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprStarred {
            range: _,
            node_index: _,
            value,
            ctx: _,
        } = starred;

        let db = self.db();
        let iterable_type = self.infer_expression(value, tcx);
        iterable_type
            .try_iterate(db)
            .map(|spec| Type::tuple(TupleType::new(db, &spec)))
            .unwrap_or_else(|err| {
                err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
                Type::homogeneous_tuple(db, err.fallback_element_type(db))
            })
    }

    fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> {
        let ast::ExprYield {
            range: _,
            node_index: _,
            value,
        } = yield_expression;
        let Some(enclosing_function) =
            nearest_enclosing_function(self.db(), self.index, self.scope())
        else {
            let _ = self.infer_optional_expression(value.as_deref(), TypeContext::default());
            return Type::unknown();
        };
        let declared_return_ty = same_module_uncached_raw_signature(
            self.db(),
            enclosing_function,
            ReturnCallableTypeVarScope::Public,
        )
        .return_ty;
        let return_type_span = enclosing_function.spans(self.db()).return_type;

        let Some(generator_type_params) = declared_return_ty.generator_types(self.db()) else {
            let _ = self.infer_optional_expression(value.as_deref(), TypeContext::default());
            return Type::unknown();
        };

        let expected_yield_ty = generator_type_params.yield_ty;
        let tcx = TypeContext::new(expected_yield_ty);
        let yielded_ty = self
            .infer_optional_expression(value.as_deref(), tcx)
            .unwrap_or_else(|| Type::none(self.db()));
        let diagnostic_node: AnyNodeRef = value
            .as_deref()
            .map_or_else(|| yield_expression.into(), AnyNodeRef::from);

        if let Some(expected_yield_ty) = expected_yield_ty
            && !yielded_ty.is_assignable_to(self.db(), expected_yield_ty)
        {
            report_invalid_generator_yield_type(
                &self.context,
                diagnostic_node,
                return_type_span,
                expected_yield_ty,
                yielded_ty,
                GeneratorMismatchKind::YieldType,
            );
        }

        generator_type_params.send_ty.unwrap_or_else(Type::unknown)
    }

    fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> {
        let ast::ExprYieldFrom {
            range: _,
            node_index: _,
            value,
        } = yield_from;

        let Some(enclosing_function) =
            nearest_enclosing_function(self.db(), self.index, self.scope())
        else {
            let _ = self.infer_expression(value, TypeContext::default());
            return Type::unknown();
        };
        let annotated_return_ty = same_module_uncached_raw_signature(
            self.db(),
            enclosing_function,
            ReturnCallableTypeVarScope::Public,
        )
        .return_ty;

        let Some(outer_expected) = annotated_return_ty.generator_types(self.db()) else {
            let _ = self.infer_expression(value, TypeContext::default());
            return Type::unknown();
        };
        let return_type_span = enclosing_function.spans(self.db()).return_type;

        let tcx = TypeContext::new(outer_expected.yield_ty.map(|yielded_ty| {
            KnownClass::Iterable.to_specialized_instance(self.db(), &[yielded_ty])
        }));
        let iterable_type = self.infer_expression(value, tcx);

        let inner_yield_ty = iterable_type
            .try_iterate(self.db())
            .map(|tuple| tuple.homogeneous_element_type(self.db()))
            .unwrap_or_else(|err| {
                err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
                err.fallback_element_type(self.db())
            });

        if let Some(outer_yield_ty) = outer_expected.yield_ty
            && !inner_yield_ty.is_assignable_to(self.db(), outer_yield_ty)
        {
            report_invalid_generator_yield_type(
                &self.context,
                value.as_ref(),
                return_type_span.clone(),
                outer_yield_ty,
                inner_yield_ty,
                GeneratorMismatchKind::YieldType,
            );
        }

        if let Some(outer_send_ty) = outer_expected.send_ty {
            let inner_send_ty = iterable_type
                .generator_send_type(self.db())
                .unwrap_or_else(|| Type::none(self.db()));
            if !outer_send_ty.is_assignable_to(self.db(), inner_send_ty) {
                report_invalid_generator_yield_type(
                    &self.context,
                    value.as_ref(),
                    return_type_span,
                    outer_send_ty,
                    inner_send_ty,
                    GeneratorMismatchKind::SendType,
                );
            }
        }

        iterable_type
            .generator_return_type(self.db())
            .unwrap_or_else(Type::unknown)
    }

    fn infer_await_expression(
        &mut self,
        await_expression: &ast::ExprAwait,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprAwait {
            range: _,
            node_index: _,
            value,
        } = await_expression;

        let expr_type = self.infer_expression(
            value,
            tcx.map(|tcx| KnownClass::Awaitable.to_specialized_instance(self.db(), &[tcx])),
        );

        expr_type.try_await(self.db()).unwrap_or_else(|err| {
            err.report_diagnostic(&self.context, expr_type, value.as_ref().into());
            Type::unknown()
        })
    }

    // Perform narrowing with applicable constraints between the current scope and the enclosing scope.
    fn narrow_place_with_applicable_constraints(
        &self,
        expr: PlaceExprRef,
        mut ty: Type<'db>,
        constraint_keys: &[(FileScopeId, ConstraintKey)],
    ) -> Type<'db> {
        let db = self.db();
        for (enclosing_scope_file_id, constraint_key) in constraint_keys {
            let use_def = self.index.use_def_map(*enclosing_scope_file_id);
            let place_table = self.index.place_table(*enclosing_scope_file_id);
            let place = place_table.place_id(expr).unwrap();

            match use_def.applicable_constraints(
                *constraint_key,
                *enclosing_scope_file_id,
                expr,
                self.index,
            ) {
                ApplicableConstraints::UnboundBinding(constraint) => {
                    ty = constraint.narrow(db, ty, place);
                }
                // Performs narrowing based on constrained bindings.
                // This handling must be performed even if narrowing is attempted and failed using `infer_place_load`.
                // The result of `infer_place_load` can be applied as is only when its boundness is `Bound`.
                // For example, this handling is required in the following case:
                // ```python
                // class C:
                //     x: int | None = None
                // c = C()
                // # c.x: int | None = <unbound>
                // if c.x is None:
                //     c.x = 1
                // # else: c.x: int = <unbound>
                // # `c.x` is not definitely bound here
                // reveal_type(c.x)  # revealed: int
                // ```
                ApplicableConstraints::ConstrainedBindings(bindings) => {
                    let reachability_constraints = bindings.reachability_constraints();
                    let predicates = bindings.predicates();
                    let mut union = UnionBuilder::new(db);
                    for binding in bindings {
                        let static_reachability = evaluate_reachability_with_cache(
                            db,
                            Some(self.reachability_cache()),
                            reachability_constraints,
                            predicates,
                            binding.reachability_constraint,
                        );
                        if static_reachability.is_always_false() {
                            continue;
                        }
                        match binding.binding {
                            DefinitionState::Defined(definition)
                                if !is_discarded_dict_key_assignment(db, definition) =>
                            {
                                let binding_ty = binding_type(db, definition);
                                union = union.add(
                                    binding.narrowing_constraint.narrow(db, binding_ty, place),
                                );
                            }
                            DefinitionState::Defined(_)
                            | DefinitionState::Undefined
                            | DefinitionState::Deleted => {
                                union =
                                    union.add(binding.narrowing_constraint.narrow(db, ty, place));
                            }
                        }
                    }
                    // If there are no visible bindings, the union becomes `Never`.
                    // Since an unbound binding is recorded even for an undefined place,
                    // this can only happen if the code is unreachable
                    // and therefore it is correct to set the result to `Never`.
                    ty = union.build();
                }
            }
        }
        ty
    }

    /// Check if the given ty is `@deprecated` or not
    fn check_deprecated<T: Ranged>(&self, ranged: T, ty: Type) {
        // First handle classes
        if let Type::ClassLiteral(class_literal) = ty {
            let Some(deprecated) = class_literal.deprecated(self.db()) else {
                return;
            };

            let Some(builder) = self.context.report_lint(&diagnostic::DEPRECATED, ranged) else {
                return;
            };

            let class_name = class_literal.name(self.db());
            let mut diag =
                builder.into_diagnostic(format_args!(r#"The class `{class_name}` is deprecated"#));
            if let Some(message) = deprecated.message {
                diag.set_primary_message(message.value(self.db()));
            }
            diag.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
            return;
        }

        // Next handle functions
        let function = match ty {
            Type::FunctionLiteral(function) => function,
            Type::BoundMethod(bound) => bound.function(self.db()),
            _ => return,
        };

        // Currently we only check the final implementation for deprecation, as
        // that check can be done on any reference to the function. Analysis of
        // deprecated overloads needs to be done in places where we resolve the
        // actual overloads being used.
        let Some(deprecated) = function.implementation_deprecated(self.db()) else {
            return;
        };

        let Some(builder) = self
            .context
            .report_lint(&crate::types::diagnostic::DEPRECATED, ranged)
        else {
            return;
        };

        let func_name = function.name(self.db());
        let mut diag =
            builder.into_diagnostic(format_args!(r#"The function `{func_name}` is deprecated"#));
        if let Some(message) = deprecated.message {
            diag.set_primary_message(message.value(self.db()));
        }
        diag.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
    }

    fn infer_name_load(&mut self, name_node: &ast::ExprName) -> Type<'db> {
        let ast::ExprName {
            range: _,
            node_index: _,
            id: symbol_name,
            ctx: _,
        } = name_node;
        let expr = PlaceExpr::from_expr_name(name_node);
        let db = self.db();

        let (resolved, constraint_keys) =
            self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));

        let resolved_after_fallback = resolved
            // Not found in the module's explicitly declared global symbols?
            // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
            // These are looked up as attributes on `types.ModuleType`.
            .or_fall_back_to(db, || {
                module_type_implicit_global_symbol(db, self.file(), symbol_name).map_type(|ty| {
                    self.narrow_place_with_applicable_constraints(
                        PlaceExprRef::from(&expr),
                        ty,
                        &constraint_keys,
                    )
                })
            })
            // Not found in globals? Fallback to builtins
            // (without infinite recursion if we're already in builtins.)
            .or_fall_back_to(db, || {
                if Some(self.scope()) == builtins_module_scope(db) {
                    Place::Undefined.into()
                } else {
                    builtins_symbol(db, symbol_name)
                }
            })
            // Still not found? It might be `reveal_type`...
            .or_fall_back_to(db, || {
                if symbol_name == "reveal_type" {
                    if let Some(builder) = self.context.report_lint(&UNDEFINED_REVEAL, name_node) {
                        let mut diag =
                            builder.into_diagnostic("`reveal_type` used without importing it");
                        diag.info(
                            "This is allowed for debugging convenience but will fail at runtime",
                        );
                    }
                    typing_extensions_symbol(db, symbol_name)
                } else {
                    Place::Undefined.into()
                }
            });

        let ty =
            resolved_after_fallback.unwrap_with_diagnostic(db, |lookup_error| match lookup_error {
                LookupError::Undefined(qualifiers) => {
                    self.report_unresolved_reference(name_node);
                    TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, qualifiers)
                }
                LookupError::PossiblyUndefined(type_when_bound) => {
                    report_possibly_unresolved_reference(&self.context, name_node);
                    type_when_bound
                }
            });

        ty.inner_type()
    }

    fn infer_local_place_load(
        &self,
        expr: PlaceExprRef,
        expr_ref: ast::ExprRef,
    ) -> (Place<'db>, Option<ScopedUseId>) {
        let db = self.db();
        let scope = self.scope();
        let file_scope_id = scope.file_scope_id(db);
        let place_table = self.index.place_table(file_scope_id);
        let use_def = self.index.use_def_map(file_scope_id);

        // If we're inferring types of deferred expressions, look them up from end-of-scope.
        if self.is_deferred() {
            let place = if let Some(place_id) = place_table.place_id(expr) {
                place_from_bindings_with_reachability_cache(
                    db,
                    use_def.reachable_bindings(place_id),
                    self.reachability_cache(),
                )
                .place
            } else {
                assert!(
                    self.in_string_annotation(),
                    "Expected the place table to create a place for every valid PlaceExpr node"
                );
                Place::Undefined
            };
            (place, None)
        } else {
            if expr_ref
                .as_name_expr()
                .is_some_and(|name| name.is_invalid())
            {
                return (Place::Undefined, None);
            }

            // A named expression can show up here when resolving the parent place of something
            // like `(foo := bar()).baz`. It binds `foo`, but it is not a normal load site and
            // therefore has no `ScopedUseId`, so resolve it from its binding definition instead.
            if let ast::ExprRef::Named(named) = expr_ref {
                let place = if named.target.is_name_expr() {
                    let definition = self.index.expect_single_definition(named);
                    Place::bound(binding_type(db, definition)).with_definition(definition)
                } else {
                    Place::Undefined
                };
                return (place, None);
            }

            let use_id = expr_ref.scoped_use_id(db, self.file());
            let place = place_from_bindings_with_reachability_cache(
                db,
                use_def.bindings_at_use(use_id),
                self.reachability_cache(),
            )
            .place;

            (place, Some(use_id))
        }
    }

    /// Resolve a load that has fallen through to the module's explicit global scope.
    ///
    /// For eager nested scopes, this uses the global enclosing snapshot instead of the completed
    /// module scope, so a class body cannot see a class name that is bound only after the body
    /// finishes:
    ///
    /// ```python
    /// class A:
    ///     A = A
    /// ```
    ///
    /// `symbol_name` is only needed when no snapshot is available: snapshots can resolve complex
    /// places like `a.x`, but the fallback global query only works for bare symbols. `assume_bound`
    /// preserves the class-body fallback behavior for names that are also local to the class body.
    fn infer_explicit_global_symbol_load(
        &self,
        place_expr: PlaceExprRef,
        symbol_name: Option<&str>,
        current_scope_id: FileScopeId,
        constraint_keys: &mut Vec<(FileScopeId, ConstraintKey)>,
        assume_bound: bool,
    ) -> PlaceAndQualifiers<'db> {
        let db = self.db();

        if current_scope_id.is_global() {
            return Place::Undefined.into();
        }

        if !self.is_deferred() {
            match self
                .index
                .enclosing_snapshot(FileScopeId::global(), place_expr, current_scope_id)
            {
                EnclosingSnapshotResult::FoundConstraint(constraint) => {
                    constraint_keys.push((
                        FileScopeId::global(),
                        ConstraintKey::NarrowingConstraint(constraint),
                    ));
                    // Reaching here means that no bindings are found in any scope.
                    // Since `explicit_global_symbol` may return a cycle initial value, we return `Place::Undefined` here.
                    return Place::Undefined.into();
                }
                EnclosingSnapshotResult::FoundBindings(bindings) => {
                    let mut place_and_qualifiers = place_from_bindings_with_reachability_cache(
                        db,
                        bindings,
                        self.reachability_cache(),
                    );
                    if assume_bound && let Place::Defined(defined) = place_and_qualifiers.place {
                        place_and_qualifiers.place =
                            Place::Defined(defined.with_definedness(Definedness::AlwaysDefined));
                    }
                    let place = place_and_qualifiers.place.map_type(|ty| {
                        self.narrow_place_with_applicable_constraints(
                            place_expr,
                            ty,
                            constraint_keys,
                        )
                    });
                    constraint_keys.push((
                        FileScopeId::global(),
                        ConstraintKey::NestedScope(current_scope_id),
                    ));
                    return place.into();
                }
                // There are no visible bindings / constraint here.
                EnclosingSnapshotResult::NotFound => {
                    return Place::Undefined.into();
                }
                EnclosingSnapshotResult::NoLongerInEagerContext => {}
            }
        }

        let Some(symbol_name) = symbol_name else {
            return Place::Undefined.into();
        };

        explicit_global_symbol(db, self.file(), symbol_name).map_type(|ty| {
            self.narrow_place_with_applicable_constraints(place_expr, ty, constraint_keys)
        })
    }

    /// Infer the type of a place expression from definitions, assuming a load context.
    /// This method also returns the [`ConstraintKey`]s for each scope associated with `expr`,
    /// which is used to narrow by condition rather than by assignment.
    fn infer_place_load(
        &self,
        place_expr: PlaceExprRef,
        expr_ref: ast::ExprRef,
    ) -> (PlaceAndQualifiers<'db>, Vec<(FileScopeId, ConstraintKey)>) {
        let db = self.db();
        let scope = self.scope();
        let file_scope_id = scope.file_scope_id(db);
        let place_table = self.index.place_table(file_scope_id);

        let mut constraint_keys = vec![];
        let (local_scope_place, use_id) = self.infer_local_place_load(place_expr, expr_ref);
        if let Some(use_id) = use_id {
            constraint_keys.push((file_scope_id, ConstraintKey::UseId(use_id)));
        }

        let place = PlaceAndQualifiers::from(local_scope_place).or_fall_back_to(db, || {
            let mut symbol_resolves_locally = false;
            if let Some(symbol) = place_expr.as_symbol()
                && let Some(symbol_id) = place_table.symbol_id(symbol.name())
            {
                // Footgun: `place_expr` and `symbol` were probably constructed with all-zero
                // flags. We need to read the place table to get correct flags.
                symbol_resolves_locally = place_table.symbol(symbol_id).is_local();
                // If we try to access a variable in a class before it has been defined, the
                // lookup will fall back to global. See the comment on `Symbol::is_local`.
                let fallback_to_global =
                    scope.node(db).scope_kind().is_class() && symbol_resolves_locally;
                if self.skip_non_global_scopes(file_scope_id, symbol_id) || fallback_to_global {
                    return self.infer_explicit_global_symbol_load(
                        place_expr,
                        Some(symbol.name()),
                        file_scope_id,
                        &mut constraint_keys,
                        fallback_to_global,
                    );
                }
            }

            // Symbols that are bound or declared in the local scope, and not marked `nonlocal` or
            // `global`, never refer to an enclosing scope. (If you reference such a symbol before
            // it's bound, you get an `UnboundLocalError`.) Short-circuit instead of walking
            // enclosing scopes in this case. The one exception to this rule is the global fallback
            // in class bodies, which we already handled above.
            if symbol_resolves_locally {
                return Place::Undefined.into();
            }

            if let PlaceExprRef::Symbol(symbol) = place_expr
                && symbol.name() == "__class__"
                && let Some(class) = self.dunder_class_cell_type()
            {
                return Place::bound(class).into();
            }

            for parent_id in place_table.parents(place_expr) {
                let parent_expr = place_table.place(parent_id);
                let mut expr_ref = expr_ref;
                for _ in 0..(place_expr.num_member_segments() - parent_expr.num_member_segments()) {
                    match expr_ref {
                        ast::ExprRef::Attribute(attribute) => {
                            expr_ref = ast::ExprRef::from(&attribute.value);
                        }
                        ast::ExprRef::Subscript(subscript) => {
                            expr_ref = ast::ExprRef::from(&subscript.value);
                        }
                        _ => unreachable!(),
                    }
                }
                let (parent_place, _use_id) = self.infer_local_place_load(parent_expr, expr_ref);
                if let Place::Defined(_) = parent_place {
                    return Place::Undefined.into();
                }
            }

            // Walk enclosing scopes to resolve a free-variable load (`LOAD_DEREF` at runtime).
            // There are two main ways we try to model these loads:
            //
            // 1. "Snapshots" record the bindings/constraints in the enclosing scope at the point
            //    just before a nested scope begins. For variables that aren't modified after that
            //    point, that's the only value that the nested scope can see. If a variable is
            //    reassigned later, lazy snapshots for that variable can be updated or swept.
            //
            // 2. Otherwise, we keep walking until we get to the variable's original defining
            //    scope, and we use its "public type" there, which respects all reachable bindings,
            //    not just end-of-scope bindings. That includes the synthetic `NestedBindings`
            //    definitions that we install after each nested scope is closed, so it has
            //    a complete view of the nested `global` and `nonlocal` writes beneath it.
            //
            // This walk only resolves free variables and explicit `nonlocal`s. A symbol that is
            // local to the current scope never falls back to an enclosing scope, even if it's only
            // possibly bound at the current use: Python would raise `UnboundLocalError` instead.
            //
            // Note that we only get to this walk via `or_fall_back_to` above. In other words, for
            // definitely-locally-bound variables, we defer to the current scope's bindings instead
            // of looking at enclosing scopes. Concretely:
            //
            // def f():
            //     x = None
            //
            //     def g():
            //         nonlocal x
            //         if flag:
            //             x = 42
            //
            //         # `x` is possibly unbound here, so we walk enclosing scopes and see the
            //         # public type in `f`.
            //         reveal_type(x)  # revealed: Literal[42, 99] | None
            //
            //         x = 99
            //         # But now `x` is definitely bound, so we don't do the walk.
            //         reveal_type(x)  # revealed: Literal[99]
            //
            // Importantly, this approach isn't generally sound. The public type could include
            // nested bindings from sibling scopes, which really could run at any time, and in some
            // cases we're being too deferential to local bindings. Unfortunately the fully sound
            // treatment would reveal `Literal[42, 99] | None` even immediately after `x = 99`,
            // which is too frustrating for users in practice.
            for (enclosing_scope_file_id, _) in self.index.ancestor_scopes(file_scope_id).skip(1) {
                // If the current enclosing scope is global, no place lookup is performed here,
                // instead falling back to the module's explicit global lookup below.
                if enclosing_scope_file_id.is_global() {
                    break;
                }

                // Class scopes are not visible to nested scopes, and we need to handle global
                // scope differently (because an unbound name there falls back to builtins), so
                // check only function-like scopes.
                // There is one exception to this rule: annotation scopes can see
                // names defined in an immediately-enclosing class scope.
                let enclosing_scope = self.index.scope(enclosing_scope_file_id);

                let is_immediately_enclosing_scope = scope.is_annotation(db)
                    && scope
                        .scope(db)
                        .parent()
                        .is_some_and(|parent| parent == enclosing_scope_file_id);

                let has_root_place_been_reassigned = || {
                    let enclosing_place_table = self.index.place_table(enclosing_scope_file_id);
                    enclosing_place_table
                        .parents(place_expr)
                        .any(|enclosing_root_place_id| {
                            enclosing_place_table
                                .place(enclosing_root_place_id)
                                .is_bound()
                        })
                };

                // If the reference is in a nested eager scope, we need to look for the place at
                // the point where the previous enclosing scope was defined, instead of at the end
                // of the scope. (Note that the semantic index builder takes care of only
                // registering eager bindings for nested scopes that are actually eager, and for
                // enclosing scopes that actually contain bindings that we should use when
                // resolving the reference.)
                let mut eagerly_resolved_place = None;
                if !self.is_deferred() {
                    match self.index.enclosing_snapshot(
                        enclosing_scope_file_id,
                        place_expr,
                        file_scope_id,
                    ) {
                        EnclosingSnapshotResult::FoundConstraint(constraint) => {
                            constraint_keys.push((
                                enclosing_scope_file_id,
                                ConstraintKey::NarrowingConstraint(constraint),
                            ));
                            // If the current scope is eager, it is certain that the place is undefined in the current scope.
                            // Do not call the `place` query below as a fallback.
                            if scope.scope(db).is_eager() {
                                eagerly_resolved_place = Some(Place::Undefined.into());
                            }
                        }
                        EnclosingSnapshotResult::FoundBindings(bindings) => {
                            let place = place_from_bindings_with_reachability_cache(
                                db,
                                bindings,
                                self.reachability_cache(),
                            )
                            .place
                            .map_type(|ty| {
                                self.narrow_place_with_applicable_constraints(
                                    place_expr,
                                    ty,
                                    &constraint_keys,
                                )
                            });
                            constraint_keys.push((
                                enclosing_scope_file_id,
                                ConstraintKey::NestedScope(file_scope_id),
                            ));
                            return place.into();
                        }
                        // There are no visible bindings / constraint here.
                        // Don't fall back to non-eager place resolution.
                        EnclosingSnapshotResult::NotFound => {
                            if has_root_place_been_reassigned() {
                                return Place::Undefined.into();
                            }
                            continue;
                        }
                        EnclosingSnapshotResult::NoLongerInEagerContext => {
                            if has_root_place_been_reassigned() {
                                return Place::Undefined.into();
                            }
                        }
                    }
                }

                if !enclosing_scope.kind().is_function_like() && !is_immediately_enclosing_scope {
                    continue;
                }

                let enclosing_place_table = self.index.place_table(enclosing_scope_file_id);
                let Some(enclosing_place_id) = enclosing_place_table.place_id(place_expr) else {
                    continue;
                };

                let enclosing_place = enclosing_place_table.place(enclosing_place_id);

                // Reads of "free" or `nonlocal` variables terminate at any enclosing scope that
                // marks the variable `global`, whether or not that scope actually binds the
                // variable. If we see a `global` declaration, stop walking scopes and proceed to
                // the global handling below. (If we're walking from a prior/inner scope where this
                // variable is `nonlocal`, then this is a semantic syntax error, but we don't
                // enforce that here. See `SemanticIndexBuilder::pop_scope`.)
                if enclosing_place.as_symbol().is_some_and(Symbol::is_global) {
                    break;
                }

                // Keep walking until we reach the defining scope of the variable. The synthetic
                // nested bindings definitions installed there will see everything below it.
                if enclosing_place.as_symbol().is_some_and(Symbol::is_nonlocal) {
                    continue;
                }
                if !(enclosing_place.is_bound() || enclosing_place.is_declared()) {
                    // Note that this check includes members like `x.y` and `x[0]`, which aren't
                    // symbols and can't be explicitly `nonlocal`.
                    continue;
                }

                // We've reached the defining scope of the variable. Infer its public type.
                debug_assert!(enclosing_place.is_bound() || enclosing_place.is_declared());
                let enclosing_scope_id = enclosing_scope_file_id.to_scope_id(db, self.file());
                return eagerly_resolved_place.unwrap_or_else(|| {
                    place_by_id(
                        db,
                        enclosing_scope_id,
                        enclosing_place_id,
                        RequiresExplicitReExport::No,
                        ConsideredDefinitions::AllReachable,
                    )
                    .map_type(|ty| {
                        self.narrow_place_with_applicable_constraints(
                            place_expr,
                            ty,
                            &constraint_keys,
                        )
                    })
                });
            }

            PlaceAndQualifiers::default()
                // If we're in a class body, check for implicit class body symbols first.
                // These take precedence over globals.
                .or_fall_back_to(db, || {
                    if scope.node(db).scope_kind().is_class()
                        && let Some(symbol) = place_expr.as_symbol()
                    {
                        let implicit = class_body_implicit_symbol(db, symbol.name());
                        if implicit.place.is_definitely_bound() {
                            return implicit.map_type(|ty| {
                                self.narrow_place_with_applicable_constraints(
                                    place_expr,
                                    ty,
                                    &constraint_keys,
                                )
                            });
                        }
                    }
                    Place::Undefined.into()
                })
                // No nonlocal binding? Check the module's explicit globals.
                // Avoid infinite recursion if `self.scope` already is the module's global scope.
                .or_fall_back_to(db, || {
                    self.infer_explicit_global_symbol_load(
                        place_expr,
                        place_expr.as_symbol().map(|symbol| symbol.name().as_str()),
                        file_scope_id,
                        &mut constraint_keys,
                        false,
                    )
                })
        });

        if let Some(ty) = place.place.ignore_possibly_undefined() {
            self.check_deprecated(expr_ref, ty);
        }

        (place, constraint_keys)
    }

    pub(super) fn report_unresolved_reference(&self, expr_name_node: &ast::ExprName) {
        let Some(builder) = self
            .context
            .report_lint(&UNRESOLVED_REFERENCE, expr_name_node)
        else {
            return;
        };

        let ast::ExprName { id, .. } = expr_name_node;
        let mut diagnostic =
            builder.into_diagnostic(format_args!("Name `{id}` used when not defined"));

        // ===
        // Subdiagnostic (1): check to see if it was added as a builtin in a later version of Python.
        // ===
        if let Some(version_added_to_builtins) = version_builtin_was_added(id) {
            diagnostic.info(format_args!(
                "`{id}` was added as a builtin in Python 3.{version_added_to_builtins}"
            ));
            add_inferred_python_version_hint_to_diagnostic(
                self.db(),
                &mut diagnostic,
                "resolving types",
            );
        }

        // ===
        // Subdiagnostic (2): check to see if it's a capitalized older type hint that is available as lowercase in this version of Python.
        // ===
        // We don't need to check for typing_extensions.Type,
        // because it's already caught by typing.Type.
        if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY39 {
            if let Some(("", builtin_name)) = as_pep_585_generic("typing", id) {
                diagnostic.set_primary_message(format_args!("Did you mean `{builtin_name}`?"));
            }
        }

        // ===
        // Subdiagnostic (3):
        // - If it's an instance method, check to see if it's available as an attribute on `self`;
        // - If it's a classmethod, check to see if it's available as an attribute on `cls`
        // ===
        let Some(current_function) = self.current_function_definition() else {
            return;
        };

        let function_parameters = &*current_function.parameters;

        // `self`/`cls` can't be a keyword-only parameter.
        if function_parameters.posonlyargs.is_empty() && function_parameters.args.is_empty() {
            return;
        }

        let Some(first_parameter) = function_parameters.iter_non_variadic_params().next() else {
            return;
        };

        let Some(class) = self.class_context_of_current_method() else {
            return;
        };

        let first_parameter_name = first_parameter.name();

        let Some(function_type) = self.current_function_type() else {
            return;
        };

        let attribute_exists = match MethodDecorator::try_from_fn_type(self.db(), function_type) {
            Some(MethodDecorator::ClassMethod) => !Type::instance(self.db(), class)
                .class_member(self.db(), id.clone())
                .place
                .is_undefined(),
            Some(MethodDecorator::None) => !Type::instance(self.db(), class)
                .member(self.db(), id)
                .place
                .is_undefined(),
            Some(MethodDecorator::StaticMethod) | None => false,
        };

        if attribute_exists {
            diagnostic.info(format_args!(
                "An attribute `{id}` is available: consider using `{first_parameter_name}.{id}`"
            ));
        }
    }

    fn infer_name_expression(&mut self, name: &ast::ExprName) -> Type<'db> {
        match name.ctx {
            ExprContext::Load => self.infer_name_load(name),
            ExprContext::Store => Type::Never,
            ExprContext::Del => {
                self.infer_name_load(name);
                Type::Never
            }
            ExprContext::Invalid => Type::unknown(),
        }
    }

    fn narrow_expr_with_applicable_constraints<'r>(
        &mut self,
        target: impl Into<ast::ExprRef<'r>>,
        target_ty: Type<'db>,
        constraint_keys: &[(FileScopeId, ConstraintKey)],
    ) -> Type<'db> {
        let target = target.into();

        if let Some(place_expr) = PlaceExpr::try_from_expr(target) {
            self.narrow_place_with_applicable_constraints(
                PlaceExprRef::from(&place_expr),
                target_ty,
                constraint_keys,
            )
        } else {
            target_ty
        }
    }

    /// Infer the type of a [`ast::ExprAttribute`] expression, assuming a load context.
    fn infer_attribute_load(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
        let value_type =
            self.infer_maybe_standalone_expression(&attribute.value, TypeContext::default());
        self.infer_attribute_load_impl(attribute, value_type)
    }

    /// Infer the type of a [`ast::ExprAttribute`] expression, assuming a load context.
    fn infer_attribute_load_impl(
        &mut self,
        attribute: &ast::ExprAttribute,
        mut value_type: Type<'db>,
    ) -> Type<'db> {
        fn union_elements_missing_attribute<'db>(
            db: &'db dyn Db,
            ty: Type<'db>,
            attr_name: &str,
            missing_types: &mut FxIndexSet<Type<'db>>,
        ) {
            if let Some(union) = ty.as_union_like(db) {
                for element in union.elements(db) {
                    union_elements_missing_attribute(db, *element, attr_name, missing_types);
                }
            } else if ty.member(db, attr_name).place.is_undefined() {
                missing_types.insert(ty);
            }
        }

        let ast::ExprAttribute { value, attr, .. } = attribute;

        let db = self.db();
        let mut constraint_keys = vec![];

        if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = value_type
            && typevar.is_paramspec(db)
            && let Some(bound_typevar) = bind_typevar(
                db,
                self.index,
                self.scope().file_scope_id(db),
                self.typevar_binding_context,
                typevar,
            )
        {
            value_type = Type::TypeVar(bound_typevar);
        }

        let mut assigned_type = None;
        if let Some(place_expr) = PlaceExpr::try_from_expr(attribute) {
            let (resolved, keys) = self.infer_place_load(
                PlaceExprRef::from(&place_expr),
                ast::ExprRef::Attribute(attribute),
            );
            constraint_keys.extend(keys);
            if let Place::Defined(DefinedPlace {
                ty,
                definedness: Definedness::AlwaysDefined,
                ..
            }) = resolved.place
            {
                assigned_type = Some(ty);
            }
        }
        let fallback_place = value_type.member(db, &attr.id).map_type(|ty| {
            self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)
        });

        let attr_name = &attr.id;
        let resolved_type =
            fallback_place.unwrap_with_diagnostic(db, |lookup_err| match lookup_err {
                LookupError::Undefined(_) => {
                    let fallback = || {
                        TypeAndQualifiers::new(
                            Type::unknown(),
                            TypeOrigin::Inferred,
                            TypeQualifiers::empty(),
                        )
                    };

                    let bound_on_instance = match value_type {
                        Type::ClassLiteral(class) => {
                            !class.instance_member(db, None, attr).is_undefined()
                        }
                        Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
                            match subclass_of.subclass_of() {
                                SubclassOfInner::Class(class) => {
                                    !class.instance_member(db, attr).is_undefined()
                                }
                                SubclassOfInner::Dynamic(_) => unreachable!(
                                    "Attribute lookup on a dynamic `SubclassOf` type \
                                    should always return a bound symbol"
                                ),
                                SubclassOfInner::TypeVar(_) => false,
                            }
                        }
                        _ => false,
                    };

                    if let Type::ModuleLiteral(module) = value_type {
                        let module = module.module(db);
                        let module_name = module.name(db);
                        if module.kind(db).is_package()
                            && let Some(relative_submodule) = ModuleName::new(attr_name)
                        {
                            let mut maybe_submodule_name = module_name.clone();
                            maybe_submodule_name.extend(&relative_submodule);
                            if resolve_module(db, self.file(), &maybe_submodule_name).is_some() {
                                if let Some(builder) = self
                                    .context
                                    .report_lint(&POSSIBLY_MISSING_SUBMODULE, attribute)
                                {
                                    let mut diag = builder.into_diagnostic(format_args!(
                                        "Submodule `{attr_name}` might not have been imported"
                                    ));
                                    diag.help(format_args!(
                                        "Consider explicitly importing `{maybe_submodule_name}`"
                                    ));
                                }
                                return fallback();
                            }
                        }
                    }

                    if let Type::SpecialForm(special_form) = value_type {
                        if let Some(builder) =
                            self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
                        {
                            let mut diag = builder.into_diagnostic(format_args!(
                                "Special form `{special_form}` has no attribute `{attr_name}`",
                            ));
                            if let Ok(defined_type) = value_type.in_type_expression(
                                db,
                                self.scope(),
                                self.typevar_binding_context,
                                self.inference_flags()
                            ) && !defined_type.member(db, attr_name).place.is_undefined()
                            {
                                diag.help(format_args!(
                                    "Objects with type `{ty}` have a{maybe_n} `{attr_name}` \
                                    attribute, but the symbol `{special_form}` \
                                    does not itself inhabit the type `{ty}`",
                                    maybe_n = if attr_name.starts_with(['a', 'e', 'i', 'o', 'u']) {
                                        "n"
                                    } else {
                                        ""
                                    },
                                    ty = defined_type.display(self.db())
                                ));
                                if is_dotted_name(value) {
                                    let source =
                                        &source_text(self.db(), self.file())[value.range()];
                                    diag.help(format_args!(
                                        "This error may indicate that `{source}` was defined as \
                                        `{source} = {special_form}` when \
                                        `{source}: {special_form}` was intended"
                                    ));
                                }
                            }
                        }
                        return fallback();
                    }

                    let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
                    else {
                        return fallback();
                    };

                    if bound_on_instance {
                        builder.into_diagnostic(format_args!(
                            "Attribute `{attr_name}` can only be accessed on instances, \
                            not on the class object `{}` itself.",
                            value_type.display(db)
                        ));
                        return fallback();
                    }

                    let mut diagnostic = match value_type {
                        Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
                            "Module `{module_name}` has no member `{attr_name}`",
                            module_name = module.module(db).name(db),
                        )),
                        Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
                            "Class `{}` has no attribute `{attr_name}`",
                            class.name(db),
                        )),
                        Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
                            "Class `{}` has no attribute `{attr_name}`",
                            alias.display(db),
                        )),
                        Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!(
                            "Function `{}` has no attribute `{attr_name}`",
                            function.name(db),
                        )),
                        _ => builder.into_diagnostic(format_args!(
                            "Object of type `{}` has no attribute `{attr_name}`",
                            value_type.display(db),
                        )),
                    };

                    if value_type.is_callable_type()
                        && KnownClass::FunctionType
                            .to_instance(db)
                            .member(db, attr_name)
                            .place
                            .is_definitely_bound()
                    {
                        diagnostic.help(format_args!(
                            "Function objects have a{maybe_n} `{attr_name}` attribute, \
                            but not all callable objects are functions",
                            maybe_n = if attr_name
                                .trim_start_matches('_')
                                .starts_with(['a', 'e', 'i', 'o', 'u'])
                            {
                                "n"
                            } else {
                                ""
                            },
                        ));

                        // without the <> around the URL, if you double click on the URL in the terminal it tries to load
                        // https://docs.astral.sh/ty/reference/typing-faq/#why-does-ty-say-callable-has-no-attribute-__name
                        // (without the __ suffix at the end of the URL). That doesn't exist, so the page loaded in the
                        // browser opens at the top of the FAQs page instead of taking you directly to the relevant FAQ.
                        diagnostic.help(
                            "See this FAQ for more information: \
                            <https://docs.astral.sh/ty/reference/typing-faq/\
                            #why-does-ty-say-callable-has-no-attribute-__name__>",
                        );
                    } else {
                        hint_if_stdlib_attribute_exists_on_other_versions(
                            db,
                            diagnostic,
                            value_type,
                            attr_name,
                            &format!("resolving the `{attr_name}` attribute"),
                        );
                    }

                    fallback()
                }
                LookupError::PossiblyUndefined(type_when_bound) => {
                    // `PossiblyUndefined` is ambiguous here. It could be because an attribute is
                    // conditionally defined, for example:
                    // ```
                    // class Foo:
                    //     if flag:
                    //         x = 42
                    // ```
                    // That is indeed a "possibly missing attribute", and it's a warning by default, because
                    // there's a high false positive rate.
                    //
                    // On the other hand, we could be looking at a union where some elements have
                    // the attribute but others definitely don't. That's a very different case, and
                    // we want it to be an error. Use `as_union_like` here to handle type aliases
                    // of unions and `NewType`s of float/complex in addition to explicit unions.
                    //
                    // Attribute lookup on a bounded type variable delegates to its upper bound, so
                    // use that bound here too when determining whether the lookup was on a union.
                    let union_like_type = if let Type::TypeVar(typevar) = value_type
                        && let Some(bound) = typevar.typevar(db).upper_bound(db)
                    {
                        bound
                    } else {
                        value_type
                    };

                    if let Some(union) = union_like_type.as_union_like(db) {
                        let mut elements_missing_the_attribute = FxIndexSet::default();
                        for element in union.elements(db) {
                            union_elements_missing_attribute(
                                db,
                                *element,
                                attr_name,
                                &mut elements_missing_the_attribute,
                            );
                        }

                        if !elements_missing_the_attribute.is_empty() {
                            if let Some(builder) =
                                self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
                            {
                                let missing_types = elements_missing_the_attribute
                                    .iter()
                                    .map(|ty| format!("`{}`", ty.display(db)))
                                    .collect::<Vec<_>>()
                                    .join(", ");

                                builder.into_diagnostic(format_args!(
                                    "Attribute `{attr_name}` is not defined on {} in union `{union_like_type}`",
                                    missing_types,
                                    union_like_type = union_like_type.display(db),
                                ));
                            }
                            return type_when_bound;
                        }
                    }

                    report_possibly_missing_attribute(
                        &self.context,
                        attribute,
                        &attr.id,
                        value_type,
                    );

                    type_when_bound
                }
            });

        let resolved_type = resolved_type.inner_type();

        self.check_deprecated(attr, resolved_type);

        // Even if we can obtain the attribute type based on the assignments, we still perform default type inference
        // (to report errors).
        assigned_type.unwrap_or(resolved_type)
    }

    fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
        let ast::ExprAttribute {
            value,
            attr,
            range: _,
            node_index: _,
            ctx,
        } = attribute;

        match ctx {
            ExprContext::Load => self.infer_attribute_load(attribute),
            ExprContext::Store => {
                self.infer_expression(value, TypeContext::default());
                Type::Never
            }
            ExprContext::Del => {
                self.infer_attribute_load(attribute);
                self.validate_attribute_deletion(
                    attribute,
                    self.expression_type(value),
                    attr.as_str(),
                    true,
                );
                Type::Never
            }
            ExprContext::Invalid => {
                self.infer_expression(value, TypeContext::default());
                Type::unknown()
            }
        }
    }

    fn report_unsupported_unary_operator(
        &self,
        unary: &ast::ExprUnaryOp,
        op: ast::UnaryOp,
        operand_type: Type<'db>,
        unary_dunder_method: &str,
        error: Option<&CallDunderError<'db>>,
    ) {
        let Some(builder) = self.context.report_lint(&UNSUPPORTED_OPERATOR, unary) else {
            return;
        };

        let mut diagnostic = builder.into_diagnostic(format_args!(
            "Unary operator `{op}` is not supported for object of type `{}`",
            operand_type.display(self.db()),
        ));

        if let Some(CallDunderError::PossiblyUnbound {
            unbound_on: Some(unbound_on),
            ..
        }) = error
        {
            for ty in unbound_on.iter().copied() {
                diagnostic.info(format_args!(
                    "`{}` does not implement `{unary_dunder_method}`",
                    ty.display(self.db())
                ));
            }
        }
    }

    fn infer_unary_expression(&mut self, unary: &ast::ExprUnaryOp) -> Type<'db> {
        let ast::ExprUnaryOp {
            range: _,
            node_index: _,
            op,
            operand,
        } = unary;

        let operand_type = self.infer_expression(operand, TypeContext::default());

        self.infer_unary_expression_type(*op, operand_type, unary)
    }

    fn infer_unary_expression_type(
        &mut self,
        op: ast::UnaryOp,
        operand_type: Type<'db>,
        unary: &ast::ExprUnaryOp,
    ) -> Type<'db> {
        let fallback_unary_expression_type = || {
            let unary_dunder_method = match op {
                ast::UnaryOp::Invert => "__invert__",
                ast::UnaryOp::UAdd => "__pos__",
                ast::UnaryOp::USub => "__neg__",
                ast::UnaryOp::Not => {
                    unreachable!("Not operator is handled in its own case");
                }
            };

            match operand_type.try_call_dunder(
                self.db(),
                unary_dunder_method,
                CallArguments::none(),
                TypeContext::default(),
            ) {
                Ok(outcome) => outcome.return_type(self.db()),
                Err(e) => {
                    self.report_unsupported_unary_operator(
                        unary,
                        op,
                        operand_type,
                        unary_dunder_method,
                        Some(&e),
                    );
                    e.fallback_return_type(self.db())
                }
            }
        };

        match (op, operand_type) {
            (ast::UnaryOp::Invert | ast::UnaryOp::UAdd | ast::UnaryOp::USub, Type::Dynamic(_))
            | (_, Type::Divergent(_)) => operand_type,
            (_, Type::Never) => Type::Never,

            (_, Type::TypeAlias(alias)) => {
                self.infer_unary_expression_type(op, alias.value_type(self.db()), unary)
            }

            (ast::UnaryOp::UAdd, Type::LiteralValue(literal)) => match literal.kind() {
                LiteralValueTypeKind::Int(value) => Type::int_literal(value.as_i64()),
                LiteralValueTypeKind::Bool(value) => Type::int_literal(i64::from(value)),
                _ => fallback_unary_expression_type(),
            },

            (ast::UnaryOp::USub, Type::LiteralValue(literal)) => match literal.kind() {
                LiteralValueTypeKind::Int(value) => value
                    .as_i64()
                    .checked_neg()
                    .map(Type::int_literal)
                    .unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
                LiteralValueTypeKind::Bool(value) => Type::int_literal(-i64::from(value)),
                _ => fallback_unary_expression_type(),
            },

            (ast::UnaryOp::Invert, Type::LiteralValue(literal)) => match literal.kind() {
                LiteralValueTypeKind::Int(value) => Type::int_literal(!value.as_i64()),
                LiteralValueTypeKind::Bool(value) => Type::int_literal(!i64::from(value)),
                _ => fallback_unary_expression_type(),
            },

            (ast::UnaryOp::Invert, Type::KnownInstance(KnownInstanceType::ConstraintSet(set))) => {
                let constraints = ConstraintSetBuilder::new();
                let result = constraints.into_owned(|constraints| {
                    let set = constraints.load(self.db(), set.constraints(self.db()));
                    set.negate(self.db(), constraints)
                });
                Type::KnownInstance(KnownInstanceType::ConstraintSet(
                    InternedConstraintSet::new(self.db(), result),
                ))
            }

            (ast::UnaryOp::Not, ty) => Type::from_truthiness(
                self.db(),
                ty.try_bool(self.db())
                    .unwrap_or_else(|err| {
                        err.report_diagnostic(&self.context, unary);
                        err.fallback_truthiness()
                    })
                    .negate(),
            ),
            // Handle constrained TypeVars specially: check each constraint individually.
            //
            // TODO: We expect to replace this with more general support once we migrate to the new
            // solver.
            (
                op @ (ast::UnaryOp::UAdd | ast::UnaryOp::USub | ast::UnaryOp::Invert),
                Type::TypeVar(tvar),
            ) => {
                let unary_dunder_method = match op {
                    ast::UnaryOp::Invert => "__invert__",
                    ast::UnaryOp::UAdd => "__pos__",
                    ast::UnaryOp::USub => "__neg__",
                    ast::UnaryOp::Not => unreachable!(),
                };

                match tvar.typevar(self.db()).bound_or_constraints(self.db()) {
                    Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
                        let db = self.db();
                        match Self::map_constrained_typevar_constraints(
                            db,
                            operand_type,
                            constraints,
                            |constraint| {
                                constraint
                                    .try_call_dunder(
                                        db,
                                        unary_dunder_method,
                                        CallArguments::none(),
                                        TypeContext::default(),
                                    )
                                    .map(|outcome| outcome.return_type(db))
                                    .ok()
                            },
                        ) {
                            Some(ty) => ty,
                            None => {
                                // At least one constraint failed; report error.
                                self.report_unsupported_unary_operator(
                                    unary,
                                    op,
                                    operand_type,
                                    unary_dunder_method,
                                    None,
                                );
                                operand_type
                                    .try_call_dunder(
                                        db,
                                        unary_dunder_method,
                                        CallArguments::none(),
                                        TypeContext::default(),
                                    )
                                    .map_or_else(
                                        |e| e.fallback_return_type(db),
                                        |b| b.return_type(db),
                                    )
                            }
                        }
                    }
                    // For bounded TypeVars with union bounds (like `bound=float` which becomes
                    // `int | float`), we need to delegate to the bound type.
                    Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
                        self.infer_unary_expression_type(op, bound, unary)
                    }
                    // For unconstrained TypeVars, fall through to default handling.
                    None => match operand_type.try_call_dunder(
                        self.db(),
                        unary_dunder_method,
                        CallArguments::none(),
                        TypeContext::default(),
                    ) {
                        Ok(outcome) => outcome.return_type(self.db()),
                        Err(e) => {
                            self.report_unsupported_unary_operator(
                                unary,
                                op,
                                operand_type,
                                unary_dunder_method,
                                Some(&e),
                            );
                            e.fallback_return_type(self.db())
                        }
                    },
                }
            }

            (
                ast::UnaryOp::UAdd | ast::UnaryOp::USub | ast::UnaryOp::Invert,
                Type::FunctionLiteral(_)
                | Type::Callable(..)
                | Type::WrapperDescriptor(_)
                | Type::KnownBoundMethod(_)
                | Type::DataclassDecorator(_)
                | Type::DataclassTransformer(_)
                | Type::BoundMethod(_)
                | Type::ModuleLiteral(_)
                | Type::ClassLiteral(_)
                | Type::GenericAlias(_)
                | Type::SubclassOf(_)
                | Type::NominalInstance(_)
                | Type::ProtocolInstance(_)
                | Type::SpecialForm(_)
                | Type::KnownInstance(_)
                | Type::PropertyInstance(_)
                | Type::Union(_)
                | Type::Intersection(_)
                | Type::EnumComplement(_)
                | Type::AlwaysTruthy
                | Type::AlwaysFalsy
                | Type::BoundSuper(_)
                | Type::TypeIs(_)
                | Type::TypeGuard(_)
                | Type::TypeForm(_)
                | Type::TypedDict(_)
                | Type::NewTypeInstance(_),
            ) => fallback_unary_expression_type(),
        }
    }

    fn infer_boolean_expression(
        &mut self,
        bool_op: &ast::ExprBoolOp,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        let ast::ExprBoolOp {
            range: _,
            node_index: _,
            op,
            values,
        } = bool_op;
        // The first operand has no peers. If no later operand is a collection literal,
        // accumulating prior types cannot affect inference.
        let track_peer_types = allows_collection_literal_peer_context(tcx)
            && values.iter().skip(1).any(is_collection_literal);
        self.infer_chained_boolean_types(
            *op,
            track_peer_types,
            values.iter().enumerate(),
            |(_, value)| is_collection_literal(value),
            |builder, (index, value), peer_ty| {
                let ty = if index == values.len() - 1 {
                    builder
                        .infer_expression_with_collection_literal_peer_context(value, tcx, peer_ty)
                } else {
                    builder.infer_maybe_standalone_expression_with_collection_literal_peer_context(
                        value, tcx, peer_ty,
                    )
                };

                (ty, value.range())
            },
        )
    }

    /// Computes the output of a chain of (one) boolean operation, consuming as input an iterator
    /// of operations and calling the `infer_ty` for each to infer their types.
    /// The iterator is consumed even if the boolean evaluation can be short-circuited,
    /// in order to ensure the invariant that all expressions are evaluated when inferring types.
    ///
    /// `infer_ty` receives the unguarded union of previous operand types that may contribute to the
    /// result. This can be used as a type context without losing generic specialization information
    /// to the truthiness guards applied to the final result type. `needs_peer_type` determines
    /// whether that union is materialized for each operand.
    fn infer_chained_boolean_types<Iterator, Item, NeedsPeerType, InferType>(
        &mut self,
        op: ast::BoolOp,
        track_peer_types: bool,
        operations: Iterator,
        needs_peer_type: NeedsPeerType,
        infer_ty: InferType,
    ) -> Type<'db>
    where
        Iterator: IntoIterator<Item = Item>,
        NeedsPeerType: Fn(&Item) -> bool,
        InferType: Fn(&mut Self, Item, Option<Type<'db>>) -> (Type<'db>, TextRange),
    {
        let mut done = false;
        let mut peer_types: Option<UnionAccumulator<'db>> = None;
        let db = self.db();

        let elements = operations
            .into_iter()
            .with_position()
            .map(|(position, item)| {
                let peer_ty = if done || !track_peer_types || !needs_peer_type(&item) {
                    None
                } else {
                    peer_types
                        .as_mut()
                        .map(|peer_types| peer_types.get_or_build(db))
                };
                let (ty, range) = infer_ty(self, item, peer_ty);

                let is_last = position.is_last();

                if is_last {
                    if done { Type::Never } else { ty }
                } else {
                    let truthiness = ty.try_bool(self.db()).unwrap_or_else(|err| {
                        err.report_diagnostic(&self.context, range);
                        err.fallback_truthiness()
                    });

                    if done {
                        return Type::Never;
                    }

                    match (truthiness, op) {
                        (Truthiness::AlwaysTrue, ast::BoolOp::And) => Type::Never,
                        (Truthiness::AlwaysFalse, ast::BoolOp::Or) => Type::Never,

                        (Truthiness::AlwaysFalse, ast::BoolOp::And)
                        | (Truthiness::AlwaysTrue, ast::BoolOp::Or) => {
                            done = true;
                            ty
                        }

                        (Truthiness::Ambiguous, _) => {
                            if track_peer_types {
                                match &mut peer_types {
                                    Some(peer_types) => peer_types.add(db, ty),
                                    None => peer_types = Some(UnionAccumulator::new(ty)),
                                }
                            }
                            IntersectionBuilder::new(db)
                                .add_positive(ty)
                                .add_negative(match op {
                                    ast::BoolOp::And => Type::AlwaysTruthy,
                                    ast::BoolOp::Or => Type::AlwaysFalsy,
                                })
                                .build()
                        }
                    }
                }
            });

        UnionType::from_elements(db, elements)
    }

    fn infer_compare_expression(&mut self, compare: &ast::ExprCompare) -> Type<'db> {
        let ast::ExprCompare {
            range: _,
            node_index: _,
            left,
            ops,
            comparators,
        } = compare;

        self.infer_expression(left, TypeContext::default());

        // https://docs.python.org/3/reference/expressions.html#comparisons
        // > Formally, if `a, b, c, …, y, z` are expressions and `op1, op2, …, opN` are comparison
        // > operators, then `a op1 b op2 c ... y opN z` is equivalent to `a op1 b and b op2 c and
        // ... > y opN z`, except that each expression is evaluated at most once.
        //
        // As some operators (==, !=, <, <=, >, >=) *can* return an arbitrary type, the logic below
        // is shared with the one in `infer_binary_type_comparison`.
        self.infer_chained_boolean_types(
            ast::BoolOp::And,
            false,
            std::iter::once(&**left)
                .chain(comparators)
                .tuple_windows::<(_, _)>()
                .zip(ops),
            |_| false,
            |builder, ((left, right), op), _peer_ty| {
                let left_ty = builder.expression_type(left);
                let right_ty = builder.infer_expression(right, TypeContext::default());

                let range = TextRange::new(left.start(), right.end());

                let ty = comparisons::infer_binary_type_comparison(
                    &builder.context,
                    left_ty,
                    *op,
                    right_ty,
                    range,
                    &BinaryComparisonVisitor::new(Ok(Type::bool_literal(true))),
                )
                .unwrap_or_else(|error| {
                    report_unsupported_comparison(
                        &builder.context,
                        &error,
                        range,
                        left,
                        right,
                        left_ty,
                        right_ty,
                    );

                    match op {
                        // `in, not in, is, is not` always return bool instances
                        ast::CmpOp::In | ast::CmpOp::NotIn | ast::CmpOp::Is | ast::CmpOp::IsNot => {
                            KnownClass::Bool.to_instance(builder.db())
                        }
                        // Other operators can return arbitrary types
                        _ => Type::unknown(),
                    }
                });

                (ty, range)
            },
        )
    }

    fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) {
        let ast::TypeParams {
            range: _,
            node_index: _,
            type_params,
        } = type_parameters;
        for type_param in type_params {
            match type_param {
                ast::TypeParam::TypeVar(node) => self.infer_definition(node),
                ast::TypeParam::ParamSpec(node) => self.infer_definition(node),
                ast::TypeParam::TypeVarTuple(node) => self.infer_definition(node),
            }
        }
    }

    pub(super) fn finish_expression(mut self) -> ExpressionInference<'db> {
        self.infer_region();

        let Self {
            context,
            expressions,
            qualifiers: _,
            type_expression_flags,
            mut collection_use_constraints,
            string_annotations,
            expected_types,
            scope,
            bindings,
            declarations,
            deferred,
            cycle_recovery,
            dataclass_field_specifiers: _,

            // Ignored; only relevant to definition regions
            undecorated_type: _,
            discards_dict_key_assignments: _,

            // builder only state
            expression_cache: _,
            reachability_cache: _,
            typevar_binding_context: _,
            deferred_state: _,
            called_functions: _,
            index: _,
            region: _,
            return_types_and_ranges: _,
        } = self;

        let diagnostics = context.finish();
        let _ = scope;

        assert!(
            declarations.is_empty(),
            "Expression region can't have declarations"
        );
        assert!(
            deferred.is_empty(),
            "Expression region can't have deferred definitions"
        );

        let extra =
            (!string_annotations.is_empty()
                || !type_expression_flags.is_empty()
                || !collection_use_constraints.is_empty()
                || !expected_types.is_empty()
                || cycle_recovery.is_some()
                || !bindings.is_empty()
                || !diagnostics.is_empty()).then(|| {
                if bindings.len() > 20 {
                    tracing::debug!(
                        "Inferred expression region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
                        self.region,
                        bindings.len()
                    );
                }

                collection_use_constraints.shrink_to_fit();
                Box::new(ExpressionInferenceExtra {
                    string_annotations: FrozenSet::from(string_annotations),
                    expected_types: FrozenMap::from(expected_types),
                    type_expression_flags: FrozenMap::from(type_expression_flags),
                    bindings: bindings.into_boxed_slice(),
                    diagnostics,
                    cycle_recovery,
                    collection_use_constraints
                })
            });

        ExpressionInference {
            expressions: FrozenMap::from(expressions),
            extra,
            #[cfg(debug_assertions)]
            scope,
        }
    }

    pub(super) fn finish_statement(mut self) -> StatementInferenceInner<'db> {
        self.infer_region();

        let Self {
            context,
            expressions,
            qualifiers,
            type_expression_flags,
            mut collection_use_constraints,
            string_annotations,
            expected_types,
            scope,
            bindings,
            declarations,
            deferred,
            cycle_recovery,
            called_functions,
            mut return_types_and_ranges,

            // Ignored; only relevant to definition regions
            undecorated_type: _,
            discards_dict_key_assignments: _,

            // builder only state
            expression_cache: _,
            reachability_cache: _,
            dataclass_field_specifiers: _,
            typevar_binding_context: _,
            deferred_state: _,
            index: _,
            region: _,
        } = self;

        let _ = scope;
        let diagnostics = context.finish();

        let extra = (!diagnostics.is_empty()
            || !string_annotations.is_empty()
            || cycle_recovery.is_some()
            || !expected_types.is_empty()
            || !deferred.is_empty()
            || !called_functions.is_empty()
            || !return_types_and_ranges.is_empty()
            || !qualifiers.is_empty()
            || !type_expression_flags.is_empty()
            || !collection_use_constraints.is_empty())
        .then(|| {
            collection_use_constraints.shrink_to_fit();
            return_types_and_ranges.shrink_to_fit();
            Box::new(StatementInferenceInnerExtra {
                string_annotations: FrozenSet::from(string_annotations),
                expected_types: FrozenMap::from(expected_types),
                called_functions: called_functions
                    .into_iter()
                    .collect::<Vec<_>>()
                    .into_boxed_slice(),
                return_types_and_ranges: return_types_and_ranges.into_boxed_slice(),
                type_expression_flags: FrozenMap::from(type_expression_flags),
                collection_use_constraints,
                cycle_recovery,
                deferred: deferred.into_boxed_slice(),
                diagnostics,
                qualifiers: FrozenMap::from(qualifiers),
            })
        });

        if bindings.len() > 20 {
            tracing::debug!(
                "Inferred statement region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
                self.region,
                bindings.len(),
            );
        }

        if declarations.len() > 20 {
            tracing::debug!(
                "Inferred statement region `{:?}` contains {} declarations. Lookups by linear scan might be slow.",
                self.region,
                declarations.len(),
            );
        }

        StatementInferenceInner {
            expressions: FrozenMap::from(expressions),
            #[cfg(debug_assertions)]
            scope,
            bindings: bindings.into_boxed_slice(),
            declarations: declarations.into_boxed_slice(),
            extra,
        }
    }

    pub(super) fn finish_function_decorator_inference(mut self) -> FunctionDecoratorInference<'db> {
        self.infer_region();

        let known_decorators = match self.region {
            InferenceRegion::FunctionDecorators(definition) => match definition.kind(self.db()) {
                DefinitionKind::Function(function) => {
                    function.node(self.module()).decorator_list.iter().fold(
                        FunctionDecorators::empty(),
                        |known_decorators, decorator| {
                            known_decorators
                                | FunctionDecorators::from_decorator_type(
                                    self.db(),
                                    self.expression_type(&decorator.expression),
                                )
                        },
                    )
                }
                _ => FunctionDecorators::empty(),
            },
            _ => FunctionDecorators::empty(),
        };

        let Self {
            context,
            expressions,
            bindings,
            called_functions,
            expression_cache: _,
            reachability_cache: _,
            declarations: _,
            deferred: _,
            scope: _,
            string_annotations: _,
            expected_types: _,
            return_types_and_ranges: _,
            collection_use_constraints: _,
            dataclass_field_specifiers: _,
            undecorated_type: _,
            discards_dict_key_assignments: _,
            typevar_binding_context: _,
            deferred_state: _,
            index: _,
            region: _,
            cycle_recovery: _,
            qualifiers: _,
            type_expression_flags: _,
        } = self;
        let diagnostics = context.finish();

        FunctionDecoratorInference {
            expression_types: FrozenMap::from(expressions),
            bindings: bindings.into_boxed_slice(),
            called_functions: called_functions
                .into_iter()
                .collect::<Vec<_>>()
                .into_boxed_slice(),
            known_decorators,
            diagnostics,
        }
    }

    pub(super) fn finish_definition(
        mut self,
        definition: Definition<'db>,
    ) -> DefinitionInference<'db> {
        self.infer_region();

        let Self {
            context,
            expressions,
            qualifiers,
            type_expression_flags,
            mut collection_use_constraints,
            string_annotations,
            expected_types,
            scope,
            bindings,
            declarations,
            deferred,
            cycle_recovery,
            undecorated_type,
            discards_dict_key_assignments,
            called_functions,

            // builder only state
            expression_cache: _,
            reachability_cache: _,
            dataclass_field_specifiers: _,
            typevar_binding_context: _,
            deferred_state: _,
            index: _,
            region: _,
            return_types_and_ranges: _,
        } = self;

        let _ = scope;
        let diagnostics = context.finish();

        let non_undecorated_extra_field_count = usize::from(!string_annotations.is_empty())
            + usize::from(!expected_types.is_empty())
            + usize::from(!collection_use_constraints.is_empty())
            + usize::from(!called_functions.is_empty())
            + usize::from(!type_expression_flags.is_empty())
            + usize::from(cycle_recovery.is_some())
            + usize::from(!deferred.is_empty())
            + usize::from(!diagnostics.is_empty())
            + usize::from(discards_dict_key_assignments)
            + usize::from(!qualifiers.is_empty());

        let extra = match (non_undecorated_extra_field_count, undecorated_type) {
            (0, None) => None,
            (1, None) if !qualifiers.is_empty() => Some(Box::new(
                DefinitionInferenceExtra::Qualifiers(FrozenMap::from(qualifiers)),
            )),
            (1, None) if !deferred.is_empty() => Some(Box::new(
                DefinitionInferenceExtra::Deferred(deferred.into_boxed_slice()),
            )),
            (1, None) if !diagnostics.is_empty() => Some(Box::new(
                DefinitionInferenceExtra::Diagnostics(Box::new(diagnostics)),
            )),
            (1, None) if !called_functions.is_empty() => {
                Some(Box::new(DefinitionInferenceExtra::CalledFunctions(
                    called_functions
                        .into_iter()
                        .collect::<Vec<_>>()
                        .into_boxed_slice(),
                )))
            }
            (1, None) if !expected_types.is_empty() => Some(Box::new(
                DefinitionInferenceExtra::ExpectedTypes(FrozenMap::from(expected_types)),
            )),
            (1, None) if !string_annotations.is_empty() => Some(Box::new(
                DefinitionInferenceExtra::StringAnnotations(FrozenSet::from(string_annotations)),
            )),
            (1, None) if discards_dict_key_assignments => Some(Box::new(
                DefinitionInferenceExtra::DiscardsDictKeyAssignments,
            )),
            (0, Some(undecorated_type)) => Some(Box::new(DefinitionInferenceExtra::Undecorated(
                Box::new(undecorated_type),
            ))),
            (1, Some(undecorated_type)) if !deferred.is_empty() => {
                Some(Box::new(DefinitionInferenceExtra::DeferredAndUndecorated(
                    Box::new(DeferredAndUndecorated {
                        deferred: deferred.into_boxed_slice(),
                        undecorated_type,
                    }),
                )))
            }
            (_, undecorated_type) => {
                collection_use_constraints.shrink_to_fit();
                let extra = OtherDefinitionInferenceExtra {
                    string_annotations: FrozenSet::from(string_annotations),
                    expected_types: FrozenMap::from(expected_types),
                    collection_use_constraints,
                    called_functions: called_functions
                        .into_iter()
                        .collect::<Vec<_>>()
                        .into_boxed_slice(),
                    type_expression_flags: FrozenMap::from(type_expression_flags),
                    cycle_recovery,
                    deferred: deferred.into_boxed_slice(),
                    diagnostics,
                    undecorated_type,
                    discards_dict_key_assignments,
                    qualifiers: FrozenMap::from(qualifiers),
                };
                Some(Box::new(DefinitionInferenceExtra::Other(Box::new(extra))))
            }
        };

        if bindings.len() > 20 {
            tracing::debug!(
                "Inferred definition region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
                self.region,
                bindings.len(),
            );
        }

        if declarations.len() > 20 {
            tracing::debug!(
                "Inferred declaration region `{:?}` contains {} declarations. Lookups by linear scan might be slow.",
                self.region,
                declarations.len(),
            );
        }

        DefinitionInference {
            expressions: FrozenMap::from(expressions),
            #[cfg(debug_assertions)]
            scope,
            types: DefinitionTypes::from_parts(
                definition,
                bindings.into_vec(),
                declarations.into_vec(),
            ),
            extra,
        }
    }

    pub(super) fn finish_scope(mut self) -> ScopeInference<'db> {
        self.infer_region();

        let Self {
            context,
            string_annotations,
            expected_types,
            type_expression_flags,
            mut collection_use_constraints,
            expressions,
            scope,
            cycle_recovery,
            qualifiers,

            // Ignored, never leaked into other scopes
            deferred: _,
            bindings: _,
            declarations: _,

            // Ignored; only relevant to definition regions
            undecorated_type: _,
            discards_dict_key_assignments: _,

            // Builder only state
            expression_cache: _,
            reachability_cache: _,
            dataclass_field_specifiers: _,
            typevar_binding_context: _,
            deferred_state: _,
            called_functions: _,
            index: _,
            region: _,
            return_types_and_ranges: _,
        } = self;

        let _ = scope;
        let diagnostics = context.finish();

        let extra = (!string_annotations.is_empty()
            || !expected_types.is_empty()
            || !diagnostics.is_empty()
            || cycle_recovery.is_some()
            || !type_expression_flags.is_empty()
            || !collection_use_constraints.is_empty()
            || !qualifiers.is_empty())
        .then(|| {
            collection_use_constraints.shrink_to_fit();
            Box::new(ScopeInferenceExtra {
                string_annotations: FrozenSet::from(string_annotations),
                qualifiers: FrozenMap::from(qualifiers),
                expected_types: FrozenMap::from(expected_types),
                type_expression_flags: FrozenMap::from(type_expression_flags),
                collection_use_constraints,
                cycle_recovery,
                diagnostics,
            })
        });

        ScopeInference {
            expressions: FrozenValueMap::from(expressions),
            extra,
        }
    }

    const fn inference_flags(&self) -> InferenceFlags {
        self.context.inference_flags
    }

    /// Returns a fresh [`TypeInferenceBuilder`] for the current scope that can be used
    /// to speculatively infer expressions during multi-inference.
    ///
    /// The inference results can be merged into the current inference region using
    /// [`TypeInferenceBuilder::extend`].
    fn speculate(&self) -> Self {
        let Self {
            region,
            index,
            cycle_recovery,
            deferred_state,
            typevar_binding_context,
            ref expression_cache,
            ref reachability_cache,
            ref return_types_and_ranges,
            ref dataclass_field_specifiers,

            // These fields are type inference results, but do not affect the inference of a given
            // expression.
            context: _,
            collection_use_constraints: _,
            expressions: _,
            string_annotations: _,
            expected_types: _,
            scope: _,
            bindings: _,
            declarations: _,
            deferred: _,
            called_functions: _,
            undecorated_type: _,
            discards_dict_key_assignments: _,
            qualifiers: _,
            type_expression_flags: _,
        } = *self;

        let mut builder = TypeInferenceBuilder::new(self.db(), region, index, self.module());

        // Speculated builders are often discarded immediately.
        builder.context.defuse();

        // Ensure the speculative builder has the same inference context as the current one.
        builder.cycle_recovery = cycle_recovery;
        builder.deferred_state = deferred_state;
        builder.typevar_binding_context = typevar_binding_context;
        builder.context.inference_flags = self.inference_flags();
        builder.expression_cache.clone_from(expression_cache);
        builder.reachability_cache.clone_from(reachability_cache);
        builder
            .return_types_and_ranges
            .clone_from(return_types_and_ranges);
        builder
            .dataclass_field_specifiers
            .clone_from(dataclass_field_specifiers);

        builder
    }

    /// Returns a speculative builder that does not construct diagnostics.
    fn speculate_without_diagnostics(&self) -> Self {
        let mut builder = self.speculate();
        builder.context.suppress_diagnostics();
        builder
    }

    /// Extend the current region with the results of a speculative [`TypeInferenceBuilder`].
    fn extend(&mut self, other: Self) {
        let Self {
            context,
            expressions,
            type_expression_flags,
            collection_use_constraints,
            string_annotations,
            expected_types,
            scope,
            bindings,
            declarations,
            deferred,
            cycle_recovery,
            dataclass_field_specifiers: _,

            // Ignored; only relevant to definition regions
            undecorated_type: _,
            discards_dict_key_assignments: _,

            // builder only state
            expression_cache: _,
            reachability_cache: _,
            typevar_binding_context: _,
            deferred_state: _,
            called_functions,
            index: _,
            region: _,
            return_types_and_ranges: _,
            qualifiers: _,
        } = other;

        let diagnostics = context.finish();
        let _ = scope;

        assert!(
            declarations.is_empty(),
            "speculative `TypeInferenceBuilder` should only be used for expression inference"
        );
        assert!(
            deferred.is_empty(),
            "speculative `TypeInferenceBuilder` should only be used for expression inference"
        );

        self.expressions.extend(expressions.iter());
        self.context.extend(&diagnostics);
        self.extend_cycle_recovery(cycle_recovery);
        self.string_annotations
            .extend(string_annotations.iter().copied());
        self.expected_types.extend(expected_types.iter());
        self.type_expression_flags
            .extend(type_expression_flags.iter());
        self.called_functions.extend(called_functions);

        if !matches!(self.region, InferenceRegion::Scope(..)) {
            self.bindings
                .extend(bindings.iter().map(|(def, ty)| (*def, *ty)));
        }

        #[expect(
            clippy::iter_over_hash_type,
            reason = "constraints for distinct collection definitions are merged independently"
        )]
        for (collection_def, constraints) in &collection_use_constraints {
            self.collection_use_constraints
                .entry(*collection_def)
                .and_modify(|this| this.extend(constraints))
                .or_insert(constraints.clone());
        }
    }
}

/// Manages the inference of a given expression.
struct MultiInferenceGuard<'db, 'ast, 'infer> {
    infer_expr:
        &'infer mut dyn FnMut(&mut TypeInferenceBuilder<'db, 'ast>, TypeContext<'db>) -> Type<'db>,
    last_tcx: Option<TypeContext<'db>>,
    finalized: bool,
}

impl<'db, 'ast, 'infer> MultiInferenceGuard<'db, 'ast, 'infer> {
    /// Creates a [`MultiInferenceGuard`] for the given expression.
    fn new(
        infer_expr: &'infer mut dyn FnMut(
            &mut TypeInferenceBuilder<'db, 'ast>,
            TypeContext<'db>,
        ) -> Type<'db>,
    ) -> Self {
        Self {
            infer_expr,
            last_tcx: None,
            finalized: false,
        }
    }

    /// Infer the expression with diagnostics enabled.
    ///
    /// This method must be called exactly once in the lifetime of the [`MultiInferenceGuard`].
    fn infer_loud(
        &mut self,
        builder: &mut TypeInferenceBuilder<'db, 'ast>,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        debug_assert!(
            !self.finalized,
            "called `infer_loud` multiple times on a `MultiInferenceGuard`"
        );

        self.finalized = true;
        (self.infer_expr)(builder, tcx)
    }

    /// Infer the expression silently, with diagnostics disabled.
    ///
    /// This method may be called an unlimited number of times.
    fn infer_silent(
        &mut self,
        builder: &mut TypeInferenceBuilder<'db, 'ast>,
        tcx: TypeContext<'db>,
    ) -> Type<'db> {
        self.last_tcx = Some(tcx);
        (self.infer_expr)(&mut builder.speculate_without_diagnostics(), tcx)
    }

    fn last_tcx(&self) -> TypeContext<'db> {
        self.last_tcx.unwrap_or_default()
    }
}

impl Drop for MultiInferenceGuard<'_, '_, '_> {
    fn drop(&mut self) {
        debug_assert!(
            self.finalized,
            "dropped `MultiInferenceGuard` without calling `infer_loud`"
        );
    }
}

/// An expression representing the function argument at the given index, along with its type
/// context.
type ArgExpr<'db, 'ast> = (usize, &'ast ast::Expr, TypeContext<'db>);

fn is_collection_literal(expression: &ast::Expr) -> bool {
    matches!(
        expression,
        ast::Expr::List(_) | ast::Expr::Set(_) | ast::Expr::Dict(_)
    )
}

/// Returns `true` if applying type context to the given expression may be deferred after inference.
///
/// For example, list literals must be inferred with type context directly, as the type context may
/// influence the type assigned to an invariant generic type parameter. However, bare name expressions
/// may be inferred without type context, and later specialized after inference.
fn can_defer_type_context(expression: &ast::Expr) -> bool {
    match expression {
        ast::Expr::StringLiteral(_)
        | ast::Expr::Tuple(_)
        | ast::Expr::List(_)
        | ast::Expr::Set(_)
        | ast::Expr::Dict(_)
        | ast::Expr::Generator(_)
        | ast::Expr::ListComp(_)
        | ast::Expr::DictComp(_)
        | ast::Expr::SetComp(_)
        | ast::Expr::BinOp(_)
        | ast::Expr::BoolOp(_)
        | ast::Expr::If(_)
        | ast::Expr::Lambda(_)
        | ast::Expr::Call(_)
        | ast::Expr::Starred(_)
        | ast::Expr::Await(_) => false,

        ast::Expr::NoneLiteral(_)
        | ast::Expr::NumberLiteral(_)
        | ast::Expr::BooleanLiteral(_)
        | ast::Expr::BytesLiteral(_)
        | ast::Expr::FString(_)
        | ast::Expr::TString(_)
        | ast::Expr::EllipsisLiteral(_)
        | ast::Expr::Name(_)
        | ast::Expr::Attribute(_)
        | ast::Expr::UnaryOp(_)
        | ast::Expr::Compare(_)
        | ast::Expr::Subscript(_)
        | ast::Expr::Slice(_)
        | ast::Expr::Yield(_)
        | ast::Expr::YieldFrom(_)
        | ast::Expr::Named(_)
        | ast::Expr::IpyEscapeCommand(_) => true,
    }
}

/// Returns `true` if `tcx` cannot provide useful type context for a collection literal.
///
/// During generic call argument inference, type variables that cannot yet be specialized are
/// replaced by `UnspecializedTypeVar`. This marker intentionally carries neither type-variable
/// identity nor a concrete expected type, and collection literal inference ignores it rather than
/// using it as a constraint.
///
/// A bare generic parameter, such as the parameter to `reveal_type`, therefore provides an exact
/// `UnspecializedTypeVar` context that should not prevent a peer expression from providing context
/// instead.
///
/// This deliberately matches only the bare marker: a partially specialized context such as
/// `list[UnspecializedTypeVar | int]` still carries useful collection structure and concrete type
/// information.
fn allows_collection_literal_peer_context(tcx: TypeContext<'_>) -> bool {
    tcx.annotation
        .is_none_or(|annotation| annotation == Type::Dynamic(DynamicType::UnspecializedTypeVar))
}

/// An iterator over arguments to a functional call.
#[derive(Clone)]
enum ArgumentsIter<'a> {
    FromAst(ArgumentsSourceOrder<'a>),
    Synthesized(std::slice::Iter<'a, ArgOrKeyword<'a>>),
}

impl<'a> ArgumentsIter<'a> {
    fn from_ast(arguments: &'a ast::Arguments) -> Self {
        Self::FromAst(arguments.iter_source_order())
    }

    fn synthesized(arguments: &'a [ArgOrKeyword<'a>]) -> Self {
        Self::Synthesized(arguments.iter())
    }
}

impl<'a> Iterator for ArgumentsIter<'a> {
    type Item = ArgOrKeyword<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        match self {
            ArgumentsIter::FromAst(args) => args.next(),
            ArgumentsIter::Synthesized(args) => args.next().copied(),
        }
    }
}

/// The deferred state of a specific expression in an inference region.
#[derive(Default, Debug, Clone, Copy)]
enum DeferredExpressionState {
    /// The expression is not deferred.
    #[default]
    None,

    /// The expression is deferred.
    ///
    /// In the following example,
    /// ```py
    /// from __future__ import annotation
    ///
    /// a: tuple[int, "ForwardRef"] = ...
    /// ```
    ///
    /// The expression `tuple` and `int` are deferred but `ForwardRef` (after parsing) is both
    /// deferred and in a string annotation context.
    Deferred,

    /// The expression is in a string annotation context.
    ///
    /// This is required to differentiate between a deferred annotation and a string annotation.
    /// The former can occur when there's a `from __future__ import annotations` statement or we're
    /// in a stub file.
    ///
    /// In the following example,
    /// ```py
    /// a: "List[int]" = ...
    /// b: tuple[int, "ForwardRef"] = ...
    /// ```
    ///
    /// The annotation of `a` is completely inside a string while for `b`, it's only partially
    /// stringified.
    ///
    /// This variant wraps a [`NodeKey`] that allows us to retrieve the original
    /// [`ast::ExprStringLiteral`] node which created the string annotation.
    InStringAnnotation(NodeKey),
}

impl DeferredExpressionState {
    const fn is_deferred(self) -> bool {
        matches!(
            self,
            DeferredExpressionState::Deferred | DeferredExpressionState::InStringAnnotation(_)
        )
    }

    const fn in_string_annotation(self) -> bool {
        matches!(self, DeferredExpressionState::InStringAnnotation(_))
    }
}

impl From<bool> for DeferredExpressionState {
    fn from(value: bool) -> Self {
        if value {
            DeferredExpressionState::Deferred
        } else {
            DeferredExpressionState::None
        }
    }
}

/// Struct collecting string parts when inferring a formatted string. Infers a string literal if the
/// concatenated string is small enough, otherwise infers a literal string.
///
/// If the formatted string contains an expression (with a representation unknown at compile time),
/// infers an instance of `builtins.str`.
#[derive(Debug)]
struct StringPartsCollector {
    concatenated: Option<CompactString>,
    contains_non_literal_str: bool,
}

impl StringPartsCollector {
    fn new() -> Self {
        Self {
            concatenated: Some(CompactString::new("")),
            contains_non_literal_str: false,
        }
    }

    fn push_str(&mut self, literal: &str) {
        if let Some(mut concatenated) = self.concatenated.take() {
            if concatenated.len().saturating_add(literal.len())
                <= TypeInferenceBuilder::MAX_STRING_LITERAL_SIZE
            {
                concatenated.push_str(literal);
                self.concatenated = Some(concatenated);
            } else {
                self.concatenated = None;
            }
        }
    }

    /// Add an expression whose `__str__` return type is `LiteralString`.
    /// The exact value is unknown, so we can't track the concatenated string,
    /// but the result is still `LiteralString`.
    fn add_literal_string_expression(&mut self) {
        self.concatenated = None;
    }

    /// Add an expression whose `__str__` return type is not `LiteralString`.
    /// The result will degrade to `str`.
    fn add_non_literal_string_expression(&mut self) {
        self.concatenated = None;
        self.contains_non_literal_str = true;
    }

    fn string_type(self, db: &dyn Db) -> Type<'_> {
        if self.contains_non_literal_str {
            KnownClass::Str.to_instance(db)
        } else if let Some(concatenated) = self.concatenated {
            Type::string_literal(db, &concatenated)
        } else {
            Type::literal_string()
        }
    }
}

/// Map based on a `Vec`. It doesn't enforce
/// uniqueness on insertion. Instead, it relies on the caller
/// that elements are unique. For example, the way we visit definitions
/// in the `TypeInference` builder already implicitly guarantees that each definition
/// is only visited once.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct VecMap<K, V>(Vec<(K, V)>);

impl<K, V> VecMap<K, V> {
    #[inline]
    fn len(&self) -> usize {
        self.0.len()
    }

    #[inline]
    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn iter(&self) -> VecMapIterator<'_, K, V> {
        VecMapIterator {
            inner: self.0.iter(),
        }
    }

    fn into_boxed_slice(self) -> Box<[(K, V)]> {
        self.0.into_boxed_slice()
    }

    fn into_vec(self) -> Vec<(K, V)> {
        self.0
    }
}

impl<K, V> VecMap<K, V>
where
    K: Eq,
    K: std::fmt::Debug,
    V: std::fmt::Debug,
{
    fn insert(&mut self, key: K, value: V) {
        debug_assert!(
            !self.0.iter().any(|(existing, _)| existing == &key),
            "An existing entry already exists for key {key:?}",
        );

        self.0.push((key, value));
    }

    #[inline]
    fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
        if cfg!(debug_assertions) {
            for (key, value) in iter {
                self.insert(key, value);
            }
        } else {
            self.0.extend(iter);
        }
    }
}

impl<K, V> Default for VecMap<K, V> {
    fn default() -> Self {
        Self(Vec::default())
    }
}

impl<'a, K, V> IntoIterator for &'a VecMap<K, V> {
    type Item = (&'a K, &'a V);
    type IntoIter = VecMapIterator<'a, K, V>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

struct VecMapIterator<'a, K, V> {
    inner: std::slice::Iter<'a, (K, V)>,
}

impl<'a, K, V> Iterator for VecMapIterator<'a, K, V> {
    type Item = (&'a K, &'a V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(k, v)| (k, v))
    }
}

impl<K, V> std::iter::FusedIterator for VecMapIterator<'_, K, V> {}

impl<K, V> ExactSizeIterator for VecMapIterator<'_, K, V> {
    fn len(&self) -> usize {
        self.inner.len()
    }
}

/// Set based on a `Vec`. It doesn't enforce
/// uniqueness on insertion. Instead, it relies on the caller
/// that elements are unique. For example, the way we visit definitions
/// in the `TypeInference` builder make already implicitly guarantees that each definition
/// is only visited once.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct VecSet<V>(Vec<V>);

impl<V> VecSet<V> {
    #[inline]
    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn into_boxed_slice(self) -> Box<[V]> {
        self.0.into_boxed_slice()
    }
}

impl<V> VecSet<V>
where
    V: Eq,
    V: std::fmt::Debug,
{
    fn insert(&mut self, value: V) {
        debug_assert!(
            !self.0.iter().any(|existing| existing == &value),
            "An existing entry already exists for {value:?}",
        );

        self.0.push(value);
    }
}

impl<V> VecSet<V>
where
    V: Eq,
    V: std::fmt::Debug,
{
    #[inline]
    fn extend<T: IntoIterator<Item = V>>(&mut self, iter: T) {
        if cfg!(debug_assertions) {
            for value in iter {
                self.insert(value);
            }
        } else {
            self.0.extend(iter);
        }
    }
}

impl<V> Default for VecSet<V> {
    fn default() -> Self {
        Self(Vec::default())
    }
}

impl<V> IntoIterator for VecSet<V> {
    type Item = V;
    type IntoIter = std::vec::IntoIter<V>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

#[must_use]
struct AddBinding<'db, 'ast> {
    declared_ty: Option<Type<'db>>,
    binding: Definition<'db>,
    node: AnyNodeRef<'ast>,
    qualifiers: TypeQualifiers,
    is_local: bool,
}

impl<'db, 'ast> AddBinding<'db, 'ast> {
    fn type_context(&self) -> TypeContext<'db> {
        TypeContext::new(self.declared_ty)
    }

    fn insert(
        self,
        builder: &mut TypeInferenceBuilder<'db, 'ast>,
        inferred_ty: Type<'db>,
    ) -> Type<'db> {
        let declared_ty = self.declared_ty.unwrap_or(Type::unknown());

        let db = builder.db();
        let file_scope_id = self.binding.file_scope(db);
        let use_def = builder.index.use_def_map(file_scope_id);
        let place_table = builder.index.place_table(file_scope_id);

        let mut bound_ty = inferred_ty;

        if self.qualifiers.contains(TypeQualifiers::FINAL) {
            let mut previous_bindings = use_def.bindings_at_definition(self.binding);

            // An assignment to a local `Final`-qualified symbol is only an error if there are prior bindings

            let previous_definition = previous_bindings.find_map(|r| r.binding.definition());

            if !self.is_local || previous_definition.is_some() {
                let place = place_table.place(self.binding.place(db));
                if let Some(diag_builder) = builder.context.report_lint(
                    &INVALID_ASSIGNMENT,
                    self.binding.full_range(builder.db(), builder.module()),
                ) {
                    let mut diagnostic = diag_builder.into_diagnostic(format_args!(
                        "Reassignment of `Final` symbol `{place}` is not allowed"
                    ));

                    diagnostic.set_primary_message("Reassignment of `Final` symbol");

                    if let Some(previous_definition) = previous_definition {
                        // It is not very helpful to show the previous definition if it results from
                        // an import. Ideally, we would show the original definition in the external
                        // module, but that information is currently not threaded through attribute
                        // lookup.
                        if !previous_definition.kind(db).is_import() {
                            if let DefinitionKind::AnnotatedAssignment(assignment) =
                                previous_definition.kind(db)
                            {
                                let range = assignment.annotation(builder.module()).range();
                                diagnostic.annotate(
                                    builder
                                        .context
                                        .secondary(range)
                                        .message("Symbol declared as `Final` here"),
                                );
                            } else {
                                let range = previous_definition.full_range(db, builder.module());
                                diagnostic.annotate(
                                    builder
                                        .context
                                        .secondary(range)
                                        .message("Symbol declared as `Final` here"),
                                );
                            }
                            diagnostic.set_primary_message("Symbol later reassigned here");
                        }
                    }
                }
            }
        }

        if !bound_ty.is_assignable_to(db, declared_ty) {
            builder.discard_dict_key_assignments_for(self.binding);
            report_invalid_assignment(
                &builder.context,
                self.node,
                self.binding,
                declared_ty,
                bound_ty,
            );

            // Allow declarations to override inference in case of invalid assignment.
            bound_ty = declared_ty;
        }
        // In the following cases, the bound type may not be the same as the RHS value type.
        if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = self.node {
            let value_ty = builder.try_expression_type(value).unwrap_or_else(|| {
                builder.infer_maybe_standalone_expression(value, TypeContext::default())
            });
            // If the member is a data descriptor, the RHS value may differ from the value actually assigned.
            if value_ty
                .class_member(db, attr.id.clone())
                .place
                .ignore_possibly_undefined()
                .is_some_and(|ty| ty.may_be_data_descriptor(db))
            {
                builder.discard_dict_key_assignments_for(self.binding);
                bound_ty = declared_ty;
            }
        } else if let AnyNodeRef::ExprSubscript(ast::ExprSubscript { value, .. }) = self.node {
            let value_ty = builder
                .try_expression_type(value)
                .unwrap_or_else(|| builder.infer_expression(value, TypeContext::default()));

            if !value_ty.is_typed_dict() && !Self::is_safe_mutable_class(db, value_ty) {
                builder.discard_dict_key_assignments_for(self.binding);
                bound_ty = declared_ty;
            }
        }

        builder.bindings.insert(self.binding, bound_ty);

        inferred_ty
    }

    /// Arbitrary `__getitem__`/`__setitem__` methods on a class do not
    /// necessarily guarantee that the passed-in value for `__setitem__` is stored and
    /// can be retrieved unmodified via `__getitem__`. Therefore, we currently only
    /// perform assignment-based narrowing on a few built-in classes (`list`, `dict`,
    /// `bytesarray`, `TypedDict`, and `collections` types) where we are confident that
    /// this kind of narrowing can be performed soundly. This is the same approach as
    /// pyright. TODO: Other standard library classes may also be considered safe. Also,
    /// subclasses of these safe classes that do not override `__getitem__/__setitem__`
    /// may be considered safe.
    fn is_safe_mutable_class(db: &'db dyn Db, ty: Type<'db>) -> bool {
        const SAFE_MUTABLE_CLASSES: &[KnownClass] = &[
            KnownClass::List,
            KnownClass::Dict,
            KnownClass::Bytearray,
            KnownClass::DefaultDict,
            KnownClass::ChainMap,
            KnownClass::Counter,
            KnownClass::Deque,
            KnownClass::OrderedDict,
        ];

        SAFE_MUTABLE_CLASSES
            .iter()
            .map(|class| class.to_instance(db))
            .any(|safe_mutable_class| {
                ty.is_equivalent_to(db, safe_mutable_class)
                    || ty
                        .generic_origin(db)
                        .zip(safe_mutable_class.generic_origin(db))
                        .is_some_and(|(l, r)| l == r)
            })
    }
}

#[derive(Copy, Clone, Debug)]
enum BoundOrConstraintsNodes<'ast> {
    Bound(&'ast ast::Expr),
    Constraints(&'ast [ast::Expr]),
}
