/*
 * typeEvaluator.ts
 * Copyright (c) Microsoft Corporation.
 * Licensed under the MIT license.
 * Author: Eric Traut
 *
 * Module that evaluates types of parse tree nodes within
 * a program.
 *
 * Note: This is a gargantuan module - much larger than I would
 * normally create. It is written this way primarily for performance,
 * with the internal methods having access to the full closure of
 * the createTypeEvaluator function. This is the same approach
 * taken by the TypeScript compiler.
 */

import { CancellationToken } from 'vscode-languageserver';

import { invalidateTypeCacheIfCanceled, throwIfCancellationRequested } from '../common/cancellationUtils';
import { appendArray } from '../common/collectionUtils';
import { DiagnosticLevel } from '../common/configOptions';
import { ConsoleInterface } from '../common/console';
import { isThenable } from '../common/core';
import { assert, assertNever, fail } from '../common/debug';
import { DiagnosticAddendum } from '../common/diagnostic';
import { DiagnosticRule } from '../common/diagnosticRules';
import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils';
import {
    PythonVersion,
    pythonVersion3_13,
    pythonVersion3_14,
    pythonVersion3_6,
    pythonVersion3_7,
    pythonVersion3_9,
} from '../common/pythonVersion';
import { TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { LocAddendum, LocMessage, ParameterizedString } from '../localization/localize';
import {
    ArgCategory,
    ArgumentNode,
    AssignmentNode,
    AugmentedAssignmentNode,
    AwaitNode,
    CallNode,
    CaseNode,
    ClassNode,
    ComprehensionForIfNode,
    ComprehensionNode,
    ConstantNode,
    DecoratorNode,
    DictionaryNode,
    ErrorExpressionCategory,
    ExceptNode,
    ExecutionScopeNode,
    ExpressionNode,
    FormatStringNode,
    ForNode,
    FunctionNode,
    ImportAsNode,
    ImportFromAsNode,
    ImportFromNode,
    IndexNode,
    isExpressionNode,
    LambdaNode,
    ListNode,
    MatchNode,
    MemberAccessNode,
    NameNode,
    NumberNode,
    ParamCategory,
    ParameterNode,
    ParseNode,
    ParseNodeType,
    SetNode,
    SliceNode,
    StringListNode,
    StringNode,
    TupleNode,
    TypeAliasNode,
    TypeAnnotationNode,
    TypeParameterListNode,
    TypeParameterNode,
    TypeParameterScopeNode,
    TypeParamKind,
    UnpackNode,
    WithItemNode,
    YieldFromNode,
    YieldNode,
} from '../parser/parseNodes';
import { ParseOptions, Parser, ParseTextMode } from '../parser/parser';
import { KeywordType, OperatorType, StringTokenFlags } from '../parser/tokenizerTypes';
import { AnalyzerFileInfo, ImportLookup, isAnnotationEvaluationPostponed } from './analyzerFileInfo';
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
import { CodeFlowAnalyzer, FlowNodeTypeOptions, FlowNodeTypeResult, getCodeFlowEngine } from './codeFlowEngine';
import {
    CodeFlowReferenceExpressionNode,
    createKeyForReference,
    FlowFlags,
    FlowNode,
    FlowWildcardImport,
    isCodeFlowSupportedForReference,
    wildcardImportReferenceKey,
} from './codeFlowTypes';
import { ConstraintSolution } from './constraintSolution';
import {
    addConstraintsForExpectedType,
    applySourceSolutionToConstraints,
    assignTypeVar,
    solveConstraints,
    solveConstraintSet,
} from './constraintSolver';
import { ConstraintSet, ConstraintTracker } from './constraintTracker';
import { createFunctionFromConstructor, getBoundInitMethod, validateConstructorArgs } from './constructors';
import { applyDataClassClassBehaviorOverrides, synthesizeDataClassMethods } from './dataClasses';
import {
    ClassDeclaration,
    Declaration,
    DeclarationType,
    FunctionDeclaration,
    isVariableDeclaration,
    ModuleLoaderActions,
    SpecialBuiltInClassDeclaration,
    VariableDeclaration,
} from './declaration';
import {
    getDeclarationsWithUsesLocalNameRemoved,
    getNameNodeForDeclaration,
    resolveAliasDeclaration as resolveAliasDeclarationUtil,
    ResolvedAliasInfo,
    synthesizeAliasDeclaration,
} from './declarationUtils';
import {
    addOverloadsToFunctionType,
    applyClassDecorator,
    applyFunctionDecorator,
    FunctionDecoratorInfo,
    getDeprecatedMessageFromCall,
    getFunctionInfoFromDecorators,
} from './decorators';
import {
    createEnumType,
    getEnumAutoValueType,
    getTypeOfEnumMember,
    isDeclInEnumClass,
    isEnumClassWithMembers,
    isEnumMetaclass,
} from './enums';
import { applyFunctionTransform } from './functionTransform';
import { createNamedTupleType } from './namedTuples';
import {
    getTypeOfAugmentedAssignment,
    getTypeOfBinaryOperation,
    getTypeOfTernaryOperation,
    getTypeOfUnaryOperation,
} from './operations';
import {
    getParamListDetails,
    isParamSpecArgs,
    isParamSpecKwargs,
    ParamAssignmentTracker,
    ParamKind,
    ParamListDetails,
    VirtualParamDetails,
} from './parameterUtils';
import * as ParseTreeUtils from './parseTreeUtils';
import { assignTypeToPatternTargets, checkForUnusedPattern, narrowTypeBasedOnPattern } from './patternMatching';
import { assignProperty } from './properties';
import { assignClassToProtocol, assignModuleToProtocol } from './protocols';
import { Scope, ScopeType, SymbolWithScope } from './scope';
import * as ScopeUtils from './scopeUtils';
import { createSentinelType } from './sentinel';
import { evaluateStaticBoolExpression } from './staticExpressions';
import { indeterminateSymbolId, Symbol, SymbolFlags, SynthesizedTypeInfo } from './symbol';
import { isConstantName, isPrivateName, isPrivateOrProtectedName } from './symbolNameUtils';
import { getLastTypedDeclarationForSymbol, isEffectivelyClassVar } from './symbolUtils';
import { assignTupleTypeArgs, expandTuple, getSlicedTupleType, getTypeOfTuple, makeTupleObject } from './tuples';
import { SpeculativeModeOptions, SpeculativeTypeTracker } from './typeCacheUtils';
import {
    assignToTypedDict,
    assignTypedDictToTypedDict,
    createTypedDictType,
    createTypedDictTypeInlined,
    getTypedDictDictEquivalent,
    getTypedDictMappingEquivalent,
    getTypedDictMembersForClass,
    getTypeOfIndexedTypedDict,
    synthesizeTypedDictClassMethods,
} from './typedDicts';
import {
    AbstractSymbol,
    Arg,
    ArgResult,
    ArgWithExpression,
    AssignTypeFlags,
    CallResult,
    CallSignature,
    CallSignatureInfo,
    CallSiteEvaluationInfo,
    ClassMemberLookup,
    ClassTypeResult,
    DeclaredSymbolTypeInfo,
    EffectiveTypeResult,
    ensureExpectedTypeCandidates,
    EvalFlags,
    EvaluatorUsage,
    ExpectedTypeOptions,
    ExpectedTypeResult,
    FunctionTypeResult,
    MagicMethodDeprecationInfo,
    MapSubtypesOptions,
    maxInferredContainerDepth,
    maxSubtypesForInferredType,
    MemberAccessDeprecationInfo,
    PrefetchedTypes,
    PrintTypeOptions,
    Reachability,
    ResolveAliasOptions,
    SolveConstraintsOptions,
    SymbolDeclInfo,
    TypeEvaluator,
    TypeResult,
    TypeResultWithNode,
    ValidateArgTypeParams,
    ValidateTypeArgsOptions,
} from './typeEvaluatorTypes';
import { enumerateLiteralsForType } from './typeGuards';
import * as TypePrinter from './typePrinter';
import {
    AnyType,
    ClassType,
    ClassTypeFlags,
    combineTypes,
    DataClassBehaviors,
    EnumLiteral,
    findSubtype,
    FunctionParam,
    FunctionParamFlags,
    FunctionType,
    FunctionTypeFlags,
    InheritanceChain,
    isAny,
    isAnyOrUnknown,
    isClass,
    isClassInstance,
    isFunction,
    isFunctionOrOverloaded,
    isInstantiableClass,
    isMethodType,
    isModule,
    isNever,
    isOverloaded,
    isParamSpec,
    isPositionOnlySeparator,
    isTypeSame,
    isTypeVar,
    isTypeVarTuple,
    isUnbound,
    isUnion,
    isUnknown,
    isUnpacked,
    isUnpackedClass,
    isUnpackedTypeVarTuple,
    LiteralValue,
    maxTypeRecursionCount,
    ModuleType,
    NeverType,
    OverloadedType,
    ParamSpecType,
    removeFromUnion,
    removeUnbound,
    SentinelLiteral,
    TupleTypeArg,
    Type,
    TypeAliasInfo,
    TypeBase,
    TypeCategory,
    TypeCondition,
    TypedDictEntries,
    TypeVarKind,
    TypeVarScopeId,
    TypeVarScopeType,
    TypeVarTupleType,
    TypeVarType,
    UnboundType,
    UnionType,
    UnknownType,
    Variance,
} from './types';
import {
    addConditionToType,
    addTypeVarsToListIfUnique,
    applySolvedTypeVars,
    ApplyTypeVarOptions,
    areTypesSame,
    buildSolutionFromSpecializedClass,
    ClassMember,
    combineSameSizedTuples,
    combineTupleTypeArgs,
    combineVariances,
    computeMroLinearization,
    containsAnyOrUnknown,
    containsLiteralType,
    convertToInstance,
    convertToInstantiable,
    convertTypeToParamSpecValue,
    derivesFromAnyOrUnknown,
    derivesFromClassRecursive,
    derivesFromStdlibClass,
    doForEachSubtype,
    ensureSignaturesAreUnique,
    explodeGenericClass,
    getContainerDepth,
    getDeclaredGeneratorReturnType,
    getGeneratorTypeArgs,
    getGeneratorYieldType,
    getSpecializedTupleType,
    getTypeCondition,
    getTypeVarArgsRecursive,
    getTypeVarScopeId,
    getTypeVarScopeIds,
    getUnknownForTypeVar,
    getUnknownTypeForCallable,
    InferenceContext,
    invertVariance,
    isDescriptorInstance,
    isEffectivelyInstantiable,
    isEllipsisType,
    isIncompleteUnknown,
    isInstantiableMetaclass,
    isLiteralLikeType,
    isLiteralType,
    isMaybeDescriptorInstance,
    isMemberReadOnly,
    isMetaclassInstance,
    isNoneInstance,
    isNoneTypeClass,
    isOptionalType,
    isPartlyUnknown,
    isProperty,
    isSentinelLiteral,
    isTupleClass,
    isTupleIndexUnambiguous,
    isTypeAliasPlaceholder,
    isTypeAliasRecursive,
    isTypeVarSame,
    isUnboundedTupleClass,
    isVarianceOfTypeArgCompatible,
    lookUpClassMember,
    lookUpObjectMember,
    makeFunctionTypeVarsBound,
    makeInferenceContext,
    makePacked,
    makeTypeVarsBound,
    makeTypeVarsFree,
    mapSignatures,
    mapSubtypes,
    MemberAccessFlags,
    partiallySpecializeType,
    preserveUnknown,
    removeNoneFromUnion,
    requiresSpecialization,
    requiresTypeArgs,
    selfSpecializeClass,
    simplifyFunctionToParamSpec,
    someSubtypes,
    sortTypes,
    specializeForBaseClass,
    specializeTupleClass,
    specializeWithDefaultTypeArgs,
    specializeWithUnknownTypeArgs,
    stripTypeForm,
    stripTypeFormRecursive,
    synthesizeTypeVarForSelfCls,
    transformExpectedType,
    transformPossibleRecursiveTypeAlias,
    UniqueSignatureTracker,
    validateTypeVarDefault,
} from './typeUtils';

interface GetTypeArgsOptions {
    isAnnotatedClass?: boolean;
    hasCustomClassGetItem?: boolean;
    isFinalAnnotation?: boolean;
    isClassVarAnnotation?: boolean;
    supportsTypedDictTypeArg?: boolean;
}

interface MatchArgsToParamsResult {
    overload: FunctionType;
    overloadIndex: number;

    argumentErrors: boolean;
    isTypeIncomplete: boolean;
    argParams: ValidateArgTypeParams[];
    activeParam?: FunctionParam | undefined;
    paramSpecTarget?: ParamSpecType | undefined;
    paramSpecArgList?: Arg[] | undefined;

    // Was there an unpacked argument of unknown length?
    unpackedArgOfUnknownLength?: boolean;

    // Did that unpacked argument map to a variadic parameter?
    unpackedArgMapsToVariadic?: boolean;

    // A score that indicates how well the overload matches with
    // supplied arguments. Used to pick the "best" for purposes
    // of error reporting when no matches are found. The higher
    // the score, the worse the match.
    argumentMatchScore: number;
}

export interface MemberAccessTypeResult {
    type: Type;
    isDescriptorApplied?: boolean;
    isAsymmetricAccessor?: boolean;
    memberAccessDeprecationInfo?: MemberAccessDeprecationInfo;
    typeErrors?: boolean;
}

interface ScopedTypeVarResult {
    type: TypeVarType;
    scopeNode: TypeParameterScopeNode | AssignmentNode | undefined;
    foundInterveningClass: boolean;
}

interface AliasMapEntry {
    alias: string;
    module: 'builtins' | 'collections' | 'internals';
    implicitBaseClass?: string;
    isSpecialForm?: boolean;
    isIllegalInIsinstance?: boolean;
    typeParamVariance?: Variance;
}

interface AssignClassToSelfInfo {
    class: ClassType;
    assumedVariance: Variance;
}

interface MatchedOverloadInfo {
    overload: FunctionType;
    matchResults: MatchArgsToParamsResult;
    constraints: ConstraintTracker;
    argResults: ArgResult[];
    returnType: Type;
}

interface ValidateArgTypeOptions {
    skipUnknownArgCheck?: boolean;
    isArgFirstPass?: boolean;
    conditionFilter?: TypeCondition[];
    skipReportError?: boolean;
}

interface EffectiveReturnTypeOptions {
    callSiteInfo?: CallSiteEvaluationInfo;
}

interface SignatureTrackerStackEntry {
    tracker: UniqueSignatureTracker;
    rootNode: ParseNode;
}

// This table contains the names of several built-in types that
// are not subscriptable at runtime on older versions of Python.
// It lists the first version of Python where subscripting is
// allowed.
const nonSubscriptableBuiltinTypes: Map<string, PythonVersion> = new Map([
    ['asyncio.futures.Future', pythonVersion3_9],
    ['asyncio.tasks.Task', pythonVersion3_9],
    ['builtins.dict', pythonVersion3_9],
    ['builtins.frozenset', pythonVersion3_9],
    ['builtins.list', pythonVersion3_9],
    ['builtins._PathLike', pythonVersion3_9],
    ['builtins.set', pythonVersion3_9],
    ['builtins.tuple', pythonVersion3_9],
    ['collections.ChainMap', pythonVersion3_9],
    ['collections.Counter', pythonVersion3_9],
    ['collections.defaultdict', pythonVersion3_9],
    ['collections.DefaultDict', pythonVersion3_9],
    ['collections.deque', pythonVersion3_9],
    ['collections.OrderedDict', pythonVersion3_9],
    ['queue.Queue', pythonVersion3_9],
]);

// Some types that do not inherit from others are still considered
// "compatible" based on the Python spec. These are sometimes referred
// to as "type promotions".
const typePromotions: Map<string, string[]> = new Map([
    ['builtins.float', ['builtins.int']],
    ['builtins.complex', ['builtins.float', 'builtins.int']],
    ['builtins.bytes', ['builtins.bytearray', 'builtins.memoryview']],
]);

interface SymbolResolutionStackEntry {
    // The symbol ID and declaration being resolved.
    symbolId: number;
    declaration: Declaration;

    // Initially true, it's set to false if a recursion
    // is detected.
    isResultValid: boolean;

    // Some limited forms of recursion are allowed. In these
    // cases, a partially-constructed type can be registered.
    partialType?: Type | undefined;
}

interface ReturnTypeInferenceContext {
    functionNode: FunctionNode;
    codeFlowAnalyzer: CodeFlowAnalyzer;
}

interface ParamSpecArgResult {
    argumentErrors: boolean;
    constraintTrackers: (ConstraintTracker | undefined)[];
}

// How many levels deep should we attempt to infer return
// types based on call-site argument types? The deeper we go,
// the more types we may be able to infer, but the worse the
// performance.
const maxReturnTypeInferenceStackSize = 2;

// What is the max number of input arguments we should allow
// for call-site return type inference? We've found that large,
// complex functions with many arguments can take too long to
// analyze.
const maxReturnTypeInferenceArgCount = 6;

// What is the max complexity of the code flow graph that
// we will analyze to determine the return type of a function
// when its parameters are unannotated? We want to keep this
// pretty low because this can be very costly.
const maxReturnTypeInferenceCodeFlowComplexity = 32;

// What is the max complexity of the code flow graph for
// call-site type inference? This is very expensive, so we
// want to keep this very low.
const maxReturnCallSiteTypeInferenceCodeFlowComplexity = 8;

// What is the max number of return types cached per function
// when using call-site inference?
const maxCallSiteReturnTypeCacheSize = 8;

// How many entries in a list, set, or dict should we examine
// when inferring the type? We need to cut it off at some point
// to avoid excessive computation.
const maxEntriesToUseForInference = 64;

// How many times should attempt to infer a return type of a
// function before giving up and assuming that it won't converge
// due to recursion?
const maxReturnTypeInferenceAttempts = 8;

// How many assignments to an unannotated variable should be used
// when inferring its type? We need to cut it off at some point
// to avoid excessive computation.
const maxDeclarationsToUseForInference = 64;

// Maximum number of times to attempt effective type evaluation
// of a variable that has no type declaration.
const maxEffectiveTypeEvaluationAttempts = 16;

// Maximum number of combinatoric argument type expansions allowed
// when resolving an overload.
const maxTotalOverloadArgTypeExpansionCount = 256;

// Maximum size of an enum that will be expanded during overload
// argument type expansion.
const maxSingleOverloadArgTypeExpansionCount = 64;

// Maximum number of recursive function return type inference attempts
// that can be concurrently pending before we give up.
const maxInferFunctionReturnRecursionCount = 12;

// Maximum recursion amount when comparing two recursive type aliases.
// Increasing this can greatly increase the time required to evaluate
// two recursive type aliases that have the same definition. Decreasing
// it can increase the chance of false negatives for such recursive
// type aliases.
const maxRecursiveTypeAliasRecursionCount = 10;

// Normally a symbol can have only one type declaration, but there are
// cases where multiple are possible (e.g. a property with a setter
// and a deleter). In extreme cases, we need to limit the number of
// type declarations we consider to avoid excessive computation.
const maxTypedDeclsPerSymbol = 16;

// This switch enables a special debug mode that attempts to catch
// bugs due to inconsistent evaluation flags used when reading types
// from the type cache.
const verifyTypeCacheEvaluatorFlags = false;

// This debugging option prints each expression and its evaluated type.
const printExpressionTypes = false;

// The following number is chosen somewhat arbitrarily. We need to cut
// off code flow analysis at some point for code flow graphs that are too
// complex. Otherwise we risk overflowing the stack or incurring extremely
// long analysis times. This number has been tuned empirically.
export const maxCodeComplexity = 768;

export interface EvaluatorOptions {
    printTypeFlags: TypePrinter.PrintTypeFlags;
    logCalls: boolean;
    minimumLoggingThreshold: number;
    evaluateUnknownImportsAsAny: boolean;
    verifyTypeCacheEvaluatorFlags: boolean;
}

// Describes a "deferred class completion" that is run when a class type is
// fully created and the "PartiallyEvaluated" flag has just been cleared.
// This allows us to properly compute information like the MRO which
// depends on a full understanding of base classes.
interface DeferredClassCompletion {
    dependsUpon: ClassType;
    classesToComplete: ClassNode[];
}

interface TypeCacheEntry {
    typeResult: TypeResult;
    incompleteGenCount: number;
    flags: EvalFlags | undefined;
}

interface CodeFlowAnalyzerCacheEntry {
    typeAtStart: TypeResult | undefined;
    codeFlowAnalyzer: CodeFlowAnalyzer;
}

interface FunctionRecursionInfo {
    callerNode: ExpressionNode | undefined;
}

type LogWrapper = <T extends (...args: any[]) => any>(func: T) => (...args: Parameters<T>) => ReturnType<T>;

interface SuppressedNodeStackEntry {
    node: ParseNode;
    suppressedDiags: string[] | undefined;
}

interface ExpectedTypeCacheEntry {
    type: Type;
    candidates: Type[];
}

export function createTypeEvaluator(
    importLookup: ImportLookup,
    evaluatorOptions: EvaluatorOptions,
    wrapWithLogger: LogWrapper
): TypeEvaluator {
    const symbolResolutionStack: SymbolResolutionStackEntry[] = [];
    const speculativeTypeTracker = new SpeculativeTypeTracker();
    const suppressedNodeStack: SuppressedNodeStackEntry[] = [];
    const assignClassToSelfStack: AssignClassToSelfInfo[] = [];

    let functionRecursionMap = new Map<number, FunctionRecursionInfo[]>();
    let codeFlowAnalyzerCache = new Map<number, CodeFlowAnalyzerCacheEntry[]>();
    let typeCache = new Map<number, TypeCacheEntry>();
    let effectiveTypeCache = new Map<number, Map<string, EffectiveTypeResult>>();
    let expectedTypeCache = new Map<number, ExpectedTypeCacheEntry>();
    let asymmetricAccessorAssignmentCache = new Set<number>();
    let deferredClassCompletions: DeferredClassCompletion[] = [];
    let cancellationToken: CancellationToken | undefined;
    let printExpressionSpaceCount = 0;
    let incompleteGenCount = 0;
    const returnTypeInferenceContextStack: ReturnTypeInferenceContext[] = [];
    let returnTypeInferenceTypeCache: Map<number, TypeCacheEntry> | undefined;
    const signatureTrackerStack: SignatureTrackerStackEntry[] = [];
    let prefetched: Partial<PrefetchedTypes> | undefined;

    function runWithCancellationToken<T>(token: CancellationToken, callback: () => T): T;
    function runWithCancellationToken<T>(token: CancellationToken, callback: () => Promise<T>): Promise<T>;
    function runWithCancellationToken<T>(token: CancellationToken, callback: () => T | Promise<T>): T | Promise<T> {
        // Save the current token and restore it after the callback to support nested calls
        const oldToken = cancellationToken;
        let result: T | Promise<T> | undefined = undefined;
        try {
            cancellationToken = token;
            result = callback();

            if (!isThenable(result)) {
                return result;
            }

            return result.finally(() => {
                cancellationToken = oldToken;
            });
        } finally {
            if (!isThenable(result)) {
                cancellationToken = oldToken;
            }
        }
    }

    function checkForCancellation() {
        if (cancellationToken) {
            throwIfCancellationRequested(cancellationToken);
        }
    }

    function getTypeCacheEntryCount(): number {
        return typeCache.size;
    }

    // This function should be called immediately prior to discarding
    // the type evaluator. It forcibly replaces existing cache maps
    // with empty equivalents. This shouldn't be necessary, but there
    // is apparently a bug in the v8 GC where it is unable to detect
    // circular references in complex data structures, so it fails
    // to clean up the objects if we don't help it out.
    function disposeEvaluator() {
        functionRecursionMap = new Map<number, FunctionRecursionInfo[]>();
        codeFlowAnalyzerCache = new Map<number, CodeFlowAnalyzerCacheEntry[]>();
        typeCache = new Map<number, TypeCacheEntry>();
        effectiveTypeCache = new Map<number, Map<string, EffectiveTypeResult>>();
        expectedTypeCache = new Map<number, ExpectedTypeCacheEntry>();
        asymmetricAccessorAssignmentCache = new Set<number>();
    }

    function readTypeCacheEntry(node: ParseNode) {
        // Should we use a temporary cache associated with a contextual
        // analysis of a function, contextualized based on call-site argument types?
        if (returnTypeInferenceTypeCache && isNodeInReturnTypeInferenceContext(node)) {
            return returnTypeInferenceTypeCache.get(node.id);
        } else {
            return typeCache.get(node.id);
        }
    }

    function isTypeCached(node: ParseNode) {
        const cacheEntry = readTypeCacheEntry(node);
        if (!cacheEntry) {
            return false;
        }

        return !cacheEntry.typeResult.isIncomplete || cacheEntry.incompleteGenCount === incompleteGenCount;
    }

    function readTypeCache(node: ParseNode, flags: EvalFlags | undefined): Type | undefined {
        const cacheEntry = readTypeCacheEntry(node);
        if (!cacheEntry || cacheEntry.typeResult.isIncomplete) {
            return undefined;
        }

        if (evaluatorOptions.verifyTypeCacheEvaluatorFlags || verifyTypeCacheEvaluatorFlags) {
            if (flags !== undefined) {
                const expectedFlags = cacheEntry.flags;

                if (expectedFlags !== undefined && flags !== expectedFlags) {
                    const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
                    const position = convertOffsetToPosition(node.start, fileInfo.lines);

                    const message =
                        `Type cache flag mismatch for node type ${node.nodeType} ` +
                        `(parent ${node.parent?.nodeType ?? 'none'}): ` +
                        `cached flags = ${expectedFlags}, access flags = ${flags}, ` +
                        `file = {${fileInfo.fileUri} [${position.line + 1}:${position.character + 1}]}`;
                    if (evaluatorOptions.verifyTypeCacheEvaluatorFlags) {
                        fail(message);
                    } else {
                        console.log(message);
                    }
                }
            }
        }

        return cacheEntry.typeResult.type;
    }

    function writeTypeCache(
        node: ParseNode,
        typeResult: TypeResult,
        flags: EvalFlags | undefined,
        inferenceContext?: InferenceContext,
        allowSpeculativeCaching = false
    ) {
        // Should we use a temporary cache associated with a contextual
        // analysis of a function, contextualized based on call-site argument types?
        const typeCacheToUse =
            returnTypeInferenceTypeCache && isNodeInReturnTypeInferenceContext(node)
                ? returnTypeInferenceTypeCache
                : typeCache;

        if (!typeResult.isIncomplete) {
            incompleteGenCount++;
        } else {
            const oldValue = typeCacheToUse.get(node.id);
            if (oldValue !== undefined && !isTypeSame(typeResult.type, oldValue.typeResult.type)) {
                incompleteGenCount++;
            }
        }

        typeCacheToUse.set(node.id, { typeResult, flags, incompleteGenCount });

        // If the entry is located within a part of the parse tree that is currently being
        // "speculatively" evaluated, track it so we delete the cached entry when we leave
        // this speculative context.
        if (isSpeculativeModeInUse(node)) {
            speculativeTypeTracker.trackEntry(typeCacheToUse, node.id);
            if (allowSpeculativeCaching) {
                speculativeTypeTracker.addSpeculativeType(
                    node,
                    typeResult,
                    incompleteGenCount,
                    inferenceContext?.expectedType
                );
            }
        }
    }

    function setTypeResultForNode(node: ParseNode, typeResult: TypeResult, flags = EvalFlags.None) {
        writeTypeCache(node, typeResult, flags);
    }

    function setAsymmetricDescriptorAssignment(node: ParseNode) {
        if (isSpeculativeModeInUse(/* node */ undefined)) {
            return;
        }

        asymmetricAccessorAssignmentCache.add(node.id);
    }

    function isAsymmetricAccessorAssignment(node: ParseNode) {
        return asymmetricAccessorAssignmentCache.has(node.id);
    }

    // Determines whether the specified node is contained within
    // the function node corresponding to the function that we
    // are currently analyzing in the context of parameter types
    // defined by a call site.
    function isNodeInReturnTypeInferenceContext(node: ParseNode) {
        const stackSize = returnTypeInferenceContextStack.length;
        if (stackSize === 0) {
            return false;
        }

        const contextNode = returnTypeInferenceContextStack[stackSize - 1];

        let curNode: ParseNode | undefined = node;
        while (curNode) {
            if (curNode === contextNode.functionNode) {
                return true;
            }
            curNode = curNode.parent;
        }

        return false;
    }

    function getCodeFlowAnalyzerForReturnTypeInferenceContext() {
        const stackSize = returnTypeInferenceContextStack.length;
        assert(stackSize > 0);
        const contextNode = returnTypeInferenceContextStack[stackSize - 1];
        return contextNode.codeFlowAnalyzer;
    }

    function getIndexOfSymbolResolution(symbol: Symbol, declaration: Declaration) {
        return symbolResolutionStack.findIndex(
            (entry) => entry.symbolId === symbol.id && entry.declaration === declaration
        );
    }

    function pushSymbolResolution(symbol: Symbol, declaration: Declaration) {
        const index = getIndexOfSymbolResolution(symbol, declaration);
        if (index >= 0) {
            // Mark all of the entries between these two as invalid.
            for (let i = index + 1; i < symbolResolutionStack.length; i++) {
                symbolResolutionStack[i].isResultValid = false;
            }
            return false;
        }

        symbolResolutionStack.push({
            symbolId: symbol.id,
            declaration,
            isResultValid: true,
        });
        return true;
    }

    function popSymbolResolution(symbol: Symbol) {
        const poppedEntry = symbolResolutionStack.pop();
        assert(
            poppedEntry !== undefined && poppedEntry.symbolId === symbol.id,
            `Symbol resolution stack mismatch: expected symbol ${symbol.id}, got ${
                poppedEntry?.symbolId ?? 'empty stack'
            }`
        );
        return poppedEntry.isResultValid;
    }

    function setSymbolResolutionPartialType(symbol: Symbol, declaration: Declaration, type: Type) {
        const index = getIndexOfSymbolResolution(symbol, declaration);
        if (index >= 0) {
            symbolResolutionStack[index].partialType = type;
        }
    }

    function getSymbolResolutionPartialType(symbol: Symbol, declaration: Declaration): Type | undefined {
        const index = getIndexOfSymbolResolution(symbol, declaration);
        if (index >= 0) {
            return symbolResolutionStack[index].partialType;
        }

        return undefined;
    }

    // Determines the type of the specified node by evaluating it in
    // context, logging any errors in the process. This may require the
    // type of surrounding statements to be evaluated.
    function getType(node: ExpressionNode): Type | undefined {
        initializePrefetchedTypes(node);

        let type = evaluateTypeForSubnode(node, () => {
            evaluateTypesForExpressionInContext(node);
        })?.type;

        // If this is a type parameter with a calculated variance, see if we
        // can swap it out for a version that has a computed variance.
        if (type && isTypeVar(type) && type.shared.declaredVariance === Variance.Auto) {
            const typeVarType = type;
            const typeParamListNode = ParseTreeUtils.getParentNodeOfType<TypeParameterListNode>(
                node,
                ParseNodeType.TypeParameterList
            );

            if (typeParamListNode?.parent?.nodeType === ParseNodeType.Class) {
                const classTypeResult = getTypeOfClass(typeParamListNode.parent);

                if (classTypeResult) {
                    inferVarianceForClass(classTypeResult.classType);

                    const typeParam = classTypeResult.classType.shared.typeParams.find((param) =>
                        isTypeSame(param, typeVarType, { ignoreTypeFlags: true })
                    );

                    if (typeParam?.priv.computedVariance !== undefined) {
                        type = TypeVarType.cloneWithComputedVariance(type, typeParam.priv.computedVariance);
                    }
                }
            } else if (typeParamListNode?.parent?.nodeType === ParseNodeType.TypeAlias) {
                const typeAliasType = getTypeOfTypeAlias(typeParamListNode.parent);
                const typeParamIndex = typeParamListNode.d.params.findIndex((param) => param.d.name === node);

                if (typeParamIndex >= 0) {
                    inferVarianceForTypeAlias(typeAliasType);

                    const typeAliasInfo = typeAliasType.props?.typeAliasInfo;
                    if (typeAliasInfo?.shared.computedVariance) {
                        const computedVariance = typeAliasInfo.shared.computedVariance[typeParamIndex];

                        type = TypeVarType.cloneWithComputedVariance(type, computedVariance);
                    }
                }
            }
        }

        if (type) {
            type = transformPossibleRecursiveTypeAlias(type);
        }

        return type;
    }

    function getTypeResult(node: ExpressionNode): TypeResult | undefined {
        return evaluateTypeForSubnode(node, () => {
            evaluateTypesForExpressionInContext(node);
        });
    }

    function getTypeResultForDecorator(node: DecoratorNode): TypeResult | undefined {
        return evaluateTypeForSubnode(node, () => {
            evaluateTypesForExpressionInContext(node.d.expr);
        });
    }

    // Reads the type of the node from the cache.
    function getCachedType(node: ExpressionNode | DecoratorNode): Type | undefined {
        return readTypeCache(node, EvalFlags.None);
    }

    // Determines the expected type of a specified node based on surrounding
    // context. For example, if it's a subexpression of an argument expression,
    // the associated parameter type might inform the expected type.
    function getExpectedType(node: ExpressionNode): ExpectedTypeResult | undefined {
        // This is a primary entry point called by language server providers,
        // and it might be called before any other type evaluation has occurred.
        // Use this opportunity to do some initialization.
        initializePrefetchedTypes(node);

        // Scan up the parse tree to find the top-most expression node
        // so we can evaluate the entire expression.
        let topExpression = node;
        let curNode: ParseNode | undefined = node;
        while (curNode) {
            if (isExpressionNode(curNode)) {
                topExpression = curNode;
            }

            curNode = curNode.parent;
        }

        // Evaluate the expression. This will have the side effect of
        // storing an expected type in the expected type cache.
        evaluateTypesForExpressionInContext(topExpression);

        // Look for the resulting expected type by scanning up the parse tree.
        curNode = node;
        while (curNode) {
            const expectedType = expectedTypeCache.get(curNode.id);
            if (expectedType) {
                return {
                    type: expectedType.type,
                    node: curNode,
                    candidates: ensureExpectedTypeCandidates(expectedType.type, expectedType.candidates),
                };
            }

            if (curNode === topExpression) {
                break;
            }

            curNode = curNode.parent;
        }

        return undefined;
    }

    function addExpectedTypeCacheEntry(node: ParseNode, expectedType: Type) {
        const cached = expectedTypeCache.get(node.id);
        if (!cached) {
            expectedTypeCache.set(node.id, {
                type: expectedType,
                candidates: [expectedType],
            });
            return;
        }

        cached.type = expectedType;
        if (!cached.candidates.some((candidate) => isTypeSame(candidate, expectedType))) {
            cached.candidates.push(expectedType);
        }
    }

    function initializePrefetchedTypes(node: ParseNode) {
        if (!prefetched) {
            // Some of these types have cyclical dependencies on each other,
            // so don't re-enter this block once we start executing it.
            prefetched = {};

            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

            prefetched.objectClass = getBuiltInType(node, 'object');
            prefetched.typeClass = getBuiltInType(node, 'type');
            prefetched.functionClass = getTypesType(node, 'FunctionType') ?? getBuiltInType(node, 'function');
            prefetched.methodClass = getTypesType(node, 'MethodType');

            prefetched.unionTypeClass = getTypesType(node, 'UnionType');
            if (prefetched.unionTypeClass && isClass(prefetched.unionTypeClass)) {
                prefetched.unionTypeClass.shared.flags |= ClassTypeFlags.SpecialFormClass;
            }

            // Initialize and cache "Collection" to break a cyclical dependency
            // that occurs when resolving tuple below.
            getTypingType(node, 'Collection');

            prefetched.noneTypeClass = getTypeshedType(node, 'NoneType') ?? UnknownType.create();
            prefetched.tupleClass = getBuiltInType(node, 'tuple');
            prefetched.boolClass = getBuiltInType(node, 'bool');
            prefetched.intClass = getBuiltInType(node, 'int');
            prefetched.strClass = getBuiltInType(node, 'str');
            prefetched.dictClass = getBuiltInType(node, 'dict');
            prefetched.moduleTypeClass = getTypingType(node, 'ModuleType');
            prefetched.typedDictPrivateClass =
                getTypeCheckerInternalsType(node, 'TypedDictFallback') ?? getTypingType(node, '_TypedDict');
            prefetched.typedDictClass = getTypingType(node, 'TypedDict');
            prefetched.awaitableClass = getTypingType(node, 'Awaitable');
            prefetched.mappingClass = getTypingType(node, 'Mapping');

            // Don't attempt to resolve the string.templatelib if pyright is configured for
            // Python 3.13 or older. Doing so will either fail to resolve (if running on Python 3.13
            // or older) or resolve to the templatelib.py source file (if running on Python 3.14).
            if (PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_14)) {
                prefetched.templateClass = getTypeOfModule(node, 'Template', ['string', 'templatelib']);
            } else {
                prefetched.templateClass = UnknownType.create();
            }

            prefetched.supportsKeysAndGetItemClass = getTypeshedType(node, 'SupportsKeysAndGetItem');
            if (!prefetched.supportsKeysAndGetItemClass) {
                // Fall back on 'Mapping' if 'SupportsKeysAndGetItem' is not available.
                prefetched.supportsKeysAndGetItemClass = prefetched.mappingClass;
            }

            // Wire up the `Any` class to the special-form version of our internal AnyType.
            if (
                prefetched.objectClass &&
                isInstantiableClass(prefetched.objectClass) &&
                prefetched.typeClass &&
                isInstantiableClass(prefetched.typeClass)
            ) {
                const anyClass = ClassType.createInstantiable(
                    'Any',
                    'typing.Any',
                    'typing',
                    Uri.empty(),
                    ClassTypeFlags.BuiltIn | ClassTypeFlags.SpecialFormClass | ClassTypeFlags.IllegalIsinstanceClass,
                    /* typeSourceId */ -1,
                    /* declaredMetaclass */ undefined,
                    /* effectiveMetaclass */ prefetched.typeClass
                );
                anyClass.shared.baseClasses.push(prefetched.objectClass);
                computeMroLinearization(anyClass);
                const anySpecialForm = AnyType.createSpecialForm();

                if (isAny(anySpecialForm)) {
                    TypeBase.setSpecialForm(anySpecialForm, anyClass);
                    TypeBase.setTypeForm(anySpecialForm, convertToInstance(anySpecialForm));
                }
            }
        }
    }

    function getTypeOfExpression(
        node: ExpressionNode,
        flags = EvalFlags.None,
        inferenceContext?: InferenceContext
    ): TypeResult {
        // Is this type already cached?
        const cacheEntry = readTypeCacheEntry(node);
        if (cacheEntry) {
            if (!cacheEntry.typeResult.isIncomplete || cacheEntry.incompleteGenCount === incompleteGenCount) {
                if (printExpressionTypes) {
                    console.log(
                        `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum(
                            node
                        )}): Cached ${printType(cacheEntry.typeResult.type)} ${
                            cacheEntry.typeResult.typeErrors ? ' Errors' : ''
                        }`
                    );
                }

                return cacheEntry.typeResult;
            }
        }

        // Is it cached in the speculative type cache?
        const specCacheEntry = speculativeTypeTracker.getSpeculativeType(node, inferenceContext?.expectedType);
        if (specCacheEntry) {
            if (
                !specCacheEntry.typeResult.isIncomplete ||
                specCacheEntry.incompleteGenerationCount === incompleteGenCount
            ) {
                if (printExpressionTypes) {
                    console.log(
                        `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum(
                            node
                        )}): Speculative ${printType(specCacheEntry.typeResult.type)}`
                    );
                }

                return specCacheEntry.typeResult;
            }
        }

        if (printExpressionTypes) {
            console.log(
                `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum(node)}): Pre`
            );
            printExpressionSpaceCount++;
        }

        // This is a frequently-called routine, so it's a good place to call
        // the cancellation check. If the operation is canceled, an exception
        // will be thrown at this point.
        checkForCancellation();

        if (inferenceContext) {
            inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType);
        }

        // If we haven't already fetched some core type definitions from the
        // typeshed stubs, do so here. It would be better to fetch this when it's
        // needed in assignType, but we don't have access to the parse tree
        // at that point.
        initializePrefetchedTypes(node);

        let typeResult = getTypeOfExpressionCore(node, flags, inferenceContext);

        // Should we disable type promotions for bytes?
        if (
            isInstantiableClass(typeResult.type) &&
            typeResult.type.priv.includePromotions &&
            !typeResult.type.priv.includeSubclasses &&
            ClassType.isBuiltIn(typeResult.type, 'bytes')
        ) {
            if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.disableBytesTypePromotions) {
                typeResult = {
                    ...typeResult,
                    type: ClassType.cloneRemoveTypePromotions(typeResult.type),
                };
            }
        }

        if (inferenceContext) {
            // Handle TypeForm assignments.
            typeResult.type = convertToTypeFormType(inferenceContext.expectedType, typeResult.type);
        }

        // Don't allow speculative caching for assignment expressions because
        // the target name node won't have a corresponding type cached speculatively.
        const allowSpeculativeCaching = node.nodeType !== ParseNodeType.AssignmentExpression;

        writeTypeCache(node, typeResult, flags, inferenceContext, allowSpeculativeCaching);

        if (node.nodeType === ParseNodeType.Name || node.nodeType === ParseNodeType.MemberAccess) {
            // If this is a generic function and there is a signature tracker,
            // make sure the signature is unique.
            typeResult.type = ensureSignatureIsUnique(typeResult.type, node);
        }

        // If there was an expected type, make sure that the result type is compatible.
        if (
            inferenceContext &&
            !isAnyOrUnknown(inferenceContext.expectedType) &&
            !isNever(inferenceContext.expectedType)
        ) {
            addExpectedTypeCacheEntry(node, inferenceContext.expectedType);

            if (!typeResult.isIncomplete && !typeResult.expectedTypeDiagAddendum) {
                const diag = new DiagnosticAddendum();

                // Make sure the resulting type is assignable to the expected type.
                if (
                    !assignType(
                        inferenceContext.expectedType,
                        typeResult.type,
                        diag,
                        /* constraints */ undefined,
                        AssignTypeFlags.Default
                    )
                ) {
                    // Set the typeErrors to true, but first make a copy of the
                    // type result because the (non-error) version may already
                    // be cached.
                    typeResult = { ...typeResult, typeErrors: true };
                    typeResult.expectedTypeDiagAddendum = diag;
                    diag.addTextRange(node);
                }
            }
        }

        if (printExpressionTypes) {
            printExpressionSpaceCount--;
            console.log(
                `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum(
                    node
                )}): Post ${printType(typeResult.type)}${typeResult.isIncomplete ? ' Incomplete' : ''}`
            );
        }

        return typeResult;
    }

    // This is a helper function that implements the core of getTypeOfExpression.
    function getTypeOfExpressionCore(
        node: ExpressionNode,
        flags = EvalFlags.None,
        inferenceContext?: InferenceContext
    ): TypeResult {
        let typeResult: TypeResult | undefined;
        let expectingInstantiable = (flags & EvalFlags.InstantiableType) !== 0;

        switch (node.nodeType) {
            case ParseNodeType.Name: {
                typeResult = getTypeOfName(node, flags);
                break;
            }

            case ParseNodeType.MemberAccess: {
                typeResult = getTypeOfMemberAccess(node, flags);
                break;
            }

            case ParseNodeType.Index: {
                typeResult = getTypeOfIndex(node, flags);
                break;
            }

            case ParseNodeType.Call: {
                typeResult = useSignatureTracker(node, () => getTypeOfCall(node, flags, inferenceContext));
                break;
            }

            case ParseNodeType.Tuple: {
                typeResult = getTypeOfTuple(evaluatorInterface, node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Constant: {
                typeResult = getTypeOfConstant(node, flags);
                break;
            }

            case ParseNodeType.StringList: {
                if ((flags & EvalFlags.StrLiteralAsType) !== 0) {
                    // Don't report expecting type errors again. We will have already
                    // reported them when analyzing the contents of the string.
                    expectingInstantiable = false;
                }

                typeResult = getTypeOfStringList(node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Number: {
                typeResult = getTypeOfNumber(node, typeResult);
                break;
            }

            case ParseNodeType.Ellipsis: {
                typeResult = getTypeOfEllipsis(flags, typeResult, node);
                break;
            }

            case ParseNodeType.UnaryOperation: {
                typeResult = getTypeOfUnaryOperation(evaluatorInterface, node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.BinaryOperation: {
                let effectiveFlags = flags;

                // If we're expecting an instantiable type and this isn't a union operator,
                // don't require that the two operands are also instantiable types.
                if (expectingInstantiable && node.d.operator !== OperatorType.BitwiseOr) {
                    effectiveFlags &= ~EvalFlags.InstantiableType;
                }

                typeResult = getTypeOfBinaryOperation(evaluatorInterface, node, effectiveFlags, inferenceContext);
                break;
            }

            case ParseNodeType.AugmentedAssignment: {
                typeResult = getTypeOfAugmentedAssignment(evaluatorInterface, node, inferenceContext);
                break;
            }

            case ParseNodeType.List:
            case ParseNodeType.Set: {
                typeResult = getTypeOfListOrSet(node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Slice: {
                typeResult = getTypeOfSlice(node);
                break;
            }

            case ParseNodeType.Await: {
                typeResult = getTypeOfAwaitOperator(node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Ternary: {
                typeResult = getTypeOfTernaryOperation(evaluatorInterface, node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Comprehension: {
                typeResult = getTypeOfComprehension(node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Dictionary: {
                typeResult = getTypeOfDictionary(node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.Lambda: {
                typeResult = getTypeOfLambda(node, inferenceContext);
                break;
            }

            case ParseNodeType.Assignment: {
                typeResult = getTypeOfExpression(node.d.rightExpr, flags, inferenceContext);
                assignTypeToExpression(
                    node.d.leftExpr,
                    typeResult,
                    node.d.rightExpr,
                    /* ignoreEmptyContainers */ true,
                    /* allowAssignmentToFinalVar */ true
                );
                break;
            }

            case ParseNodeType.AssignmentExpression: {
                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.walrusNotAllowed(), node);
                }

                typeResult = getTypeOfExpression(node.d.rightExpr, flags, inferenceContext);
                assignTypeToExpression(node.d.name, typeResult, node.d.rightExpr, /* ignoreEmptyContainers */ true);
                break;
            }

            case ParseNodeType.Yield: {
                typeResult = getTypeOfYield(node);
                break;
            }

            case ParseNodeType.YieldFrom: {
                typeResult = getTypeOfYieldFrom(node);
                break;
            }

            case ParseNodeType.Unpack: {
                typeResult = getTypeOfUnpackOperator(node, flags, inferenceContext);
                break;
            }

            case ParseNodeType.TypeAnnotation: {
                typeResult = getTypeOfExpression(
                    node.d.annotation,
                    EvalFlags.InstantiableType |
                        EvalFlags.TypeExpression |
                        EvalFlags.StrLiteralAsType |
                        EvalFlags.NoParamSpec |
                        EvalFlags.NoTypeVarTuple |
                        EvalFlags.VarTypeAnnotation
                );
                break;
            }

            case ParseNodeType.String:
            case ParseNodeType.FormatString: {
                typeResult = getTypeOfString(node);
                break;
            }

            case ParseNodeType.Error: {
                // Evaluate the child expression as best we can so the
                // type information is cached for the completion handler.
                suppressDiagnostics(node, () => {
                    if (node.d.child) {
                        getTypeOfExpression(node.d.child);
                    }
                });
                typeResult = { type: UnknownType.create() };
                break;
            }

            default:
                assertNever(node, `Illegal node type: ${(node as any).nodeType}`);
        }

        if (!typeResult) {
            // We shouldn't get here. If we do, report an error.
            fail(`Unhandled expression type '${ParseTreeUtils.printExpression(node)}'`);
        }

        // Do we need to validate that the type is instantiable?
        if (expectingInstantiable) {
            validateTypeIsInstantiable(typeResult, flags, node);
        }

        // If this is a PEP 695 type alias, remove the special form so the type
        // printer prints it as its aliased type rather than TypeAliasType.
        if ((flags & EvalFlags.TypeExpression) !== 0 && typeResult.type.props?.typeForm === undefined) {
            const specialForm = typeResult.type.props?.specialForm;
            if (specialForm && ClassType.isBuiltIn(specialForm, 'TypeAliasType')) {
                typeResult.type = TypeBase.cloneAsSpecialForm(typeResult.type, undefined);
            }
        }

        return typeResult;
    }

    // Reports the case where a function or class has been decorated with
    // @type_check_only and is used in a value expression.
    function reportUseOfTypeCheckOnly(type: Type, node: ExpressionNode) {
        let isTypeCheckingOnly = false;
        let name = '';

        if (isInstantiableClass(type) && !type.priv.includeSubclasses) {
            isTypeCheckingOnly = ClassType.isTypeCheckOnly(type);
            name = type.shared.name;
        } else if (isFunction(type)) {
            isTypeCheckingOnly = FunctionType.isTypeCheckOnly(type);
            name = type.shared.name;
        }

        if (isTypeCheckingOnly) {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

            if (!fileInfo.isStubFile) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeCheckOnly().format({ name }),
                    node
                );
            }
        }
    }

    function validateTypeIsInstantiable(typeResult: TypeResult, flags: EvalFlags, node: ExpressionNode) {
        // If the type is incomplete, don't log any diagnostics yet.
        if (typeResult.isIncomplete) {
            return;
        }

        if ((flags & EvalFlags.NoTypeVarTuple) !== 0) {
            if (isTypeVarTuple(typeResult.type) && !typeResult.type.priv.isInUnion) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeVarTupleContext(), node);
                typeResult.type = UnknownType.create();
            }
        }

        if (isEffectivelyInstantiable(typeResult.type, { honorTypeVarBounds: true })) {
            return;
        }

        // Exempt ellipses.
        if (isClassInstance(typeResult.type) && ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis'])) {
            return;
        }

        // Emit these errors only if we know we're evaluating a type expression.
        if ((flags & EvalFlags.TypeExpression) !== 0) {
            const diag = new DiagnosticAddendum();
            if (isUnion(typeResult.type)) {
                doForEachSubtype(typeResult.type, (subtype) => {
                    if (!isEffectivelyInstantiable(subtype, { honorTypeVarBounds: true })) {
                        diag.addMessage(LocAddendum.typeNotClass().format({ type: printType(subtype) }));
                    }
                });
            }

            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeExpectedClass().format({ type: printType(typeResult.type) }) + diag.getString(),
                node
            );

            typeResult.type = UnknownType.create();
        }

        typeResult.typeErrors = true;
    }

    function getTypeOfAwaitOperator(node: AwaitNode, flags: EvalFlags, inferenceContext?: InferenceContext) {
        if ((flags & EvalFlags.TypeExpression) !== 0) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.awaitNotAllowed(), node);
            return { type: UnknownType.create() };
        }

        const expectedType = inferenceContext
            ? createAwaitableReturnType(
                  node,
                  inferenceContext.expectedType,
                  /* isGenerator */ false,
                  /* useCoroutine */ false
              )
            : undefined;

        const exprTypeResult = getTypeOfExpression(node.d.expr, flags, makeInferenceContext(expectedType));
        const awaitableResult = getTypeOfAwaitable(exprTypeResult, node.d.expr);
        const typeResult: TypeResult = {
            type: awaitableResult.type,
            isIncomplete: exprTypeResult.isIncomplete || awaitableResult.isIncomplete,
            typeErrors: exprTypeResult.typeErrors,
        };

        if (exprTypeResult.isIncomplete) {
            typeResult.isIncomplete = true;
        }
        return typeResult;
    }

    function getTypeOfEllipsis(flags: EvalFlags, typeResult: TypeResult | undefined, node: ExpressionNode) {
        if ((flags & EvalFlags.ConvertEllipsisToAny) !== 0) {
            typeResult = { type: AnyType.create(/* isEllipsis */ true) };
        } else {
            if ((flags & EvalFlags.TypeExpression) !== 0 && (flags & EvalFlags.AllowEllipsis) === 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), node);
                typeResult = { type: UnknownType.create() };
            } else {
                const ellipsisType =
                    getBuiltInObject(node, 'EllipsisType') ?? getBuiltInObject(node, 'ellipsis') ?? AnyType.create();
                typeResult = { type: ellipsisType };
            }
        }
        return typeResult;
    }

    function getTypeOfNumber(node: NumberNode, typeResult: TypeResult | undefined) {
        if (node.d.isImaginary) {
            typeResult = { type: getBuiltInObject(node, 'complex') };
        } else if (node.d.isInteger) {
            typeResult = { type: cloneBuiltinObjectWithLiteral(node, 'int', node.d.value) };
        } else {
            typeResult = { type: getBuiltInObject(node, 'float') };
        }
        return typeResult;
    }

    function getTypeOfUnpackOperator(node: UnpackNode, flags: EvalFlags, inferenceContext?: InferenceContext) {
        let typeResult: TypeResult | undefined;
        let iterExpectedType: Type | undefined;

        if (inferenceContext) {
            const iterableType = getBuiltInType(node, 'Iterable');
            if (iterableType && isInstantiableClass(iterableType)) {
                iterExpectedType = ClassType.cloneAsInstance(
                    ClassType.specialize(iterableType, [inferenceContext.expectedType])
                );
            }
        }

        const iterTypeResult = getTypeOfExpression(node.d.expr, flags, makeInferenceContext(iterExpectedType));
        const iterType = iterTypeResult.type;
        if ((flags & EvalFlags.NoTypeVarTuple) === 0 && isTypeVarTuple(iterType) && !iterType.priv.isUnpacked) {
            typeResult = { type: TypeVarType.cloneForUnpacked(iterType) };
        } else if (
            (flags & EvalFlags.AllowUnpackedTuple) !== 0 &&
            isInstantiableClass(iterType) &&
            ClassType.isBuiltIn(iterType, 'tuple')
        ) {
            typeResult = { type: ClassType.cloneForUnpacked(iterType) };
        } else if ((flags & EvalFlags.TypeExpression) !== 0) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.unpackInAnnotation(),
                node,
                node.d.starToken
            );
            typeResult = { type: UnknownType.create() };
        } else {
            const iteratorTypeResult = getTypeOfIterator(iterTypeResult, /* isAsync */ false, node) ?? {
                type: UnknownType.create(!!iterTypeResult.isIncomplete),
                isIncomplete: iterTypeResult.isIncomplete,
            };
            typeResult = {
                type: iteratorTypeResult.type,
                typeErrors: iterTypeResult.typeErrors,
                unpackedType: iterType,
                isIncomplete: iteratorTypeResult.isIncomplete,
            };
        }

        return typeResult;
    }

    function getTypeOfStringList(
        node: StringListNode,
        flags: EvalFlags,
        inferenceContext?: InferenceContext
    ): TypeResult {
        let typeResult: TypeResult | undefined;

        if ((flags & EvalFlags.StrLiteralAsType) !== 0 && (flags & EvalFlags.TypeFormArg) === 0) {
            return getTypeOfStringListAsType(node, flags);
        }

        const isBytesNode = (node: StringNode | FormatStringNode) =>
            (node.d.token.flags & StringTokenFlags.Bytes) !== 0;

        // Check for mixing of bytes and str, which is not allowed.
        const firstStrIndex = node.d.strings.findIndex((str) => !isBytesNode(str));
        const firstBytesIndex = node.d.strings.findIndex((str) => isBytesNode(str));
        if (firstStrIndex >= 0 && firstBytesIndex >= 0) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.mixingBytesAndStr(),
                node.d.strings[Math.max(firstBytesIndex, firstStrIndex)]
            );

            return { type: UnknownType.create() };
        }

        const isBytes = firstBytesIndex >= 0;
        let isLiteralString = true;
        let isIncomplete = false;
        let isTemplate = false;

        node.d.strings.forEach((expr) => {
            // Handle implicit concatenation.
            const typeResult = getTypeOfString(expr);

            if (typeResult.isIncomplete) {
                isIncomplete = true;
            }

            let isExprLiteralString = false;

            if (isClassInstance(typeResult.type)) {
                if (ClassType.isBuiltIn(typeResult.type, 'str') && typeResult.type.priv.literalValue !== undefined) {
                    isExprLiteralString = true;
                } else if (ClassType.isBuiltIn(typeResult?.type, 'LiteralString')) {
                    isExprLiteralString = true;
                }

                if (typeResult.type.shared.name === 'Template') {
                    isTemplate = true;
                }
            }

            if (!isExprLiteralString) {
                isLiteralString = false;
            }
        });

        if (isTemplate) {
            const templateType =
                prefetched?.templateClass && isInstantiableClass(prefetched?.templateClass)
                    ? ClassType.cloneAsInstance(prefetched.templateClass)
                    : UnknownType.create();

            typeResult = { type: templateType, isIncomplete };
        } else if (node.d.strings.some((str) => str.nodeType === ParseNodeType.FormatString)) {
            if (isLiteralString) {
                const literalStringType = getTypingType(node, 'LiteralString');
                if (literalStringType && isInstantiableClass(literalStringType)) {
                    typeResult = { type: ClassType.cloneAsInstance(literalStringType) };
                }
            }

            if (!typeResult) {
                typeResult = {
                    type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'),
                    isIncomplete,
                };
            }
        } else {
            typeResult = {
                type: cloneBuiltinObjectWithLiteral(
                    node,
                    isBytes ? 'bytes' : 'str',
                    node.d.strings.map((s) => s.d.value).join('')
                ),
                isIncomplete,
            };
        }

        if (node.d.strings.length !== 1 || node.d.strings[0].nodeType !== ParseNodeType.String) {
            return typeResult;
        }

        // Only attempt to interpret the string as a TypeForm forward reference when
        // there's a signal that a TypeForm value is wanted in this context. Doing it
        // unconditionally can trigger expensive (and recursion-prone) type lookups
        // for plain string literals in non-type contexts.
        const wantsTypeForm =
            (flags & EvalFlags.TypeFormArg) !== 0 ||
            (inferenceContext !== undefined && expectedTypeWantsTypeForm(inferenceContext.expectedType));
        if (!wantsTypeForm) {
            return typeResult;
        }

        // For performance reasons, do not attempt to treat the string literal
        // as a TypeForm if it's going to fail anyway or is unlikely to be a
        // TypeForm (really long, triple-quoted, etc.).
        const stringNode = node.d.strings[0];
        const tokenFlags = stringNode.d.token.flags;
        const disallowedTokenFlags =
            StringTokenFlags.Bytes |
            StringTokenFlags.Raw |
            StringTokenFlags.Format |
            StringTokenFlags.Template |
            StringTokenFlags.Triplicate;
        const maxTypeFormStringLength = 256;

        if (
            (tokenFlags & disallowedTokenFlags) !== 0 ||
            stringNode.d.token.escapedValue.length >= maxTypeFormStringLength
        ) {
            return typeResult;
        }

        const typeFormResult = getTypeOfStringListAsType(node, flags);
        if (typeFormResult.type.props?.typeForm) {
            typeResult.type = TypeBase.cloneWithTypeForm(typeResult.type, typeFormResult.type.props.typeForm);
        }

        return typeResult;
    }

    function getTypeOfStringListAsType(node: StringListNode, flags: EvalFlags): TypeResult {
        const reportTypeErrors = (flags & EvalFlags.StrLiteralAsType) !== 0;
        let updatedFlags = flags | EvalFlags.ForwardRefs | EvalFlags.InstantiableType;
        let typeResult: TypeResult | undefined;

        // In most cases, annotations within a string are not parsed by the interpreter.
        // There are a few exceptions (e.g. the "bound" value for a TypeVar constructor).
        if ((flags & EvalFlags.ParsesStringLiteral) === 0) {
            updatedFlags |= EvalFlags.NotParsed;
        }

        updatedFlags &= ~EvalFlags.TypeFormArg;

        if (node.d.annotation && (flags & EvalFlags.TypeExpression) !== 0) {
            return getTypeOfExpression(node.d.annotation, updatedFlags);
        }

        if (node.d.strings.length === 1) {
            const tokenFlags = node.d.strings[0].d.token.flags;

            if (tokenFlags & StringTokenFlags.Bytes) {
                if (reportTypeErrors) {
                    addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationBytesString(), node);
                }
                return { type: UnknownType.create() };
            }

            if (tokenFlags & StringTokenFlags.Raw) {
                if (reportTypeErrors) {
                    addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationRawString(), node);
                }
                return { type: UnknownType.create() };
            }

            if (tokenFlags & StringTokenFlags.Format) {
                if (reportTypeErrors) {
                    addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationFormatString(), node);
                }
                return { type: UnknownType.create() };
            }

            if (tokenFlags & StringTokenFlags.Template) {
                if (reportTypeErrors) {
                    addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.annotationTemplateString(), node);
                }
                return { type: UnknownType.create() };
            }

            // We didn't know at parse time that this string node was going
            // to be evaluated as a forward-referenced type. We need
            // to re-invoke the parser at this stage.
            const expr = parseStringAsTypeAnnotation(node, reportTypeErrors);
            if (expr) {
                typeResult = useSpeculativeMode(reportTypeErrors ? undefined : node, () => {
                    return getTypeOfExpression(expr, updatedFlags);
                });
            }
        }

        if (!typeResult) {
            if (reportTypeErrors) {
                addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.expectedTypeNotString(), node);
            }
            typeResult = { type: UnknownType.create() };
        }

        return typeResult;
    }

    function getTypeOfString(node: StringNode | FormatStringNode): TypeResult {
        const isBytes = (node.d.token.flags & StringTokenFlags.Bytes) !== 0;
        let typeResult: TypeResult | undefined;
        let isIncomplete = false;

        if (node.nodeType === ParseNodeType.String) {
            typeResult = {
                type: cloneBuiltinObjectWithLiteral(node, isBytes ? 'bytes' : 'str', node.d.value),
                isIncomplete,
            };
        } else {
            const isTemplateString = (node.d.token.flags & StringTokenFlags.Template) !== 0;
            let isLiteralString = true;

            // If all of the format expressions are of type LiteralString, then
            // the resulting formatted string is also LiteralString.
            node.d.fieldExprs.forEach((expr) => {
                const exprTypeResult = getTypeOfExpression(expr);
                const exprType = exprTypeResult.type;

                if (exprTypeResult.isIncomplete) {
                    isIncomplete = true;
                }

                doForEachSubtype(exprType, (exprSubtype) => {
                    if (!isClassInstance(exprSubtype)) {
                        isLiteralString = false;
                        return;
                    }

                    if (ClassType.isBuiltIn(exprSubtype, 'LiteralString')) {
                        return;
                    }

                    if (ClassType.isBuiltIn(exprSubtype, 'str') && exprSubtype.priv.literalValue !== undefined) {
                        return;
                    }

                    isLiteralString = false;
                });
            });

            if (isTemplateString) {
                const templateType =
                    prefetched?.templateClass && isInstantiableClass(prefetched?.templateClass)
                        ? ClassType.cloneAsInstance(prefetched.templateClass)
                        : UnknownType.create();

                typeResult = { type: templateType, isIncomplete };
            } else if (!isBytes && isLiteralString) {
                const literalStringType = getTypingType(node, 'LiteralString');
                if (literalStringType && isInstantiableClass(literalStringType)) {
                    typeResult = { type: ClassType.cloneAsInstance(literalStringType), isIncomplete };
                }
            }

            if (!typeResult) {
                typeResult = {
                    type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'),
                    isIncomplete,
                };

                if (isClass(typeResult.type) && typeResult.type.priv.includePromotions) {
                    typeResult.type = ClassType.cloneRemoveTypePromotions(typeResult.type);
                }
            }
        }

        return typeResult;
    }

    function stripLiteralValue(type: Type): Type {
        // Handle the not-uncommon case where the type is a union that consists
        // only of literal values.
        if (isUnion(type) && type.priv.subtypes.length > 0) {
            if (
                type.priv.literalInstances.literalStrMap?.size === type.priv.subtypes.length ||
                type.priv.literalInstances.literalIntMap?.size === type.priv.subtypes.length ||
                type.priv.literalInstances.literalEnumMap?.size === type.priv.subtypes.length
            ) {
                return stripLiteralValue(type.priv.subtypes[0]);
            }
        }

        return mapSubtypes(type, (subtype) => {
            if (isClass(subtype)) {
                if (subtype.priv.literalValue !== undefined) {
                    subtype = ClassType.cloneWithLiteral(subtype, /* value */ undefined);
                }

                if (ClassType.isBuiltIn(subtype, 'LiteralString')) {
                    // Handle "LiteralString" specially.
                    if (prefetched?.strClass && isInstantiableClass(prefetched.strClass)) {
                        let strInstance = ClassType.cloneAsInstance(prefetched.strClass);
                        strInstance = TypeBase.cloneForCondition(strInstance, getTypeCondition(subtype));
                        return strInstance;
                    }
                }
            }

            return subtype;
        });
    }

    function getTypeOfParamAnnotation(paramTypeNode: ExpressionNode, paramCategory: ParamCategory) {
        return getTypeOfAnnotation(paramTypeNode, {
            typeVarGetsCurScope: true,
            allowUnpackedTuple: paramCategory === ParamCategory.ArgsList,
            allowUnpackedTypedDict: paramCategory === ParamCategory.KwargsDict,
        });
    }

    function getTypeOfAnnotation(node: ExpressionNode, options?: ExpectedTypeOptions): Type {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

        // Special-case the typing.pyi file, which contains some special
        // types that the type analyzer needs to interpret differently.
        if (fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) {
            const specialType = handleTypingStubTypeAnnotation(node);
            if (specialType) {
                return specialType;
            }
        }

        const adjustedOptions: ExpectedTypeOptions = options ? { ...options } : {};

        adjustedOptions.typeExpression = true;
        adjustedOptions.convertEllipsisToAny = true;

        // If the annotation is part of a comment, allow forward references
        // even if it's not enclosed in quotes.
        if (node?.parent?.nodeType === ParseNodeType.Assignment && node.parent.d.annotationComment === node) {
            adjustedOptions.forwardRefs = true;
            adjustedOptions.notParsed = true;
        } else if (node?.parent?.nodeType === ParseNodeType.FunctionAnnotation) {
            if (node.parent.d.returnAnnotation === node || node.parent.d.paramAnnotations.some((n) => n === node)) {
                adjustedOptions.forwardRefs = true;
                adjustedOptions.notParsed = true;
            }
        } else if (node?.parent?.nodeType === ParseNodeType.Parameter) {
            if (node.parent.d.annotationComment === node) {
                adjustedOptions.forwardRefs = true;
                adjustedOptions.notParsed = true;
            }
        }

        const annotationType = getTypeOfExpressionExpectingType(node, adjustedOptions).type;

        if (isModule(annotationType)) {
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.moduleAsType(), node);
        }

        return convertToInstance(annotationType);
    }

    function canBeFalsy(type: Type, recursionCount = 0): boolean {
        type = makeTopLevelTypeVarsConcrete(type);

        if (recursionCount > maxTypeRecursionCount) {
            return true;
        }
        recursionCount++;

        switch (type.category) {
            case TypeCategory.Unbound:
            case TypeCategory.Unknown:
            case TypeCategory.Any:
            case TypeCategory.Never: {
                return true;
            }

            case TypeCategory.Union: {
                return findSubtype(type, (subtype) => canBeFalsy(subtype, recursionCount)) !== undefined;
            }

            case TypeCategory.Function:
            case TypeCategory.Overloaded:
            case TypeCategory.Module:
            case TypeCategory.TypeVar: {
                return false;
            }

            case TypeCategory.Class: {
                if (TypeBase.isInstantiable(type)) {
                    return false;
                }

                // Sentinels are always truthy.
                if (isSentinelLiteral(type)) {
                    return false;
                }

                // Handle tuples specially.
                if (isTupleClass(type) && type.priv.tupleTypeArgs) {
                    return isUnboundedTupleClass(type) || type.priv.tupleTypeArgs.length === 0;
                }

                // Handle subclasses of tuple, such as NamedTuple.
                const tupleBaseClass = type.shared.mro.find((mroClass) => !isClass(mroClass) || isTupleClass(mroClass));
                if (tupleBaseClass && isClass(tupleBaseClass) && tupleBaseClass.priv.tupleTypeArgs) {
                    return isUnboundedTupleClass(tupleBaseClass) || tupleBaseClass.priv.tupleTypeArgs.length === 0;
                }

                // Handle TypedDicts specially. If one or more entries are required
                // or known to exist, we can say for sure that the type is not falsy.
                if (ClassType.isTypedDictClass(type)) {
                    const tdEntries = getTypedDictMembersForClass(evaluatorInterface, type, /* allowNarrowed */ true);
                    if (tdEntries) {
                        for (const tdEntry of tdEntries.knownItems.values()) {
                            if (tdEntry.isRequired || tdEntry.isProvided) {
                                return false;
                            }
                        }
                    }
                }

                // Check for bool, int, str and bytes literals that are never falsy.
                if (type.priv.literalValue !== undefined) {
                    if (ClassType.isBuiltIn(type, ['bool', 'int', 'str', 'bytes'])) {
                        return !type.priv.literalValue || type.priv.literalValue === BigInt(0);
                    }

                    if (type.priv.literalValue instanceof EnumLiteral) {
                        // Does the Enum class forward the truthiness check to the
                        // underlying member type?
                        if (type.priv.literalValue.isReprEnum) {
                            return canBeFalsy(type.priv.literalValue.itemType, recursionCount);
                        }
                    }
                }

                // If this is a protocol class, don't make any assumptions about the absence
                // of specific methods. These could be provided by a class that conforms
                // to the protocol.
                if (ClassType.isProtocolClass(type)) {
                    return true;
                }

                const lenMethod = lookUpObjectMember(type, '__len__');
                if (lenMethod) {
                    return true;
                }

                const boolMethod = lookUpObjectMember(type, '__bool__');
                if (boolMethod) {
                    const boolMethodType = getTypeOfMember(boolMethod);

                    // If the __bool__ function unconditionally returns True, it can never be falsy.
                    if (isFunction(boolMethodType) && boolMethodType.shared.declaredReturnType) {
                        const returnType = boolMethodType.shared.declaredReturnType;
                        if (
                            isClassInstance(returnType) &&
                            ClassType.isBuiltIn(returnType, 'bool') &&
                            returnType.priv.literalValue === true
                        ) {
                            return false;
                        }
                    }

                    return true;
                }

                // If the class is not final, it's possible that it could be overridden
                // such that it is falsy. To be fully correct, we'd need to do the
                // following:
                // return !ClassType.isFinal(type);
                // However, pragmatically if the class is not an `object`, it's typically
                // OK to assume that it will not be overridden in this manner.
                return ClassType.isBuiltIn(type, 'object');
            }
        }
    }

    function canBeTruthy(type: Type, recursionCount = 0): boolean {
        type = makeTopLevelTypeVarsConcrete(type);

        if (recursionCount > maxTypeRecursionCount) {
            return true;
        }
        recursionCount++;

        switch (type.category) {
            case TypeCategory.Unknown:
            case TypeCategory.Function:
            case TypeCategory.Overloaded:
            case TypeCategory.Module:
            case TypeCategory.TypeVar:
            case TypeCategory.Never:
            case TypeCategory.Any: {
                return true;
            }

            case TypeCategory.Union: {
                return findSubtype(type, (subtype) => canBeTruthy(subtype, recursionCount)) !== undefined;
            }

            case TypeCategory.Unbound: {
                return false;
            }

            case TypeCategory.Class: {
                if (TypeBase.isInstantiable(type)) {
                    return true;
                }

                if (isNoneInstance(type)) {
                    return false;
                }

                // // Check for tuple[()] (an empty tuple).
                if (type.priv.tupleTypeArgs && type.priv.tupleTypeArgs.length === 0) {
                    return false;
                }

                // Check for bool, int, str and bytes literals that are never falsy.
                if (type.priv.literalValue !== undefined) {
                    if (ClassType.isBuiltIn(type, ['bool', 'int', 'str', 'bytes'])) {
                        return !!type.priv.literalValue && type.priv.literalValue !== BigInt(0);
                    }

                    if (type.priv.literalValue instanceof EnumLiteral) {
                        // Does the Enum class forward the truthiness check to the
                        // underlying member type?
                        if (type.priv.literalValue.isReprEnum) {
                            return canBeTruthy(type.priv.literalValue.itemType, recursionCount);
                        }
                    }
                }

                // If this is a protocol class, don't make any assumptions about the absence
                // of specific methods. These could be provided by a class that conforms
                // to the protocol.
                if (ClassType.isProtocolClass(type)) {
                    return true;
                }

                const boolMethod = lookUpObjectMember(type, '__bool__');
                if (boolMethod) {
                    const boolMethodType = getTypeOfMember(boolMethod);

                    // If the __bool__ function unconditionally returns False, it can never be truthy.
                    if (isFunction(boolMethodType) && boolMethodType.shared.declaredReturnType) {
                        const returnType = boolMethodType.shared.declaredReturnType;
                        if (
                            isClassInstance(returnType) &&
                            ClassType.isBuiltIn(returnType, 'bool') &&
                            returnType.priv.literalValue === false
                        ) {
                            return false;
                        }
                    }
                }

                return true;
            }
        }
    }

    // Filters a type such that that no part of it is definitely
    // truthy. For example, if a type is a union of None
    // and a custom class "Foo" that has no __len__ or __nonzero__
    // method, this method would strip off the "Foo"
    // and return only the "None".
    function removeTruthinessFromType(type: Type): Type {
        return mapSubtypes(type, (subtype) => {
            const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isClassInstance(concreteSubtype)) {
                if (concreteSubtype.priv.literalValue !== undefined) {
                    let isLiteralFalsy: boolean;

                    if (concreteSubtype.priv.literalValue instanceof EnumLiteral) {
                        isLiteralFalsy = !canBeTruthy(concreteSubtype);
                    } else {
                        isLiteralFalsy = !concreteSubtype.priv.literalValue;
                    }

                    // If the object is already definitely falsy, it's fine to
                    // include, otherwise it should be removed.
                    return isLiteralFalsy ? subtype : undefined;
                }

                // If the object is a sentinel, we can eliminate it.
                if (isSentinelLiteral(concreteSubtype)) {
                    return undefined;
                }

                // If the object is a bool, make it "false", since
                // "true" is a truthy value.
                if (ClassType.isBuiltIn(concreteSubtype, 'bool')) {
                    return ClassType.cloneWithLiteral(concreteSubtype, /* value */ false);
                }

                // If the object is an int, str or bytes, narrow to a literal type.
                // This is slightly unsafe in that someone could subclass `int`, `str`
                // or `bytes` and override the `__bool__` method to change its behavior,
                // but this is extremely unlikely (and ill advised).
                if (ClassType.isBuiltIn(concreteSubtype, 'int')) {
                    return ClassType.cloneWithLiteral(concreteSubtype, /* value */ 0);
                } else if (ClassType.isBuiltIn(concreteSubtype, ['str', 'bytes'])) {
                    return ClassType.cloneWithLiteral(concreteSubtype, /* value */ '');
                }
            }

            // If it's possible for the type to be falsy, include it.
            if (canBeFalsy(subtype)) {
                return subtype;
            }

            return undefined;
        });
    }

    // Filters a type such that that no part of it is definitely
    // falsy. For example, if a type is a union of None
    // and an "int", this method would strip off the "None"
    // and return only the "int".
    function removeFalsinessFromType(type: Type): Type {
        return mapSubtypes(type, (subtype) => {
            const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isClassInstance(concreteSubtype)) {
                if (concreteSubtype.priv.literalValue !== undefined) {
                    let isLiteralTruthy: boolean;

                    if (concreteSubtype.priv.literalValue instanceof EnumLiteral) {
                        isLiteralTruthy = !canBeFalsy(concreteSubtype);
                    } else if (concreteSubtype.priv.literalValue instanceof SentinelLiteral) {
                        isLiteralTruthy = true;
                    } else {
                        isLiteralTruthy = !!concreteSubtype.priv.literalValue;
                    }

                    // If the object is already definitely truthy, it's fine to
                    // include, otherwise it should be removed.
                    return isLiteralTruthy ? subtype : undefined;
                }

                // If the object is a bool, make it "true", since
                // "false" is a falsy value.
                if (ClassType.isBuiltIn(concreteSubtype, 'bool')) {
                    return ClassType.cloneWithLiteral(concreteSubtype, /* value */ true);
                }

                // If the object is a "None" instance, we can eliminate it.
                if (isNoneInstance(concreteSubtype)) {
                    return undefined;
                }

                // If this is an instance of a class that cannot be subclassed,
                // we cannot say definitively that it's not falsy because a subclass
                // could override `__bool__`. For this reason, the code should not
                // remove any classes that are not final.
                // if (!ClassType.isFinal(concreteSubtype)) {
                //     return subtype;
                // }
                // However, we're going to pragmatically assume that any classes
                // other than `object` will not be overridden in this manner.
                if (ClassType.isBuiltIn(concreteSubtype, 'object')) {
                    return subtype;
                }
            }

            // If it's possible for the type to be truthy, include it.
            if (canBeTruthy(subtype)) {
                return subtype;
            }

            return undefined;
        });
    }

    // If a type contains a TypeGuard or TypeIs, convert it to a bool.
    function stripTypeGuard(type: Type): Type {
        return mapSubtypes(type, (subtype) => {
            if (isClassInstance(subtype) && ClassType.isBuiltIn(subtype, ['TypeGuard', 'TypeIs'])) {
                return prefetched?.boolClass ? convertToInstance(prefetched.boolClass) : UnknownType.create();
            }

            return subtype;
        });
    }

    function solveAndApplyConstraints(
        type: Type,
        constraints: ConstraintTracker,
        applyOptions?: ApplyTypeVarOptions,
        solveOptions?: SolveConstraintsOptions
    ): Type {
        const solution = solveConstraints(evaluatorInterface, constraints, solveOptions);
        return applySolvedTypeVars(type, solution, applyOptions);
    }

    // Gets a member type from an object or class. If it's a function, binds
    // it to the object or class. If selfType is undefined, the binding is done
    // using the objectType parameter. Callers can specify these separately
    // to handle the case where we're fetching the object member from a
    // metaclass but binding to the class.
    function getTypeOfBoundMember(
        errorNode: ExpressionNode | undefined,
        objectType: ClassType,
        memberName: string,
        usage: EvaluatorUsage = { method: 'get' },
        diag: DiagnosticAddendum | undefined = undefined,
        flags = MemberAccessFlags.Default,
        selfType?: ClassType | TypeVarType,
        recursionCount = 0
    ): TypeResult | undefined {
        if (ClassType.isPartiallyEvaluated(objectType)) {
            if (errorNode) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.classDefinitionCycle().format({ name: objectType.shared.name }),
                    errorNode
                );
            }
            return { type: UnknownType.create() };
        }

        // If this is an unspecialized generic class, specialize it using the default
        // values for its type parameters.
        if (
            isInstantiableClass(objectType) &&
            !objectType.priv.includeSubclasses &&
            objectType.shared.typeParams.length > 0
        ) {
            // Skip this if we're suppressing the use of attribute access override,
            // such as with dundered methods (like __call__).
            if ((flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0) {
                objectType = specializeWithDefaultTypeArgs(objectType);
            }
        }

        // Determine the class that was used to instantiate the objectType.
        // If the objectType is a class itself, then the class used to instantiate
        // it is the metaclass.
        const objectTypeIsInstantiable = TypeBase.isInstantiable(objectType);
        const metaclass = objectType.shared.effectiveMetaclass;

        let memberInfo: ClassMemberLookup | undefined;

        // If the object type is an instantiable (i.e. it derives from "type") and
        // we've been asked not to consider instance members, don't look in the class.
        // Consider only the metaclass class variables in this case.
        let skipObjectTypeLookup = objectTypeIsInstantiable && (flags & MemberAccessFlags.SkipInstanceMembers) !== 0;

        // Look up the attribute in the metaclass first. If the member is a descriptor
        // (an object with a __get__ and __set__ method) and the access is a 'get',
        // the Python runtime uses this descriptor to satisfy the lookup. Skip this
        // costly lookup in the common case where the metaclass is 'type' since we know
        // that `type` doesn't have any attributes that are descriptors.
        if (
            usage.method === 'get' &&
            objectTypeIsInstantiable &&
            metaclass &&
            isInstantiableClass(metaclass) &&
            !ClassType.isBuiltIn(metaclass, 'type') &&
            !ClassType.isSameGenericClass(metaclass, objectType)
        ) {
            const descMemberInfo = getTypeOfClassMemberName(
                errorNode,
                metaclass,
                memberName,
                usage,
                /* diag */ undefined,
                flags | MemberAccessFlags.SkipAttributeAccessOverride | MemberAccessFlags.SkipTypedDictEntries,
                objectType,
                recursionCount
            );

            if (descMemberInfo) {
                const isProperty =
                    isClassInstance(descMemberInfo.type) && ClassType.isPropertyClass(descMemberInfo.type);
                if (isDescriptorInstance(descMemberInfo.type, /* requireSetter */ true) || isProperty) {
                    skipObjectTypeLookup = true;
                }
            }
        }

        let subDiag: DiagnosticAddendum | undefined;

        if (!skipObjectTypeLookup) {
            let effectiveFlags = flags | MemberAccessFlags.SkipTypedDictEntries;

            if (objectTypeIsInstantiable) {
                effectiveFlags |=
                    MemberAccessFlags.SkipInstanceMembers |
                    MemberAccessFlags.SkipAttributeAccessOverride |
                    MemberAccessFlags.DisallowGenericInstanceVariableAccess;
                effectiveFlags &= ~MemberAccessFlags.SkipClassMembers;
            } else {
                effectiveFlags |= MemberAccessFlags.DisallowClassVarWrites;
            }

            subDiag = diag ? new DiagnosticAddendum() : undefined;

            // See if the member is present in the object itself.
            memberInfo = getTypeOfClassMemberName(
                errorNode,
                objectType,
                memberName,
                usage,
                subDiag,
                effectiveFlags,
                selfType,
                recursionCount
            );
        }

        // If it wasn't found on the object, see if it's part of the metaclass.
        if (!memberInfo && metaclass && isInstantiableClass(metaclass)) {
            let effectiveFlags = flags;

            // Class members cannot be accessed on a class's metaclass through
            // an instance of a class. Limit access to metaclass instance members
            // in this case.
            if (!objectTypeIsInstantiable) {
                effectiveFlags |=
                    MemberAccessFlags.SkipClassMembers |
                    MemberAccessFlags.SkipAttributeAccessOverride |
                    MemberAccessFlags.SkipTypeBaseClass;
                effectiveFlags &= ~MemberAccessFlags.SkipInstanceMembers;
            }

            const metaclassDiag = diag ? new DiagnosticAddendum() : undefined;
            memberInfo = getTypeOfClassMemberName(
                errorNode,
                ClassType.cloneAsInstance(metaclass),
                memberName,
                usage,
                metaclassDiag,
                effectiveFlags,
                objectTypeIsInstantiable ? objectType : ClassType.cloneAsInstantiable(objectType),
                recursionCount
            );

            // If there was a descriptor error (as opposed to an error where the members
            // was simply not found), use this diagnostic message.
            if (memberInfo?.isDescriptorError) {
                subDiag = metaclassDiag;
            }
        }

        if (memberInfo) {
            if (memberInfo.isDescriptorError && diag && subDiag) {
                diag.addAddendum(subDiag);
            }

            return {
                type: memberInfo.type,
                classType: memberInfo.classType,
                isIncomplete: !!memberInfo.isTypeIncomplete,
                isAsymmetricAccessor: memberInfo.isAsymmetricAccessor,
                narrowedTypeForSet: memberInfo.narrowedTypeForSet,
                memberAccessDeprecationInfo: memberInfo.memberAccessDeprecationInfo,
                typeErrors: memberInfo.isDescriptorError,
            };
        }

        // If this is a type[Any] or type[Unknown], allow any other members.
        if (
            isClassInstance(objectType) &&
            ClassType.isBuiltIn(objectType, 'type') &&
            objectType.priv.includeSubclasses
        ) {
            if ((flags & (MemberAccessFlags.SkipTypeBaseClass | MemberAccessFlags.SkipAttributeAccessOverride)) === 0) {
                const typeArg =
                    objectType.priv.typeArgs && objectType.priv.typeArgs.length >= 1
                        ? objectType.priv.typeArgs[0]
                        : UnknownType.create();

                if (isAnyOrUnknown(typeArg)) {
                    return { type: typeArg, classType: UnknownType.create() };
                }
            }
        }

        if (diag && subDiag) {
            diag.addAddendum(subDiag);
        }

        return undefined;
    }

    function getBoundMagicMethod(
        classType: ClassType,
        memberName: string,
        selfType?: ClassType | TypeVarType | undefined,
        errorNode?: ExpressionNode | undefined,
        diag?: DiagnosticAddendum,
        recursionCount = 0
    ): FunctionType | OverloadedType | undefined {
        const boundMethodResult = getTypeOfBoundMember(
            errorNode,
            classType,
            memberName,
            /* usage */ undefined,
            diag,
            MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride,
            selfType,
            recursionCount
        );

        if (!boundMethodResult || boundMethodResult.typeErrors) {
            return undefined;
        }

        if (isFunctionOrOverloaded(boundMethodResult.type)) {
            return boundMethodResult.type;
        }

        if (isClassInstance(boundMethodResult.type)) {
            if (recursionCount > maxTypeRecursionCount) {
                return undefined;
            }
            recursionCount++;

            return getBoundMagicMethod(
                boundMethodResult.type,
                '__call__',
                /* selfType */ undefined,
                errorNode,
                diag,
                recursionCount
            );
        }

        if (isAnyOrUnknown(boundMethodResult.type)) {
            return getUnknownTypeForCallable();
        }

        return undefined;
    }

    // Returns the signature(s) associated with a call node that contains
    // the specified node. It also returns the index of the argument
    // that contains the node.
    function getCallSignatureInfo(
        callNode: CallNode,
        activeIndex: number,
        activeOrFake: boolean
    ): CallSignatureInfo | undefined {
        const exprNode = callNode.d.leftExpr;
        const callType = getType(exprNode);
        if (!callType) {
            return undefined;
        }

        const argList: Arg[] = [];
        let previousCategory = ArgCategory.Simple;

        // Empty arguments do not enter the AST as nodes, but instead are left blank.
        // Instead, we detect when we appear to be between two known arguments or at the
        // end of the argument list and insert a fake argument of an unknown type to have
        // something to match later.
        function addFakeArg() {
            argList.push({
                argCategory: previousCategory,
                typeResult: { type: UnknownType.create() },
                active: true,
            });
        }

        callNode.d.args.forEach((arg, index) => {
            let active = false;
            if (index === activeIndex) {
                if (activeOrFake) {
                    active = true;
                } else {
                    addFakeArg();
                }
            }

            previousCategory = arg.d.argCategory;

            argList.push({
                valueExpression: arg.d.valueExpr,
                argCategory: arg.d.argCategory,
                name: arg.d.name,
                active: active,
            });
        });

        if (callNode.d.args.length < activeIndex) {
            addFakeArg();
        }

        let signatures: CallSignature[] = [];

        function addOneFunctionToSignature(type: FunctionType) {
            let callResult: CallResult | undefined;
            const constraints = new ConstraintTracker();

            useSpeculativeMode(callNode, () => {
                callResult = validateArgs(
                    exprNode,
                    argList,
                    { type },
                    constraints,
                    /* skipUnknownArgCheck */ true,
                    /* inferenceContext */ undefined
                );
            });

            const specializedType = solveAndApplyConstraints(type, constraints);
            const finalType = isFunction(specializedType) ? specializedType : type;
            const hasActiveArg = argList.some((arg) => arg.active);

            // If the type was specialized (e.g. ParamSpec expansion), the activeParam
            // from the original validateArgs refers to parameters in the unspecialized
            // type and won't match the specialized type's parameters. Re-run validateArgs
            // against the specialized type to get the correct activeParam mapping.
            let activeParam = callResult?.activeParam;
            if (hasActiveArg && finalType !== type) {
                let specializedActiveParam: typeof activeParam | undefined;
                useSpeculativeMode(callNode, () => {
                    const specializedResult = validateArgs(
                        exprNode,
                        argList,
                        { type: finalType },
                        new ConstraintTracker(),
                        /* skipUnknownArgCheck */ true,
                        /* inferenceContext */ undefined
                    );
                    specializedActiveParam = specializedResult?.activeParam;
                });
                if (specializedActiveParam) {
                    activeParam = specializedActiveParam;
                }
            }

            signatures.push({
                type: expandTypedKwargs(finalType),
                activeParam,
            });
        }

        function addFunctionToSignature(type: FunctionType | OverloadedType) {
            if (isFunction(type)) {
                addOneFunctionToSignature(type);
            } else {
                OverloadedType.getOverloads(type).forEach((func) => {
                    addOneFunctionToSignature(func);
                });
            }
        }

        doForEachSubtype(callType, (subtype) => {
            switch (subtype.category) {
                case TypeCategory.Function:
                case TypeCategory.Overloaded: {
                    addFunctionToSignature(subtype);
                    break;
                }

                case TypeCategory.Class: {
                    if (TypeBase.isInstantiable(subtype)) {
                        const constructorType = createFunctionFromConstructor(evaluatorInterface, subtype);

                        if (constructorType) {
                            doForEachSubtype(constructorType, (subtype) => {
                                if (isFunctionOrOverloaded(subtype)) {
                                    addFunctionToSignature(subtype);
                                }
                            });

                            // It's common for either the `__new__` or `__init__` methods to be
                            // simple (*args: Any, **kwargs: Any) signatures. If so, we'll try
                            // to filter out these signatures if they add nothing of value.
                            const filteredSignatures = signatures.filter(
                                (sig) =>
                                    !FunctionType.isGradualCallableForm(sig.type) ||
                                    sig.type.shared.parameters.length > 2 ||
                                    sig.type.shared.docString ||
                                    sig.type.shared.deprecatedMessage
                            );

                            if (filteredSignatures.length > 0) {
                                signatures = filteredSignatures;
                            }
                        }
                    } else {
                        const methodType = getBoundMagicMethod(subtype, '__call__');
                        if (methodType) {
                            addFunctionToSignature(methodType);
                        }
                    }
                    break;
                }
            }
        });

        if (signatures.length === 0) {
            return undefined;
        }

        return { callNode, signatures };
    }

    // If the function includes a `**kwargs: Unpack[TypedDict]` parameter, the
    // parameter is expanded to include individual keyword args.
    function expandTypedKwargs(functionType: FunctionType): FunctionType {
        const kwargsIndex = functionType.shared.parameters.findIndex(
            (param) => param.category === ParamCategory.KwargsDict
        );
        if (kwargsIndex < 0) {
            return functionType;
        }
        assert(kwargsIndex === functionType.shared.parameters.length - 1);

        const kwargsType = FunctionType.getParamType(functionType, kwargsIndex);
        if (!isClassInstance(kwargsType) || !ClassType.isTypedDictClass(kwargsType) || !kwargsType.priv.isUnpacked) {
            return functionType;
        }

        const tdEntries = kwargsType.priv.typedDictNarrowedEntries ?? kwargsType.shared.typedDictEntries?.knownItems;
        if (!tdEntries) {
            return functionType;
        }

        const newFunction = FunctionType.clone(functionType);
        newFunction.shared.parameters.splice(kwargsIndex);
        if (newFunction.priv.specializedTypes) {
            newFunction.priv.specializedTypes.parameterTypes.splice(kwargsIndex);
        }

        const kwSeparatorIndex = functionType.shared.parameters.findIndex(
            (param) => param.category === ParamCategory.ArgsList
        );

        // Add a keyword separator if necessary.
        if (kwSeparatorIndex < 0 && tdEntries.size > 0) {
            FunctionType.addKeywordOnlyParamSeparator(newFunction);
        }

        tdEntries.forEach((tdEntry, name) => {
            // A TypedDict entry type may carry the TypedDict's own type parameters (e.g. an unpacked
            // generic TypedDict `**kwargs: Unpack[TD[int]]`), so specialize each entry against the
            // (possibly generic) TypedDict instance. Specializing an already-concrete type is a
            // no-op, so this is safe for non-generic TypedDicts.
            const specializedValueType = partiallySpecializeType(
                tdEntry.valueType,
                kwargsType,
                /* typeClassType */ undefined
            );

            FunctionType.addParam(
                newFunction,
                FunctionParam.create(
                    ParamCategory.Simple,
                    specializedValueType,
                    FunctionParamFlags.TypeDeclared,
                    name,
                    tdEntry.isRequired ? undefined : specializedValueType
                )
            );
        });

        const extraItemsType = kwargsType.shared.typedDictEntries?.extraItems?.valueType;

        if (extraItemsType && !isNever(extraItemsType)) {
            // Specialized for the same reason as the per-entry types above. This PEP 728
            // `extra_items` branch uses the identical mechanism and is intentionally not
            // separately tested.
            const specializedExtraItemsType = partiallySpecializeType(
                extraItemsType,
                kwargsType,
                /* typeClassType */ undefined
            );

            FunctionType.addParam(
                newFunction,
                FunctionParam.create(
                    ParamCategory.KwargsDict,
                    specializedExtraItemsType,
                    FunctionParamFlags.TypeDeclared,
                    'kwargs'
                )
            );
        }

        return newFunction;
    }

    // Determines whether the specified expression is an explicit TypeAlias declaration.
    function isDeclaredTypeAlias(expression: ExpressionNode): boolean {
        if (expression.nodeType === ParseNodeType.TypeAnnotation) {
            if (expression.d.valueExpr.nodeType === ParseNodeType.Name) {
                const symbolWithScope = lookUpSymbolRecursive(
                    expression,
                    expression.d.valueExpr.d.value,
                    /* honorCodeFlow */ false
                );
                if (symbolWithScope) {
                    const symbol = symbolWithScope.symbol;
                    return symbol.getDeclarations().find((decl) => isExplicitTypeAliasDeclaration(decl)) !== undefined;
                }
            }
        }

        return false;
    }

    // Given a member symbol and the context in which it was accessed, computes
    // the declared type of the member (applying descriptor setter types, partial
    // specialization, and function binding as appropriate). Returns undefined if
    // the symbol has no declared type.
    function resolveDeclaredMemberType(
        symbol: Symbol,
        classOrObjectBase: ClassType | undefined,
        memberAccessClass: Type | undefined,
        useDescriptorSetterType: boolean,
        bindFunction: boolean,
        selfType: ClassType | TypeVarType | undefined
    ): Type | undefined {
        let declaredType = getDeclaredTypeOfSymbol(symbol)?.type;
        if (!declaredType) {
            return undefined;
        }

        // If it's a descriptor, we need to get the setter type.
        if (useDescriptorSetterType && isClassInstance(declaredType)) {
            const setter = getBoundMagicMethod(declaredType, '__set__');
            if (setter && isFunction(setter) && setter.shared.parameters.length >= 2) {
                declaredType = FunctionType.getParamType(setter, 1);

                if (isAnyOrUnknown(declaredType)) {
                    return undefined;
                }
            }
        }

        if (classOrObjectBase) {
            if (memberAccessClass && isInstantiableClass(memberAccessClass)) {
                declaredType = partiallySpecializeType(declaredType, memberAccessClass, getTypeClassType(), selfType);
            }

            if (isFunctionOrOverloaded(declaredType)) {
                if (bindFunction) {
                    declaredType = bindFunctionToClassOrObject(
                        classOrObjectBase,
                        declaredType,
                        /* memberClass */ undefined,
                        /* treatConstructorAsClassMethod */ undefined,
                        selfType
                    );
                }
            }
        }

        return declaredType;
    }

    // Determines whether the specified expression is a symbol with a declared type.
    function getDeclaredTypeForExpression(expression: ExpressionNode, usage?: EvaluatorUsage): Type | undefined {
        let symbol: Symbol | undefined;
        let selfType: ClassType | TypeVarType | undefined;
        let classOrObjectBase: ClassType | undefined;
        let memberAccessClass: Type | undefined;
        let bindFunction = true;
        let useDescriptorSetterType = false;

        switch (expression.nodeType) {
            case ParseNodeType.Name: {
                const symbolWithScope = lookUpSymbolRecursive(expression, expression.d.value, /* honorCodeFlow */ true);
                if (symbolWithScope) {
                    symbol = symbolWithScope.symbol;

                    // Handle the case where the symbol is a class-level variable
                    // where the type isn't declared in this class but is in
                    // a parent class.
                    if (
                        !getDeclaredTypeOfSymbol(symbol, expression)?.type &&
                        symbolWithScope.scope.type === ScopeType.Class
                    ) {
                        const enclosingClass = ParseTreeUtils.getEnclosingClassOrFunction(expression);
                        if (enclosingClass && enclosingClass.nodeType === ParseNodeType.Class) {
                            const classTypeInfo = getTypeOfClass(enclosingClass);
                            if (classTypeInfo) {
                                const classMemberInfo = lookUpClassMember(
                                    classTypeInfo.classType,
                                    expression.d.value,
                                    MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.DeclaredTypesOnly
                                );
                                if (classMemberInfo) {
                                    symbol = classMemberInfo.symbol;
                                }
                            }
                        }
                    }
                }
                break;
            }

            case ParseNodeType.TypeAnnotation: {
                return getDeclaredTypeForExpression(expression.d.valueExpr, usage);
            }

            case ParseNodeType.MemberAccess: {
                const baseType = getTypeOfExpression(expression.d.leftExpr, EvalFlags.MemberAccessBaseDefaults).type;
                const baseTypeConcrete = makeTopLevelTypeVarsConcrete(baseType);
                const memberName = expression.d.member.d.value;

                if (isTypeVar(baseType)) {
                    selfType = baseType;
                }

                // Normally, baseType will not be a composite type (a union), but
                // this can occur. In this case, we compute the declared type of the
                // member for each subtype. If the subtypes declare the member with
                // the same generic class but different (invariant) type arguments
                // (e.g. "list[int]" vs. "list[str]"), there is no single declared
                // type that can serve as the expected type for bidirectional
                // inference of an assigned value. Committing to one subtype's
                // declared type produces a false positive when assigning a value
                // (such as an empty container) that is compatible with every
                // subtype, so in that case we return undefined and let the value be
                // evaluated without an expected type. We sort the subtypes for
                // determinism.
                //
                // This is deliberately limited to the "same class, differing type
                // arguments" case. When the subtypes declare the member with
                // unrelated types, we retain the previous behavior (use one
                // subtype's declared type) so that genuine assignment errors are
                // still reported at the same location and downstream inference is
                // unchanged.
                //
                // This handling is further limited to cases where the declared base
                // type is itself a union. We intentionally don't apply it when the
                // base is a type variable that merely concretizes to a union.
                const isUnionBase = isUnion(baseType);
                let firstMemberDeclaredType: Type | undefined;
                let sawMemberDeclaredType = false;
                let hasDivergentMemberDeclaredTypes = false;
                let divergesOnlyByTypeArgs = true;

                doForEachSubtype(
                    baseTypeConcrete,
                    (baseSubtype) => {
                        if (isClassInstance(baseSubtype)) {
                            const classMemberInfo = lookUpObjectMember(
                                baseSubtype,
                                memberName,
                                MemberAccessFlags.DeclaredTypesOnly
                            );

                            classOrObjectBase = baseSubtype;
                            memberAccessClass = classMemberInfo?.classType;
                            symbol = classMemberInfo?.symbol;
                            useDescriptorSetterType = true;

                            // If this is an instance member (e.g. a dataclass field), don't
                            // bind it to the object if it's a function.
                            bindFunction = !classMemberInfo?.isInstanceMember;
                        } else if (isInstantiableClass(baseSubtype)) {
                            const classMemberInfo = lookUpClassMember(
                                baseSubtype,
                                memberName,
                                MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.DeclaredTypesOnly
                            );

                            classOrObjectBase = baseSubtype;
                            memberAccessClass = classMemberInfo?.classType;
                            symbol = classMemberInfo?.symbol;
                            useDescriptorSetterType = false;
                            bindFunction = true;
                        } else if (isModule(baseSubtype)) {
                            classOrObjectBase = undefined;
                            memberAccessClass = undefined;
                            symbol = ModuleType.getField(baseSubtype, memberName);
                            if (symbol && !symbol.hasTypedDeclarations()) {
                                // Do not use inferred types for the declared type.
                                symbol = undefined;
                            }
                            useDescriptorSetterType = false;
                            bindFunction = false;
                        }

                        // If the base is a union, verify that the subtypes agree on a
                        // single declared type for the member.
                        if (isUnionBase) {
                            const subtypeDeclaredType = symbol
                                ? resolveDeclaredMemberType(
                                      symbol,
                                      classOrObjectBase,
                                      memberAccessClass,
                                      useDescriptorSetterType,
                                      bindFunction,
                                      selfType
                                  )
                                : undefined;

                            if (subtypeDeclaredType) {
                                if (!sawMemberDeclaredType) {
                                    firstMemberDeclaredType = subtypeDeclaredType;
                                    sawMemberDeclaredType = true;
                                } else if (
                                    !firstMemberDeclaredType ||
                                    !isTypeSame(firstMemberDeclaredType, subtypeDeclaredType)
                                ) {
                                    hasDivergentMemberDeclaredTypes = true;

                                    // The false positive we're addressing is specific
                                    // to invariant type arguments of the same generic
                                    // class. If the divergent types aren't instances of
                                    // the same generic class, don't treat this as an
                                    // ambiguous declared type.
                                    if (
                                        !firstMemberDeclaredType ||
                                        !isClassInstance(firstMemberDeclaredType) ||
                                        !isClassInstance(subtypeDeclaredType) ||
                                        !ClassType.isSameGenericClass(
                                            ClassType.cloneAsInstantiable(firstMemberDeclaredType),
                                            ClassType.cloneAsInstantiable(subtypeDeclaredType)
                                        )
                                    ) {
                                        divergesOnlyByTypeArgs = false;
                                    }
                                }
                            }
                        }
                    },
                    /* sortSubtypes */ true
                );

                if (hasDivergentMemberDeclaredTypes && divergesOnlyByTypeArgs) {
                    return undefined;
                }

                break;
            }

            case ParseNodeType.Index: {
                const baseType = makeTopLevelTypeVarsConcrete(
                    getTypeOfExpression(expression.d.leftExpr, EvalFlags.IndexBaseDefaults).type
                );

                if (baseType && isClassInstance(baseType)) {
                    if (ClassType.isTypedDictClass(baseType)) {
                        const typeFromTypedDict = getTypeOfIndexedTypedDict(
                            evaluatorInterface,
                            expression,
                            baseType,
                            usage || { method: 'get' }
                        );
                        if (typeFromTypedDict) {
                            return typeFromTypedDict.type;
                        }
                    }

                    let setItemType = getBoundMagicMethod(baseType, '__setitem__');
                    if (!setItemType) {
                        break;
                    }

                    if (isOverloaded(setItemType)) {
                        // Determine whether we need to use the slice overload.
                        const expectsSlice =
                            expression.d.items.length === 1 &&
                            expression.d.items[0].d.valueExpr.nodeType === ParseNodeType.Slice;
                        const overloads = OverloadedType.getOverloads(setItemType);
                        setItemType = overloads.find((overload) => {
                            if (overload.shared.parameters.length < 2) {
                                return false;
                            }

                            const keyType = FunctionType.getParamType(overload, 0);
                            const isSlice = isClassInstance(keyType) && ClassType.isBuiltIn(keyType, 'slice');
                            return expectsSlice === isSlice;
                        });

                        if (!setItemType) {
                            break;
                        }
                    }

                    if (isFunction(setItemType) && setItemType.shared.parameters.length >= 2) {
                        const paramType = FunctionType.getParamType(setItemType, 1);
                        if (!isAnyOrUnknown(paramType)) {
                            return paramType;
                        }
                    }
                }
                break;
            }

            case ParseNodeType.Tuple: {
                // If this is a tuple expression with at least one item and no
                // unpacked items, and all of the items have declared types,
                // we can assume a declared type for the resulting tuple. This
                // is needed to enable bidirectional type inference when assigning
                // to an unpacked tuple.
                if (
                    expression.d.items.length > 0 &&
                    !expression.d.items.some((item) => item.nodeType === ParseNodeType.Unpack)
                ) {
                    const itemTypes: Type[] = [];
                    expression.d.items.forEach((expr) => {
                        const itemType = getDeclaredTypeForExpression(expr, usage);
                        if (itemType) {
                            itemTypes.push(itemType);
                        }
                    });

                    if (itemTypes.length === expression.d.items.length) {
                        // If all items have a declared type, return a tuple of those types.
                        return makeTupleObject(
                            evaluatorInterface,
                            itemTypes.map((t) => {
                                return { type: t, isUnbounded: false };
                            })
                        );
                    }
                }
                break;
            }
        }

        if (symbol) {
            return resolveDeclaredMemberType(
                symbol,
                classOrObjectBase,
                memberAccessClass,
                useDescriptorSetterType,
                bindFunction,
                selfType
            );
        }

        return undefined;
    }

    // Applies an "await" operation to the specified type and returns
    // the result. According to PEP 492, await operates on an Awaitable
    // (object that provides an __await__ that returns a generator object).
    // If errorNode is undefined, no errors are reported.
    function getTypeOfAwaitable(typeResult: TypeResult, errorNode?: ExpressionNode): TypeResult {
        if (
            !prefetched?.awaitableClass ||
            !isInstantiableClass(prefetched.awaitableClass) ||
            prefetched.awaitableClass.shared.typeParams.length !== 1
        ) {
            return { type: UnknownType.create(), isIncomplete: typeResult.isIncomplete };
        }

        const awaitableProtocolObj = ClassType.cloneAsInstance(prefetched.awaitableClass);
        const isIncomplete = !!typeResult.isIncomplete;

        const type = mapSubtypes(typeResult.type, (subtype) => {
            subtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isAnyOrUnknown(subtype)) {
                return subtype;
            }

            const diag = errorNode ? new DiagnosticAddendum() : undefined;

            if (isClassInstance(subtype)) {
                const constraints = new ConstraintTracker();

                if (assignType(awaitableProtocolObj, subtype, diag, constraints)) {
                    const specializedType = solveAndApplyConstraints(awaitableProtocolObj, constraints);

                    if (
                        isClass(specializedType) &&
                        specializedType.priv.typeArgs &&
                        specializedType.priv.typeArgs.length > 0
                    ) {
                        return specializedType.priv.typeArgs[0];
                    }

                    return UnknownType.create();
                }
            }

            if (errorNode && !typeResult.isIncomplete) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeNotAwaitable().format({ type: printType(subtype) }) + diag?.getString(),
                    errorNode
                );
            }

            return UnknownType.create();
        });

        return { type, isIncomplete };
    }

    // Validates that the type is an iterator and returns the iterated type
    // (i.e. the type returned from the '__next__' or '__anext__' method).
    function getTypeOfIterator(
        typeResult: TypeResult,
        isAsync: boolean,
        errorNode: ExpressionNode,
        emitNotIterableError = true
    ): TypeResult | undefined {
        const iterMethodName = isAsync ? '__aiter__' : '__iter__';
        const nextMethodName = isAsync ? '__anext__' : '__next__';
        let isValidIterator = true;
        let isIncomplete = typeResult.isIncomplete;

        let type = transformPossibleRecursiveTypeAlias(typeResult.type);
        type = makeTopLevelTypeVarsConcrete(type);
        type = removeUnbound(type);

        if (isOptionalType(type) && emitNotIterableError) {
            if (!typeResult.isIncomplete) {
                addDiagnostic(DiagnosticRule.reportOptionalIterable, LocMessage.noneNotIterable(), errorNode);
            }
            type = removeNoneFromUnion(type);
        }

        const iterableType = mapSubtypes(type, (subtype) => {
            subtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isAnyOrUnknown(subtype)) {
                return subtype;
            }

            const diag = new DiagnosticAddendum();
            if (isClass(subtype)) {
                // Handle an empty tuple specially.
                if (
                    TypeBase.isInstance(subtype) &&
                    isTupleClass(subtype) &&
                    subtype.priv.tupleTypeArgs &&
                    subtype.priv.tupleTypeArgs.length === 0
                ) {
                    return NeverType.createNever();
                }

                const iterReturnType = getTypeOfMagicMethodCall(subtype, iterMethodName, [], errorNode)?.type;

                if (!iterReturnType) {
                    // There was no __iter__. See if we can fall back to
                    // the __getitem__ method instead.
                    if (!isAsync && isClassInstance(subtype)) {
                        const getItemReturnType = getTypeOfMagicMethodCall(
                            subtype,
                            '__getitem__',
                            [
                                {
                                    type:
                                        prefetched?.intClass && isInstantiableClass(prefetched.intClass)
                                            ? ClassType.cloneAsInstance(prefetched.intClass)
                                            : UnknownType.create(),
                                },
                            ],
                            errorNode
                        )?.type;
                        if (getItemReturnType) {
                            return getItemReturnType;
                        }
                    }

                    diag.addMessage(LocMessage.methodNotDefined().format({ name: iterMethodName }));
                } else {
                    const iterReturnTypeDiag = new DiagnosticAddendum();

                    const returnType = mapSubtypesExpandTypeVars(iterReturnType, /* options */ undefined, (subtype) => {
                        if (isAnyOrUnknown(subtype)) {
                            return subtype;
                        }

                        let nextReturnType = getTypeOfMagicMethodCall(subtype, nextMethodName, [], errorNode)?.type;

                        if (!nextReturnType) {
                            iterReturnTypeDiag.addMessage(
                                LocMessage.methodNotDefinedOnType().format({
                                    name: nextMethodName,
                                    type: printType(subtype),
                                })
                            );
                        } else {
                            // Convert any unpacked TypeVarTuples into object instances. We don't
                            // know anything more about them.
                            nextReturnType = mapSubtypes(nextReturnType, (returnSubtype) => {
                                if (isTypeVar(returnSubtype) && isUnpackedTypeVarTuple(returnSubtype)) {
                                    return getObjectType();
                                }

                                return returnSubtype;
                            });

                            if (!isAsync) {
                                return nextReturnType;
                            }

                            // If it's an async iteration, there's an implicit
                            // 'await' operator applied.
                            const awaitableResult = getTypeOfAwaitable(
                                { type: nextReturnType, isIncomplete: typeResult.isIncomplete },
                                errorNode
                            );
                            if (awaitableResult.isIncomplete) {
                                isIncomplete = true;
                            }
                            return awaitableResult.type;
                        }

                        return undefined;
                    });

                    if (iterReturnTypeDiag.isEmpty()) {
                        return returnType;
                    }

                    diag.addAddendum(iterReturnTypeDiag);
                }
            }

            if (!isIncomplete && emitNotIterableError) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeNotIterable().format({ type: printType(subtype) }) + diag.getString(),
                    errorNode
                );
            }

            isValidIterator = false;
            return undefined;
        });

        return isValidIterator ? { type: iterableType, isIncomplete } : undefined;
    }

    // Validates that the type is an iterable and returns the iterable type argument.
    function getTypeOfIterable(
        typeResult: TypeResult,
        isAsync: boolean,
        errorNode: ExpressionNode,
        emitNotIterableError = true
    ): TypeResult | undefined {
        const iterMethodName = isAsync ? '__aiter__' : '__iter__';
        let isValidIterable = true;

        let type = makeTopLevelTypeVarsConcrete(typeResult.type);

        if (isOptionalType(type)) {
            if (!typeResult.isIncomplete && emitNotIterableError) {
                addDiagnostic(DiagnosticRule.reportOptionalIterable, LocMessage.noneNotIterable(), errorNode);
            }
            type = removeNoneFromUnion(type);
        }

        const iterableType = mapSubtypes(type, (subtype) => {
            if (isAnyOrUnknown(subtype)) {
                return subtype;
            }

            if (isClass(subtype)) {
                const iterReturnType = getTypeOfMagicMethodCall(subtype, iterMethodName, [], errorNode)?.type;

                if (iterReturnType) {
                    return makeTopLevelTypeVarsConcrete(iterReturnType);
                }
            }

            if (emitNotIterableError) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeNotIterable().format({ type: printType(subtype) }),
                    errorNode
                );
            }

            isValidIterable = false;
            return undefined;
        });

        return isValidIterable ? { type: iterableType, isIncomplete: typeResult.isIncomplete } : undefined;
    }

    function isTypeHashable(type: Type): boolean {
        let isTypeHashable = true;

        doForEachSubtype(makeTopLevelTypeVarsConcrete(type), (subtype) => {
            if (isClassInstance(subtype)) {
                // Assume the class is hashable.
                let isObjectHashable = true;

                // Have we already computed and cached the hashability?
                if (subtype.shared.isInstanceHashable !== undefined) {
                    isObjectHashable = subtype.shared.isInstanceHashable;
                } else {
                    const hashMember = lookUpObjectMember(subtype, '__hash__', MemberAccessFlags.SkipObjectBaseClass);

                    if (hashMember && hashMember.isTypeDeclared) {
                        const decls = hashMember.symbol.getTypedDeclarations();
                        const synthesizedType = hashMember.symbol.getSynthesizedType();

                        // Handle the case where the type is synthesized (used for
                        // dataclasses).
                        if (synthesizedType) {
                            isObjectHashable = !isNoneInstance(synthesizedType.type);
                        } else {
                            // Assume that if '__hash__' is declared as a variable, it is
                            // not hashable. If it's declared as a function, it is. We'll
                            // skip evaluating its full type because that's not needed in
                            // this case.
                            if (decls.every((decl) => decl.type === DeclarationType.Variable)) {
                                isObjectHashable = false;
                            }
                        }
                    }

                    // Cache the hashability for next time.
                    subtype.shared.isInstanceHashable = isObjectHashable;
                }

                if (!isObjectHashable) {
                    isTypeHashable = false;
                }
            }
        });

        return isTypeHashable;
    }

    function getTypedDictClassType(): ClassType | undefined {
        return prefetched?.typedDictPrivateClass && isInstantiableClass(prefetched.typedDictPrivateClass)
            ? prefetched.typedDictPrivateClass
            : undefined;
    }

    function getTupleClassType(): ClassType | undefined {
        return prefetched?.tupleClass && isInstantiableClass(prefetched.tupleClass) ? prefetched.tupleClass : undefined;
    }

    function getDictClassType(): ClassType | undefined {
        return prefetched?.dictClass && isInstantiableClass(prefetched.dictClass) ? prefetched.dictClass : undefined;
    }

    function getStrClassType(): ClassType | undefined {
        return prefetched?.strClass && isInstantiableClass(prefetched.strClass) ? prefetched.strClass : undefined;
    }

    function getObjectType(): Type {
        return prefetched?.objectClass ? convertToInstance(prefetched.objectClass) : UnknownType.create();
    }

    function getNoneType(): Type {
        return prefetched?.noneTypeClass ? convertToInstance(prefetched.noneTypeClass) : UnknownType.create();
    }

    function getUnionClassType(): Type {
        return prefetched?.unionTypeClass ?? UnknownType.create();
    }

    function getTypeClassType(): ClassType | undefined {
        if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) {
            return prefetched.typeClass;
        }
        return undefined;
    }

    function getTypingType(node: ParseNode, symbolName: string): Type | undefined {
        return (
            getTypeOfModule(node, symbolName, ['typing']) ?? getTypeOfModule(node, symbolName, ['typing_extensions'])
        );
    }

    function getTypeCheckerInternalsType(node: ParseNode, symbolName: string): Type | undefined {
        return getTypeOfModule(node, symbolName, ['_typeshed', '_type_checker_internals']);
    }

    function getTypesType(node: ParseNode, symbolName: string): Type | undefined {
        return getTypeOfModule(node, symbolName, ['types']);
    }

    function getTypeshedType(node: ParseNode, symbolName: string): Type | undefined {
        return getTypeOfModule(node, symbolName, ['_typeshed']);
    }

    function getTypeOfModule(node: ParseNode, symbolName: string, nameParts: string[]) {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const lookupResult = importLookup({ nameParts, importingFileUri: fileInfo.fileUri });

        if (!lookupResult) {
            return undefined;
        }

        const symbol = lookupResult.symbolTable.get(symbolName);
        if (!symbol) {
            return undefined;
        }

        return getEffectiveTypeOfSymbol(symbol);
    }

    function checkCodeFlowTooComplex(node: ParseNode): boolean {
        const scopeNode = node.nodeType === ParseNodeType.Function ? node : ParseTreeUtils.getExecutionScopeNode(node);
        const codeComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(scopeNode);

        if (codeComplexity > maxCodeComplexity) {
            let errorRange: TextRange = scopeNode;
            if (scopeNode.nodeType === ParseNodeType.Function) {
                errorRange = scopeNode.d.name;
            } else if (scopeNode.nodeType === ParseNodeType.Module) {
                errorRange = { start: 0, length: 0 };
            }

            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            addDiagnosticForTextRange(
                fileInfo,
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.codeTooComplexToAnalyze(),
                errorRange
            );

            return true;
        }

        return false;
    }

    function isNodeReachable(node: ParseNode, sourceNode?: ParseNode): boolean {
        return getNodeReachability(node, sourceNode) === Reachability.Reachable;
    }

    function isAfterNodeReachable(node: ParseNode): boolean {
        return getAfterNodeReachability(node) === Reachability.Reachable;
    }

    function getNodeReachability(node: ParseNode, sourceNode?: ParseNode): Reachability {
        if (checkCodeFlowTooComplex(node)) {
            return Reachability.Reachable;
        }

        const flowNode = AnalyzerNodeInfo.getFlowNode(node);
        if (!flowNode) {
            if (node.parent) {
                return getNodeReachability(node.parent, sourceNode);
            }
            return Reachability.UnreachableStructural;
        }

        const sourceFlowNode = sourceNode ? AnalyzerNodeInfo.getFlowNode(sourceNode) : undefined;

        return codeFlowEngine.getFlowNodeReachability(flowNode, sourceFlowNode);
    }

    function getAfterNodeReachability(node: ParseNode): Reachability {
        const returnFlowNode = AnalyzerNodeInfo.getAfterFlowNode(node);
        if (!returnFlowNode) {
            return Reachability.UnreachableStructural;
        }

        if (checkCodeFlowTooComplex(node)) {
            return Reachability.Reachable;
        }

        const reachability = codeFlowEngine.getFlowNodeReachability(returnFlowNode);
        if (reachability !== Reachability.Reachable) {
            return reachability;
        }

        const executionScopeNode = ParseTreeUtils.getExecutionScopeNode(node);
        if (!isFlowNodeReachableUsingNeverNarrowing(executionScopeNode, returnFlowNode)) {
            return Reachability.UnreachableByAnalysis;
        }

        return Reachability.Reachable;
    }

    // Although isFlowNodeReachable indicates that the node is reachable, it
    // may not be reachable if we apply "never narrowing".
    function isFlowNodeReachableUsingNeverNarrowing(node: ExecutionScopeNode, flowNode: FlowNode) {
        const analyzer = getCodeFlowAnalyzerForNode(node, /* typeAtStart */ undefined);

        if (checkCodeFlowTooComplex(node)) {
            return true;
        }

        const codeFlowResult = analyzer.getTypeFromCodeFlow(flowNode, /* reference */ undefined, {
            typeAtStart: { type: UnboundType.create() },
        });

        return codeFlowResult.type !== undefined && !isNever(codeFlowResult.type);
    }

    // Determines whether there is a code flow path from sourceNode to sinkNode.
    function isFlowPathBetweenNodes(sourceNode: ParseNode, sinkNode: ParseNode, allowSelf = true) {
        if (checkCodeFlowTooComplex(sourceNode)) {
            return true;
        }

        const sourceFlowNode = AnalyzerNodeInfo.getFlowNode(sourceNode);
        const sinkFlowNode = AnalyzerNodeInfo.getFlowNode(sinkNode);
        if (!sourceFlowNode || !sinkFlowNode) {
            return false;
        }
        if (sourceFlowNode === sinkFlowNode) {
            return allowSelf;
        }

        return (
            codeFlowEngine.getFlowNodeReachability(sinkFlowNode, sourceFlowNode, /* ignoreNoReturn */ true) ===
            Reachability.Reachable
        );
    }

    function addInformation(message: string, node: ParseNode, range?: TextRange) {
        return addDiagnosticWithSuppressionCheck('information', message, node, range);
    }

    function addUnreachableCode(node: ParseNode, reachability: Reachability, textRange: TextRange) {
        if (reachability === Reachability.Reachable) {
            return;
        }

        if (!isDiagnosticSuppressedForNode(node)) {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            const reportTypeReachability = fileInfo.diagnosticRuleSet.enableReachabilityAnalysis;

            if (
                reachability === Reachability.UnreachableStructural ||
                reachability === Reachability.UnreachableStaticCondition ||
                reportTypeReachability
            ) {
                fileInfo.diagnosticSink.addUnreachableCodeWithTextRange(
                    reachability === Reachability.UnreachableStructural
                        ? LocMessage.unreachableCodeStructure()
                        : reachability === Reachability.UnreachableStaticCondition
                        ? LocMessage.unreachableCodeCondition()
                        : LocMessage.unreachableCodeType(),
                    textRange
                );
            }
        }
    }

    function addDeprecated(message: string, node: ParseNode) {
        if (!isDiagnosticSuppressedForNode(node)) {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            fileInfo.diagnosticSink.addDeprecatedWithTextRange(message, node);
        }
    }

    function addDiagnosticWithSuppressionCheck(
        diagLevel: DiagnosticLevel,
        message: string,
        node: ParseNode,
        range?: TextRange
    ) {
        if (isDiagnosticSuppressedForNode(node)) {
            // See if this node is suppressed but the diagnostic should be generated
            // anyway so it can be used by the caller that requested the suppression.
            const suppressionEntry = suppressedNodeStack.find(
                (suppressedNode) =>
                    ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node) && suppressedNode.suppressedDiags
            );
            suppressionEntry?.suppressedDiags?.push(message);

            return undefined;
        }

        if (isNodeReachable(node)) {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            return fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range ?? node);
        }

        return undefined;
    }

    function isDiagnosticSuppressedForNode(node: ParseNode) {
        if (speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true)) {
            return true;
        }

        return suppressedNodeStack.some((suppressedNode) =>
            ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node)
        );
    }

    // This function is similar to isDiagnosticSuppressedForNode except that it
    // returns false if diagnostics are suppressed for the node but the caller
    // has requested that diagnostics be generated anyway.
    function canSkipDiagnosticForNode(node: ParseNode) {
        if (speculativeTypeTracker.isSpeculative(node, /* ignoreIfDiagnosticsAllowed */ true)) {
            return true;
        }

        const suppressedEntries = suppressedNodeStack.filter((suppressedNode) =>
            ParseTreeUtils.isNodeContainedWithin(node, suppressedNode.node)
        );

        if (suppressedEntries.length === 0) {
            return false;
        }

        return suppressedEntries.every((entry) => !entry.suppressedDiags);
    }

    function addDiagnostic(rule: DiagnosticRule, message: string, node: ParseNode, range?: TextRange) {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel;

        if (diagLevel === 'none') {
            return undefined;
        }

        const containingFunction = ParseTreeUtils.getEnclosingFunction(node);

        if (containingFunction) {
            // Should we suppress this diagnostic because it's within an unannotated function?
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) {
                // Is the target node within the body of the function? If so, suppress the diagnostic.
                if (
                    ParseTreeUtils.isUnannotatedFunction(containingFunction) &&
                    ParseTreeUtils.isNodeContainedWithin(node, containingFunction.d.suite)
                ) {
                    return undefined;
                }
            }

            // Should we suppress this diagnostic because it's within a no_type_check function?
            const containingClassNode = ParseTreeUtils.getEnclosingClass(containingFunction, /* stopAtFunction */ true);
            const functionInfo = getFunctionInfoFromDecorators(
                evaluatorInterface,
                containingFunction,
                !!containingClassNode
            );

            if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) {
                return undefined;
            }
        }

        const diagnostic = addDiagnosticWithSuppressionCheck(diagLevel, message, node, range);
        if (diagnostic) {
            diagnostic.setRule(rule);
        }

        return diagnostic;
    }

    function addDiagnosticForTextRange(
        fileInfo: AnalyzerFileInfo,
        rule: DiagnosticRule,
        message: string,
        range: TextRange
    ) {
        const diagLevel = fileInfo.diagnosticRuleSet[rule] as DiagnosticLevel;

        if (diagLevel === 'none') {
            return undefined;
        }

        const diagnostic = fileInfo.diagnosticSink.addDiagnosticWithTextRange(diagLevel, message, range);
        if (rule) {
            diagnostic.setRule(rule);
        }

        return diagnostic;
    }

    function assignTypeToNameNode(
        nameNode: NameNode,
        typeResult: TypeResult,
        ignoreEmptyContainers: boolean,
        srcExpression?: ParseNode,
        allowAssignmentToFinalVar = false,
        expectedTypeDiagAddendum?: DiagnosticAddendum
    ) {
        const nameValue = nameNode.d.value;

        const symbolWithScope = lookUpSymbolRecursive(nameNode, nameValue, /* honorCodeFlow */ false);
        if (!symbolWithScope) {
            // This can happen when we are evaluating a piece of code that was
            // determined to be unreachable by the binder.
            return;
        }

        const declarations = symbolWithScope.symbol.getDeclarations();
        let declaredType = getDeclaredTypeOfSymbol(symbolWithScope.symbol)?.type;
        const fileInfo = AnalyzerNodeInfo.getFileInfo(nameNode);

        // If this is a class scope and there is no type declared for this class variable,
        // see if a parent class has a type declared.
        if (declaredType === undefined && symbolWithScope.scope.type === ScopeType.Class) {
            const containingClass = ParseTreeUtils.getEnclosingClass(nameNode);
            if (containingClass) {
                const classType = getTypeOfClass(containingClass);
                if (classType) {
                    const memberInfo = lookUpClassMember(
                        classType.classType,
                        nameNode.d.value,
                        MemberAccessFlags.SkipOriginalClass
                    );
                    if (memberInfo?.isTypeDeclared) {
                        declaredType = getTypeOfMember(memberInfo);
                    }
                }
            }
        }

        // We found an existing declared type. Make sure the type is assignable.
        let destType = typeResult.type;
        const isTypeAlias =
            !!declaredType && isClassInstance(declaredType) && ClassType.isBuiltIn(declaredType, 'TypeAlias');

        if (declaredType && !isTypeAlias) {
            let diagAddendum = new DiagnosticAddendum();

            const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(nameNode);
            const boundDeclaredType = makeTypeVarsBound(declaredType, liveScopeIds);
            const srcType = makeTypeVarsBound(typeResult.type, liveScopeIds);

            if (!assignType(boundDeclaredType, srcType, diagAddendum)) {
                // If there was an expected type mismatch, use that diagnostic
                // addendum because it will be more informative.
                if (expectedTypeDiagAddendum) {
                    diagAddendum = expectedTypeDiagAddendum;
                }

                if (!typeResult.isIncomplete) {
                    addDiagnostic(
                        DiagnosticRule.reportAssignmentType,
                        LocMessage.typeAssignmentMismatch().format(printSrcDestTypes(typeResult.type, declaredType)) +
                            diagAddendum.getString(),
                        srcExpression ?? nameNode,
                        diagAddendum.getEffectiveTextRange() ?? srcExpression ?? nameNode
                    );
                }

                // Replace the assigned type with the (unnarrowed) declared type.
                destType = declaredType;
            } else {
                // Constrain the resulting type to match the declared type.
                destType = narrowTypeBasedOnAssignment(declaredType, typeResult).type;
            }
        } else {
            // If this is a member name (within a class scope) and the member name
            // appears to be a constant, use the strict source type. If it's a member
            // variable that can be overridden by a child class, use the more general
            // version by stripping off the literal and TypeForm.
            const scope = ScopeUtils.getScopeForNode(nameNode);
            if (scope?.type === ScopeType.Class) {
                if (
                    TypeBase.isInstance(destType) &&
                    !isConstantName(nameValue) &&
                    !isFinalVariable(symbolWithScope.symbol)
                ) {
                    destType = stripTypeForm(stripLiteralValue(destType));
                }
            }
        }

        const varDeclIndex = declarations.findIndex((decl) => decl.type === DeclarationType.Variable);
        const varDecl = varDeclIndex >= 0 ? declarations[varDeclIndex] : undefined;

        // Are there any non-var decls before the var decl?
        const nonVarDecl = declarations.find(
            (decl, index) => varDeclIndex < index && decl.type !== DeclarationType.Variable
        );

        if (varDecl && varDecl.type === DeclarationType.Variable) {
            if (varDecl.isConstant) {
                // A constant variable can be assigned only once. If this
                // isn't the first assignment, generate an error.
                if (nameNode !== getNameNodeForDeclaration(declarations[0]) || !!nonVarDecl) {
                    addDiagnostic(
                        DiagnosticRule.reportConstantRedefinition,
                        LocMessage.constantRedefinition().format({ name: nameValue }),
                        nameNode
                    );
                }
            } else if (isFinalVariableDeclaration(varDecl) && !allowAssignmentToFinalVar) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.finalReassigned().format({ name: nameValue }),
                    nameNode
                );
            }
        }

        if (!typeResult.isIncomplete) {
            reportPossibleUnknownAssignment(
                fileInfo.diagnosticRuleSet.reportUnknownVariableType,
                DiagnosticRule.reportUnknownVariableType,
                nameNode,
                typeResult.type,
                nameNode,
                ignoreEmptyContainers
            );
        }

        writeTypeCache(nameNode, { type: destType, isIncomplete: typeResult.isIncomplete }, EvalFlags.None);
    }

    function assignTypeToMemberAccessNode(
        target: MemberAccessNode,
        typeResult: TypeResult,
        srcExpr?: ExpressionNode,
        expectedTypeDiagAddendum?: DiagnosticAddendum
    ) {
        const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.MemberAccessBaseDefaults);
        const baseType = makeTopLevelTypeVarsConcrete(baseTypeResult.type);
        let enclosingClass: ClassType | undefined;

        // Handle member accesses (e.g. self.x or cls.y).
        if (target.d.leftExpr.nodeType === ParseNodeType.Name) {
            // Determine whether we're writing to a class or instance member.
            const enclosingClassNode = ParseTreeUtils.getEnclosingClass(target);

            if (enclosingClassNode) {
                const classTypeResults = getTypeOfClass(enclosingClassNode);

                if (classTypeResults && isInstantiableClass(classTypeResults.classType)) {
                    enclosingClass = classTypeResults.classType;

                    if (isClassInstance(baseType)) {
                        if (
                            ClassType.isSameGenericClass(
                                ClassType.cloneAsInstantiable(baseType),
                                classTypeResults.classType
                            )
                        ) {
                            assignTypeToMemberVariable(target, typeResult, /* isInstanceMember */ true, srcExpr);
                        }
                    } else if (isInstantiableClass(baseType)) {
                        if (ClassType.isSameGenericClass(baseType, classTypeResults.classType)) {
                            assignTypeToMemberVariable(target, typeResult, /* isInstanceMember */ false, srcExpr);
                        }
                    }

                    // Assignments to instance or class variables through "self" or "cls" is not
                    // allowed for protocol classes unless it is also declared within the class.
                    if (ClassType.isProtocolClass(classTypeResults.classType)) {
                        const memberSymbol = ClassType.getSymbolTable(classTypeResults.classType).get(
                            target.d.member.d.value
                        );
                        if (memberSymbol) {
                            const classLevelDecls = memberSymbol.getDeclarations().filter((decl) => {
                                return !ParseTreeUtils.getEnclosingFunction(decl.node);
                            });
                            if (classLevelDecls.length === 0) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.assignmentInProtocol(),
                                    target.d.member
                                );
                            }
                        }
                    }
                }
            }
        }

        const setTypeResult = getTypeOfMemberAccessWithBaseType(
            target,
            baseTypeResult,
            {
                method: 'set',
                setType: typeResult,
                setErrorNode: srcExpr,
                setExpectedTypeDiag: expectedTypeDiagAddendum,
            },
            EvalFlags.None
        );

        if (setTypeResult.isAsymmetricAccessor) {
            setAsymmetricDescriptorAssignment(target);
        }

        const resultToCache: TypeResult = {
            type: setTypeResult.narrowedTypeForSet ?? typeResult.type,
            isIncomplete: typeResult.isIncomplete,
            memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo,
        };
        writeTypeCache(target, resultToCache, EvalFlags.None);

        // If the target is an instance or class variable, update any class-scoped
        // type variables so the inferred type of the variable uses "external"
        // type variables.
        let memberResultToCache = resultToCache;
        if (enclosingClass?.shared.typeVarScopeId) {
            memberResultToCache = {
                ...resultToCache,
                type: makeTypeVarsFree(resultToCache.type, [enclosingClass.shared.typeVarScopeId]),
                memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo,
            };
        }
        writeTypeCache(target.d.member, memberResultToCache, EvalFlags.None);
    }

    function assignTypeToMemberVariable(
        node: MemberAccessNode,
        typeResult: TypeResult,
        isInstanceMember: boolean,
        srcExprNode?: ExpressionNode
    ) {
        const memberName = node.d.member.d.value;
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

        const classDef = ParseTreeUtils.getEnclosingClass(node);
        if (!classDef) {
            return;
        }

        const classTypeInfo = getTypeOfClass(classDef);
        if (classTypeInfo && isInstantiableClass(classTypeInfo.classType)) {
            let memberInfo = lookUpClassMember(
                classTypeInfo.classType,
                memberName,
                isInstanceMember ? MemberAccessFlags.Default : MemberAccessFlags.SkipInstanceMembers
            );

            const memberFields = ClassType.getSymbolTable(classTypeInfo.classType);
            if (memberInfo) {
                // Are we accessing an existing member on this class, or is
                // it a member on a parent class?
                const memberClass = isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined;
                const isThisClass = memberClass && ClassType.isSameGenericClass(classTypeInfo.classType, memberClass);

                // Check for an attempt to write to an instance variable that is
                // not defined by __slots__.
                if (isThisClass && isInstanceMember && memberClass) {
                    const inheritedSlotsNames = ClassType.getInheritedSlotsNames(memberClass);

                    if (inheritedSlotsNames && memberClass.shared.localSlotsNames) {
                        // Skip this check if the local slots is specified but empty
                        // and the class isn't final. This pattern is used in a
                        // legitimate manner for mix-in classes.
                        if (
                            (memberClass.shared.localSlotsNames.length > 0 || ClassType.isFinal(memberClass)) &&
                            !inheritedSlotsNames.some((name) => name === memberName)
                        ) {
                            // Determine whether the assignment corresponds to a descriptor
                            // that was assigned as a class variable. If so, then slots will not
                            // apply in this case.
                            const classMemberDetails = lookUpClassMember(
                                memberClass,
                                memberName,
                                MemberAccessFlags.SkipInstanceMembers
                            );
                            let isPotentiallyDescriptor = false;

                            if (classMemberDetails) {
                                const classMemberSymbolType = getEffectiveTypeOfSymbol(classMemberDetails.symbol);
                                if (
                                    isAnyOrUnknown(classMemberSymbolType) ||
                                    isUnbound(classMemberSymbolType) ||
                                    isMaybeDescriptorInstance(classMemberSymbolType)
                                ) {
                                    isPotentiallyDescriptor = true;
                                }
                            }

                            if (!isPotentiallyDescriptor) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.slotsAttributeError().format({ name: memberName }),
                                    node.d.member
                                );
                            }
                        }
                    }
                }

                if (isThisClass && memberInfo.isInstanceMember === isInstanceMember) {
                    const symbol = memberFields.get(memberName)!;
                    assert(symbol !== undefined);

                    const typedDecls = symbol.getDeclarations();

                    // Check for an attempt to overwrite a constant member variable.
                    if (
                        typedDecls.length > 0 &&
                        typedDecls[0].type === DeclarationType.Variable &&
                        srcExprNode &&
                        node.d.member !== typedDecls[0].node
                    ) {
                        if (typedDecls[0].isConstant) {
                            addDiagnostic(
                                DiagnosticRule.reportConstantRedefinition,
                                LocMessage.constantRedefinition().format({ name: node.d.member.d.value }),
                                node.d.member
                            );
                        }
                    }
                } else {
                    // Is the target a property?
                    const declaredType = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type;
                    if (declaredType && !isProperty(declaredType)) {
                        // Handle the case where there is a class variable defined with the same
                        // name, but there's also now an instance variable introduced. Combine the
                        // type of the class variable with that of the new instance variable.
                        if (!memberInfo.isInstanceMember && isInstanceMember) {
                            // The class variable is accessed in this case.
                            setSymbolAccessed(fileInfo, memberInfo.symbol, node.d.member);
                            const memberType = getTypeOfMember(memberInfo);
                            typeResult = { ...typeResult, type: combineTypes([typeResult.type, memberType]) };
                        }
                    }
                }
            }

            // Look up the member info again, now that we've potentially updated it.
            memberInfo = lookUpClassMember(classTypeInfo.classType, memberName, MemberAccessFlags.DeclaredTypesOnly);

            if (!memberInfo && srcExprNode && !typeResult.isIncomplete) {
                reportPossibleUnknownAssignment(
                    fileInfo.diagnosticRuleSet.reportUnknownMemberType,
                    DiagnosticRule.reportUnknownMemberType,
                    node.d.member,
                    typeResult.type,
                    node,
                    /* ignoreEmptyContainers */ true
                );
            }
        }
    }

    function assignTypeToTupleOrListNode(
        target: TupleNode | ListNode,
        typeResult: TypeResult,
        srcExpr: ExpressionNode
    ) {
        const targetExpressions = target.nodeType === ParseNodeType.List ? target.d.items : target.d.items;

        // Initialize the array of target types, one for each target.
        const targetTypes: Type[][] = new Array(targetExpressions.length);
        for (let i = 0; i < targetExpressions.length; i++) {
            targetTypes[i] = [];
        }
        const targetUnpackIndex = targetExpressions.findIndex((expr) => expr.nodeType === ParseNodeType.Unpack);

        // Do any of the targets use an unpack operator? If so, it will consume all of the
        // entries at that location.
        const unpackIndex = targetExpressions.findIndex((expr) => expr.nodeType === ParseNodeType.Unpack);

        typeResult = { ...typeResult, type: makeTopLevelTypeVarsConcrete(typeResult.type) };

        const diagAddendum = new DiagnosticAddendum();

        doForEachSubtype(typeResult.type, (subtype) => {
            // Is this subtype a tuple?
            const tupleType = getSpecializedTupleType(subtype);
            if (tupleType && tupleType.priv.tupleTypeArgs) {
                const sourceEntryTypes = tupleType.priv.tupleTypeArgs.map((t) =>
                    addConditionToType(t.type, getTypeCondition(subtype), { skipSelfCondition: true })
                );

                const unboundedIndex = tupleType.priv.tupleTypeArgs.findIndex((t) => t.isUnbounded);

                if (unboundedIndex >= 0) {
                    if (sourceEntryTypes.length < targetTypes.length) {
                        const typeToReplicate =
                            sourceEntryTypes.length > 0 ? sourceEntryTypes[unboundedIndex] : AnyType.create();

                        // Add elements to make the count match the target count.
                        while (sourceEntryTypes.length < targetTypes.length) {
                            sourceEntryTypes.splice(unboundedIndex, 0, typeToReplicate);
                        }
                    }

                    if (sourceEntryTypes.length > targetTypes.length) {
                        // Remove elements to make the count match the target count.
                        sourceEntryTypes.splice(unboundedIndex, 1);
                    }
                }

                // If there's an unpack operator in the target and we have too many source elements,
                // combine them to assign to the unpacked target.
                if (targetUnpackIndex >= 0) {
                    if (sourceEntryTypes.length > targetTypes.length) {
                        const removedEntries = sourceEntryTypes.splice(
                            targetUnpackIndex,
                            sourceEntryTypes.length - targetTypes.length + 1
                        );
                        let combinedTypes = combineTypes(removedEntries);
                        if (target.nodeType === ParseNodeType.List) {
                            combinedTypes = stripLiteralValue(combinedTypes);
                        }
                        sourceEntryTypes.splice(targetUnpackIndex, 0, combinedTypes);
                    } else if (sourceEntryTypes.length === targetTypes.length - 1) {
                        sourceEntryTypes.splice(targetUnpackIndex, 0, NeverType.createNever());
                    }
                }

                sourceEntryTypes.forEach((type, targetIndex) => {
                    if (targetIndex < targetTypes.length) {
                        targetTypes[targetIndex].push(type);
                    }
                });

                // Have we accounted for all of the targets and sources? If not, we have a size mismatch.
                if (sourceEntryTypes.length !== targetExpressions.length) {
                    const subDiag = diagAddendum.createAddendum();
                    subDiag.addMessage(
                        (target.nodeType === ParseNodeType.List
                            ? LocAddendum.listAssignmentMismatch()
                            : LocAddendum.tupleAssignmentMismatch()
                        ).format({
                            type: printType(subtype),
                        })
                    );

                    subDiag.createAddendum().addMessage(
                        (unpackIndex >= 0
                            ? LocAddendum.tupleSizeMismatchIndeterminateDest()
                            : LocAddendum.tupleSizeMismatch()
                        ).format({
                            expected: unpackIndex >= 0 ? targetExpressions.length - 1 : targetExpressions.length,
                            received: sourceEntryTypes.length,
                        })
                    );
                }
            } else {
                // The assigned expression isn't a tuple, so it had better
                // be some iterable type.
                const iterableType =
                    getTypeOfIterator(
                        { type: subtype, isIncomplete: typeResult.isIncomplete },
                        /* isAsync */ false,
                        srcExpr
                    )?.type ?? UnknownType.create();
                for (let index = 0; index < targetExpressions.length; index++) {
                    targetTypes[index].push(addConditionToType(iterableType, getTypeCondition(subtype)));
                }
            }
        });

        if (!diagAddendum.isEmpty()) {
            addDiagnostic(
                DiagnosticRule.reportAssignmentType,
                (target.nodeType === ParseNodeType.List
                    ? LocMessage.listAssignmentMismatch()
                    : LocMessage.tupleAssignmentMismatch()
                ).format({
                    type: printType(typeResult.type),
                }) + diagAddendum.getString(),
                target
            );
        }

        // Assign the resulting types to the individual names in the tuple
        // or list target expression.
        targetExpressions.forEach((expr, index) => {
            const typeList = targetTypes[index];
            const targetType = typeList.length === 0 ? UnknownType.create() : combineTypes(typeList);

            assignTypeToExpression(
                expr,
                { type: targetType, isIncomplete: typeResult.isIncomplete },
                srcExpr,
                /* ignoreEmptyContainers */ true
            );
        });

        writeTypeCache(target, typeResult, EvalFlags.None);
    }

    // If the type includes promotion types, expand these to their constituent types.
    function expandPromotionTypes(node: ParseNode, type: Type, excludeBytes = false): Type {
        return mapSubtypes(type, (subtype) => {
            if (!isClass(subtype) || !subtype.priv.includePromotions || subtype.priv.literalValue !== undefined) {
                return subtype;
            }

            if (excludeBytes && ClassType.isBuiltIn(subtype, 'bytes')) {
                return subtype;
            }

            const typesToCombine: Type[] = [ClassType.cloneRemoveTypePromotions(subtype)];

            const promotionTypeNames = typePromotions.get(subtype.shared.fullName);
            if (promotionTypeNames) {
                for (const promotionTypeName of promotionTypeNames) {
                    const nameSplit = promotionTypeName.split('.');
                    let promotionSubtype = getBuiltInType(node, nameSplit[nameSplit.length - 1]);

                    if (promotionSubtype && isInstantiableClass(promotionSubtype)) {
                        promotionSubtype = ClassType.cloneRemoveTypePromotions(promotionSubtype);

                        if (isClassInstance(subtype)) {
                            promotionSubtype = ClassType.cloneAsInstance(promotionSubtype);
                        }

                        promotionSubtype = addConditionToType(promotionSubtype, subtype.props?.condition);
                        typesToCombine.push(promotionSubtype);
                    }
                }
            }

            return combineTypes(typesToCombine);
        });
    }

    // Replaces all of the top-level TypeVars (as opposed to TypeVars
    // used as type arguments in other types) with their concrete form.
    // If conditionFilter is specified and the TypeVar is a constrained
    // TypeVar, only the conditions that match the filter will be included.
    function makeTopLevelTypeVarsConcrete(
        type: Type,
        makeParamSpecsConcrete = false,
        conditionFilter?: TypeCondition[]
    ): Type {
        type = transformPossibleRecursiveTypeAlias(type);

        return mapSubtypes(type, (subtype) => {
            if (isParamSpec(subtype)) {
                if (subtype.priv.paramSpecAccess === 'args') {
                    return makeTupleObject(evaluatorInterface, [{ type: getObjectType(), isUnbounded: true }]);
                } else if (subtype.priv.paramSpecAccess === 'kwargs') {
                    if (
                        prefetched?.dictClass &&
                        isInstantiableClass(prefetched.dictClass) &&
                        prefetched?.strClass &&
                        isInstantiableClass(prefetched.strClass)
                    ) {
                        return ClassType.cloneAsInstance(
                            ClassType.specialize(prefetched.dictClass, [
                                convertToInstance(prefetched.strClass),
                                getObjectType(),
                            ])
                        );
                    }

                    return UnknownType.create();
                }
            }

            // If this is a function that contains only a ParamSpec (no additional
            // parameters), convert it to a concrete type of (*args: Unknown, **kwargs: Unknown).
            if (makeParamSpecsConcrete && isFunction(subtype)) {
                const convertedType = simplifyFunctionToParamSpec(subtype);
                if (isParamSpec(convertedType)) {
                    return ParamSpecType.getUnknown();
                }
            }

            if (isTypeVarTuple(subtype)) {
                // If it's in a union, convert to type or object.
                if (subtype.priv.isInUnion) {
                    if (TypeBase.isInstantiable(subtype)) {
                        if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) {
                            return prefetched.typeClass;
                        }
                    } else {
                        return getObjectType();
                    }

                    return AnyType.create();
                }

                // Fall back to "*tuple[object, ...]".
                return makeTupleObject(
                    evaluatorInterface,
                    [{ type: getObjectType(), isUnbounded: true }],
                    /* isUnpacked */ true
                );
            }

            if (isTypeVar(subtype)) {
                // If this is a recursive type alias placeholder
                // that hasn't yet been resolved, return it as is.
                if (subtype.shared.recursiveAlias) {
                    return subtype;
                }

                if (TypeVarType.hasConstraints(subtype)) {
                    const typesToCombine: Type[] = [];

                    // Expand the list of constrained subtypes, filtering out any that are
                    // disallowed by the conditionFilter.
                    subtype.shared.constraints.forEach((constraintType, constraintIndex) => {
                        if (conditionFilter) {
                            const typeVarName = TypeVarType.getNameWithScope(subtype);
                            const applicableConstraint = conditionFilter.find(
                                (filter) => filter.typeVar.priv.nameWithScope === typeVarName
                            );

                            // If this type variable is being constrained to a single index,
                            // don't include the other indices.
                            if (applicableConstraint && applicableConstraint.constraintIndex !== constraintIndex) {
                                return;
                            }
                        }

                        if (TypeBase.isInstantiable(subtype)) {
                            constraintType = convertToInstantiable(constraintType);
                        }

                        typesToCombine.push(
                            addConditionToType(constraintType, [{ typeVar: subtype, constraintIndex }])
                        );
                    });

                    return combineTypes(typesToCombine);
                }

                if (subtype.shared.isExemptFromBoundCheck) {
                    return AnyType.create();
                }

                // Fall back to a bound of "object" if no bound is provided.
                let boundType = subtype.shared.boundType ?? getObjectType();

                // If this is a synthesized self/cls type var, self-specialize its type arguments.
                if (TypeVarType.isSelf(subtype) && isClass(boundType) && !ClassType.isPseudoGenericClass(boundType)) {
                    boundType = selfSpecializeClass(boundType, {
                        useBoundTypeVars: TypeVarType.isBound(subtype),
                    });
                }

                if (subtype.priv.isUnpacked && isClass(boundType)) {
                    boundType = ClassType.cloneForUnpacked(boundType);
                }

                boundType = TypeBase.isInstantiable(subtype) ? convertToInstantiable(boundType) : boundType;

                return addConditionToType(boundType, [{ typeVar: subtype, constraintIndex: 0 }]);
            }

            return subtype;
        });
    }

    // Creates a new type by mapping an existing type (which could be a union)
    // to another type or types. The callback is called for each subtype.
    // Top-level TypeVars are expanded (e.g. a bound TypeVar is expanded to
    // its bound type and a constrained TypeVar is expanded to its individual
    // constrained types). If conditionFilter is specified, conditions that
    // do not match will be ignored.
    function mapSubtypesExpandTypeVars(
        type: Type,
        options: MapSubtypesOptions | undefined,
        callback: (expandedSubtype: Type, unexpandedSubtype: Type, isLastIteration: boolean) => Type | undefined,
        recursionCount = 0
    ): Type {
        const newSubtypes: Type[] = [];
        let typeChanged = false;

        function expandSubtype(unexpandedType: Type, isLastSubtype: boolean) {
            let expandedType = isUnion(unexpandedType) ? unexpandedType : makeTopLevelTypeVarsConcrete(unexpandedType);

            expandedType = transformPossibleRecursiveTypeAlias(expandedType);
            if (options?.expandCallback) {
                expandedType = options.expandCallback(expandedType);
            }

            doForEachSubtype(
                expandedType,
                (subtype, index, allSubtypes) => {
                    if (options?.conditionFilter) {
                        const filteredType = applyConditionFilterToType(
                            subtype,
                            options.conditionFilter,
                            recursionCount
                        );
                        if (!filteredType) {
                            return undefined;
                        }

                        subtype = filteredType;
                    }

                    let transformedType = callback(
                        subtype,
                        unexpandedType,
                        isLastSubtype && index === allSubtypes.length - 1
                    );

                    if (transformedType !== unexpandedType) {
                        typeChanged = true;
                    }

                    if (transformedType) {
                        // Apply the type condition if it's associated with a constrained TypeVar.
                        const typeCondition = getTypeCondition(subtype)?.filter((condition) =>
                            TypeVarType.hasConstraints(condition.typeVar)
                        );

                        if (typeCondition && typeCondition.length > 0) {
                            transformedType = addConditionToType(transformedType, typeCondition);
                        }

                        // This code path can often produce many duplicate subtypes. We can
                        // reduce the cost of the combineTypes call below by filtering out these
                        // duplicates proactively.
                        if (
                            newSubtypes.length === 0 ||
                            !isTypeSame(transformedType, newSubtypes[newSubtypes.length - 1])
                        ) {
                            newSubtypes.push(transformedType);
                        }
                    }
                    return undefined;
                },
                options?.sortSubtypes
            );
        }

        if (isUnion(type)) {
            const subtypes = options?.sortSubtypes ? sortTypes(type.priv.subtypes) : type.priv.subtypes;
            subtypes.forEach((subtype, index) => {
                expandSubtype(subtype, index === type.priv.subtypes.length - 1);
            });
        } else {
            expandSubtype(type, /* isLastSubtype */ true);
        }

        if (!typeChanged) {
            return type;
        }

        const newType = combineTypes(newSubtypes);

        // Do our best to retain type aliases.
        if (newType.category === TypeCategory.Union) {
            UnionType.addTypeAliasSource(newType, type);
        }
        return newType;
    }

    function applyConditionFilterToType(
        type: Type,
        conditionFilter: TypeCondition[],
        recursionCount: number
    ): Type | undefined {
        if (recursionCount > maxTypeRecursionCount) {
            return type;
        }
        recursionCount++;

        // If the type has a condition associated with it, make sure it's compatible.
        if (!TypeCondition.isCompatible(getTypeCondition(type), conditionFilter)) {
            return undefined;
        }

        // If the type is generic, see if any of its type arguments should be filtered.
        // This is possible only in cases where the type parameter is covariant.

        // TODO - handle functions and tuples
        if (isClass(type) && type.priv.typeArgs && !type.priv.tupleTypeArgs) {
            inferVarianceForClass(type);

            let typeWasTransformed = false;

            const filteredTypeArgs = type.priv.typeArgs.map((typeArg, index) => {
                if (index >= type.shared.typeParams.length) {
                    return typeArg;
                }

                const variance = TypeVarType.getVariance(type.shared.typeParams[index]);
                if (variance !== Variance.Covariant) {
                    return typeArg;
                }

                // Don't expand recursive type aliases because they can
                // cause infinite recursion.
                if (isTypeVar(typeArg) && typeArg.shared.recursiveAlias) {
                    return typeArg;
                }

                const filteredTypeArg = mapSubtypesExpandTypeVars(
                    typeArg,
                    { conditionFilter },
                    (expandedSubtype) => {
                        return expandedSubtype;
                    },
                    recursionCount
                );

                if (filteredTypeArg !== typeArg) {
                    typeWasTransformed = true;
                }

                return filteredTypeArg;
            });

            if (typeWasTransformed) {
                return ClassType.specialize(type, filteredTypeArgs);
            }
        }

        return type;
    }

    function markNamesAccessed(node: ParseNode, names: string[]) {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const scope = ScopeUtils.getScopeForNode(node);

        if (scope) {
            names.forEach((symbolName) => {
                const symbolInScope = scope.lookUpSymbolRecursive(symbolName);
                if (symbolInScope) {
                    setSymbolAccessed(fileInfo, symbolInScope.symbol, node);
                }
            });
        }
    }

    function assignTypeToExpression(
        target: ExpressionNode,
        typeResult: TypeResult,
        srcExpr: ExpressionNode,
        ignoreEmptyContainers = false,
        allowAssignmentToFinalVar = false,
        expectedTypeDiagAddendum?: DiagnosticAddendum
    ) {
        // Is the source expression a TypeVar() call?
        if (isTypeVar(typeResult.type)) {
            if (srcExpr && srcExpr.nodeType === ParseNodeType.Call) {
                const callType = getTypeOfExpression(srcExpr.d.leftExpr, EvalFlags.CallBaseDefaults).type;
                if (
                    isInstantiableClass(callType) &&
                    (ClassType.isBuiltIn(callType, 'TypeVar') ||
                        ClassType.isBuiltIn(callType, 'TypeVarTuple') ||
                        ClassType.isBuiltIn(callType, 'ParamSpec'))
                ) {
                    const typeVarTarget =
                        target.nodeType === ParseNodeType.TypeAnnotation ? target.d.valueExpr : target;
                    if (
                        typeVarTarget.nodeType !== ParseNodeType.Name ||
                        typeVarTarget.d.value !== typeResult.type.shared.name
                    ) {
                        const name = TypeVarType.getReadableName(typeResult.type);
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            isParamSpec(typeResult.type)
                                ? LocMessage.paramSpecAssignedName().format({ name })
                                : LocMessage.typeVarAssignedName().format({ name }),
                            typeVarTarget
                        );
                    }
                }
            }
        }

        // If the type was partially unbound, an error will have already been logged.
        // Remove the unbound before assigning to the target expression so the unbound
        // error doesn't propagate.
        if (findSubtype(typeResult.type, (subtype) => isUnbound(subtype))) {
            typeResult = { ...typeResult, type: removeUnbound(typeResult.type) };
        }

        switch (target.nodeType) {
            case ParseNodeType.Name: {
                assignTypeToNameNode(
                    target,
                    typeResult,
                    ignoreEmptyContainers,
                    srcExpr,
                    allowAssignmentToFinalVar,
                    expectedTypeDiagAddendum
                );
                break;
            }

            case ParseNodeType.MemberAccess: {
                assignTypeToMemberAccessNode(target, typeResult, srcExpr, expectedTypeDiagAddendum);
                break;
            }

            case ParseNodeType.Index: {
                const baseTypeResult = getTypeOfExpression(target.d.leftExpr, EvalFlags.IndexBaseDefaults);

                getTypeOfIndexWithBaseType(
                    target,
                    baseTypeResult,
                    {
                        method: 'set',
                        setType: typeResult,
                        setErrorNode: srcExpr,
                        setExpectedTypeDiag: expectedTypeDiagAddendum,
                    },
                    EvalFlags.None
                );

                writeTypeCache(target, typeResult, EvalFlags.None);
                break;
            }

            case ParseNodeType.List:
            case ParseNodeType.Tuple: {
                assignTypeToTupleOrListNode(target, typeResult, srcExpr);
                break;
            }

            case ParseNodeType.TypeAnnotation: {
                getTypeOfAnnotation(target.d.annotation, {
                    varTypeAnnotation: true,
                    allowFinal: isFinalAllowedForAssignmentTarget(target.d.valueExpr),
                    allowClassVar: isClassVarAllowedForAssignmentTarget(target.d.valueExpr),
                });

                assignTypeToExpression(
                    target.d.valueExpr,
                    typeResult,
                    srcExpr,
                    ignoreEmptyContainers,
                    allowAssignmentToFinalVar,
                    expectedTypeDiagAddendum
                );
                break;
            }

            case ParseNodeType.Unpack: {
                assignTypeToExpression(
                    target.d.expr,
                    {
                        type: getBuiltInObject(target.d.expr, 'list', [typeResult.type]),
                        isIncomplete: typeResult.isIncomplete,
                    },
                    srcExpr,
                    ignoreEmptyContainers,
                    allowAssignmentToFinalVar,
                    expectedTypeDiagAddendum
                );
                break;
            }

            case ParseNodeType.Error: {
                // Evaluate the child expression as best we can so the
                // type information is cached for the completion handler.
                if (target.d.child) {
                    suppressDiagnostics(target.d.child, () => {
                        getTypeOfExpression(target.d.child!);
                    });
                }
                break;
            }

            default: {
                addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.assignmentTargetExpr(), target);
                break;
            }
        }
    }

    function isClassVarAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
        // ClassVar is allowed only in a class body.
        const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true);
        if (!classNode) {
            return false;
        }

        // ClassVar is not allowed in a TypedDict or a NamedTuple class.
        return !isInTypedDictOrNamedTuple(classNode);
    }

    function isFinalAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
        const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true);

        // Final is not allowed in the body of a TypedDict or NamedTuple class.
        if (classNode && isInTypedDictOrNamedTuple(classNode)) {
            return false;
        }

        return ParseTreeUtils.isFinalAllowedForAssignmentTarget(targetNode);
    }

    function isInTypedDictOrNamedTuple(classNode: ClassNode): boolean {
        const classType = getTypeOfClass(classNode)?.classType;
        if (!classType) {
            return false;
        }

        return ClassType.isTypedDictClass(classType) || !!classType.shared.namedTupleEntries;
    }

    function verifyRaiseExceptionType(node: ExpressionNode, allowNone: boolean) {
        const baseExceptionType = getBuiltInType(node, 'BaseException');
        const exceptionType = getTypeOfExpression(node).type;

        // Validate that the argument of "raise" is an exception object or class.
        // If it is a class, validate that the class's constructor accepts zero
        // arguments.
        if (exceptionType && baseExceptionType && isInstantiableClass(baseExceptionType)) {
            const diag = new DiagnosticAddendum();

            doForEachSubtype(exceptionType, (subtype) => {
                const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);

                if (isAnyOrUnknown(concreteSubtype) || isNever(concreteSubtype)) {
                    return;
                }

                if (allowNone && isNoneInstance(concreteSubtype)) {
                    return;
                }

                if (isInstantiableClass(concreteSubtype) && concreteSubtype.priv.literalValue === undefined) {
                    if (!derivesFromClassRecursive(concreteSubtype, baseExceptionType, /* ignoreUnknown */ false)) {
                        diag.addMessage(
                            LocMessage.exceptionTypeIncorrect().format({
                                type: printType(subtype),
                            })
                        );
                    } else {
                        let callResult: CallResult | undefined;
                        suppressDiagnostics(node, () => {
                            callResult = validateConstructorArgs(
                                evaluatorInterface,
                                node,
                                [],
                                concreteSubtype,
                                /* skipUnknownArgCheck */ false,
                                /* inferenceContext */ undefined
                            );
                        });

                        if (callResult && callResult.argumentErrors) {
                            diag.addMessage(
                                LocMessage.exceptionTypeNotInstantiable().format({
                                    type: printType(subtype),
                                })
                            );
                        }
                    }
                } else if (isClassInstance(concreteSubtype)) {
                    if (
                        !derivesFromClassRecursive(
                            ClassType.cloneAsInstantiable(concreteSubtype),
                            baseExceptionType,
                            /* ignoreUnknown */ false
                        )
                    ) {
                        diag.addMessage(
                            LocMessage.exceptionTypeIncorrect().format({
                                type: printType(subtype),
                            })
                        );
                    }
                } else {
                    diag.addMessage(
                        LocMessage.exceptionTypeIncorrect().format({
                            type: printType(subtype),
                        })
                    );
                }
            });

            if (!diag.isEmpty()) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.expectedExceptionClass() + diag.getString(),
                    node
                );
            }
        }
    }

    function verifyDeleteExpression(node: ExpressionNode) {
        switch (node.nodeType) {
            case ParseNodeType.Name: {
                // Get the type to evaluate whether it's bound
                // and to mark it accessed.
                getTypeOfExpression(node);
                break;
            }

            case ParseNodeType.MemberAccess: {
                const baseTypeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.MemberAccessBaseDefaults);
                const delAccessResult = getTypeOfMemberAccessWithBaseType(
                    node,
                    baseTypeResult,
                    { method: 'del' },
                    EvalFlags.None
                );
                const resultToCache: TypeResult = {
                    type: delAccessResult.type,
                    memberAccessDeprecationInfo: delAccessResult.memberAccessDeprecationInfo,
                };
                writeTypeCache(node.d.member, resultToCache, EvalFlags.None);
                writeTypeCache(node, resultToCache, EvalFlags.None);
                break;
            }

            case ParseNodeType.Index: {
                const baseTypeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.IndexBaseDefaults);
                getTypeOfIndexWithBaseType(node, baseTypeResult, { method: 'del' }, EvalFlags.None);
                writeTypeCache(node, { type: UnboundType.create() }, EvalFlags.None);
                break;
            }

            case ParseNodeType.Tuple: {
                node.d.items.forEach((expr) => {
                    verifyDeleteExpression(expr);
                });
                break;
            }

            case ParseNodeType.Error: {
                // Evaluate the child expression as best we can so the
                // type information is cached for the completion handler.
                if (node.d.child) {
                    suppressDiagnostics(node.d.child, () => {
                        getTypeOfExpression(node.d.child!);
                    });
                }
                break;
            }

            default: {
                addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.delTargetExpr(), node);
                break;
            }
        }
    }

    function setSymbolAccessed(fileInfo: AnalyzerFileInfo, symbol: Symbol, node: ParseNode) {
        if (!isSpeculativeModeInUse(node)) {
            fileInfo.accessedSymbolSet.add(symbol.id);
        }
    }

    function getTypeOfName(node: NameNode, flags: EvalFlags): TypeResult {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const name = node.d.value;
        let symbol: Symbol | undefined;
        let type: Type | undefined;
        let isIncomplete = false;
        const allowForwardReferences = (flags & EvalFlags.ForwardRefs) !== 0 || fileInfo.isStubFile;

        // Look for the scope that contains the value definition and
        // see if it has a declared type.
        let symbolWithScope = lookUpSymbolRecursive(
            node,
            name,
            !allowForwardReferences,
            allowForwardReferences && (flags & EvalFlags.TypeExpression) !== 0
        );

        if (!symbolWithScope) {
            // If the node is part of a "from X import Y as Z" statement and the node
            // is the "Y" (non-aliased) name, we need to look up the alias symbol
            // since the non-aliased name is not in the symbol table.
            const alias = getAliasFromImport(node);
            if (alias) {
                symbolWithScope = lookUpSymbolRecursive(
                    alias,
                    alias.d.value,
                    !allowForwardReferences,
                    allowForwardReferences && (flags & EvalFlags.TypeExpression) !== 0
                );
            }
        }

        if (symbolWithScope) {
            let useCodeFlowAnalysis = !allowForwardReferences;

            // If the symbol is implicitly imported from the builtin
            // scope, there's no need to use code flow analysis.
            if (symbolWithScope.scope.type === ScopeType.Builtin) {
                useCodeFlowAnalysis = false;
            }

            symbol = symbolWithScope.symbol;
            setSymbolAccessed(fileInfo, symbol, node);

            // If we're not supposed to be analyzing this function, skip the remaining work
            // to determine the name's type. Simply evaluate its type as Any.
            if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) {
                const containingFunction = ParseTreeUtils.getEnclosingFunction(node);
                if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) {
                    return {
                        type: AnyType.create(),
                        isIncomplete: false,
                    };
                }
            }

            // Get the effective type (either the declared type or the inferred type).
            // If we're using code flow analysis, pass the usage node so we consider
            // only the assignment nodes that are reachable from this usage.
            const effectiveTypeInfo = getEffectiveTypeOfSymbolForUsage(symbol, useCodeFlowAnalysis ? node : undefined);
            let effectiveType = transformPossibleRecursiveTypeAlias(effectiveTypeInfo.type);

            if (effectiveTypeInfo.isIncomplete) {
                if (isUnbound(effectiveType)) {
                    effectiveType = UnknownType.create(/* isIncomplete */ true);
                }
                isIncomplete = true;
            }

            if (effectiveTypeInfo.isRecursiveDefinition && isNodeReachable(node)) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.recursiveDefinition().format({ name }),
                    node
                );
            }

            const isSpecialBuiltIn =
                !!effectiveType && isInstantiableClass(effectiveType) && ClassType.isSpecialBuiltIn(effectiveType);

            type = effectiveType;
            if (useCodeFlowAnalysis && !isSpecialBuiltIn) {
                // See if code flow analysis can tell us anything more about the type.
                // If the symbol is declared outside of our execution scope, use its effective
                // type. If it's declared inside our execution scope, it generally starts
                // as unbound at the start of the code flow.
                let typeAtStart = effectiveType;
                let isTypeAtStartIncomplete = false;

                if (!symbolWithScope.isBeyondExecutionScope && symbol.isInitiallyUnbound()) {
                    typeAtStart = UnboundType.create();

                    // Is this a module-level scope? If so, see if it's an alias of a builtin.
                    if (symbolWithScope.scope.type === ScopeType.Module) {
                        assert(symbolWithScope.scope.parent);
                        const builtInSymbol = symbolWithScope.scope.parent.lookUpSymbol(name);
                        if (builtInSymbol) {
                            const builtInEffectiveType = getEffectiveTypeOfSymbolForUsage(builtInSymbol);
                            typeAtStart = builtInEffectiveType.type;
                        }
                    }
                }

                if (symbolWithScope.isBeyondExecutionScope) {
                    const outerScopeTypeResult = getCodeFlowTypeForCapturedVariable(
                        node,
                        symbolWithScope,
                        effectiveType
                    );

                    if (outerScopeTypeResult?.type) {
                        type = outerScopeTypeResult.type;
                        typeAtStart = type;
                        isTypeAtStartIncomplete = !!outerScopeTypeResult.isIncomplete;
                    }
                }

                const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, {
                    targetSymbolId: symbol.id,
                    typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete },
                    skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0,
                });

                if (codeFlowTypeResult.type) {
                    type = codeFlowTypeResult.type;
                }

                if (codeFlowTypeResult.isIncomplete) {
                    isIncomplete = true;
                }
            }

            // Detect, report, and fill in missing type arguments if appropriate.
            type = reportMissingTypeArgs(node, type, flags);

            // Report inappropriate use of variables in type expressions.
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                type = validateSymbolIsTypeExpression(node, type, !!effectiveTypeInfo.includesVariableDecl);
            }

            if (isTypeVar(type) && !type.shared.isSynthesized) {
                type = validateTypeVarUsage(node, type, flags);
            }

            // Add TypeForm details if appropriate.
            type = addTypeFormForSymbol(node, type, flags, !!effectiveTypeInfo.includesVariableDecl);
        } else {
            // Handle the special case of "reveal_type" and "reveal_locals".
            if (name === 'reveal_type' || name === 'reveal_locals') {
                type = AnyType.create();
            } else {
                addDiagnostic(
                    DiagnosticRule.reportUndefinedVariable,
                    LocMessage.symbolIsUndefined().format({ name }),
                    node
                );

                type = UnknownType.create();
            }
        }

        if (isParamSpec(type) && type.priv.scopeId) {
            if (flags & EvalFlags.NoParamSpec) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), node);
                type = UnknownType.create();
            }
        }

        // If we're expecting a type expression and got a sentinel literal instance,
        // treat it as its instantiable counterpart. This is similar to how None
        // is treated in a type expression context.
        if ((flags & EvalFlags.InstantiableType) !== 0 && isClassInstance(type) && isSentinelLiteral(type)) {
            type = ClassType.cloneAsInstantiable(type);
        }

        type = convertSpecialFormToRuntimeValue(type, flags);

        if ((flags & EvalFlags.TypeExpression) === 0) {
            reportUseOfTypeCheckOnly(type, node);
        }

        if ((flags & EvalFlags.InstantiableType) !== 0) {
            if ((flags & EvalFlags.AllowGeneric) === 0) {
                if (isInstantiableClass(type) && ClassType.isBuiltIn(type, 'Generic')) {
                    addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.genericNotAllowed(), node);
                }
            }
        }

        return { type, isIncomplete };
    }

    function addTypeFormForSymbol(node: ExpressionNode, type: Type, flags: EvalFlags, includesVarDecl: boolean): Type {
        const isValid = isSymbolValidTypeExpression(type, includesVarDecl);

        // If the type already has type information associated with it, don't replace.
        if (type.props?.typeForm) {
            // If the NoConvertSpecialForm flag is set, we are evaluating in
            // the interior of a type expression, so variables are not allowed.
            // Clear any existing type form type for this symbol in this case.
            if ((flags & EvalFlags.NoConvertSpecialForm) !== 0 && !isValid) {
                type = TypeBase.cloneWithTypeForm(type, undefined);
            }
            return type;
        }

        // If the symbol is not valid for a type expression (e.g. it's a variable),
        // don't add TypeForm info.
        if (!isValid) {
            return type;
        }

        if (isTypeVar(type) && type.priv.scopeId && !type.shared.isSynthesized) {
            if (!isTypeVarTuple(type) || !type.priv.isInUnion) {
                const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node);
                type = TypeBase.cloneWithTypeForm(type, convertToInstance(makeTypeVarsBound(type, liveScopeIds)));
            }
        } else if (isInstantiableClass(type) && !type.priv.includeSubclasses && !ClassType.isSpecialBuiltIn(type)) {
            if (ClassType.isBuiltIn(type, 'Any')) {
                type = TypeBase.cloneWithTypeForm(type, AnyType.create());
            } else {
                type = TypeBase.cloneWithTypeForm(type, ClassType.cloneAsInstance(specializeWithDefaultTypeArgs(type)));
            }
        }

        if (type.props?.typeAliasInfo && TypeBase.isInstantiable(type)) {
            let typeFormType = type;
            if ((flags & EvalFlags.NoSpecialize) === 0) {
                typeFormType = specializeTypeAliasWithDefaults(typeFormType, /* errorNode */ undefined);
            }

            type = TypeBase.cloneWithTypeForm(type, convertToInstance(typeFormType));
        }

        return type;
    }

    function isSymbolValidTypeExpression(type: Type, includesVarDecl: boolean): boolean {
        // Verify that the name does not refer to a (non type alias) variable.
        if (!includesVarDecl || type.props?.typeAliasInfo) {
            return true;
        }

        if (isTypeAliasPlaceholder(type)) {
            return true;
        }

        if (isTypeVar(type)) {
            if (type.props?.specialForm || type.props?.typeAliasInfo) {
                return true;
            }
        }

        // Exempts class types that are created by calling NewType, NamedTuple, etc.
        if (isClass(type) && !type.priv.includeSubclasses && ClassType.isValidTypeAliasClass(type)) {
            return true;
        }

        if (isSentinelLiteral(type)) {
            return true;
        }

        return false;
    }

    // Reports diagnostics if type isn't valid within a type expression.
    function validateSymbolIsTypeExpression(node: ExpressionNode, type: Type, includesVarDecl: boolean): Type {
        if (isSymbolValidTypeExpression(type, includesVarDecl)) {
            return type;
        }

        // Disable for assignments in the typings.pyi file, since it defines special forms.
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        if (fileInfo.isTypingStubFile) {
            return type;
        }

        addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeAnnotationVariable(), node);
        return UnknownType.create();
    }

    // If the value is a special form (like a TypeVar or `Any`) and is being
    // evaluated in a value expression context, convert it from its special
    // meaning to its runtime value. If convertModule is true, a module is
    // converted to an instance of types.ModuleType.
    function convertSpecialFormToRuntimeValue(type: Type, flags: EvalFlags, convertModule = false) {
        const exemptFlags = EvalFlags.TypeExpression | EvalFlags.InstantiableType | EvalFlags.NoConvertSpecialForm;

        if ((flags & exemptFlags) !== 0) {
            return type;
        }

        if (
            convertModule &&
            isModule(type) &&
            prefetched?.moduleTypeClass &&
            isInstantiableClass(prefetched.moduleTypeClass)
        ) {
            return ClassType.cloneAsInstance(prefetched.moduleTypeClass);
        }

        // Isinstance treats traditional (non-PEP 695) type aliases that are unions
        // as tuples of classes rather than unions.
        if ((flags & EvalFlags.IsinstanceArg) !== 0) {
            if (isUnion(type) && type.props?.typeAliasInfo && !type.props.typeAliasInfo.shared.isTypeAliasType) {
                return type;
            }
        }

        if (!type.props?.specialForm) {
            return type;
        }

        // If this is a type alias and we are not supposed to specialize it, return it as is.
        if ((flags & EvalFlags.NoSpecialize) !== 0 && type.props?.typeAliasInfo) {
            // Special-case TypeAliasType which should be converted in this case.
            if (!ClassType.isBuiltIn(type.props.specialForm, 'TypeAliasType')) {
                return type;
            }
        }

        if (type.props?.typeForm) {
            return TypeBase.cloneWithTypeForm(type.props.specialForm, type.props.typeForm);
        }

        return type.props.specialForm;
    }

    // Handles the case where a variable or parameter is defined in an outer
    // scope and captured by an inner scope (a function, lambda, or comprehension).
    function getCodeFlowTypeForCapturedVariable(
        node: NameNode,
        symbolWithScope: SymbolWithScope,
        effectiveType: Type
    ): FlowNodeTypeResult | undefined {
        // This function applies only to captured variables, not those that
        // are accessed via an explicit nonlocal or global binding.
        if (symbolWithScope.usesGlobalBinding || symbolWithScope.usesNonlocalBinding) {
            return undefined;
        }

        // This function applies only to variables, parameters, and imports, not to other
        // types of symbols.
        const decls = symbolWithScope.symbol.getDeclarations();
        if (
            !decls.every(
                (decl) =>
                    decl.type === DeclarationType.Variable ||
                    decl.type === DeclarationType.Param ||
                    decl.type === DeclarationType.Alias
            )
        ) {
            return undefined;
        }

        // If the symbol is modified in scopes other than the one in which it is
        // declared (e.g. through a nonlocal or global binding), it is not eligible
        // for code flow analysis.
        if (
            !decls.every(
                (decl) =>
                    decl.type === DeclarationType.Param ||
                    ScopeUtils.getScopeForNode(decl.node) === symbolWithScope.scope
            )
        ) {
            return undefined;
        }

        // If the symbol is a non-final variable in the global scope, it is not
        // eligible because it could be modified by other modules.
        if (
            !decls.every(
                (decl) =>
                    decl.type !== DeclarationType.Variable ||
                    decl.isFinal ||
                    ScopeUtils.getScopeForNode(decl.node)?.type !== ScopeType.Module
            )
        ) {
            return undefined;
        }

        // If the symbol is a variable captured by an inner function
        // or lambda, see if we can infer the type from the outer scope.
        const scopeHierarchy = ScopeUtils.getScopeHierarchy(node, symbolWithScope.scope);

        if (scopeHierarchy && scopeHierarchy.length >= 2) {
            // Find the parse node associated with the scope that is just inside of the
            // scope that declares the captured variable.
            const innerScopeNode = ScopeUtils.findTopNodeInScope(node, scopeHierarchy[scopeHierarchy.length - 2]);
            if (
                innerScopeNode?.nodeType === ParseNodeType.Function ||
                innerScopeNode?.nodeType === ParseNodeType.Lambda ||
                innerScopeNode?.nodeType === ParseNodeType.Class
            ) {
                const innerScopeCodeFlowNode = AnalyzerNodeInfo.getFlowNode(innerScopeNode);
                if (innerScopeCodeFlowNode) {
                    // See if any of the assignments of the symbol are reachable
                    // from this node. If so, we cannot apply any narrowing because
                    // the type could change after the capture.
                    if (
                        symbolWithScope.symbol.getDeclarations().every((decl) => {
                            // Parameter declarations always start life at the beginning
                            // of the execution scope, so they are always safe to narrow.
                            if (decl.type === DeclarationType.Param) {
                                return true;
                            }

                            const declCodeFlowNode = AnalyzerNodeInfo.getFlowNode(decl.node);
                            if (!declCodeFlowNode) {
                                return false;
                            }

                            return (
                                codeFlowEngine.getFlowNodeReachability(
                                    declCodeFlowNode,
                                    innerScopeCodeFlowNode,
                                    /* ignoreNoReturn */ true
                                ) !== Reachability.Reachable
                            );
                        })
                    ) {
                        let typeAtStart = effectiveType;
                        if (symbolWithScope.symbol.isInitiallyUnbound()) {
                            typeAtStart = UnboundType.create();
                        }

                        return getFlowTypeOfReference(node, innerScopeNode, {
                            targetSymbolId: symbolWithScope.symbol.id,
                            typeAtStart: { type: typeAtStart },
                        });
                    }
                }
            }
        }

        return undefined;
    }

    // Validates that a TypeVar is valid in this context. If so, it clones it
    // and provides a scope ID defined by its containing scope (class, function
    // or type alias). If not, it emits errors indicating why the TypeVar
    // cannot be used in this location.
    function validateTypeVarUsage(node: ExpressionNode, type: TypeVarType, flags: EvalFlags) {
        if (!TypeBase.isInstantiable(type) || isTypeAliasPlaceholder(type)) {
            return type;
        }

        // If the TypeVar doesn't have a scope ID, try to assign one.
        if (!type.priv.scopeId) {
            type = assignTypeVarScopeId(node, type, flags);
        }

        // If this is a free type var, see if we need to make it into a bound type var.
        if (type.priv.scopeId && !TypeVarType.isBound(type)) {
            // If this is a reference to a TypeVar defined in an outer scope,
            // mark it as bound.
            const scopedNode = findScopedTypeVar(node, type)?.scopeNode;

            if (scopedNode) {
                const enclosingSuite = ParseTreeUtils.getEnclosingClassOrFunctionSuite(node);

                if (enclosingSuite && ParseTreeUtils.isNodeContainedWithin(enclosingSuite, scopedNode)) {
                    if (scopedNode.nodeType !== ParseNodeType.Class || scopedNode.d.suite !== enclosingSuite) {
                        type = TypeVarType.cloneAsBound(type);
                    }
                }
            }
        }

        // If this is a TypeVarTuple, the name refers to the packed form. It
        // must be unpacked in most contexts.
        if (isUnpackedTypeVarTuple(type)) {
            type = TypeVarType.cloneForPacked(type);
        }

        if ((flags & EvalFlags.EnforceClassTypeVarScope) !== 0 && !enforceClassTypeVarScope(node, type)) {
            return UnknownType.create();
        }

        return type;
    }

    function assignTypeVarScopeId(node: ExpressionNode, type: TypeVarType, flags: EvalFlags): TypeVarType {
        const scopedTypeVarInfo = findScopedTypeVar(node, type);
        type = scopedTypeVarInfo.type;

        if ((flags & EvalFlags.NoTypeVarWithScopeId) !== 0 && !!type.priv.scopeId) {
            if (type.shared.isSynthesized || isParamSpec(type)) {
                return type;
            }

            // This TypeVar already has a scope ID assigned to it. See if it
            // originates from type parameter syntax. If so, allow it.
            if (type.shared.isTypeParamSyntax) {
                return type;
            }

            // If this type variable expression is used within a generic class,
            // function, or type alias that uses type parameter syntax, there is
            // no need to report an error here.
            const typeVarScopeNode = ParseTreeUtils.getTypeVarScopeNode(node);
            if (
                typeVarScopeNode &&
                typeVarScopeNode.d.typeParams &&
                !typeVarScopeNode.d.typeParams.d.params.some((t) => t.d.name === node)
            ) {
                return type;
            }

            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeVarUsedByOuterScope().format({ name: type.shared.name }),
                node
            );

            return type;
        }

        if ((flags & EvalFlags.TypeVarGetsCurScope) !== 0) {
            if (type.priv.scopeId) {
                return type;
            }

            if (scopedTypeVarInfo.foundInterveningClass) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarUsedByOuterScope().format({ name: type.shared.name }),
                    node
                );
                return type;
            }

            let enclosingScope = ParseTreeUtils.getEnclosingClassOrFunction(node);

            // Handle P.args and P.kwargs as a special case for inner functions.
            if (
                enclosingScope &&
                node.parent?.nodeType === ParseNodeType.MemberAccess &&
                node.parent.d.leftExpr === node
            ) {
                const memberName = node.parent.d.member.d.value;
                if (memberName === 'args' || memberName === 'kwargs') {
                    const outerFunctionScope = ParseTreeUtils.getEnclosingClassOrFunction(enclosingScope);

                    if (outerFunctionScope?.nodeType === ParseNodeType.Function) {
                        enclosingScope = outerFunctionScope;
                    } else if (!scopedTypeVarInfo.type.priv.scopeId) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.paramSpecNotUsedByOuterScope().format({
                                name: type.shared.name,
                            }),
                            node
                        );
                    }
                }
            }

            if (!enclosingScope) {
                fail('AssociateTypeVarsWithCurrentScope flag was set but enclosing scope not found');
            }

            // If the enclosing scope is using type parameter syntax, traditional
            // type variables can't be used in this context.
            if (
                enclosingScope.d.typeParams &&
                !enclosingScope.d.typeParams.d.params.some((param) => param.d.name.d.value === type.shared.name)
            ) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeParameterNotDeclared().format({
                        name: type.shared.name,
                        container: enclosingScope.d.name.d.value,
                    }),
                    node
                );
            }

            const scopeIdToAssign = ParseTreeUtils.getScopeIdForNode(enclosingScope);

            return TypeVarType.cloneForScopeId(
                type,
                scopeIdToAssign,
                enclosingScope.d.name.d.value,
                enclosingScope.nodeType === ParseNodeType.Function ? TypeVarScopeType.Function : TypeVarScopeType.Class
            );
        }

        if ((flags & EvalFlags.AllowTypeVarWithoutScopeId) === 0) {
            if (type.priv.scopeId && !scopedTypeVarInfo.foundInterveningClass) {
                return type;
            }

            if (!type.shared.isSynthesized && (flags & EvalFlags.InstantiableType) !== 0) {
                const message = isParamSpec(type)
                    ? LocMessage.paramSpecNotUsedByOuterScope()
                    : LocMessage.typeVarNotUsedByOuterScope();
                addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, message.format({ name: type.shared.name }), node);
            }
        }

        return type;
    }

    // Enforce that the type variable is scoped to the enclosing class or
    // an outer class that contains the class definition.
    function enforceClassTypeVarScope(node: ExpressionNode, type: TypeVarType): boolean {
        const scopeId = type.priv.freeTypeVar?.priv.scopeId ?? type.priv.scopeId;
        if (!scopeId) {
            return true;
        }

        const enclosingClass = ParseTreeUtils.getEnclosingClass(node);
        if (enclosingClass) {
            const liveTypeVarScopeIds = ParseTreeUtils.getTypeVarScopesForNode(enclosingClass);
            if (!liveTypeVarScopeIds.includes(scopeId)) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarInvalidForMemberVariable().format({
                        name: TypeVarType.getReadableName(type),
                    }),
                    node
                );

                return false;
            }
        }

        return true;
    }

    // Determines if the type is a generic class or type alias with missing
    // type arguments. If so, it fills in these type arguments with Unknown
    // and optionally reports an error.
    function reportMissingTypeArgs(node: ExpressionNode, type: Type, flags: EvalFlags): Type {
        if ((flags & EvalFlags.NoSpecialize) !== 0) {
            return type;
        }

        // Is this a generic class that needs to be specialized?
        if (isInstantiableClass(type)) {
            if ((flags & EvalFlags.InstantiableType) !== 0 && (flags & EvalFlags.AllowMissingTypeArgs) === 0) {
                if (!type.props?.typeAliasInfo && requiresTypeArgs(type)) {
                    if (!type.priv.typeArgs || !type.priv.isTypeArgExplicit) {
                        addDiagnostic(
                            DiagnosticRule.reportMissingTypeArgument,
                            LocMessage.typeArgsMissingForClass().format({
                                name: type.priv.aliasName || type.shared.name,
                            }),
                            node
                        );
                    }
                }
            }

            if (!type.priv.typeArgs) {
                type = createSpecializedClassType(type, /* typeArgs */ undefined, flags, node)?.type;
            }
        }

        // Is this a generic type alias that needs to be specialized?
        if ((flags & EvalFlags.InstantiableType) !== 0) {
            type = specializeTypeAliasWithDefaults(type, node);
        }

        return type;
    }

    // Walks up the parse tree to find a function, class, or type alias
    // declaration that provides the context for a type variable.
    function findScopedTypeVar(node: ExpressionNode, type: TypeVarType): ScopedTypeVarResult {
        let curNode: ParseNode | undefined = node;
        let nestedClassCount = 0;

        assert(TypeBase.isInstantiable(type));

        while (curNode) {
            const scopeNode = ParseTreeUtils.getTypeVarScopeNode(curNode);
            if (!scopeNode) {
                break;
            }
            curNode = scopeNode;

            let typeParamsForScope: TypeVarType[] | undefined;
            let scopeUsesTypeParamSyntax = false;

            if (curNode.nodeType === ParseNodeType.Class) {
                const classTypeInfo = getTypeOfClass(curNode);
                if (classTypeInfo && !ClassType.isPartiallyEvaluated(classTypeInfo.classType)) {
                    typeParamsForScope = classTypeInfo.classType.shared.typeParams;
                }

                scopeUsesTypeParamSyntax = !!curNode.d.typeParams;
                nestedClassCount++;
            } else if (curNode.nodeType === ParseNodeType.Function) {
                const functionType = getTypeOfFunctionPredecorated(curNode);
                if (functionType) {
                    const functionDetails = functionType.shared;
                    typeParamsForScope = functionDetails.typeParams;
                }

                scopeUsesTypeParamSyntax = !!curNode.d.typeParams;
            } else if (curNode.nodeType === ParseNodeType.TypeAlias) {
                scopeUsesTypeParamSyntax = !!curNode.d.typeParams;
            }

            if (typeParamsForScope) {
                const match = typeParamsForScope.find((typeVar) => typeVar.shared.name === type.shared.name);

                if (
                    match?.priv.scopeId !== undefined &&
                    match.priv.scopeName !== undefined &&
                    match.priv.scopeType !== undefined
                ) {
                    // Use the scoped version of the TypeVar rather than the (unscoped) original type.
                    type = TypeVarType.cloneForScopeId(
                        type,
                        match.priv.scopeId,
                        match.priv.scopeName,
                        match.priv.scopeType
                    );
                    type.shared.declaredVariance = match.shared.declaredVariance;
                    return {
                        type,
                        scopeNode,
                        foundInterveningClass: nestedClassCount > 1 && !scopeUsesTypeParamSyntax,
                    };
                }
            }

            curNode = curNode.parent;
        }

        // See if this is part of an assignment statement that is defining a type alias.
        curNode = node;
        while (curNode) {
            let leftType: Type | undefined;
            let typeAliasNode: TypeAliasNode | undefined;
            let scopeNode: TypeAliasNode | AssignmentNode | undefined;

            if (curNode.nodeType === ParseNodeType.TypeAlias) {
                leftType = readTypeCache(curNode.d.name, EvalFlags.None);
                typeAliasNode = curNode;
                scopeNode = curNode;
            } else if (curNode.nodeType === ParseNodeType.Assignment) {
                leftType = readTypeCache(curNode.d.leftExpr, EvalFlags.None);
                scopeNode = curNode;
            }

            if (leftType && scopeNode) {
                // Is this a placeholder that was temporarily written to the cache for
                // purposes of resolving type aliases?
                if (leftType && isTypeVar(leftType) && leftType.shared.recursiveAlias) {
                    // Type alias statements cannot be used with old-style type variables.
                    if (typeAliasNode && !type.shared.isTypeParamSyntax && !type.props?.typeAliasInfo) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeParameterNotDeclared().format({
                                name: type.shared.name,
                                container: typeAliasNode.d.name.d.value,
                            }),
                            node
                        );
                    } else {
                        // If this is a TypeAliasType call, the recursive type parameters will already
                        // be populated, and we need to verify that the type parameter is in the
                        // list of allowed type parameters.
                        const allowedTypeParams = leftType.shared.recursiveAlias?.typeParams;
                        if (allowedTypeParams) {
                            if (!allowedTypeParams.some((param) => param.shared.name === type.shared.name)) {
                                // Return the original type.
                                return { type, scopeNode, foundInterveningClass: false };
                            }
                        }
                    }

                    return {
                        type: TypeVarType.cloneForScopeId(
                            type,
                            leftType.shared.recursiveAlias.typeVarScopeId,
                            leftType.shared.recursiveAlias.name,
                            TypeVarScopeType.TypeAlias
                        ),
                        scopeNode,
                        foundInterveningClass: false,
                    };
                }
            }

            curNode = curNode.parent;
        }

        // Return the original type.
        return { type, scopeNode: undefined, foundInterveningClass: false };
    }

    function getTypeOfMemberAccess(node: MemberAccessNode, flags: EvalFlags): TypeResult {
        // Compute flags specifically for evaluating the left expression.
        let leftExprFlags = EvalFlags.MemberAccessBaseDefaults;
        leftExprFlags |=
            flags &
            (EvalFlags.TypeExpression |
                EvalFlags.VarTypeAnnotation |
                EvalFlags.ForwardRefs |
                EvalFlags.NotParsed |
                EvalFlags.NoTypeVarWithScopeId |
                EvalFlags.TypeVarGetsCurScope);

        // Handle special casing for ParamSpec "args" and "kwargs" accesses.
        if ((flags & EvalFlags.InstantiableType) !== 0) {
            const memberName = node.d.member.d.value;
            if (memberName === 'args' || memberName === 'kwargs') {
                leftExprFlags |= EvalFlags.NoConvertSpecialForm;
            }
        }
        const baseTypeResult = getTypeOfExpression(node.d.leftExpr, leftExprFlags);

        if (isTypeAliasPlaceholder(baseTypeResult.type)) {
            return {
                type: UnknownType.create(/* isIncomplete */ true),
                isIncomplete: true,
            };
        }

        const typeResult = getTypeOfMemberAccessWithBaseType(
            node,
            baseTypeResult,
            { method: 'get' },
            flags | EvalFlags.NoSpecialize
        );

        if (isCodeFlowSupportedForReference(node)) {
            // Before performing code flow analysis, update the cache to prevent recursion.
            writeTypeCache(node, { ...typeResult, isIncomplete: true }, flags);
            writeTypeCache(node.d.member, { ...typeResult, isIncomplete: true }, flags);

            // If the type is initially unbound, see if there's a parent class that
            // potentially initialized the value.
            let typeAtStart = typeResult.type;
            let isTypeAtStartIncomplete = !!typeResult.isIncomplete;
            if (isUnbound(typeAtStart)) {
                const baseType = makeTopLevelTypeVarsConcrete(baseTypeResult.type);

                let classMemberInfo: ClassMember | undefined;
                if (isInstantiableClass(baseType)) {
                    classMemberInfo = lookUpClassMember(
                        baseType,
                        node.d.member.d.value,
                        MemberAccessFlags.SkipOriginalClass
                    );
                } else if (isClassInstance(baseType)) {
                    classMemberInfo = lookUpObjectMember(
                        baseType,
                        node.d.member.d.value,
                        MemberAccessFlags.SkipOriginalClass
                    );
                }

                if (classMemberInfo) {
                    typeAtStart = getTypeOfMember(classMemberInfo);
                    isTypeAtStartIncomplete = false;
                }
            }

            // See if we can refine the type based on code flow analysis.
            const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, {
                targetSymbolId: indeterminateSymbolId,
                typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete },
                skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0,
            });

            if (codeFlowTypeResult.type) {
                typeResult.type = codeFlowTypeResult.type;
            }

            if (codeFlowTypeResult.isIncomplete) {
                typeResult.isIncomplete = true;
            }

            // Detect, report, and fill in missing type arguments if appropriate.
            typeResult.type = reportMissingTypeArgs(node, typeResult.type, flags);

            // Add TypeForm details if appropriate.
            typeResult.type = addTypeFormForSymbol(node, typeResult.type, flags, /* includesVarDecl */ false);
        }

        if (baseTypeResult.isIncomplete) {
            typeResult.isIncomplete = true;
        }

        // See if we need to log an "unknown member access" diagnostic.
        let skipPartialUnknownCheck = typeResult.isIncomplete;

        // Don't report an error if the type is a partially-specialized
        // class being passed as an argument. This comes up frequently in
        // cases where a type is passed as an argument (e.g. "defaultdict(list)").
        // It can also come up in cases like "isinstance(x, (list, dict))".
        // We need to check for functions as well to handle Callable.
        if (
            (isInstantiableClass(typeResult.type) && !typeResult.type.priv.includeSubclasses) ||
            typeResult.type.props?.specialForm
        ) {
            const argNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.Argument);
            if (argNode && argNode?.parent?.nodeType === ParseNodeType.Call) {
                skipPartialUnknownCheck = true;
            }
        }

        if (!skipPartialUnknownCheck) {
            reportPossibleUnknownAssignment(
                AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportUnknownMemberType,
                DiagnosticRule.reportUnknownMemberType,
                node.d.member,
                typeResult.type,
                node,
                /* ignoreEmptyContainers */ false
            );
        }

        // Cache the type information in the member name node.
        writeTypeCache(node.d.member, typeResult, flags);

        return typeResult;
    }

    function getTypeOfMemberAccessWithBaseType(
        node: MemberAccessNode,
        baseTypeResult: TypeResult,
        usage: EvaluatorUsage,
        flags: EvalFlags
    ): TypeResult {
        let baseType = transformPossibleRecursiveTypeAlias(baseTypeResult.type);
        const memberName = node.d.member.d.value;
        let diag = new DiagnosticAddendum();
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        let type: Type | undefined;
        let narrowedTypeForSet: Type | undefined;
        let typeErrors = false;
        let isIncomplete = !!baseTypeResult.isIncomplete;
        let isAsymmetricAccessor: boolean | undefined;
        const isRequired = false;
        const isNotRequired = false;
        let memberAccessDeprecationInfo: MemberAccessDeprecationInfo | undefined;

        if (usage?.setType?.isIncomplete) {
            isIncomplete = true;
        }

        // If the base type was incomplete and unbound, don't proceed
        // because false positive errors will be generated.
        if (baseTypeResult.isIncomplete && isUnbound(baseType)) {
            return { type: UnknownType.create(/* isIncomplete */ true), isIncomplete: true };
        }

        if (baseType.props?.specialForm && (flags & EvalFlags.TypeExpression) === 0) {
            baseType = baseType.props.specialForm;
        }

        if (isParamSpec(baseType) && baseType.priv.paramSpecAccess) {
            baseType = makeTopLevelTypeVarsConcrete(baseType);
        }

        switch (baseType.category) {
            case TypeCategory.Any:
            case TypeCategory.Unknown:
            case TypeCategory.Never: {
                type = baseType;
                break;
            }

            case TypeCategory.Unbound: {
                break;
            }

            case TypeCategory.TypeVar: {
                if (isParamSpec(baseType)) {
                    // Handle special cases for "P.args" and "P.kwargs".
                    if (memberName === 'args' || memberName === 'kwargs') {
                        const isArgs = memberName === 'args';
                        const paramNode = ParseTreeUtils.getEnclosingParam(node);
                        const expectedCategory = isArgs ? ParamCategory.ArgsList : ParamCategory.KwargsDict;

                        if (!paramNode || paramNode.d.category !== expectedCategory) {
                            const errorMessage = isArgs
                                ? LocMessage.paramSpecArgsUsage()
                                : LocMessage.paramSpecKwargsUsage();
                            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, errorMessage, node);
                            type = UnknownType.create(isIncomplete);
                            break;
                        }

                        type = TypeVarType.cloneForParamSpecAccess(baseType, memberName);
                        break;
                    }

                    if (!isIncomplete) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.paramSpecUnknownMember().format({ name: memberName }),
                            node
                        );
                    }

                    type = UnknownType.create(isIncomplete);
                    break;
                }

                // It's illegal to reference a member from a type variable.
                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    if (!isIncomplete) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarNoMember().format({
                                type: printType(baseType),
                                name: memberName,
                            }),
                            node.d.leftExpr
                        );
                    }

                    type = UnknownType.create(isIncomplete);
                    break;
                }

                if (baseType.shared.recursiveAlias) {
                    type = UnknownType.create(/* isIncomplete */ true);
                    isIncomplete = true;
                    break;
                }

                if (isTypeVarTuple(baseType)) {
                    break;
                }

                return getTypeOfMemberAccessWithBaseType(
                    node,
                    {
                        type: makeTopLevelTypeVarsConcrete(baseType),
                        bindToSelfType: TypeBase.isInstantiable(baseType) ? convertToInstance(baseType) : baseType,
                        isIncomplete,
                    },
                    usage,
                    EvalFlags.None
                );
            }

            case TypeCategory.Class: {
                let typeResult: TypeResult | undefined;

                // If this is a class-like function created via NewType, treat
                // it like a function for purposes of member accesses.
                if (
                    ClassType.isNewTypeClass(baseType) &&
                    !baseType.priv.includeSubclasses &&
                    prefetched?.functionClass &&
                    isClass(prefetched.functionClass)
                ) {
                    baseType = ClassType.cloneAsInstance(prefetched.functionClass);
                }

                const enumMemberResult = getTypeOfEnumMember(
                    evaluatorInterface,
                    node,
                    baseType,
                    memberName,
                    isIncomplete
                );

                if (enumMemberResult) {
                    if (usage.method === 'get') {
                        typeResult = enumMemberResult;
                    } else {
                        // Is this an attempt to delete or overwrite an enum member?
                        if (
                            isClassInstance(enumMemberResult.type) &&
                            ClassType.isSameGenericClass(enumMemberResult.type, ClassType.cloneAsInstance(baseType)) &&
                            enumMemberResult.type.priv.literalValue !== undefined
                        ) {
                            const diagMessage =
                                usage.method === 'set' ? LocMessage.enumMemberSet() : LocMessage.enumMemberDelete();
                            addDiagnostic(
                                DiagnosticRule.reportAttributeAccessIssue,
                                diagMessage.format({ name: memberName }) + diag.getString(),
                                node.d.member,
                                diag.getEffectiveTextRange() ?? node.d.member
                            );
                        }
                    }
                }

                if (!typeResult) {
                    typeResult = getTypeOfBoundMember(
                        node.d.member,
                        baseType,
                        memberName,
                        usage,
                        diag,
                        (flags & EvalFlags.TypeExpression) === 0 ? undefined : MemberAccessFlags.TypeExpression,
                        baseTypeResult.bindToSelfType
                    );
                }

                if (typeResult) {
                    if (!typeResult.typeErrors) {
                        type = addConditionToType(typeResult.type, getTypeCondition(baseType), {
                            skipSelfCondition: true,
                            skipBoundTypeVars: true,
                        });
                    } else {
                        typeErrors = true;
                    }

                    if (typeResult.isAsymmetricAccessor) {
                        isAsymmetricAccessor = true;
                    }

                    if (typeResult.isIncomplete) {
                        isIncomplete = true;
                    }

                    if (typeResult.narrowedTypeForSet) {
                        narrowedTypeForSet = addConditionToType(
                            typeResult.narrowedTypeForSet,
                            getTypeCondition(baseType),
                            { skipSelfCondition: true, skipBoundTypeVars: true }
                        );
                    }

                    if (typeResult.memberAccessDeprecationInfo) {
                        memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo;
                    }
                }
                break;
            }

            case TypeCategory.Module: {
                let symbol = ModuleType.getField(baseType, memberName);

                // If the symbol isn't found in the module's symbol table,
                // see if it's defined in the `ModuleType` class. This is
                // needed for modules that are synthesized for namespace
                // packages.
                if (!symbol && prefetched?.moduleTypeClass && isInstantiableClass(prefetched.moduleTypeClass)) {
                    symbol = ClassType.getSymbolTable(prefetched.moduleTypeClass).get(memberName);
                }

                if (symbol && !symbol.isExternallyHidden()) {
                    if (usage.method === 'get') {
                        setSymbolAccessed(fileInfo, symbol, node.d.member);
                    }

                    const typeResult = getEffectiveTypeOfSymbolForUsage(
                        symbol,
                        /* usageNode */ undefined,
                        /* useLastDecl */ true
                    );
                    type = typeResult.type;

                    if ((flags & EvalFlags.TypeExpression) !== 0) {
                        type = validateSymbolIsTypeExpression(node, type, !!typeResult.includesVariableDecl);
                    }

                    // Add TypeForm details if appropriate.
                    type = addTypeFormForSymbol(node, type, flags, !!typeResult.includesVariableDecl);

                    if (isTypeVar(type)) {
                        type = validateTypeVarUsage(node, type, flags);
                    }

                    // If the type resolved to "unbound", treat it as "unknown" in
                    // the case of a module reference because if it's truly unbound,
                    // that error will be reported within the module and should not
                    // leak into other modules that import it.
                    if (isUnbound(type)) {
                        type = UnknownType.create(/* isIncomplete */ true);
                    }

                    if (symbol.isPrivateMember()) {
                        addDiagnostic(
                            DiagnosticRule.reportPrivateUsage,
                            LocMessage.privateUsedOutsideOfModule().format({
                                name: memberName,
                            }),
                            node.d.member
                        );
                    }

                    if (symbol.isPrivatePyTypedImport()) {
                        addDiagnostic(
                            DiagnosticRule.reportPrivateImportUsage,
                            LocMessage.privateImportFromPyTypedModule().format({
                                name: memberName,
                                module: baseType.priv.moduleName,
                            }),
                            node.d.member
                        );
                    }
                } else {
                    // Does the module export a top-level __getattr__ function?
                    if (usage.method === 'get') {
                        const getAttrSymbol = ModuleType.getField(baseType, '__getattr__');
                        if (getAttrSymbol) {
                            const isModuleGetAttrSupported =
                                PythonVersion.isGreaterOrEqualTo(
                                    fileInfo.executionEnvironment.pythonVersion,
                                    pythonVersion3_7
                                ) || getAttrSymbol.getDeclarations().some((decl) => decl.uri.hasExtension('.pyi'));

                            if (isModuleGetAttrSupported) {
                                const getAttrTypeResult = getEffectiveTypeOfSymbolForUsage(getAttrSymbol);
                                if (isFunction(getAttrTypeResult.type)) {
                                    const returnTypeResult = getEffectiveReturnTypeResult(getAttrTypeResult.type);
                                    type = returnTypeResult.type;
                                    if (getAttrTypeResult.isIncomplete || returnTypeResult.isIncomplete) {
                                        isIncomplete = true;
                                    }
                                }
                            }
                        }
                    }

                    // If the field was not found and the module type is marked
                    // such that all fields should be Any/Unknown, return that type.
                    if (!type && baseType.priv.notPresentFieldType) {
                        type = baseType.priv.notPresentFieldType;
                    }

                    if (!type) {
                        if (!isIncomplete) {
                            addDiagnostic(
                                DiagnosticRule.reportAttributeAccessIssue,
                                LocMessage.moduleUnknownMember().format({
                                    memberName,
                                    moduleName: baseType.priv.moduleName,
                                }),
                                node.d.member
                            );
                        }
                        type = evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create();
                    }
                }
                break;
            }

            case TypeCategory.Union: {
                type = mapSubtypes(baseType, (subtype) => {
                    if (isUnbound(subtype)) {
                        // Don't do anything if it's unbound. The error will already
                        // be reported elsewhere.
                        return undefined;
                    }

                    if (isNoneInstance(subtype)) {
                        assert(isClassInstance(subtype));
                        const typeResult = getTypeOfBoundMember(node.d.member, subtype, memberName, usage, diag);

                        if (typeResult && !typeResult.typeErrors) {
                            type = addConditionToType(typeResult.type, getTypeCondition(baseType), {
                                skipBoundTypeVars: true,
                            });
                            if (typeResult.isIncomplete) {
                                isIncomplete = true;
                            }

                            return type;
                        }

                        if (!isIncomplete) {
                            addDiagnostic(
                                DiagnosticRule.reportOptionalMemberAccess,
                                LocMessage.noneUnknownMember().format({ name: memberName }),
                                node.d.member
                            );
                        }

                        return undefined;
                    }

                    const typeResult = getTypeOfMemberAccessWithBaseType(
                        node,
                        {
                            type: subtype,
                            isIncomplete: baseTypeResult.isIncomplete,
                        },
                        usage,
                        EvalFlags.None
                    );

                    if (typeResult.isIncomplete) {
                        isIncomplete = true;
                    }

                    if (typeResult.memberAccessDeprecationInfo) {
                        memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo;
                    }

                    if (typeResult.typeErrors) {
                        typeErrors = true;
                    }

                    return typeResult.type;
                });
                break;
            }

            case TypeCategory.Function:
            case TypeCategory.Overloaded: {
                const hasSelf = isMethodType(baseType);

                if (memberName === '__self__' && hasSelf) {
                    // Handle "__self__" specially because MethodType defines
                    // it simply as "object". We can do better here.
                    let functionType: FunctionType | undefined;

                    if (isFunction(baseType)) {
                        functionType = baseType;
                    } else {
                        const overloads = OverloadedType.getOverloads(baseType);
                        if (overloads.length > 0) {
                            functionType = overloads[0];
                        }
                    }

                    type = functionType?.priv.boundToType;
                } else {
                    const altType = hasSelf ? prefetched?.methodClass : prefetched?.functionClass;
                    type = getTypeOfMemberAccessWithBaseType(
                        node,
                        { type: altType ? convertToInstance(altType) : UnknownType.create() },
                        usage,
                        flags
                    ).type;
                }
                break;
            }

            default:
                assertNever(baseType);
        }

        // If type is undefined, emit a general error message indicating that the
        // member could not be accessed.
        if (!type) {
            const isFunctionRule =
                isFunctionOrOverloaded(baseType) ||
                (isClassInstance(baseType) && ClassType.isBuiltIn(baseType, ['function', 'FunctionType']));

            if (!baseTypeResult.isIncomplete) {
                let diagMessage = LocMessage.memberAccess();
                if (usage.method === 'set') {
                    diagMessage = LocMessage.memberSet();
                } else if (usage.method === 'del') {
                    diagMessage = LocMessage.memberDelete();
                }

                // If there is an expected type diagnostic addendum (used for assignments),
                // use that rather than the local diagnostic addendum because it will be
                // more informative.
                if (usage.setExpectedTypeDiag && !usage.setExpectedTypeDiag.isEmpty()) {
                    diag = usage.setExpectedTypeDiag;
                }

                // If the class is a TypedDict, and there's a key with the same name,
                // suggest that they user want to use ["key"] name instead.
                if (isClass(baseType) && baseType.shared.typedDictEntries) {
                    const tdKey = baseType.shared.typedDictEntries.knownItems.get(memberName);
                    if (tdKey) {
                        const subDiag = new DiagnosticAddendum();
                        subDiag.addMessage(LocAddendum.typedDictKeyAccess().format({ name: memberName }));
                        diag.addAddendum(subDiag);
                    }
                }

                const rule = isFunctionRule
                    ? DiagnosticRule.reportFunctionMemberAccess
                    : DiagnosticRule.reportAttributeAccessIssue;

                addDiagnostic(
                    rule,
                    diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(),
                    node.d.member,
                    diag.getEffectiveTextRange() ?? node.d.member
                );
            }

            // If this is member access on a function, use "Any" so if the
            // reportFunctionMemberAccess rule is disabled, we don't trigger
            // additional reportUnknownMemberType diagnostics.
            type = isFunctionRule ? AnyType.create() : UnknownType.create();
        }

        if ((flags & EvalFlags.TypeExpression) === 0) {
            reportUseOfTypeCheckOnly(type, node.d.member);
        }

        type = convertSpecialFormToRuntimeValue(type, flags);

        return {
            type,
            isIncomplete,
            isAsymmetricAccessor,
            narrowedTypeForSet,
            isRequired,
            isNotRequired,
            memberAccessDeprecationInfo,
            typeErrors,
        };
    }

    function getTypeOfClassMemberName(
        errorNode: ExpressionNode | undefined,
        classType: ClassType,
        memberName: string,
        usage: EvaluatorUsage,
        diag: DiagnosticAddendum | undefined,
        flags: MemberAccessFlags,
        selfType?: ClassType | TypeVarType,
        recursionCount = 0
    ): ClassMemberLookup | undefined {
        const isAccessedThroughObject = TypeBase.isInstance(classType);

        // Always look for a member with a declared type first.
        let memberInfo = lookUpClassMember(classType, memberName, flags | MemberAccessFlags.DeclaredTypesOnly);

        // If we couldn't find a symbol with a declared type, use
        // a symbol with an inferred type.
        if (!memberInfo) {
            memberInfo = lookUpClassMember(classType, memberName, flags);
        }

        if (!memberInfo) {
            // No attribute of that name was found. If this is a member access
            // through an object, see if there's an attribute access override
            // method ("__getattr__", etc.).
            if ((flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0 && errorNode) {
                const generalAttrType = applyAttributeAccessOverride(errorNode, classType, usage, memberName, selfType);
                if (generalAttrType) {
                    return {
                        symbol: undefined,
                        type: generalAttrType.type,
                        isTypeIncomplete: false,
                        isDescriptorError: false,
                        isClassMember: false,
                        isClassVar: false,
                        isAsymmetricAccessor: !!generalAttrType.isAsymmetricAccessor,
                    };
                }
            }

            // Report that the member could not be accessed.
            diag?.addMessage(LocAddendum.memberUnknown().format({ name: memberName }));
            return undefined;
        }

        let type: Type | undefined;
        let isTypeIncomplete = false;
        let narrowedTypeForSet: Type | undefined;

        if (memberInfo.symbol.isInitVar()) {
            diag?.addMessage(LocAddendum.memberIsInitVar().format({ name: memberName }));
            return undefined;
        }

        if (usage.method !== 'get' && errorNode) {
            // If the usage indicates a 'set' or 'delete' and the access is within the
            // class definition itself, use only the declared type to avoid circular
            // type evaluation.
            const containingClass = ParseTreeUtils.getEnclosingClass(errorNode);
            if (containingClass) {
                const containingClassType = getTypeOfClass(containingClass)?.classType;
                if (
                    containingClassType &&
                    isInstantiableClass(containingClassType) &&
                    ClassType.isSameGenericClass(
                        isAccessedThroughObject ? ClassType.cloneAsInstance(containingClassType) : containingClassType,
                        classType
                    )
                ) {
                    type = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type;
                    if (type && isInstantiableClass(memberInfo.classType)) {
                        type = partiallySpecializeType(
                            type,
                            memberInfo.classType,
                            /* typeClassType */ undefined,
                            selfType
                        );
                    }

                    // If we're setting a class variable via a write through an object,
                    // this is normally considered a type violation. But it is allowed
                    // if the class variable is a descriptor object. In this case, we will
                    // clear the flag that causes an error to be generated.
                    if (
                        usage.method === 'set' &&
                        isEffectivelyClassVar(memberInfo.symbol, ClassType.isDataClass(containingClassType)) &&
                        isAccessedThroughObject
                    ) {
                        const selfClass = selfType ?? memberName === '__new__' ? undefined : classType;
                        const typeResult = getTypeOfMemberInternal(errorNode, memberInfo, selfClass, flags);

                        if (typeResult) {
                            if (isDescriptorInstance(typeResult.type, /* requireSetter */ true)) {
                                type = typeResult.type;
                                flags &= MemberAccessFlags.DisallowClassVarWrites;
                            }
                        }
                    }

                    if (!type) {
                        type = UnknownType.create();
                    }
                }
            }
        }

        if (!type) {
            let selfClass: ClassType | TypeVarType | undefined;

            if (selfType) {
                selfClass = convertToInstantiable(selfType) as TypeVarType | ClassType;
            } else {
                // Skip this for __new__ methods because they are not bound
                // to the class but rather assume the type of the cls argument.
                if (memberName !== '__new__') {
                    selfClass = classType;
                }
            }

            const typeResult = getTypeOfMemberInternal(errorNode, memberInfo, selfClass, flags);

            type = typeResult?.type ?? UnknownType.create();
            if (typeResult?.isIncomplete) {
                isTypeIncomplete = true;
            }
        }

        // Don't include variables within typed dict classes.
        if (isClass(memberInfo.classType) && ClassType.isTypedDictClass(memberInfo.classType)) {
            const typedDecls = memberInfo.symbol.getTypedDeclarations();
            if (typedDecls.length > 0 && typedDecls[0].type === DeclarationType.Variable) {
                diag?.addMessage(LocAddendum.memberUnknown().format({ name: memberName }));
                return undefined;
            }
        }

        if (usage.method === 'get') {
            // Mark the member accessed if it's not coming from a parent class.
            if (
                errorNode &&
                isInstantiableClass(memberInfo.classType) &&
                ClassType.isSameGenericClass(
                    memberInfo.classType,
                    isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType
                )
            ) {
                setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(errorNode), memberInfo.symbol, errorNode);
            }

            // Special-case `__init_subclass` and `__class_getitem__` because
            // these are always treated as class methods even if they're not
            // decorated as such.
            if (memberName === '__init_subclass__' || memberName === '__class_getitem__') {
                if (isFunction(type) && !FunctionType.isClassMethod(type)) {
                    type = FunctionType.cloneWithNewFlags(type, type.shared.flags | FunctionTypeFlags.ClassMethod);
                }
            }
        }

        // If the member is a descriptor object, apply the descriptor protocol
        // now. If the member is an instance or class method, bind the method.
        let isDescriptorError = false;
        let isAsymmetricAccessor = false;
        let isDescriptorApplied = false;
        let memberAccessDeprecationInfo: MemberAccessDeprecationInfo | undefined;

        type = mapSubtypes(
            type,
            (subtype) => {
                const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);
                const isClassMember = !memberInfo || (memberInfo.isClassMember && !memberInfo.isSlotsMember);
                let resultType: Type;

                if (isClass(concreteSubtype) && isClassMember && errorNode) {
                    const descResult = applyDescriptorAccessMethod(
                        subtype,
                        concreteSubtype,
                        memberInfo,
                        classType,
                        selfType,
                        flags,
                        errorNode,
                        memberName,
                        usage,
                        diag
                    );

                    if (descResult.isAsymmetricAccessor) {
                        isAsymmetricAccessor = true;
                    }

                    if (descResult.memberAccessDeprecationInfo) {
                        memberAccessDeprecationInfo = descResult.memberAccessDeprecationInfo;
                    }

                    if (descResult.typeErrors) {
                        isDescriptorError = true;
                    }

                    if (descResult.isDescriptorApplied) {
                        isDescriptorApplied = true;
                    }

                    resultType = descResult.type;
                } else if (isFunctionOrOverloaded(concreteSubtype) && TypeBase.isInstance(concreteSubtype)) {
                    const typeResult = bindMethodForMemberAccess(
                        subtype,
                        concreteSubtype,
                        memberInfo,
                        classType,
                        selfType,
                        flags,
                        memberName,
                        usage,
                        diag,
                        recursionCount
                    );

                    resultType = typeResult.type;
                    if (typeResult.typeErrors) {
                        isDescriptorError = true;
                    }
                } else {
                    resultType = subtype;
                }

                // If this is a "set" or "delete" operation, we have a bit more work to do.
                if (usage.method === 'get') {
                    return resultType;
                }

                // Check for an attempt to overwrite or delete a ClassVar member from an instance.
                if (
                    !isDescriptorApplied &&
                    memberInfo &&
                    isEffectivelyClassVar(memberInfo.symbol, ClassType.isDataClass(classType)) &&
                    (flags & MemberAccessFlags.DisallowClassVarWrites) !== 0
                ) {
                    diag?.addMessage(LocAddendum.memberSetClassVar().format({ name: memberName }));
                    isDescriptorError = true;
                }

                // Check for an attempt to overwrite or delete a final member variable.
                const finalVarTypeDecl = memberInfo?.symbol
                    .getDeclarations()
                    .find((decl) => isFinalVariableDeclaration(decl));

                if (
                    finalVarTypeDecl &&
                    errorNode &&
                    !ParseTreeUtils.isNodeContainedWithin(errorNode, finalVarTypeDecl.node)
                ) {
                    // If a Final instance variable is declared in the class body but is
                    // being assigned within an __init__ method, it's allowed.
                    const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(errorNode);
                    if (
                        !enclosingFunctionNode ||
                        enclosingFunctionNode.d.name.d.value !== '__init__' ||
                        (finalVarTypeDecl as VariableDeclaration).inferredTypeSource !== undefined ||
                        isInstantiableClass(classType)
                    ) {
                        diag?.addMessage(LocMessage.finalReassigned().format({ name: memberName }));
                        isDescriptorError = true;
                    }
                }

                // Check for an attempt to overwrite or delete an instance variable that is
                // read-only (e.g. in a named tuple).
                if (memberInfo?.isInstanceMember && isClass(memberInfo.classType) && memberInfo.isReadOnly) {
                    diag?.addMessage(LocAddendum.readOnlyAttribute().format({ name: memberName }));
                    isDescriptorError = true;
                }

                return resultType;
            },
            { retainTypeAlias: true }
        );

        if (!isDescriptorError && usage.method === 'set' && usage.setType) {
            if (errorNode && memberInfo.symbol.hasTypedDeclarations()) {
                // This is an assignment to a member with a declared type. Apply
                // narrowing logic based on the assigned type. Skip this for
                // descriptor-based accesses.
                narrowedTypeForSet = isDescriptorApplied
                    ? usage.setType.type
                    : narrowTypeBasedOnAssignment(type, usage.setType).type;
            }

            // Verify that the assigned type is compatible.
            if (!assignType(type, usage.setType.type, diag?.createAddendum())) {
                if (!usage.setType.isIncomplete) {
                    diag?.addMessage(
                        LocAddendum.memberAssignment().format({
                            type: printType(usage.setType.type),
                            name: memberName,
                            classType: printObjectTypeForClass(classType),
                        })
                    );
                }

                // Do not narrow the type in this case. Assume the declared type.
                narrowedTypeForSet = type;
                isDescriptorError = true;
            }

            if (
                isInstantiableClass(memberInfo.classType) &&
                ClassType.isDataClassFrozen(memberInfo.classType) &&
                isAccessedThroughObject
            ) {
                diag?.addMessage(
                    LocAddendum.dataClassFrozen().format({
                        name: printType(ClassType.cloneAsInstance(memberInfo.classType)),
                    })
                );

                isDescriptorError = true;
            }
        }

        return {
            symbol: memberInfo.symbol,
            type,
            isTypeIncomplete,
            isDescriptorError,
            isClassMember: !memberInfo.isInstanceMember,
            isClassVar: memberInfo.isClassVar,
            classType: memberInfo.classType,
            isAsymmetricAccessor,
            narrowedTypeForSet,
            memberAccessDeprecationInfo,
        };
    }

    // Applies descriptor access methods "__get__", "__set__", or "__delete__"
    // if they apply.
    function applyDescriptorAccessMethod(
        memberType: Type,
        concreteMemberType: ClassType,
        memberInfo: ClassMember | undefined,
        classType: ClassType,
        selfType: ClassType | TypeVarType | undefined,
        flags: MemberAccessFlags,
        errorNode: ExpressionNode,
        memberName: string,
        usage: EvaluatorUsage,
        diag: DiagnosticAddendum | undefined
    ): MemberAccessTypeResult {
        const isAccessedThroughObject = TypeBase.isInstance(classType);

        let accessMethodName: string;
        if (usage.method === 'get') {
            accessMethodName = '__get__';
        } else if (usage.method === 'set') {
            accessMethodName = '__set__';
        } else {
            accessMethodName = '__delete__';
        }

        const subDiag = diag ? new DiagnosticAddendum() : undefined;

        const methodTypeResult = getTypeOfBoundMember(
            errorNode,
            concreteMemberType,
            accessMethodName,
            /* usage */ undefined,
            subDiag,
            MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride
        );

        if (!methodTypeResult || methodTypeResult.typeErrors) {
            // Provide special error messages for properties.
            if (ClassType.isPropertyClass(concreteMemberType) && usage.method !== 'get') {
                const message =
                    usage.method === 'set' ? LocAddendum.propertyMissingSetter() : LocAddendum.propertyMissingDeleter();
                diag?.addMessage(message.format({ name: memberName }));
                return { type: AnyType.create(), typeErrors: true };
            }

            if (classType.shared.typeVarScopeId) {
                memberType = makeTypeVarsBound(memberType, [classType.shared.typeVarScopeId]);
            }

            return { type: memberType };
        }

        const methodClassType = methodTypeResult.classType;
        let methodType = methodTypeResult.type;

        if (methodTypeResult.typeErrors || !methodClassType) {
            if (diag && subDiag) {
                diag.addAddendum(subDiag);
            }
            return { type: UnknownType.create(), typeErrors: true };
        }

        if (!isFunctionOrOverloaded(methodType)) {
            if (isAnyOrUnknown(methodType)) {
                return { type: methodType };
            }

            // TODO - emit an error for this condition.
            return { type: memberType, typeErrors: true };
        }

        // Special-case logic for properties.
        if (
            ClassType.isPropertyClass(concreteMemberType) &&
            memberInfo &&
            isInstantiableClass(memberInfo.classType) &&
            methodType
        ) {
            // If the property is being accessed from a protocol class (not an instance),
            // flag this as an error because a property within a protocol is meant to be
            // interpreted as a read-only attribute rather than a protocol, so accessing
            // it directly from the class has an ambiguous meaning.
            if ((flags & MemberAccessFlags.SkipInstanceMembers) !== 0 && ClassType.isProtocolClass(classType)) {
                diag?.addMessage(LocAddendum.propertyAccessFromProtocolClass());
                return { type: memberType, typeErrors: true };
            }

            // Infer return types before specializing. Otherwise a generic inferred
            // return type won't be properly specialized.
            inferReturnTypeIfNecessary(methodType);

            // This specialization is required specifically for properties, which should be
            // generic but are not defined that way. Because of this, we use type variables
            // in the synthesized methods (e.g. __get__) for the property class that are
            // defined in the class that declares the fget method.
            let accessMethodClass: ClassType | undefined;
            if (usage.method === 'get') {
                accessMethodClass = concreteMemberType.priv.fgetInfo?.classType;
            } else if (usage.method === 'set') {
                accessMethodClass = concreteMemberType.priv.fsetInfo?.classType;
            } else {
                accessMethodClass = concreteMemberType.priv.fdelInfo?.classType;
            }

            if (accessMethodClass) {
                const constraints = new ConstraintTracker();
                accessMethodClass = selfSpecializeClass(accessMethodClass);
                assignType(
                    ClassType.cloneAsInstance(accessMethodClass),
                    ClassType.cloneAsInstance(memberInfo.classType),
                    /* diag */ undefined,
                    constraints
                );
                accessMethodClass = solveAndApplyConstraints(accessMethodClass, constraints) as ClassType;

                const specializedType = partiallySpecializeType(
                    methodType,
                    accessMethodClass,
                    getTypeClassType(),
                    selfType ? (convertToInstantiable(selfType) as ClassType | TypeVarType) : classType
                );

                if (isFunctionOrOverloaded(specializedType)) {
                    methodType = specializedType;
                }
            }
        }

        // Determine if we're calling __set__ on an asymmetric descriptor or property.
        let isAsymmetricAccessor = false;
        if (usage.method === 'set' && isClass(methodClassType)) {
            if (isAsymmetricDescriptorClass(methodClassType)) {
                isAsymmetricAccessor = true;
            }
        }

        if (!methodType) {
            diag?.addMessage(
                LocAddendum.descriptorAccessBindingFailed().format({
                    name: accessMethodName,
                    className: printType(convertToInstance(methodClassType)),
                })
            );

            return {
                type: UnknownType.create(),
                typeErrors: true,
                isDescriptorApplied: true,
                isAsymmetricAccessor,
            };
        }

        // Simulate a call to the access method.
        const argList: Arg[] = [];

        // Provide "obj" argument.
        let objArgType: Type;
        if (ClassType.isClassProperty(concreteMemberType)) {
            // Handle "class properties" as a special case. We need to pass
            // the class rather than the object instance in this case.
            objArgType = isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType;
        } else if (isAccessedThroughObject) {
            objArgType = selfType ?? ClassType.cloneAsInstance(classType);
        } else {
            objArgType = getNoneType();
        }

        argList.push({
            argCategory: ArgCategory.Simple,
            typeResult: { type: objArgType },
        });

        if (usage.method === 'get') {
            let classArgType: Type;
            if (selfType) {
                classArgType = convertToInstantiable(selfType);
            } else {
                classArgType = isAccessedThroughObject ? ClassType.cloneAsInstantiable(classType) : classType;
            }

            // Provide "owner" argument.
            argList.push({
                argCategory: ArgCategory.Simple,
                typeResult: { type: classArgType },
            });
        } else if (usage.method === 'set') {
            // Provide "value" argument.
            argList.push({
                argCategory: ArgCategory.Simple,
                typeResult: {
                    type: usage.setType?.type ?? UnknownType.create(),
                    isIncomplete: !!usage.setType?.isIncomplete,
                },
            });
        }

        // Suppress diagnostics for these method calls because they would be redundant.
        const callResult = suppressDiagnostics(
            errorNode,
            () => {
                return validateCallArgs(
                    errorNode,
                    argList,
                    { type: methodType },
                    /* constraints */ undefined,
                    /* skipUnknownArgCheck */ true,
                    /* inferenceContext */ undefined
                );
            },
            (suppressedDiags) => {
                // If diagnostics were recorded when suppressed, add them to the
                // diagnostic as messages.
                if (diag) {
                    suppressedDiags.forEach((message) => {
                        diag?.addMessageMultiline(message);
                    });
                }
            }
        );

        // Collect deprecation information associated with the member access method.
        let deprecationInfo: MemberAccessDeprecationInfo | undefined;
        if (callResult.overloadsUsedForCall && callResult.overloadsUsedForCall.length >= 1) {
            const overloadUsed = callResult.overloadsUsedForCall[0];
            if (overloadUsed.shared.deprecatedMessage) {
                deprecationInfo = {
                    deprecatedMessage: overloadUsed.shared.deprecatedMessage,
                    accessType: ClassType.isPropertyClass(concreteMemberType) ? 'property' : 'descriptor',
                    accessMethod: usage.method,
                };
            }
        }

        if (!callResult.argumentErrors) {
            return {
                // For set or delete, always return Any.
                type: usage.method === 'get' ? callResult.returnType ?? UnknownType.create() : AnyType.create(),
                isDescriptorApplied: true,
                isAsymmetricAccessor,
                memberAccessDeprecationInfo: deprecationInfo,
            };
        }

        return {
            type: UnknownType.create(),
            typeErrors: true,
            isDescriptorApplied: true,
            isAsymmetricAccessor,
            memberAccessDeprecationInfo: deprecationInfo,
        };
    }

    function bindMethodForMemberAccess(
        type: Type,
        concreteType: FunctionType | OverloadedType,
        memberInfo: ClassMember | undefined,
        classType: ClassType,
        selfType: ClassType | TypeVarType | undefined,
        flags: MemberAccessFlags,
        memberName: string,
        usage: EvaluatorUsage,
        diag: DiagnosticAddendum | undefined,
        recursionCount = 0
    ): TypeResult {
        // Check for an attempt to overwrite a final method.
        if (usage.method === 'set') {
            const impl = isFunction(concreteType) ? concreteType : OverloadedType.getImplementation(concreteType);

            if (impl && isFunction(impl) && FunctionType.isFinal(impl) && memberInfo && isClass(memberInfo.classType)) {
                diag?.addMessage(
                    LocMessage.finalMethodOverride().format({
                        name: memberName,
                        className: memberInfo.classType.shared.name,
                    })
                );

                return { type: UnknownType.create(), typeErrors: true };
            }
        }

        // If this function is an instance member (e.g. a lambda that was
        // assigned to an instance variable), don't perform any binding.
        if (TypeBase.isInstance(classType)) {
            if (!memberInfo || memberInfo.isInstanceMember) {
                return { type: type };
            }
        }

        const boundType = bindFunctionToClassOrObject(
            classType,
            concreteType,
            memberInfo && isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined,
            (flags & MemberAccessFlags.TreatConstructorAsClassMethod) !== 0,
            selfType && isClass(selfType) ? ClassType.cloneIncludeSubclasses(selfType) : selfType,
            diag,
            recursionCount
        );

        return { type: boundType ?? UnknownType.create(), typeErrors: !boundType };
    }

    function isAsymmetricDescriptorClass(classType: ClassType): boolean {
        // If the value has already been cached in this type, return the cached value.
        if (classType.priv.isAsymmetricDescriptor !== undefined) {
            return classType.priv.isAsymmetricDescriptor;
        }

        let isAsymmetric = false;

        const getterSymbolResult = lookUpClassMember(classType, '__get__', MemberAccessFlags.SkipBaseClasses);
        const setterSymbolResult = lookUpClassMember(classType, '__set__', MemberAccessFlags.SkipBaseClasses);

        if (!getterSymbolResult || !setterSymbolResult) {
            isAsymmetric = false;
        } else {
            let getterType = getTypeOfMember(getterSymbolResult);
            const setterType = getTypeOfMember(setterSymbolResult);

            // If this is an overload, find the appropriate overload.
            if (isOverloaded(getterType)) {
                const getOverloads = OverloadedType.getOverloads(getterType).filter((overload) => {
                    if (overload.shared.parameters.length < 2) {
                        return false;
                    }
                    const param1Type = FunctionType.getParamType(overload, 1);
                    return !isNoneInstance(param1Type);
                });

                if (getOverloads.length === 1) {
                    getterType = getOverloads[0];
                } else {
                    isAsymmetric = true;
                }
            }

            // If this is an overload, find the appropriate overload.
            if (isOverloaded(setterType)) {
                isAsymmetric = true;
            }

            // If either the setter or getter is an overload (or some other non-function type),
            // conservatively assume that it's not asymmetric.
            if (isFunction(getterType) && isFunction(setterType)) {
                // If there's no declared return type on the getter, assume it's symmetric.
                if (setterType.shared.parameters.length >= 3 && getterType.shared.declaredReturnType) {
                    const setterValueType = FunctionType.getParamType(setterType, 2);
                    const getterReturnType = FunctionType.getEffectiveReturnType(getterType) ?? UnknownType.create();

                    if (!isTypeSame(setterValueType, getterReturnType)) {
                        isAsymmetric = true;
                    }
                }
            }
        }

        // Cache the value for next time.
        classType.priv.isAsymmetricDescriptor = isAsymmetric;
        return isAsymmetric;
    }

    function isClassWithAsymmetricAttributeAccessor(classType: ClassType): boolean {
        // If the value has already been cached in this type, return the cached value.
        if (classType.priv.isAsymmetricAttributeAccessor !== undefined) {
            return classType.priv.isAsymmetricAttributeAccessor;
        }

        let isAsymmetric = false;

        const getterSymbolResult = lookUpClassMember(classType, '__getattr__', MemberAccessFlags.SkipBaseClasses);
        const setterSymbolResult = lookUpClassMember(classType, '__setattr__', MemberAccessFlags.SkipBaseClasses);

        if (!getterSymbolResult || !setterSymbolResult) {
            isAsymmetric = false;
        } else {
            const getterType = getEffectiveTypeOfSymbol(getterSymbolResult.symbol);
            const setterType = getEffectiveTypeOfSymbol(setterSymbolResult.symbol);

            // If either the setter or getter is an overload (or some other non-function type),
            // conservatively assume that it's not asymmetric.
            if (isFunction(getterType) && isFunction(setterType)) {
                // If there's no declared return type on the getter, assume it's symmetric.
                if (setterType.shared.parameters.length >= 3 && getterType.shared.declaredReturnType) {
                    const setterValueType = FunctionType.getParamType(setterType, 2);
                    const getterReturnType = FunctionType.getEffectiveReturnType(getterType) ?? UnknownType.create();

                    if (!isTypeSame(setterValueType, getterReturnType)) {
                        isAsymmetric = true;
                    }
                }
            }
        }

        // Cache the value for next time.
        classType.priv.isAsymmetricAttributeAccessor = isAsymmetric;
        return isAsymmetric;
    }

    // Applies the __getattr__, __setattr__ or __delattr__ method if present.
    // If it's not applicable, returns undefined.
    function applyAttributeAccessOverride(
        errorNode: ExpressionNode,
        classType: ClassType,
        usage: EvaluatorUsage,
        memberName: string,
        selfType?: ClassType | TypeVarType
    ): MemberAccessTypeResult | undefined {
        const getAttributeAccessMember = (name: string) => {
            return getTypeOfBoundMember(
                errorNode,
                classType,
                name,
                /* usage */ undefined,
                /* diag */ undefined,
                MemberAccessFlags.SkipInstanceMembers |
                    MemberAccessFlags.SkipObjectBaseClass |
                    MemberAccessFlags.SkipTypeBaseClass |
                    MemberAccessFlags.SkipAttributeAccessOverride,
                selfType
            )?.type;
        };

        let accessMemberType: Type | undefined;
        if (usage.method === 'get') {
            accessMemberType = getAttributeAccessMember('__getattribute__') ?? getAttributeAccessMember('__getattr__');
        } else if (usage.method === 'set') {
            accessMemberType = getAttributeAccessMember('__setattr__');
        } else {
            assert(usage.method === 'del');
            accessMemberType = getAttributeAccessMember('__delattr__');
        }

        if (!accessMemberType) {
            return undefined;
        }

        const argList: Arg[] = [];

        // Provide "name" argument.
        argList.push({
            argCategory: ArgCategory.Simple,
            typeResult: {
                type:
                    prefetched?.strClass && isInstantiableClass(prefetched.strClass)
                        ? ClassType.cloneWithLiteral(ClassType.cloneAsInstance(prefetched.strClass), memberName)
                        : AnyType.create(),
            },
        });

        if (usage.method === 'set') {
            // Provide "value" argument.
            argList.push({
                argCategory: ArgCategory.Simple,
                typeResult: {
                    type: usage.setType?.type ?? UnknownType.create(),
                    isIncomplete: !!usage.setType?.isIncomplete,
                },
            });
        }

        if (!isFunctionOrOverloaded(accessMemberType)) {
            if (isAnyOrUnknown(accessMemberType)) {
                return { type: accessMemberType };
            }

            // TODO - emit an error for this condition.
            return undefined;
        }

        const callResult = validateCallArgs(
            errorNode,
            argList,
            { type: accessMemberType },
            /* constraints */ undefined,
            /* skipUnknownArgCheck */ true,
            /* inferenceContext */ undefined
        );

        let isAsymmetricAccessor = false;
        if (usage.method === 'set') {
            isAsymmetricAccessor = isClassWithAsymmetricAttributeAccessor(classType);
        }

        return {
            type: callResult.returnType ?? UnknownType.create(),
            typeErrors: callResult.argumentErrors,
            isAsymmetricAccessor,
        };
    }

    function getTypeOfIndex(node: IndexNode, flags = EvalFlags.None): TypeResult {
        const baseTypeResult = getTypeOfExpression(node.d.leftExpr, flags | EvalFlags.IndexBaseDefaults);

        // If this is meant to be a type and the base expression is a string expression,
        // emit an error because this is an illegal annotation form and will generate a
        // runtime exception.
        if (flags & EvalFlags.InstantiableType) {
            if (node.d.leftExpr.nodeType === ParseNodeType.StringList) {
                addDiagnostic(DiagnosticRule.reportIndexIssue, LocMessage.stringNotSubscriptable(), node.d.leftExpr);
            }
        }

        // Check for builtin classes that will generate runtime exceptions if subscripted.
        if ((flags & EvalFlags.ForwardRefs) === 0) {
            // We can skip this check if the class is used within a PEP 526 variable
            // type annotation within a class or function. For some undocumented reason,
            // they don't result in runtime exceptions when used in this manner.
            let skipSubscriptCheck = (flags & EvalFlags.VarTypeAnnotation) !== 0;
            if (skipSubscriptCheck) {
                const scopeNode = ParseTreeUtils.getExecutionScopeNode(node);
                if (scopeNode?.nodeType === ParseNodeType.Module) {
                    skipSubscriptCheck = false;
                }
            }

            if (!skipSubscriptCheck) {
                const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
                if (
                    isInstantiableClass(baseTypeResult.type) &&
                    ClassType.isBuiltIn(baseTypeResult.type) &&
                    !baseTypeResult.type.priv.aliasName
                ) {
                    const minPythonVersion = nonSubscriptableBuiltinTypes.get(baseTypeResult.type.shared.fullName);
                    if (
                        minPythonVersion !== undefined &&
                        PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, minPythonVersion) &&
                        !fileInfo.isStubFile
                    ) {
                        addDiagnostic(
                            DiagnosticRule.reportIndexIssue,
                            LocMessage.classNotRuntimeSubscriptable().format({
                                name: baseTypeResult.type.priv.aliasName || baseTypeResult.type.shared.name,
                            }),
                            node.d.leftExpr
                        );
                    }
                }
            }
        }

        const indexTypeResult = getTypeOfIndexWithBaseType(node, baseTypeResult, { method: 'get' }, flags);

        if (isCodeFlowSupportedForReference(node)) {
            // We limit type narrowing for index expressions to built-in types that are
            // known to have symmetric __getitem__ and __setitem__ methods (i.e. the value
            // passed to __setitem__ is the same type as the value returned by __getitem__).
            let baseTypeSupportsIndexNarrowing = !isAny(baseTypeResult.type);
            mapSubtypesExpandTypeVars(baseTypeResult.type, /* options */ undefined, (subtype) => {
                if (
                    !isClassInstance(subtype) ||
                    !(ClassType.isBuiltIn(subtype) || ClassType.isTypedDictClass(subtype))
                ) {
                    baseTypeSupportsIndexNarrowing = false;
                }

                return undefined;
            });

            if (baseTypeSupportsIndexNarrowing) {
                // Before performing code flow analysis, update the cache to prevent recursion.
                writeTypeCache(node, { ...indexTypeResult, isIncomplete: true }, flags);

                // See if we can refine the type based on code flow analysis.
                const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, {
                    targetSymbolId: indeterminateSymbolId,
                    typeAtStart: {
                        type: indexTypeResult.type,
                        isIncomplete: !!baseTypeResult.isIncomplete || !!indexTypeResult.isIncomplete,
                    },
                    skipConditionalNarrowing: (flags & EvalFlags.TypeExpression) !== 0,
                });

                if (codeFlowTypeResult.type) {
                    indexTypeResult.type = codeFlowTypeResult.type;
                }

                if (codeFlowTypeResult.isIncomplete) {
                    indexTypeResult.isIncomplete = true;
                }
            }
        }

        if (baseTypeResult.isIncomplete) {
            indexTypeResult.isIncomplete = true;
        }

        return indexTypeResult;
    }

    // If the list of type parameters includes a TypeVarTuple, we may need to adjust
    // the supplied type arguments to map to the type parameter list.
    function adjustTypeArgsForTypeVarTuple(
        typeArgs: TypeResultWithNode[],
        typeParams: TypeVarType[],
        errorNode: ExpressionNode
    ): TypeResultWithNode[] {
        const variadicIndex = typeParams.findIndex((param) => isTypeVarTuple(param));

        // Is there a *tuple[T, ...] somewhere in the type arguments that we can expand if needed?
        let srcUnboundedTupleType: Type | undefined;
        const findUnboundedTupleIndex = (startArgIndex: number) => {
            return typeArgs.findIndex((arg, index) => {
                if (index < startArgIndex) {
                    return false;
                }
                if (
                    isUnpackedClass(arg.type) &&
                    arg.type.priv.tupleTypeArgs &&
                    arg.type.priv.tupleTypeArgs.length === 1 &&
                    arg.type.priv.tupleTypeArgs[0].isUnbounded
                ) {
                    srcUnboundedTupleType = arg.type.priv.tupleTypeArgs[0].type;
                    return true;
                }

                return false;
            });
        };
        let srcUnboundedTupleIndex = findUnboundedTupleIndex(0);

        // Allow only one unpacked tuple that maps to a TypeVarTuple.
        if (srcUnboundedTupleIndex >= 0) {
            const secondUnboundedTupleIndex = findUnboundedTupleIndex(srcUnboundedTupleIndex + 1);
            if (secondUnboundedTupleIndex >= 0) {
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeForm,
                    LocMessage.variadicTypeArgsTooMany(),
                    typeArgs[secondUnboundedTupleIndex].node
                );
            }
        }

        if (
            srcUnboundedTupleType &&
            srcUnboundedTupleIndex >= 0 &&
            variadicIndex >= 0 &&
            typeArgs.length < typeParams.length
        ) {
            // "Smear" the tuple type across type argument slots prior to the TypeVarTuple.
            while (variadicIndex > srcUnboundedTupleIndex) {
                typeArgs = [
                    ...typeArgs.slice(0, srcUnboundedTupleIndex),
                    { node: typeArgs[srcUnboundedTupleIndex].node, type: srcUnboundedTupleType },
                    ...typeArgs.slice(srcUnboundedTupleIndex),
                ];
                srcUnboundedTupleIndex++;
            }

            // "Smear" the tuple type across type argument slots following the TypeVarTuple.
            while (typeArgs.length < typeParams.length) {
                typeArgs = [
                    ...typeArgs.slice(0, srcUnboundedTupleIndex + 1),
                    { node: typeArgs[srcUnboundedTupleIndex].node, type: srcUnboundedTupleType },
                    ...typeArgs.slice(srcUnboundedTupleIndex + 1),
                ];
            }
        }

        // Do we need to adjust the type arguments to map to a variadic type
        // param somewhere in the list?
        if (variadicIndex >= 0) {
            const variadicTypeVar = typeParams[variadicIndex];

            // If the type param list ends with a ParamSpec with a default value,
            // we can ignore it for purposes of finding type args that map to the
            // TypeVarTuple.
            let typeParamCount = typeParams.length;
            while (typeParamCount > 0) {
                const lastTypeParam = typeParams[typeParamCount - 1];
                if (!isParamSpec(lastTypeParam) || !lastTypeParam.shared.isDefaultExplicit) {
                    break;
                }

                typeParamCount--;
            }

            if (variadicIndex < typeArgs.length) {
                // If there are typeArg lists at the end, these should map to ParamSpecs rather
                // than the TypeVarTuple, so exclude them.
                let variadicEndIndex = variadicIndex + 1 + typeArgs.length - typeParamCount;
                while (variadicEndIndex > variadicIndex) {
                    if (!typeArgs[variadicEndIndex - 1].typeList) {
                        break;
                    }
                    variadicEndIndex--;
                }
                const variadicTypeResults = typeArgs.slice(variadicIndex, variadicEndIndex);

                // If the type args consist of a lone TypeVarTuple, don't wrap it in a tuple.
                if (variadicTypeResults.length === 1 && isTypeVarTuple(variadicTypeResults[0].type)) {
                    validateTypeVarTupleIsUnpacked(variadicTypeResults[0].type, variadicTypeResults[0].node);
                } else {
                    variadicTypeResults.forEach((arg, index) => {
                        validateTypeArg(arg, {
                            allowEmptyTuple: index === 0,
                            allowTypeVarTuple: true,
                            allowUnpackedTuples: true,
                        });
                    });

                    const variadicTypes: TupleTypeArg[] = [];
                    if (variadicTypeResults.length !== 1 || !variadicTypeResults[0].isEmptyTupleShorthand) {
                        variadicTypeResults.forEach((typeResult) => {
                            if (isUnpackedClass(typeResult.type) && typeResult.type.priv.tupleTypeArgs) {
                                appendArray(variadicTypes, typeResult.type.priv.tupleTypeArgs);
                            } else {
                                variadicTypes.push({
                                    type: convertToInstance(typeResult.type),
                                    isUnbounded: false,
                                });
                            }
                        });
                    }

                    const tupleObject = makeTupleObject(evaluatorInterface, variadicTypes, /* isUnpacked */ true);

                    typeArgs = [
                        ...typeArgs.slice(0, variadicIndex),
                        { node: typeArgs[variadicIndex].node, type: tupleObject },
                        ...typeArgs.slice(variadicEndIndex, typeArgs.length),
                    ];
                }
            } else if (!variadicTypeVar.shared.isDefaultExplicit) {
                // Add an empty tuple that maps to the TypeVarTuple type parameter.
                typeArgs.push({
                    node: errorNode,
                    type: makeTupleObject(evaluatorInterface, [], /* isUnpacked */ true),
                });
            }
        }

        return typeArgs;
    }

    // If the variadic type variable is not unpacked, report an error.
    function validateTypeVarTupleIsUnpacked(type: TypeVarTupleType, node: ParseNode) {
        if (!type.priv.isUnpacked) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.unpackedTypeVarTupleExpected().format({
                    name1: type.shared.name,
                    name2: type.shared.name,
                }),
                node
            );
            return false;
        }

        return true;
    }

    // If the type is a generic type alias that is not specialized, provides
    // default type arguments for the type alias. It optionally logs diagnostics
    // for missing type arguments.
    function specializeTypeAliasWithDefaults(type: Type, errorNode: ExpressionNode | undefined) {
        // Is this a type alias?
        const aliasInfo = type.props?.typeAliasInfo;
        if (!aliasInfo) {
            return type;
        }

        // Is this a generic type alias that needs specializing?
        if (!aliasInfo.shared.typeParams || aliasInfo.shared.typeParams.length === 0 || aliasInfo.typeArgs) {
            return type;
        }

        let reportDiag = false;
        const defaultTypeArgs: Type[] = [];
        const constraints = new ConstraintTracker();

        aliasInfo.shared.typeParams.forEach((param) => {
            if (!param.shared.isDefaultExplicit) {
                reportDiag = true;
            }

            let defaultType: Type;
            if (param.shared.isDefaultExplicit || isParamSpec(param)) {
                defaultType = solveAndApplyConstraints(param, constraints, {
                    replaceUnsolved: {
                        scopeIds: [aliasInfo.shared.typeVarScopeId],
                        tupleClassType: getTupleClassType(),
                    },
                });
            } else if (isTypeVarTuple(param) && prefetched?.tupleClass && isInstantiableClass(prefetched.tupleClass)) {
                defaultType = makeTupleObject(
                    evaluatorInterface,
                    [{ type: UnknownType.create(), isUnbounded: true }],
                    /* isUnpacked */ true
                );
            } else {
                defaultType = UnknownType.create();
            }

            defaultTypeArgs.push(defaultType);
            constraints.setBounds(param, defaultType);
        });

        if (reportDiag && errorNode) {
            addDiagnostic(
                DiagnosticRule.reportMissingTypeArgument,
                LocMessage.typeArgsMissingForAlias().format({
                    name: aliasInfo.shared.name,
                }),
                errorNode
            );
        }

        type = TypeBase.cloneForTypeAlias(
            solveAndApplyConstraints(type, constraints, {
                replaceUnsolved: {
                    scopeIds: [aliasInfo.shared.typeVarScopeId],
                    tupleClassType: getTupleClassType(),
                },
            }),
            { ...aliasInfo, typeArgs: defaultTypeArgs }
        );

        return type;
    }

    // Handles index expressions that are providing type arguments for a
    // generic type alias.
    function createSpecializedTypeAlias(
        node: IndexNode,
        baseType: Type,
        flags: EvalFlags
    ): TypeResultWithNode | undefined {
        let aliasInfo = baseType.props?.typeAliasInfo;
        let aliasBaseType = baseType;

        if (!aliasInfo && baseType.props?.typeForm) {
            aliasInfo = baseType.props.typeForm?.props?.typeAliasInfo;
            aliasBaseType = convertToInstantiable(baseType.props.typeForm);
        }

        if (!aliasInfo?.shared.typeParams || (aliasInfo.shared.typeParams.length === 0 && aliasInfo.typeArgs)) {
            return undefined;
        }

        // If this is not instantiable, then the index expression isn't a specialization.
        if (!TypeBase.isInstantiable(aliasBaseType)) {
            return undefined;
        }

        // If this is already specialized, the index expression isn't a specialization.
        if (aliasInfo.typeArgs) {
            return undefined;
        }

        inferVarianceForTypeAlias(baseType);

        const typeParams = aliasInfo.shared.typeParams;
        let typeArgs: TypeResultWithNode[] | undefined;
        typeArgs = adjustTypeArgsForTypeVarTuple(getTypeArgs(node, flags), typeParams, node);
        let reportedError = false;

        typeArgs = transformTypeArgsForParamSpec(typeParams, typeArgs, node);
        if (!typeArgs) {
            typeArgs = [];
            reportedError = true;
        }

        let minTypeArgCount = typeParams.length;
        const firstDefaultParamIndex = typeParams.findIndex((param) => !!param.shared.isDefaultExplicit);
        if (firstDefaultParamIndex >= 0) {
            minTypeArgCount = firstDefaultParamIndex;
        }

        if (typeArgs.length > typeParams.length) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.typeArgsTooMany().format({
                    name: printType(aliasBaseType),
                    expected: typeParams.length,
                    received: typeArgs.length,
                }),
                typeArgs[typeParams.length].node
            );
            reportedError = true;
        } else if (typeArgs.length < minTypeArgCount) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.typeArgsTooFew().format({
                    name: printType(aliasBaseType),
                    expected: typeParams.length,
                    received: typeArgs.length,
                }),
                node.d.items[node.d.items.length - 1]
            );
            reportedError = true;
        }

        // Handle the mypy_extensions.FlexibleAlias type specially.
        if (
            isInstantiableClass(aliasBaseType) &&
            aliasBaseType.shared.fullName === 'mypy_extensions.FlexibleAlias' &&
            typeArgs.length >= 1
        ) {
            return { node, type: typeArgs[0].type };
        }

        const constraints = new ConstraintTracker();
        const diag = new DiagnosticAddendum();

        typeParams.forEach((param, index) => {
            if (isParamSpec(param) && index < typeArgs.length) {
                const typeArgType = typeArgs[index].type;
                const typeList = typeArgs[index].typeList;

                if (typeList) {
                    const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue);
                    typeList.forEach((paramTypeResult, paramIndex) => {
                        let paramType = paramTypeResult.type;

                        if (!validateTypeArg(paramTypeResult)) {
                            paramType = UnknownType.create();
                        }

                        FunctionType.addParam(
                            functionType,
                            FunctionParam.create(
                                ParamCategory.Simple,
                                convertToInstance(paramType),
                                FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                                `__p${paramIndex}`
                            )
                        );
                    });

                    if (typeList.length > 0) {
                        FunctionType.addPositionOnlyParamSeparator(functionType);
                    }

                    assignTypeVar(
                        evaluatorInterface,
                        param,
                        functionType,
                        diag,
                        constraints,
                        AssignTypeFlags.RetainLiteralsForTypeVar
                    );
                } else if (isParamSpec(typeArgType)) {
                    assignTypeVar(
                        evaluatorInterface,
                        param,
                        convertToInstance(typeArgType),
                        diag,
                        constraints,
                        AssignTypeFlags.RetainLiteralsForTypeVar
                    );
                } else if (isInstantiableClass(typeArgType) && ClassType.isBuiltIn(typeArgType, 'Concatenate')) {
                    const concatTypeArgs = typeArgType.priv.typeArgs;
                    const functionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.None);

                    if (concatTypeArgs && concatTypeArgs.length > 0) {
                        concatTypeArgs.forEach((typeArg, index) => {
                            if (index === concatTypeArgs.length - 1) {
                                FunctionType.addPositionOnlyParamSeparator(functionType);

                                if (isParamSpec(typeArg)) {
                                    FunctionType.addParamSpecVariadics(functionType, typeArg);
                                } else if (isEllipsisType(typeArg)) {
                                    FunctionType.addDefaultParams(functionType);
                                    functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
                                }
                            } else {
                                FunctionType.addParam(
                                    functionType,
                                    FunctionParam.create(
                                        ParamCategory.Simple,
                                        typeArg,
                                        FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                                        `__p${index}`
                                    )
                                );
                            }
                        });
                    }

                    assignTypeVar(
                        evaluatorInterface,
                        param,
                        functionType,
                        diag,
                        constraints,
                        AssignTypeFlags.RetainLiteralsForTypeVar
                    );
                } else if (isEllipsisType(typeArgType)) {
                    const functionType = FunctionType.createSynthesizedInstance(
                        '',
                        FunctionTypeFlags.ParamSpecValue | FunctionTypeFlags.GradualCallableForm
                    );
                    FunctionType.addDefaultParams(functionType);
                    assignTypeVar(evaluatorInterface, param, functionType, diag, constraints);
                } else {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.typeArgListExpected(),
                        typeArgs[index].node
                    );
                    reportedError = true;
                }
            } else {
                if (index < typeArgs.length && typeArgs[index].typeList) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.typeArgListNotAllowed(),
                        typeArgs[index].node
                    );
                    reportedError = true;
                }

                let typeArgType: Type;
                if (index < typeArgs.length) {
                    typeArgType = convertToInstance(typeArgs[index].type);
                } else if (param.shared.isDefaultExplicit) {
                    typeArgType = solveAndApplyConstraints(param, constraints, {
                        replaceUnsolved: {
                            scopeIds: [aliasInfo.shared.typeVarScopeId],
                            tupleClassType: getTupleClassType(),
                        },
                    });
                } else {
                    typeArgType = UnknownType.create();
                }

                if ((flags & EvalFlags.EnforceVarianceConsistency) !== 0) {
                    const usageVariances = inferVarianceForTypeAlias(aliasBaseType);
                    if (usageVariances && index < usageVariances.length) {
                        const usageVariance = usageVariances[index];

                        if (!isVarianceOfTypeArgCompatible(typeArgType, usageVariance)) {
                            const messageDiag = diag.createAddendum();
                            messageDiag.addMessage(
                                LocAddendum.varianceMismatchForTypeAlias().format({
                                    typeVarName: printType(typeArgType),
                                    typeAliasParam: printType(typeParams[index]),
                                })
                            );
                            messageDiag.addTextRange(typeArgs[index].node);
                        }
                    }
                }

                if (isUnpacked(typeArgType) && !isTypeVarTuple(param)) {
                    const messageDiag = diag.createAddendum();
                    messageDiag.addMessage(LocMessage.unpackedArgInTypeArgument());
                    messageDiag.addTextRange(typeArgs[index].node);
                    typeArgType = UnknownType.create();
                }

                assignTypeVar(
                    evaluatorInterface,
                    param,
                    typeArgType,
                    diag,
                    constraints,
                    AssignTypeFlags.RetainLiteralsForTypeVar
                );
            }
        });

        if (!diag.isEmpty()) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.typeNotSpecializable().format({ type: printType(aliasBaseType) }) + diag.getString(),
                node,
                diag.getEffectiveTextRange() ?? node
            );
            reportedError = true;
        }

        const solutionSet = solveConstraints(evaluatorInterface, constraints).getMainSolutionSet();
        const aliasTypeArgs: Type[] = [];

        aliasInfo.shared.typeParams?.forEach((typeParam) => {
            let typeVarType = solutionSet.getType(typeParam);

            // Fill in any unsolved type arguments with unknown.
            if (!typeVarType) {
                typeVarType = getUnknownForTypeVar(typeParam, getTupleClassType());
                constraints.setBounds(typeParam, typeVarType);
            }

            aliasTypeArgs.push(typeVarType);
        });

        let type = TypeBase.cloneForTypeAlias(solveAndApplyConstraints(aliasBaseType, constraints), {
            ...aliasInfo,
            typeArgs: aliasTypeArgs,
        });

        type = TypeBase.cloneWithTypeForm(type, reportedError ? undefined : convertToInstance(type));

        if (baseType.props?.typeAliasInfo) {
            return { type, node };
        }

        return { type: TypeBase.cloneWithTypeForm(baseType, convertToInstance(type)), node };
    }

    function getTypeOfIndexWithBaseType(
        node: IndexNode,
        baseTypeResult: TypeResult,
        usage: EvaluatorUsage,
        flags: EvalFlags
    ): TypeResult {
        // Handle the case where we're specializing a generic type alias.
        const typeAliasResult = createSpecializedTypeAlias(node, baseTypeResult.type, flags);
        if (typeAliasResult) {
            return typeAliasResult;
        }

        // Handle the case where Never or NoReturn are being specialized.
        if (isNever(baseTypeResult.type) && baseTypeResult.type.props?.specialForm) {
            // Swap in the special form type, which is the Never or NoReturn class.
            baseTypeResult = { ...baseTypeResult, type: baseTypeResult.type.props.specialForm };
        }

        // Handle the case where a TypeAliasType symbol is being specialized
        // in a value expression.
        if (
            isClassInstance(baseTypeResult.type) &&
            ClassType.isBuiltIn(baseTypeResult.type, 'TypeAliasType') &&
            baseTypeResult.type.props?.typeForm
        ) {
            const typeAliasInfo = baseTypeResult.type.props.typeForm.props?.typeAliasInfo;
            if (typeAliasInfo && typeAliasInfo.shared.typeParams) {
                const origTypeAlias = TypeBase.cloneForTypeAlias(
                    convertToInstantiable(baseTypeResult.type.props.typeForm),
                    { ...typeAliasInfo, typeArgs: undefined }
                );
                const typeFormType = createSpecializedTypeAlias(node, origTypeAlias, flags);
                if (typeFormType) {
                    return {
                        type: TypeBase.cloneWithTypeForm(baseTypeResult.type, convertToInstance(typeFormType.type)),
                    };
                }
            }
        }

        if (isTypeVar(baseTypeResult.type) && isTypeAliasPlaceholder(baseTypeResult.type)) {
            const typeArgTypes = getTypeArgs(node, flags).map((t) => convertToInstance(t.type));
            const type = TypeBase.cloneForTypeAlias(baseTypeResult.type, {
                shared: baseTypeResult.type.shared.recursiveAlias!,
                typeArgs: typeArgTypes,
            });
            return { type };
        }

        let isIncomplete = baseTypeResult.isIncomplete;
        let isRequired = false;
        let isNotRequired = false;
        let isReadOnly = false;

        const type = mapSubtypesExpandTypeVars(
            baseTypeResult.type,
            /* options */ undefined,
            (concreteSubtype, unexpandedSubtype) => {
                const selfType = isTypeVar(unexpandedSubtype) ? unexpandedSubtype : undefined;

                if (isAnyOrUnknown(concreteSubtype)) {
                    if ((flags & EvalFlags.TypeExpression) !== 0) {
                        // If we are expecting a type annotation here, assume that
                        // the subscripts are type arguments and evaluate them
                        // accordingly.
                        getTypeArgs(node, flags);
                    }

                    return concreteSubtype;
                }

                if (flags & EvalFlags.InstantiableType) {
                    if (isTypeVar(unexpandedSubtype)) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarNotSubscriptable().format({
                                type: printType(unexpandedSubtype),
                            }),
                            node.d.leftExpr
                        );

                        // Evaluate the index expressions as though they are type arguments for error-reporting.
                        getTypeArgs(node, flags);

                        return UnknownType.create();
                    }
                }

                if (isInstantiableClass(concreteSubtype)) {
                    // See if the class has a custom metaclass that supports __getitem__, etc.
                    if (
                        concreteSubtype.shared.effectiveMetaclass &&
                        isInstantiableClass(concreteSubtype.shared.effectiveMetaclass) &&
                        !ClassType.isBuiltIn(concreteSubtype.shared.effectiveMetaclass, ['type', '_InitVarMeta']) &&
                        (flags & EvalFlags.InstantiableType) === 0
                    ) {
                        const itemMethodType = getBoundMagicMethod(
                            concreteSubtype,
                            getIndexAccessMagicMethodName(usage),
                            /* selfType */ undefined,
                            node.d.leftExpr
                        );

                        if ((flags & EvalFlags.TypeExpression) !== 0) {
                            // If the class doesn't derive from Generic, a type argument should not be allowed.
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeArguments,
                                LocMessage.typeArgsExpectingNone().format({
                                    name: printType(ClassType.cloneAsInstance(concreteSubtype)),
                                }),
                                node
                            );
                        }

                        if (itemMethodType) {
                            return getTypeOfIndexedObjectOrClass(node, concreteSubtype, selfType, usage).type;
                        }
                    }

                    // Setting the value of an indexed class will always result
                    // in an exception.
                    if (usage.method === 'set') {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.genericClassAssigned(),
                            node.d.leftExpr
                        );
                    } else if (usage.method === 'del') {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.genericClassDeleted(),
                            node.d.leftExpr
                        );
                    }

                    if (ClassType.isSpecialBuiltIn(concreteSubtype, 'Literal')) {
                        // Special-case Literal types.
                        return createLiteralType(concreteSubtype, node, flags);
                    }

                    if (ClassType.isBuiltIn(concreteSubtype, 'InitVar')) {
                        // Special-case InitVar, used in dataclasses.
                        const typeArgs = getTypeArgs(node, flags);

                        if ((flags & EvalFlags.TypeExpression) !== 0) {
                            if ((flags & EvalFlags.VarTypeAnnotation) === 0) {
                                addDiagnostic(
                                    DiagnosticRule.reportInvalidTypeForm,
                                    LocMessage.initVarNotAllowed(),
                                    node.d.leftExpr
                                );
                            }
                        }

                        if (typeArgs.length === 1) {
                            return typeArgs[0].type;
                        } else {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.typeArgsMismatchOne().format({ received: typeArgs.length }),
                                node.d.leftExpr
                            );

                            return UnknownType.create();
                        }
                    }

                    if (ClassType.isEnumClass(concreteSubtype)) {
                        // Special-case Enum types.
                        // TODO - validate that there's only one index entry
                        // that is a str type.
                        // TODO - validate that literal strings are referencing
                        // a known enum member.
                        return ClassType.cloneAsInstance(concreteSubtype);
                    }

                    const isAnnotatedClass =
                        isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'Annotated');
                    const hasCustomClassGetItem =
                        isInstantiableClass(concreteSubtype) && ClassType.hasCustomClassGetItem(concreteSubtype);
                    const isGenericClass =
                        concreteSubtype.shared.typeParams?.length > 0 ||
                        ClassType.isSpecialBuiltIn(concreteSubtype) ||
                        ClassType.isBuiltIn(concreteSubtype, 'type') ||
                        ClassType.isPartiallyEvaluated(concreteSubtype);
                    const isFinalAnnotation =
                        isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'Final');
                    const isClassVarAnnotation =
                        isInstantiableClass(concreteSubtype) && ClassType.isBuiltIn(concreteSubtype, 'ClassVar');

                    // This feature is currently experimental.
                    const supportsTypedDictTypeArg =
                        AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.enableExperimentalFeatures &&
                        ClassType.isBuiltIn(concreteSubtype, 'TypedDict');

                    let typeArgs = getTypeArgs(node, flags, {
                        isAnnotatedClass,
                        hasCustomClassGetItem: hasCustomClassGetItem || !isGenericClass,
                        isFinalAnnotation,
                        isClassVarAnnotation,
                        supportsTypedDictTypeArg,
                    });

                    if (!isAnnotatedClass) {
                        typeArgs = adjustTypeArgsForTypeVarTuple(typeArgs, concreteSubtype.shared.typeParams, node);
                    }

                    // If this is a custom __class_getitem__, there's no need to specialize the class.
                    // Just return it as is.
                    if (hasCustomClassGetItem) {
                        return concreteSubtype;
                    }

                    if (concreteSubtype.priv.typeArgs) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeArguments,
                            LocMessage.classAlreadySpecialized().format({
                                type: printType(convertToInstance(concreteSubtype), { expandTypeAlias: true }),
                            }),
                            node.d.leftExpr
                        );
                        return concreteSubtype;
                    }

                    const result = createSpecializedClassType(concreteSubtype, typeArgs, flags, node);
                    if (result.isRequired) {
                        isRequired = true;
                    } else if (result.isNotRequired) {
                        isNotRequired = true;
                    }

                    if (result.isReadOnly) {
                        isReadOnly = true;
                    }

                    return result.type;
                }

                if (isNoneInstance(concreteSubtype)) {
                    if (!isIncomplete) {
                        addDiagnostic(
                            DiagnosticRule.reportOptionalSubscript,
                            LocMessage.noneNotSubscriptable(),
                            node.d.leftExpr
                        );
                    }

                    return UnknownType.create();
                }

                if (isClassInstance(concreteSubtype)) {
                    const typeResult = getTypeOfIndexedObjectOrClass(node, concreteSubtype, selfType, usage);
                    if (typeResult.isIncomplete) {
                        isIncomplete = true;
                    }
                    return typeResult.type;
                }

                if (isNever(concreteSubtype)) {
                    return NeverType.createNever();
                }

                if (isUnbound(concreteSubtype)) {
                    return UnknownType.create();
                }

                if (!isIncomplete) {
                    addDiagnostic(
                        DiagnosticRule.reportIndexIssue,
                        LocMessage.typeNotSubscriptable().format({ type: printType(concreteSubtype) }),
                        node.d.leftExpr
                    );
                }

                return UnknownType.create();
            }
        );

        // In case we didn't walk the list items above, do so now.
        // If we have, this information will be cached.
        if (!baseTypeResult.isIncomplete) {
            node.d.items.forEach((item) => {
                if (!isTypeCached(item.d.valueExpr)) {
                    getTypeOfExpression(item.d.valueExpr, flags & EvalFlags.ForwardRefs);
                }
            });
        }

        return { type, isIncomplete, isReadOnly, isRequired, isNotRequired };
    }

    // Determines the effective variance of the type parameters for a generic
    // type alias. Normally, variance is not important for type aliases, but
    // it can be important in cases where the type alias is used to specify
    // a base class in a class definition.
    function inferVarianceForTypeAlias(type: Type): Variance[] | undefined {
        const aliasInfo = type.props?.typeAliasInfo;

        // If this isn't a generic type alias, there's nothing to do.
        if (!aliasInfo || !aliasInfo.shared.typeParams) {
            return undefined;
        }

        // Is the computed variance info already cached?
        if (aliasInfo.shared.computedVariance) {
            return aliasInfo.shared.computedVariance;
        }

        const typeParams = aliasInfo.shared.typeParams;

        // Start with all of the usage variances unknown.
        const usageVariances: Variance[] = typeParams.map(() => Variance.Unknown);

        // Prepopulate the cached value for the type alias to handle
        // recursive type aliases.
        aliasInfo.shared.computedVariance = usageVariances;

        // Traverse the type alias type definition and adjust the usage
        // variances accordingly.
        updateUsageVariancesRecursive(type, typeParams, usageVariances, Variance.Covariant);

        return usageVariances;
    }

    // Looks at uses of the type parameters within the type and adjusts the
    // variances accordingly. For example, if the type is `Mapping[T1, T2]`,
    // then T1 will be set to invariant and T2 will be set to covariant.
    function updateUsageVariancesRecursive(
        type: Type,
        typeAliasTypeParams: TypeVarType[],
        usageVariances: Variance[],
        varianceContext: Variance,
        pendingTypes: Type[] = [],
        recursionCount = 0
    ) {
        if (recursionCount > maxTypeRecursionCount) {
            return;
        }

        const transformedType = transformPossibleRecursiveTypeAlias(type);
        const isRecursiveTypeAlias = transformedType !== type;

        // If this is a recursive type alias, see if we've already recursed
        // seen it once before in the recursion stack. If so, don't recurse
        // further.
        if (isRecursiveTypeAlias) {
            const pendingOverlaps = pendingTypes.filter((pendingType) => isTypeSame(pendingType, type));
            if (pendingOverlaps.length > 1) {
                return;
            }

            pendingTypes.push(type);
        }

        recursionCount++;

        // Define a helper function that performs the actual usage variant update.
        function updateUsageVarianceForType(type: Type, variance: Variance) {
            doForEachSubtype(type, (subtype) => {
                const typeParamIndex = typeAliasTypeParams.findIndex((param) => isTypeSame(param, subtype));
                if (typeParamIndex >= 0) {
                    usageVariances[typeParamIndex] = combineVariances(usageVariances[typeParamIndex], variance);
                } else {
                    updateUsageVariancesRecursive(
                        subtype,
                        typeAliasTypeParams,
                        usageVariances,
                        variance,
                        pendingTypes,
                        recursionCount
                    );
                }
            });
        }

        doForEachSubtype(transformedType, (subtype) => {
            if (subtype.category === TypeCategory.Function) {
                subtype.shared.parameters.forEach((param, index) => {
                    const paramType = FunctionType.getParamType(subtype, index);
                    updateUsageVarianceForType(paramType, invertVariance(varianceContext));
                });

                const returnType = FunctionType.getEffectiveReturnType(subtype);
                if (returnType) {
                    updateUsageVarianceForType(returnType, varianceContext);
                }
            } else if (subtype.category === TypeCategory.Class) {
                if (subtype.priv.typeArgs) {
                    // If the class includes type parameters that uses auto variance,
                    // compute the calculated variance.
                    inferVarianceForClass(subtype);

                    // Is the class specialized using any type arguments that correspond to
                    // the type alias' type parameters?
                    subtype.priv.typeArgs.forEach((typeArg, classParamIndex) => {
                        if (isTupleClass(subtype)) {
                            updateUsageVarianceForType(typeArg, varianceContext);
                        } else if (classParamIndex < subtype.shared.typeParams.length) {
                            const classTypeParam = subtype.shared.typeParams[classParamIndex];
                            if (isUnpackedClass(typeArg) && typeArg.priv.tupleTypeArgs) {
                                typeArg.priv.tupleTypeArgs.forEach((tupleTypeArg) => {
                                    updateUsageVarianceForType(tupleTypeArg.type, Variance.Invariant);
                                });
                            } else {
                                const effectiveVariance =
                                    classTypeParam.priv.computedVariance ?? classTypeParam.shared.declaredVariance;
                                updateUsageVarianceForType(
                                    typeArg,
                                    varianceContext === Variance.Contravariant
                                        ? invertVariance(effectiveVariance)
                                        : effectiveVariance
                                );
                            }
                        }
                    });
                }
            }
        });

        if (isRecursiveTypeAlias) {
            pendingTypes.pop();
        }
    }

    function getIndexAccessMagicMethodName(usage: EvaluatorUsage): string {
        if (usage.method === 'get') {
            return '__getitem__';
        } else if (usage.method === 'set') {
            return '__setitem__';
        } else {
            assert(usage.method === 'del');
            return '__delitem__';
        }
    }

    function getTypeOfIndexedObjectOrClass(
        node: IndexNode,
        baseType: ClassType,
        selfType: ClassType | TypeVarType | undefined,
        usage: EvaluatorUsage
    ): TypeResult {
        // Handle index operations for TypedDict classes specially.
        if (isClassInstance(baseType) && ClassType.isTypedDictClass(baseType)) {
            const typeFromTypedDict = getTypeOfIndexedTypedDict(evaluatorInterface, node, baseType, usage);
            if (typeFromTypedDict) {
                return typeFromTypedDict;
            }
        }

        const magicMethodName = getIndexAccessMagicMethodName(usage);
        const itemMethodType = getBoundMagicMethod(baseType, magicMethodName, selfType, node.d.leftExpr);

        if (!itemMethodType) {
            addDiagnostic(
                DiagnosticRule.reportIndexIssue,
                LocMessage.methodNotDefinedOnType().format({
                    name: magicMethodName,
                    type: printType(baseType),
                }),
                node.d.leftExpr
            );
            return { type: UnknownType.create() };
        }

        // Handle the special case where the object is a tuple and
        // the index is a constant number (integer) or a slice with integer
        // start and end values. In these cases, we can determine
        // the exact type by indexing into the tuple type array.
        if (
            node.d.items.length === 1 &&
            !node.d.trailingComma &&
            !node.d.items[0].d.name &&
            node.d.items[0].d.argCategory === ArgCategory.Simple &&
            isClassInstance(baseType)
        ) {
            const index0Expr = node.d.items[0].d.valueExpr;
            const valueType = getTypeOfExpression(index0Expr).type;

            if (
                isClassInstance(valueType) &&
                ClassType.isBuiltIn(valueType, 'int') &&
                isLiteralType(valueType) &&
                typeof valueType.priv.literalValue === 'number'
            ) {
                const indexValue = valueType.priv.literalValue;
                const tupleType = getSpecializedTupleType(baseType);

                if (tupleType && tupleType.priv.tupleTypeArgs) {
                    if (isTupleIndexUnambiguous(tupleType, indexValue)) {
                        if (indexValue >= 0 && indexValue < tupleType.priv.tupleTypeArgs.length) {
                            return { type: tupleType.priv.tupleTypeArgs[indexValue].type };
                        } else if (indexValue < 0 && tupleType.priv.tupleTypeArgs.length + indexValue >= 0) {
                            return {
                                type: tupleType.priv.tupleTypeArgs[tupleType.priv.tupleTypeArgs.length + indexValue]
                                    .type,
                            };
                        }
                    }
                }
            } else if (isClassInstance(valueType) && ClassType.isBuiltIn(valueType, 'slice')) {
                const tupleType = getSpecializedTupleType(baseType);

                if (tupleType && index0Expr.nodeType === ParseNodeType.Slice) {
                    const slicedTupleType = getSlicedTupleType(evaluatorInterface, tupleType, index0Expr);
                    if (slicedTupleType) {
                        return { type: slicedTupleType };
                    }
                }
            }
        }

        const positionalArgs = node.d.items.filter((item) => item.d.argCategory === ArgCategory.Simple);
        const unpackedListArgs = node.d.items.filter((item) => item.d.argCategory === ArgCategory.UnpackedList);

        let positionalIndexType: Type;
        let isPositionalIndexTypeIncomplete = false;

        if (positionalArgs.length === 1 && unpackedListArgs.length === 0 && !node.d.trailingComma) {
            // Handle the common case where there is a single positional argument.
            const typeResult = getTypeOfExpression(positionalArgs[0].d.valueExpr);
            positionalIndexType = typeResult.type;
            if (typeResult.isIncomplete) {
                isPositionalIndexTypeIncomplete = true;
            }
        } else {
            // Package up all of the positionals into a tuple.
            const tupleTypeArgs: TupleTypeArg[] = [];

            const getDeterministicTupleEntries = (type: Type): TupleTypeArg[] | undefined => {
                let aggregatedArgs: TupleTypeArg[] | undefined;
                let isDeterministic = true;

                doForEachSubtype(type, (subtype) => {
                    if (!isDeterministic) {
                        return;
                    }

                    const tupleType = getSpecializedTupleType(subtype);
                    const tupleTypeArgs = tupleType?.priv.tupleTypeArgs;

                    if (
                        !tupleTypeArgs ||
                        tupleTypeArgs.some((entry) => entry.isUnbounded || isTypeVarTuple(entry.type))
                    ) {
                        isDeterministic = false;
                        return;
                    }

                    if (!aggregatedArgs) {
                        aggregatedArgs = tupleTypeArgs.map((entry) => ({ type: entry.type, isUnbounded: false }));
                        return;
                    }

                    if (aggregatedArgs.length !== tupleTypeArgs.length) {
                        isDeterministic = false;
                        return;
                    }

                    for (let i = 0; i < aggregatedArgs.length; i++) {
                        aggregatedArgs[i] = {
                            type: combineTypes([aggregatedArgs[i].type, tupleTypeArgs[i].type]),
                            isUnbounded: false,
                        };
                    }
                });

                if (!isDeterministic || !aggregatedArgs) {
                    return undefined;
                }

                return aggregatedArgs;
            };

            node.d.items.forEach((arg) => {
                if (arg.d.argCategory === ArgCategory.Simple) {
                    const typeResult = getTypeOfExpression(arg.d.valueExpr);
                    tupleTypeArgs.push({ type: typeResult.type, isUnbounded: false });
                    if (typeResult.isIncomplete) {
                        isPositionalIndexTypeIncomplete = true;
                    }
                    return;
                }

                if (arg.d.argCategory === ArgCategory.UnpackedList) {
                    const typeResult = getTypeOfExpression(arg.d.valueExpr);
                    if (typeResult.isIncomplete) {
                        isPositionalIndexTypeIncomplete = true;
                    }

                    const deterministicEntries = getDeterministicTupleEntries(typeResult.type);
                    if (deterministicEntries) {
                        appendArray(tupleTypeArgs, deterministicEntries);
                        return;
                    }

                    const iterableType =
                        getTypeOfIterator(typeResult, /* isAsync */ false, arg.d.valueExpr)?.type ??
                        UnknownType.create();
                    tupleTypeArgs.push({ type: iterableType, isUnbounded: true });
                }
            });

            const unboundedCount = tupleTypeArgs.filter((typeArg) => typeArg.isUnbounded).length;
            if (unboundedCount > 1) {
                const firstUnboundedIndex = tupleTypeArgs.findIndex((typeArg) => typeArg.isUnbounded);
                const removedEntries = tupleTypeArgs.splice(firstUnboundedIndex);
                tupleTypeArgs.push({
                    type: combineTypes(removedEntries.map((entry) => entry.type)),
                    isUnbounded: true,
                });
            }

            positionalIndexType = makeTupleObject(evaluatorInterface, tupleTypeArgs);
        }

        const argList: Arg[] = [
            {
                argCategory: ArgCategory.Simple,
                typeResult: { type: positionalIndexType, isIncomplete: isPositionalIndexTypeIncomplete },
            },
        ];

        if (usage.method === 'set') {
            let setType = usage.setType?.type ?? AnyType.create();

            // Expand constrained type variables.
            if (isTypeVar(setType) && TypeVarType.hasConstraints(setType)) {
                const conditionFilter = isClassInstance(baseType) ? baseType.props?.condition : undefined;
                setType = makeTopLevelTypeVarsConcrete(
                    setType,
                    /* makeParamSpecsConcrete */ undefined,
                    conditionFilter
                );
            }

            argList.push({
                argCategory: ArgCategory.Simple,
                typeResult: {
                    type: setType,
                    isIncomplete: !!usage.setType?.isIncomplete,
                },
            });
        }

        const callResult = validateCallArgs(
            node,
            argList,
            { type: itemMethodType },
            /* constraints */ undefined,
            /* skipUnknownArgCheck */ true,
            /* inferenceContext */ undefined
        );

        return {
            type: callResult.returnType ?? UnknownType.create(),
            isIncomplete: !!callResult.isTypeIncomplete,
        };
    }

    function getTypeArgs(node: IndexNode, flags: EvalFlags, options?: GetTypeArgsOptions): TypeResultWithNode[] {
        const typeArgs: TypeResultWithNode[] = [];
        let adjFlags = flags | EvalFlags.NoConvertSpecialForm;
        adjFlags &= ~EvalFlags.TypeFormArg;

        const allowFinalClassVar = () => {
            // If the annotation is a variable within the body of a dataclass, a
            // Final is allowed with a ClassVar annotation. In all other cases,
            // it's disallowed.
            const enclosingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopeAtFunction */ true);
            if (enclosingClassNode) {
                const classTypeInfo = getTypeOfClass(enclosingClassNode);
                if (classTypeInfo && ClassType.isDataClass(classTypeInfo.classType)) {
                    return true;
                }
            }
            return false;
        };

        if (options?.isFinalAnnotation) {
            adjFlags |= EvalFlags.NoFinal;

            if (!allowFinalClassVar()) {
                adjFlags |= EvalFlags.NoClassVar;
            }
        } else if (options?.isClassVarAnnotation) {
            adjFlags |= EvalFlags.NoClassVar;

            if (!allowFinalClassVar()) {
                adjFlags |= EvalFlags.NoFinal;
            }
        } else {
            adjFlags &= ~(
                EvalFlags.NoSpecialize |
                EvalFlags.NoParamSpec |
                EvalFlags.NoTypeVarTuple |
                EvalFlags.AllowRequired |
                EvalFlags.EnforceVarianceConsistency
            );

            if (!options?.isAnnotatedClass) {
                adjFlags |= EvalFlags.NoClassVar | EvalFlags.NoFinal;
            }

            adjFlags |= EvalFlags.AllowUnpackedTuple | EvalFlags.AllowConcatenate;
        }

        // Create a local function that validates a single type argument.
        const getTypeArgTypeResult = (expr: ExpressionNode, argIndex: number) => {
            let typeResult: TypeResultWithNode;

            // If it's a custom __class_getitem__, none of the arguments should be
            // treated as types.
            if (options?.hasCustomClassGetItem) {
                adjFlags =
                    EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar;
                typeResult = {
                    ...getTypeOfExpression(expr, adjFlags),
                    node: expr,
                };
            } else if (options?.isAnnotatedClass && argIndex > 0) {
                // If it's an Annotated[a, b, c], only the first index should be
                // treated as a type. The others can be regular (non-type) objects.
                adjFlags =
                    EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar;
                if (isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node))) {
                    adjFlags |= EvalFlags.ForwardRefs;
                }

                typeResult = {
                    ...getTypeOfExpression(expr, adjFlags),
                    node: expr,
                };
            } else {
                typeResult = getTypeArg(expr, adjFlags, !!options?.supportsTypedDictTypeArg && argIndex === 0);
            }

            return typeResult;
        };

        // A tuple is treated the same as a list of items in the index.
        if (
            node.d.items.length === 1 &&
            !node.d.trailingComma &&
            !node.d.items[0].d.name &&
            node.d.items[0].d.valueExpr.nodeType === ParseNodeType.Tuple
        ) {
            node.d.items[0].d.valueExpr.d.items.forEach((item, index) => {
                typeArgs.push(getTypeArgTypeResult(item, index));
            });

            // Set the node's type so it isn't reevaluated later.
            setTypeResultForNode(node.d.items[0].d.valueExpr, { type: UnknownType.create() });

            return typeArgs;
        }

        node.d.items.forEach((arg, index) => {
            const typeResult = getTypeArgTypeResult(arg.d.valueExpr, index);

            if (arg.d.argCategory !== ArgCategory.Simple) {
                if (arg.d.argCategory === ArgCategory.UnpackedList) {
                    if (!options?.isAnnotatedClass || index === 0) {
                        const unpackedType = applyUnpackToTupleLike(typeResult.type);

                        if (unpackedType) {
                            typeResult.type = unpackedType;
                        } else {
                            if ((flags & EvalFlags.TypeExpression) !== 0) {
                                addDiagnostic(
                                    DiagnosticRule.reportInvalidTypeForm,
                                    LocMessage.unpackNotAllowed(),
                                    arg.d.valueExpr
                                );
                                typeResult.typeErrors = true;
                            } else {
                                typeResult.type = UnknownType.create();
                            }
                        }
                    }
                }
            }

            if (arg.d.name) {
                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.keywordArgInTypeArgument(),
                        arg.d.valueExpr
                    );
                    typeResult.typeErrors = true;
                } else {
                    typeResult.type = UnknownType.create();
                }
            }

            if (
                arg.d.valueExpr.nodeType !== ParseNodeType.Error ||
                arg.d.valueExpr.d.category !== ErrorExpressionCategory.MissingIndexOrSlice
            ) {
                typeArgs.push(typeResult);
            }
        });

        return typeArgs;
    }

    function applyUnpackToTupleLike(type: Type): Type | undefined {
        if (isTypeVarTuple(type)) {
            if (!type.priv.isUnpacked) {
                return TypeVarType.cloneForUnpacked(type);
            }

            return undefined;
        }

        if (isParamSpec(type)) {
            return undefined;
        }

        // Is this a TypeVar that has a tuple upper bound?
        if (isTypeVar(type)) {
            const upperBound = type.shared.boundType;

            if (upperBound && isClassInstance(upperBound) && isTupleClass(upperBound)) {
                return TypeVarType.cloneForUnpacked(type);
            }

            return undefined;
        }

        if (isInstantiableClass(type) && !type.priv.includeSubclasses) {
            if (isTupleClass(type)) {
                return ClassType.cloneForUnpacked(type);
            }
        }

        return undefined;
    }

    function getTypeArg(node: ExpressionNode, flags: EvalFlags, supportsDictExpression: boolean): TypeResultWithNode {
        let typeResult: TypeResultWithNode;

        let adjustedFlags =
            flags | EvalFlags.InstantiableType | EvalFlags.ConvertEllipsisToAny | EvalFlags.StrLiteralAsType;

        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        if (fileInfo.isStubFile) {
            adjustedFlags |= EvalFlags.ForwardRefs;
        }

        if (node.nodeType === ParseNodeType.List) {
            typeResult = {
                type: UnknownType.create(),
                typeList: node.d.items.map((entry) => {
                    return { ...getTypeOfExpression(entry, adjustedFlags), node: entry };
                }),
                node,
            };

            // Set the node's type so it isn't reevaluated later.
            setTypeResultForNode(node, { type: UnknownType.create() });
        } else if (node.nodeType === ParseNodeType.Dictionary && supportsDictExpression) {
            const inlinedTypeDict =
                prefetched?.typedDictClass && isInstantiableClass(prefetched.typedDictClass)
                    ? createTypedDictTypeInlined(evaluatorInterface, node, prefetched.typedDictClass)
                    : undefined;
            const keyTypeFallback =
                prefetched?.strClass && isInstantiableClass(prefetched.strClass)
                    ? prefetched.strClass
                    : UnknownType.create();

            typeResult = {
                type: keyTypeFallback,
                inlinedTypeDict,
                node,
            };
        } else {
            typeResult = { ...getTypeOfExpression(node, adjustedFlags), node };

            if (node.nodeType === ParseNodeType.Dictionary) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.dictInAnnotation(), node);
            }

            if ((flags & EvalFlags.NoClassVar) !== 0) {
                // "ClassVar" is not allowed as a type argument.
                if (isClass(typeResult.type) && ClassType.isBuiltIn(typeResult.type, 'ClassVar')) {
                    addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarNotAllowed(), node);
                }
            }
        }

        return typeResult;
    }

    function buildTupleTypesList(
        entryTypeResults: TypeResult[],
        stripLiterals: boolean,
        convertModule: boolean
    ): TupleTypeArg[] {
        const entryTypes: TupleTypeArg[] = [];

        for (const typeResult of entryTypeResults) {
            let possibleUnpackedTuple: Type | undefined;
            if (typeResult.unpackedType) {
                possibleUnpackedTuple = typeResult.unpackedType;
            } else if (isUnpacked(typeResult.type)) {
                possibleUnpackedTuple = typeResult.type;
            }

            // Is this an unpacked tuple? If so, we can append the individual
            // unpacked entries onto the new tuple. If it's not an upacked tuple
            // but some other iterator (e.g. a List), we won't know the number of
            // items, so we'll need to leave the Tuple open-ended.
            if (
                possibleUnpackedTuple &&
                isClassInstance(possibleUnpackedTuple) &&
                possibleUnpackedTuple.priv.tupleTypeArgs
            ) {
                const typeArgs = possibleUnpackedTuple.priv.tupleTypeArgs;

                if (!typeArgs) {
                    entryTypes.push({ type: UnknownType.create(), isUnbounded: true });
                } else {
                    appendArray(entryTypes, typeArgs);
                }
            } else if (isNever(typeResult.type) && typeResult.isIncomplete && !typeResult.unpackedType) {
                entryTypes.push({ type: UnknownType.create(/* isIncomplete */ true), isUnbounded: false });
            } else {
                let entryType = convertSpecialFormToRuntimeValue(typeResult.type, EvalFlags.None, convertModule);
                entryType = stripLiterals ? stripTypeForm(stripLiteralValue(entryType)) : entryType;
                entryTypes.push({ type: entryType, isUnbounded: !!typeResult.unpackedType });
            }
        }

        // If there are multiple unbounded entries, combine all of them into a single
        // unbounded entry to avoid violating the invariant that there can be at most
        // one unbounded entry in a tuple.
        if (entryTypes.filter((t) => t.isUnbounded).length > 1) {
            const firstUnboundedEntryIndex = entryTypes.findIndex((t) => t.isUnbounded);
            const removedEntries = entryTypes.splice(firstUnboundedEntryIndex);
            entryTypes.push({ type: combineTypes(removedEntries.map((t) => t.type)), isUnbounded: true });
        }

        return entryTypes;
    }

    function getTypeOfCall(
        node: CallNode,
        flags: EvalFlags,
        inferenceContext: InferenceContext | undefined
    ): TypeResult {
        let baseTypeResult: TypeResult | undefined;

        // Check for the use of `type(x)` within a type annotation. This isn't
        // allowed, and it's a common mistake, so we want to emit a diagnostic
        // that guides the user to the right solution.
        if (
            (flags & EvalFlags.TypeExpression) !== 0 &&
            node.d.leftExpr.nodeType === ParseNodeType.Name &&
            node.d.leftExpr.d.value === 'type'
        ) {
            const diag = new DiagnosticAddendum();
            diag.addMessage(LocAddendum.useTypeInstead());
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.typeCallNotAllowed() + diag.getString(),
                node
            );
        }

        // Handle immediate calls of lambdas specially.
        if (node.d.leftExpr.nodeType === ParseNodeType.Lambda) {
            baseTypeResult = getTypeOfLambdaForCall(node, inferenceContext);
        } else {
            baseTypeResult = getTypeOfExpression(
                node.d.leftExpr,
                EvalFlags.CallBaseDefaults | (flags & EvalFlags.ForwardRefs)
            );
        }

        const argList = ParseTreeUtils.getArgsByRuntimeOrder(node).map((arg) => {
            const functionArg: Arg = {
                valueExpression: arg.d.valueExpr,
                argCategory: arg.d.argCategory,
                node: arg,
                name: arg.d.name,
            };
            return functionArg;
        });

        let typeResult: TypeResult = { type: UnknownType.create() };

        baseTypeResult.type = ensureSignatureIsUnique(baseTypeResult.type, node);

        if (!isTypeAliasPlaceholder(baseTypeResult.type)) {
            if (node.d.leftExpr.nodeType === ParseNodeType.Name && node.d.leftExpr.d.value === 'super') {
                // Handle the built-in "super" call specially.
                typeResult = getTypeOfSuperCall(node);
            } else if (
                isAnyOrUnknown(baseTypeResult.type) &&
                node.d.leftExpr.nodeType === ParseNodeType.Name &&
                node.d.leftExpr.d.value === 'reveal_type'
            ) {
                // Handle the implicit "reveal_type" call.
                typeResult = getTypeOfRevealType(node, inferenceContext);
            } else if (isFunction(baseTypeResult.type) && FunctionType.isBuiltIn(baseTypeResult.type, 'reveal_type')) {
                // Handle the "typing.reveal_type" call.
                typeResult = getTypeOfRevealType(node, inferenceContext);
            } else if (isFunction(baseTypeResult.type) && FunctionType.isBuiltIn(baseTypeResult.type, 'assert_type')) {
                // Handle the "typing.assert_type" call.
                typeResult = getTypeOfAssertType(node, inferenceContext);
            } else if (isClass(baseTypeResult.type) && ClassType.isBuiltIn(baseTypeResult.type, 'TypeForm')) {
                // Handle the "typing.TypeForm" call.
                typeResult = getTypeOfTypeForm(node, baseTypeResult.type);
            } else if (
                isAnyOrUnknown(baseTypeResult.type) &&
                node.d.leftExpr.nodeType === ParseNodeType.Name &&
                node.d.leftExpr.d.value === 'reveal_locals'
            ) {
                if (node.d.args.length === 0) {
                    // Handle the special-case "reveal_locals" call.
                    typeResult.type = getTypeOfRevealLocals(node);
                } else {
                    addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.revealLocalsArgs(), node);
                }
            } else {
                const callResult = validateCallArgs(
                    node,
                    argList,
                    baseTypeResult,
                    /* constraints */ undefined,
                    /* skipUnknownArgCheck */ false,
                    inferenceContext
                );

                typeResult.type = callResult.returnType ?? UnknownType.create();

                if (callResult.argumentErrors) {
                    typeResult.typeErrors = true;
                } else {
                    typeResult.overloadsUsedForCall = callResult.overloadsUsedForCall;
                }

                if (callResult.isTypeIncomplete) {
                    typeResult.isIncomplete = true;
                }
            }

            if (baseTypeResult.isIncomplete) {
                typeResult.isIncomplete = true;
            }
        } else {
            typeResult.isIncomplete = true;
        }

        // Don't bother evaluating the arguments if we're speculatively evaluating the call
        // or the base type is incomplete.
        if (!isSpeculativeModeInUse(node) && !baseTypeResult.isIncomplete) {
            // Touch all of the args so they're marked accessed even if there were errors.
            // We skip this if it's a TypeVar() call in the typing.pyi module because
            // this results in a cyclical type resolution problem whereby we try to
            // retrieve the str class, which inherits from Sequence, which inherits from
            // Iterable, which uses a TypeVar. Without this, Iterable and Sequence classes
            // have invalid type parameters.
            const isCyclicalTypeVarCall =
                isInstantiableClass(baseTypeResult.type) &&
                ClassType.isBuiltIn(baseTypeResult.type, 'TypeVar') &&
                AnalyzerNodeInfo.getFileInfo(node).isTypingStubFile;

            if (!isCyclicalTypeVarCall) {
                argList.forEach((arg) => {
                    if (
                        arg.valueExpression &&
                        arg.valueExpression.nodeType !== ParseNodeType.StringList &&
                        !isTypeCached(arg.valueExpression)
                    ) {
                        getTypeOfExpression(arg.valueExpression);
                    }
                });
            }
        }

        if ((flags & EvalFlags.TypeExpression) !== 0) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeAnnotationCall(), node);

            typeResult = { type: UnknownType.create() };
        }

        return typeResult;
    }

    // This function is used in cases where a lambda is defined and immediately
    // called. In this case, we can't use normal bidirectional type inference
    // to determine the lambda's type. It needs to be inferred from the argument
    // types instead.
    function getTypeOfLambdaForCall(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult {
        assert(node.d.leftExpr.nodeType === ParseNodeType.Lambda);

        const expectedType = FunctionType.createSynthesizedInstance('');
        expectedType.shared.declaredReturnType = inferenceContext
            ? inferenceContext.expectedType
            : UnknownType.create();

        let isArgTypeIncomplete = false;
        node.d.args.forEach((arg, index) => {
            const argTypeResult = getTypeOfExpression(arg.d.valueExpr);
            if (argTypeResult.isIncomplete) {
                isArgTypeIncomplete = true;
            }

            FunctionType.addParam(
                expectedType,
                FunctionParam.create(
                    ParamCategory.Simple,
                    argTypeResult.type,
                    FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                    `p${index.toString()}`
                )
            );
        });

        // If the lambda's param list ends with a "/" positional parameter separator,
        // add a corresponding separator to the expected type.
        const lambdaParams = node.d.leftExpr.d.params;
        if (lambdaParams.length > 0) {
            const lastParam = lambdaParams[lambdaParams.length - 1];
            if (lastParam.d.category === ParamCategory.Simple && !lastParam.d.name) {
                FunctionType.addPositionOnlyParamSeparator(expectedType);
            }
        }

        function getLambdaType() {
            return getTypeOfExpression(node.d.leftExpr, EvalFlags.CallBaseDefaults, makeInferenceContext(expectedType));
        }

        // If one or more of the arguments are incomplete, use speculative mode
        // for the lambda evaluation because it may need to be reevaluated once
        // the arg types are complete.
        let typeResult =
            isArgTypeIncomplete || isSpeculativeModeInUse(node) || inferenceContext?.isTypeIncomplete
                ? useSpeculativeMode(node.d.leftExpr, getLambdaType)
                : getLambdaType();

        // If bidirectional type inference failed, use normal type inference instead.
        if (typeResult.typeErrors) {
            typeResult = getTypeOfExpression(node.d.leftExpr, EvalFlags.CallBaseDefaults);
        }

        return typeResult;
    }

    function getTypeOfTypeForm(node: CallNode, typeFormClass: ClassType): TypeResult {
        if (
            node.d.args.length !== 1 ||
            node.d.args[0].d.argCategory !== ArgCategory.Simple ||
            node.d.args[0].d.name !== undefined
        ) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.typeFormArgs(), node);
            return { type: UnknownType.create() };
        }

        const typeFormResult = getTypeOfArgExpectingType(convertNodeToArg(node.d.args[0]), {
            typeFormArg: true,
            noNonTypeSpecialForms: true,
            typeExpression: true,
        });

        if (!typeFormResult.typeErrors && typeFormResult.type.props?.typeForm) {
            typeFormResult.type = convertToInstance(
                ClassType.specialize(typeFormClass, [convertToInstance(typeFormResult.type.props.typeForm)])
            );
        }

        return typeFormResult;
    }

    function getTypeOfAssertType(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult {
        if (
            node.d.args.length !== 2 ||
            node.d.args[0].d.argCategory !== ArgCategory.Simple ||
            node.d.args[0].d.name !== undefined ||
            node.d.args[0].d.argCategory !== ArgCategory.Simple ||
            node.d.args[1].d.name !== undefined
        ) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.assertTypeArgs(), node);
            return { type: UnknownType.create() };
        }

        const arg0TypeResult = getTypeOfExpression(node.d.args[0].d.valueExpr, /* flags */ undefined, inferenceContext);
        if (arg0TypeResult.isIncomplete) {
            return { type: UnknownType.create(/* isIncomplete */ true), isIncomplete: true };
        }

        const assertedType = convertToInstance(
            getTypeOfArgExpectingType(convertNodeToArg(node.d.args[1]), {
                typeExpression: true,
            }).type
        );

        // We'll replace TypeGuard and TypeIs with bool for purposes of assert_type testing.
        // The spec is unclear on whether this is the correct behavior, but it seems to be
        // what mypy does -- and what various library authors expect.
        const arg0Type = stripTypeGuard(arg0TypeResult.type);

        if (
            !isTypeSame(assertedType, arg0Type, {
                treatAnySameAsUnknown: true,
                ignorePseudoGeneric: true,
                ignoreConditions: true,
            })
        ) {
            const srcDestTypes = printSrcDestTypes(arg0TypeResult.type, assertedType, { expandTypeAlias: true });

            addDiagnostic(
                DiagnosticRule.reportAssertTypeFailure,
                LocMessage.assertTypeTypeMismatch().format({
                    expected: srcDestTypes.destType,
                    received: srcDestTypes.sourceType,
                }),
                node.d.args[0].d.valueExpr
            );
        }

        return { type: arg0TypeResult.type };
    }

    function convertNodeToArg(node: ArgumentNode): ArgWithExpression {
        return {
            argCategory: node.d.argCategory,
            name: node.d.name,
            valueExpression: node.d.valueExpr,
        };
    }

    function getTypeOfRevealType(node: CallNode, inferenceContext: InferenceContext | undefined): TypeResult {
        let arg0Value: ExpressionNode | undefined;
        let expectedRevealTypeNode: ExpressionNode | undefined;
        let expectedRevealType: Type | undefined;
        let expectedTextNode: ExpressionNode | undefined;
        let expectedText: string | undefined;

        // Make sure there is only one positional argument passed as arg 0.
        node.d.args.forEach((arg, index) => {
            if (index === 0) {
                if (arg.d.argCategory === ArgCategory.Simple && !arg.d.name) {
                    arg0Value = arg.d.valueExpr;
                }
            } else if (arg.d.argCategory !== ArgCategory.Simple || !arg.d.name) {
                arg0Value = undefined;
            } else if (arg.d.name.d.value === 'expected_text') {
                expectedTextNode = arg.d.valueExpr;
                const expectedTextType = getTypeOfExpression(arg.d.valueExpr).type;

                if (
                    !isClassInstance(expectedTextType) ||
                    !ClassType.isBuiltIn(expectedTextType, 'str') ||
                    typeof expectedTextType.priv.literalValue !== 'string'
                ) {
                    addDiagnostic(
                        DiagnosticRule.reportCallIssue,
                        LocMessage.revealTypeExpectedTextArg(),
                        arg.d.valueExpr
                    );
                } else {
                    expectedText = expectedTextType.priv.literalValue;
                }
            } else if (arg.d.name.d.value === 'expected_type') {
                expectedRevealTypeNode = arg.d.valueExpr;
                expectedRevealType = convertToInstance(
                    getTypeOfArgExpectingType(convertNodeToArg(arg), {
                        typeExpression: true,
                    }).type
                );
            }
        });

        if (!arg0Value) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.revealTypeArgs(), node);
            return { type: UnknownType.create() };
        }

        const typeResult = getTypeOfExpression(arg0Value, /* flags */ undefined, inferenceContext);
        const type = typeResult.type;

        const exprString = ParseTreeUtils.printExpression(arg0Value);
        const typeString = printType(type, { expandTypeAlias: true });

        if (!typeResult.isIncomplete) {
            if (expectedText !== undefined) {
                if (expectedText !== typeString) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.revealTypeExpectedTextMismatch().format({
                            expected: expectedText,
                            received: typeString,
                        }),
                        expectedTextNode ?? arg0Value
                    );
                }
            }

            if (expectedRevealType) {
                if (!isTypeSame(expectedRevealType, type, { ignorePseudoGeneric: true })) {
                    const expectedRevealTypeText = printType(expectedRevealType);
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.revealTypeExpectedTypeMismatch().format({
                            expected: expectedRevealTypeText,
                            received: typeString,
                        }),
                        expectedRevealTypeNode ?? arg0Value
                    );
                }
            }

            addInformation(LocAddendum.typeOfSymbol().format({ name: exprString, type: typeString }), node.d.args[0]);
        }

        return { type, isIncomplete: typeResult.isIncomplete };
    }

    function getTypeOfRevealLocals(node: CallNode) {
        let curNode: ParseNode | undefined = node;
        let scope: Scope | undefined;

        while (curNode) {
            scope = ScopeUtils.getScopeForNode(curNode);

            // Stop when we get a valid scope that's not a list comprehension
            // scope. That includes lambdas, functions, classes, and modules.
            if (scope && scope.type !== ScopeType.Comprehension) {
                break;
            }

            curNode = curNode.parent;
        }

        const infoMessages: string[] = [];

        if (scope) {
            scope.symbolTable.forEach((symbol, name) => {
                if (!symbol.isIgnoredForProtocolMatch()) {
                    const typeOfSymbol = getEffectiveTypeOfSymbol(symbol);
                    infoMessages.push(
                        LocAddendum.typeOfSymbol().format({
                            name,
                            type: printType(typeOfSymbol, { expandTypeAlias: true }),
                        })
                    );
                }
            });
        }

        if (infoMessages.length > 0) {
            addInformation(infoMessages.join('\n'), node);
        } else {
            addInformation(LocMessage.revealLocalsNone(), node);
        }

        return getNoneType();
    }

    function getTypeOfSuperCall(node: CallNode): TypeResult {
        if (node.d.args.length > 2) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.superCallArgCount(), node.d.args[2]);
        }

        const enclosingFunction = ParseTreeUtils.getEnclosingFunctionEvaluationScope(node);
        const enclosingClass = enclosingFunction ? ParseTreeUtils.getEnclosingClass(enclosingFunction) : undefined;
        const enclosingClassType = enclosingClass ? getTypeOfClass(enclosingClass)?.classType : undefined;

        // Determine which class the "super" call is applied to. If
        // there is no first argument, then the class is implicit.
        let targetClassType: Type;
        if (node.d.args.length > 0) {
            targetClassType = getTypeOfExpression(node.d.args[0].d.valueExpr).type;
            const concreteTargetClassType = makeTopLevelTypeVarsConcrete(targetClassType);

            if (
                !isAnyOrUnknown(concreteTargetClassType) &&
                !isInstantiableClass(concreteTargetClassType) &&
                !isMetaclassInstance(concreteTargetClassType)
            ) {
                addDiagnostic(
                    DiagnosticRule.reportArgumentType,
                    LocMessage.superCallFirstArg().format({ type: printType(targetClassType) }),
                    node.d.args[0].d.valueExpr
                );
            }
        } else {
            if (enclosingClassType) {
                targetClassType = enclosingClassType ?? UnknownType.create();

                // Zero-argument forms of super are not allowed within static methods.
                // This results in a runtime exception.
                if (enclosingFunction) {
                    const functionInfo = getFunctionInfoFromDecorators(
                        evaluatorInterface,
                        enclosingFunction,
                        /* isInClass */ true
                    );

                    if ((functionInfo?.flags & FunctionTypeFlags.StaticMethod) !== 0) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.superCallZeroArgFormStaticMethod(),
                            node.d.leftExpr
                        );
                    }
                }
            } else {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.superCallZeroArgForm(),
                    node.d.leftExpr
                );
                targetClassType = UnknownType.create();
            }
        }

        const concreteTargetClassType = makeTopLevelTypeVarsConcrete(targetClassType);

        // Determine whether to further narrow the type.
        let secondArgType: Type | undefined;
        let bindToType: ClassType | undefined;

        if (node.d.args.length > 1) {
            secondArgType = getTypeOfExpression(node.d.args[1].d.valueExpr).type;
            const secondArgConcreteType = makeTopLevelTypeVarsConcrete(secondArgType);

            let reportError = false;

            doForEachSubtype(secondArgConcreteType, (secondArgSubtype) => {
                if (isAnyOrUnknown(secondArgSubtype)) {
                    // Ignore unknown or any types.
                } else if (isClassInstance(secondArgSubtype)) {
                    if (isInstantiableClass(concreteTargetClassType)) {
                        if (
                            !derivesFromClassRecursive(
                                ClassType.cloneAsInstantiable(secondArgSubtype),
                                concreteTargetClassType,
                                /* ignoreUnknown */ true
                            )
                        ) {
                            reportError = true;
                        }
                    }
                    bindToType = secondArgSubtype;
                } else if (isInstantiableClass(secondArgSubtype)) {
                    if (isInstantiableClass(concreteTargetClassType)) {
                        if (
                            !ClassType.isBuiltIn(concreteTargetClassType, 'type') &&
                            !derivesFromClassRecursive(
                                secondArgSubtype,
                                concreteTargetClassType,
                                /* ignoreUnknown */ true
                            )
                        ) {
                            reportError = true;
                        }
                    }
                    bindToType = secondArgSubtype;
                } else {
                    reportError = true;
                }
            });

            if (reportError) {
                addDiagnostic(
                    DiagnosticRule.reportArgumentType,
                    LocMessage.superCallSecondArg().format({ type: printType(targetClassType) }),
                    node.d.args[1].d.valueExpr
                );

                return { type: UnknownType.create() };
            }
        } else if (enclosingClassType) {
            bindToType = ClassType.cloneAsInstance(enclosingClassType);

            // Get the type from the self or cls parameter if it is explicitly annotated.
            // If it's a TypeVar, change the bindToType into a conditional type.
            const enclosingMethod = ParseTreeUtils.getEnclosingFunction(node);
            let implicitBindToType: Type | undefined;

            if (enclosingMethod) {
                const methodTypeInfo = getTypeOfFunction(enclosingMethod);
                if (methodTypeInfo) {
                    const methodType = methodTypeInfo.functionType;
                    if (
                        FunctionType.isClassMethod(methodType) ||
                        FunctionType.isConstructorMethod(methodType) ||
                        FunctionType.isInstanceMethod(methodType)
                    ) {
                        if (
                            methodType.shared.parameters.length > 0 &&
                            FunctionParam.isTypeDeclared(methodType.shared.parameters[0])
                        ) {
                            let paramType = FunctionType.getParamType(methodType, 0);
                            const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node);
                            paramType = makeTypeVarsBound(paramType, liveScopeIds);
                            implicitBindToType = makeTopLevelTypeVarsConcrete(paramType);
                        }
                    }
                }
            }

            if (bindToType && implicitBindToType) {
                const typeCondition = getTypeCondition(implicitBindToType);
                if (typeCondition) {
                    bindToType = addConditionToType(bindToType, typeCondition);
                } else if (isClass(implicitBindToType)) {
                    bindToType = implicitBindToType;
                }
            }
        }

        // Determine whether super() should return an instance of the class or
        // the class itself. It depends on whether the super() call is located
        // within an instance method or not.
        let resultIsInstance = true;
        if (node.d.args.length <= 1) {
            const enclosingMethod = ParseTreeUtils.getEnclosingFunction(node);
            if (enclosingMethod) {
                const methodType = getTypeOfFunction(enclosingMethod);
                if (methodType) {
                    if (
                        FunctionType.isStaticMethod(methodType.functionType) ||
                        FunctionType.isConstructorMethod(methodType.functionType) ||
                        FunctionType.isClassMethod(methodType.functionType)
                    ) {
                        resultIsInstance = false;
                    }
                }
            }
        }

        // Python docs indicate that super() isn't valid for
        // operations other than member accesses or attribute lookups.
        const parentNode = node.parent;
        if (parentNode?.nodeType === ParseNodeType.MemberAccess) {
            const memberName = parentNode.d.member.d.value;
            let effectiveTargetClass = isClass(concreteTargetClassType) ? concreteTargetClassType : undefined;

            // If the bind-to type is a protocol, don't use the effective target class.
            // This pattern is used for mixins, where the mixin type is a protocol class
            // that is used to decorate the "self" or "cls" parameter.
            let isProtocolClass = false;
            if (
                bindToType &&
                ClassType.isProtocolClass(bindToType) &&
                effectiveTargetClass &&
                !ClassType.isSameGenericClass(
                    TypeBase.isInstance(bindToType) ? ClassType.cloneAsInstantiable(bindToType) : bindToType,
                    effectiveTargetClass
                )
            ) {
                isProtocolClass = true;
                effectiveTargetClass = undefined;
            }

            if (bindToType) {
                bindToType = selfSpecializeClass(bindToType, { useBoundTypeVars: true });
            }

            const lookupResults = bindToType
                ? lookUpClassMember(bindToType, memberName, MemberAccessFlags.Default, effectiveTargetClass)
                : undefined;

            let resultType: Type;
            if (lookupResults && isInstantiableClass(lookupResults.classType)) {
                resultType = lookupResults.classType;

                if (isProtocolClass) {
                    // If the bindToType is a protocol class, set the "include subclasses" flag
                    // so we don't enforce that called methods are implemented within the protocol.
                    resultType = ClassType.cloneIncludeSubclasses(resultType);
                }
            } else if (
                effectiveTargetClass &&
                !isAnyOrUnknown(effectiveTargetClass) &&
                !derivesFromAnyOrUnknown(effectiveTargetClass)
            ) {
                resultType = prefetched?.objectClass ?? UnknownType.create();
            } else {
                resultType = UnknownType.create();
            }

            let bindToSelfType: ClassType | TypeVarType | undefined;
            if (bindToType) {
                if (secondArgType) {
                    // If a TypeVar was passed as the second argument, use it
                    // to derive the the self type.
                    if (isTypeVar(secondArgType)) {
                        bindToSelfType = convertToInstance(secondArgType);
                    }
                } else {
                    // If this is a zero-argument form of super(), synthesize
                    // a Self type to bind to.
                    bindToSelfType = TypeBase.cloneForCondition(
                        TypeVarType.cloneAsBound(
                            synthesizeTypeVarForSelfCls(
                                ClassType.cloneIncludeSubclasses(bindToType, /* includeSubclasses */ false),
                                /* isClsParam */ false
                            )
                        ),
                        bindToType.props?.condition
                    );
                }
            }

            const type = resultIsInstance ? convertToInstance(resultType, /* includeSubclasses */ false) : resultType;

            return { type, bindToSelfType };
        }

        // Handle the super() call when used outside of a member access expression.
        if (isInstantiableClass(concreteTargetClassType)) {
            // We don't know which member is going to be accessed, so we cannot
            // deterministically determine the correct type in this case. We'll
            // use a heuristic that produces the "correct" (desired) behavior in
            // most cases. If there's a bindToType and the targetClassType is one
            // of the base classes of the bindToType, we'll return the next base
            // class.
            if (bindToType) {
                let nextBaseClassType: Type | undefined;

                if (
                    ClassType.isSameGenericClass(
                        TypeBase.isInstance(bindToType) ? ClassType.cloneAsInstantiable(bindToType) : bindToType,
                        concreteTargetClassType
                    )
                ) {
                    if (bindToType.shared.baseClasses.length > 0) {
                        nextBaseClassType = bindToType.shared.baseClasses[0];
                    }
                } else {
                    const baseClassIndex = bindToType.shared.baseClasses.findIndex(
                        (baseClass) =>
                            isClass(baseClass) &&
                            ClassType.isSameGenericClass(baseClass, concreteTargetClassType as ClassType)
                    );

                    if (baseClassIndex >= 0 && baseClassIndex < bindToType.shared.baseClasses.length - 1) {
                        nextBaseClassType = bindToType.shared.baseClasses[baseClassIndex + 1];
                    }
                }

                if (nextBaseClassType) {
                    if (isInstantiableClass(nextBaseClassType)) {
                        nextBaseClassType = specializeForBaseClass(bindToType, nextBaseClassType);
                    }
                    return { type: resultIsInstance ? convertToInstance(nextBaseClassType) : nextBaseClassType };
                }

                // There's not much we can say about the type. Simply return object or type.
                if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) {
                    return {
                        type: resultIsInstance ? getObjectType() : convertToInstance(prefetched.typeClass),
                    };
                }
            } else {
                // If the class derives from one or more unknown classes,
                // return unknown here to prevent spurious errors.
                if (concreteTargetClassType.shared.mro.some((mroBase) => isAnyOrUnknown(mroBase))) {
                    return { type: UnknownType.create() };
                }

                const baseClasses = concreteTargetClassType.shared.baseClasses;
                if (baseClasses.length > 0) {
                    const baseClassType = baseClasses[0];
                    if (isInstantiableClass(baseClassType)) {
                        return {
                            type: resultIsInstance ? ClassType.cloneAsInstance(baseClassType) : baseClassType,
                        };
                    }
                }
            }
        }

        return { type: UnknownType.create() };
    }

    // When evaluating a call, the errorNode is typically the call node, which
    // encompasses all of the argument expressions. This means we can normally
    // use the errorNode as the root for speculative evaluation. However, there
    // are some cases where we don't have a call node (e.g. in the case of an
    // __init_subclass__ validation). Here we need to find some other parent
    // node of the error node that encompasses all of the arguments.
    function getSpeculativeNodeForCall(errorNode: ExpressionNode): ParseNode {
        // If the error node is within an arg, expand to include the parent of the arg list.
        const argParent = ParseTreeUtils.getParentNodeOfType(errorNode, ParseNodeType.Argument);
        if (argParent?.parent) {
            return argParent.parent;
        }

        // If the error node is the name in a class declaration, expand to include the class node.
        if (
            errorNode.nodeType === ParseNodeType.Name &&
            errorNode.parent?.nodeType === ParseNodeType.Class &&
            errorNode.parent.d.name === errorNode
        ) {
            return errorNode.parent;
        }

        return errorNode;
    }

    // Attempts to find an overloaded function for each set of argument
    // types in the expandedArgTypes list. If an argument type is undefined,
    // its type is evaluated from the argument's expression using the
    // corresponding parameter's expected type. The first time this is called,
    // there will be only one argument list in expandedArgTypes, and all entries
    // (one for each argument) will be undefined. On subsequent calls, this
    // list will grow to include union expansions.
    function validateOverloadsWithExpandedTypes(
        errorNode: ExpressionNode,
        expandedArgTypes: (Type | undefined)[][],
        argParamMatches: MatchArgsToParamsResult[],
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        const returnTypes: Type[] = [];
        let matchedOverloads: MatchedOverloadInfo[] = [];
        let isTypeIncomplete = false;
        let overloadsUsedForCall: FunctionType[] = [];
        let isDefinitiveMatchFound = false;
        const speculativeNode = getSpeculativeNodeForCall(errorNode);

        for (let expandedTypesIndex = 0; expandedTypesIndex < expandedArgTypes.length; expandedTypesIndex++) {
            let matchedOverload: FunctionType | undefined;
            const argTypeOverride = expandedArgTypes[expandedTypesIndex];
            const hasArgTypeOverride = argTypeOverride.some((a) => a !== undefined);
            let possibleMatchResults: MatchedOverloadInfo[] = [];
            let possibleMatchInvolvesIncompleteUnknown = false;
            isDefinitiveMatchFound = false;

            for (let overloadIndex = 0; overloadIndex < argParamMatches.length; overloadIndex++) {
                const overload = argParamMatches[overloadIndex].overload;

                let matchResults = argParamMatches[overloadIndex];
                if (hasArgTypeOverride) {
                    matchResults = { ...argParamMatches[overloadIndex] };
                    matchResults.argParams = matchResults.argParams.map((argParam, argIndex) => {
                        if (!argTypeOverride[argIndex]) {
                            return argParam;
                        }
                        const argParamCopy = { ...argParam };
                        argParamCopy.argType = argTypeOverride[argIndex];
                        return argParamCopy;
                    });
                }

                // Clone the constraints so we don't modify the original.
                const effectiveConstraints = constraints?.clone() ?? new ConstraintTracker();

                // Use speculative mode so we don't output any diagnostics or
                // record any final types in the type cache.
                const callResult = useSpeculativeMode(speculativeNode, () => {
                    return validateArgTypesWithContext(
                        errorNode,
                        matchResults,
                        effectiveConstraints,
                        /* skipUnknownArgCheck */ true,
                        inferenceContext
                    );
                });

                if (callResult.isTypeIncomplete) {
                    isTypeIncomplete = true;
                }

                if (!callResult.argumentErrors && callResult.returnType) {
                    overloadsUsedForCall.push(overload);

                    matchedOverload = overload;
                    const matchedOverloadInfo: MatchedOverloadInfo = {
                        overload: matchedOverload,
                        matchResults,
                        constraints: effectiveConstraints,
                        returnType: callResult.returnType,
                        argResults: callResult.argResults ?? [],
                    };
                    matchedOverloads.push(matchedOverloadInfo);

                    if (callResult.anyOrUnknownArg || matchResults.unpackedArgOfUnknownLength) {
                        possibleMatchResults.push(matchedOverloadInfo);

                        if (callResult.anyOrUnknownArg) {
                            if (isIncompleteUnknown(callResult.anyOrUnknownArg)) {
                                possibleMatchInvolvesIncompleteUnknown = true;
                            }
                        }
                    } else {
                        returnTypes.push(callResult.returnType);
                        isDefinitiveMatchFound = true;
                        break;
                    }
                }
            }

            // If we didn't find a definitive match that doesn't depend on
            // an Any or Unknown argument, fall back on the possible match.
            // If there were multiple possible matches, evaluate the type as
            // Unknown, but include the "possible types" to allow for completion
            // suggestions.
            if (!isDefinitiveMatchFound && possibleMatchResults.length > 0) {
                possibleMatchResults = filterOverloadMatchesForUnpackedArgs(possibleMatchResults);
                possibleMatchResults = filterOverloadMatchesForAnyArgs(possibleMatchResults);

                // Did the filtering produce a single result? If so, we're done.
                if (possibleMatchResults.length === 1) {
                    overloadsUsedForCall = [possibleMatchResults[0].overload];
                    returnTypes.push(possibleMatchResults[0].returnType);
                    matchedOverloads = [possibleMatchResults[0]];
                } else {
                    // Eliminate any return types that are subsumed by other return types.
                    let dedupedMatchResults: Type[] = [];
                    let dedupedResultsIncludeAny = false;

                    possibleMatchResults.forEach((result) => {
                        let isSubtypeSubsumed = false;

                        for (let dedupedIndex = 0; dedupedIndex < dedupedMatchResults.length; dedupedIndex++) {
                            if (assignType(dedupedMatchResults[dedupedIndex], result.returnType)) {
                                const anyOrUnknown = containsAnyOrUnknown(
                                    dedupedMatchResults[dedupedIndex],
                                    /* recurse */ false
                                );
                                if (!anyOrUnknown) {
                                    isSubtypeSubsumed = true;
                                } else if (isAny(anyOrUnknown)) {
                                    dedupedResultsIncludeAny = true;
                                }
                                break;
                            } else if (assignType(result.returnType, dedupedMatchResults[dedupedIndex])) {
                                const anyOrUnknown = containsAnyOrUnknown(result.returnType, /* recurse */ false);
                                if (!anyOrUnknown) {
                                    dedupedMatchResults[dedupedIndex] = NeverType.createNever();
                                } else if (isAny(anyOrUnknown)) {
                                    dedupedResultsIncludeAny = true;
                                }
                                break;
                            }
                        }

                        if (!isSubtypeSubsumed) {
                            dedupedMatchResults.push(result.returnType);
                        }
                    });

                    dedupedMatchResults = dedupedMatchResults.filter((t) => !isNever(t));
                    const combinedTypes = combineTypes(dedupedMatchResults);

                    let returnType = combinedTypes;
                    if (dedupedMatchResults.length > 1) {
                        // If one or more of the deduped types is Any or contains Any,
                        // we will assume that the person who defined the overload really
                        // wanted Any rather than Unknown. In cases where the deduped types
                        // simply contains conflicting results without an Any, we'll use
                        // an UnknownType.
                        if (dedupedResultsIncludeAny) {
                            returnType = AnyType.create();
                        } else {
                            returnType = UnknownType.createPossibleType(
                                combinedTypes,
                                possibleMatchInvolvesIncompleteUnknown
                            );
                        }
                    }

                    returnTypes.push(returnType);
                }
            }

            if (!matchedOverload) {
                return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall };
            }
        }

        // We found a match for all of the expanded argument lists. Copy the
        // resulting type var context back into the caller's type var context.
        // Use the type var context from the last matched overload because it
        // includes the type var solutions for all earlier matched overloads.
        if (constraints && isDefinitiveMatchFound) {
            constraints.copyFromClone(matchedOverloads[matchedOverloads.length - 1].constraints);
        }

        // And run through the first expanded argument list one more time to
        // populate the type cache.
        const finalConstraints = constraints ?? matchedOverloads[0].constraints;
        const finalCallResult = validateArgTypesWithContext(
            errorNode,
            matchedOverloads[0].matchResults,
            finalConstraints,
            skipUnknownArgCheck,
            inferenceContext
        );

        if (finalCallResult.isTypeIncomplete) {
            isTypeIncomplete = true;
        }

        return {
            argumentErrors: finalCallResult.argumentErrors,
            anyOrUnknownArg: finalCallResult.anyOrUnknownArg,
            returnType: combineTypes(returnTypes),
            isTypeIncomplete,
            specializedInitSelfType: finalCallResult.specializedInitSelfType,
            overloadsUsedForCall,
        };
    }

    // Determines whether one or more overloads can be eliminated because they
    // rely on an unpacked argument of unknown length when there is at least
    // one overload that doesn't because it maps to an *args parameter.
    function filterOverloadMatchesForUnpackedArgs(matches: MatchedOverloadInfo[]): MatchedOverloadInfo[] {
        if (matches.length < 2) {
            return matches;
        }

        // Is there at least one overload that relies on unpacked args for a match?
        const unpackedArgsOverloads = matches.filter((match) => match.matchResults.unpackedArgMapsToVariadic);
        if (unpackedArgsOverloads.length === matches.length || unpackedArgsOverloads.length === 0) {
            return matches;
        }

        return unpackedArgsOverloads;
    }

    // Determines whether multiple incompatible overloads match
    // due to an Any or Unknown argument type.
    function filterOverloadMatchesForAnyArgs(matches: MatchedOverloadInfo[]): MatchedOverloadInfo[] {
        if (matches.length < 2) {
            return matches;
        }

        // If all of the return types match, select the first one.
        if (
            areTypesSame(
                matches.map((match) => match.returnType),
                { treatAnySameAsUnknown: true }
            )
        ) {
            return [matches[0]];
        }

        const firstArgResults = matches[0].argResults;
        if (!firstArgResults) {
            return matches;
        }

        let foundAmbiguousAnyArg = false;
        for (let i = 0; i < firstArgResults.length; i++) {
            // If the arg is Any or Unknown, see if the corresponding
            // parameter types differ in any way.
            if (isAnyOrUnknown(firstArgResults[i].argType)) {
                const paramTypes = matches.map((match) =>
                    i < match.matchResults.argParams.length
                        ? match.matchResults.argParams[i].paramType
                        : UnknownType.create()
                );
                if (!areTypesSame(paramTypes, { treatAnySameAsUnknown: true })) {
                    foundAmbiguousAnyArg = true;
                }
            }
        }

        // If the first overload has a different number of effective arguments
        // than latter overloads, don't filter any of them. This typically means
        // that one of the arguments is an unpacked iterator, and it maps to
        // an indeterminate number of parameters, which means that the overload
        // selection is ambiguous.
        if (foundAmbiguousAnyArg || matches.some((match) => match.argResults.length !== firstArgResults.length)) {
            return matches;
        }

        return [matches[0]];
    }

    function getBestOverloadForArgs(
        errorNode: ExpressionNode,
        typeResult: TypeResult<OverloadedType>,
        argList: Arg[]
    ): FunctionType | undefined {
        let overloadIndex = 0;
        const matches: MatchArgsToParamsResult[] = [];
        const speculativeNode = getSpeculativeNodeForCall(errorNode);

        useSignatureTracker(errorNode, () => {
            // Create a list of potential overload matches based on arguments.
            OverloadedType.getOverloads(typeResult.type).forEach((overload) => {
                useSpeculativeMode(speculativeNode, () => {
                    const matchResults = matchArgsToParams(
                        errorNode,
                        argList,
                        { type: overload, isIncomplete: typeResult.isIncomplete },
                        overloadIndex
                    );

                    if (!matchResults.argumentErrors) {
                        matches.push(matchResults);
                    }

                    overloadIndex++;
                });
            });
        });

        let winningOverloadIndex: number | undefined;

        matches.forEach((match, matchIndex) => {
            if (winningOverloadIndex === undefined) {
                useSpeculativeMode(speculativeNode, () => {
                    const callResult = validateArgTypes(
                        errorNode,
                        match,
                        new ConstraintTracker(),
                        /* skipUnknownArgCheck */ true
                    );

                    if (callResult && !callResult.argumentErrors) {
                        winningOverloadIndex = matchIndex;
                    }
                });
            }
        });

        return winningOverloadIndex === undefined ? undefined : matches[winningOverloadIndex].overload;
    }

    function validateOverloadedArgTypes(
        errorNode: ExpressionNode,
        argList: Arg[],
        typeResult: TypeResult<OverloadedType>,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        const filteredMatchResults: MatchArgsToParamsResult[] = [];
        let contextFreeArgTypes: Type[] | undefined;
        let isTypeIncomplete = !!typeResult.isIncomplete;
        const type = typeResult.type;
        const speculativeNode = getSpeculativeNodeForCall(errorNode);

        // Start by evaluating the types of the arguments without any expected
        // type. Also, filter the list of overloads based on the number of
        // positional and keyword arguments that are present. We do all of this
        // speculatively because we don't want to record any types in the type
        // cache or record any diagnostics at this stage.
        useSpeculativeMode(speculativeNode, () => {
            let overloadIndex = 0;
            OverloadedType.getOverloads(type).forEach((overload) => {
                // Consider only the functions that have the @overload decorator,
                // not the final function that omits the overload. This is the
                // intended behavior according to PEP 484.
                const matchResults = matchArgsToParams(
                    errorNode,
                    argList,
                    { type: overload, isIncomplete: typeResult.isIncomplete },
                    overloadIndex
                );

                if (!matchResults.argumentErrors) {
                    filteredMatchResults.push(matchResults);
                }

                overloadIndex++;
            });
        });

        // If there are no possible arg/param matches among the overloads,
        // emit an error that includes the argument types.
        if (filteredMatchResults.length === 0) {
            // Skip the error message if we're in speculative mode because it's very
            // expensive, and we're going to suppress the diagnostic anyway.
            if (!canSkipDiagnosticForNode(errorNode)) {
                const overloads = OverloadedType.getOverloads(type);
                const functionName =
                    overloads.length > 0 && overloads[0].shared.name
                        ? overloads[0].shared.name
                        : '<anonymous function>';
                const diagAddendum = new DiagnosticAddendum();
                const argTypes = argList.map((t) => {
                    const typeString = printType(getTypeOfArg(t, /* inferenceContext */ undefined).type);

                    if (t.argCategory === ArgCategory.UnpackedList) {
                        return `*${typeString}`;
                    }

                    if (t.argCategory === ArgCategory.UnpackedDictionary) {
                        return `**${typeString}`;
                    }

                    return typeString;
                });

                diagAddendum.addMessage(LocAddendum.argumentTypes().format({ types: argTypes.join(', ') }));
                addDiagnostic(
                    DiagnosticRule.reportCallIssue,
                    LocMessage.noOverload().format({ name: functionName }) + diagAddendum.getString(),
                    errorNode
                );
            }

            return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall: [] };
        }

        // Create a helper function that evaluates the overload that best
        // matches the arg/param lists.
        function evaluateUsingBestMatchingOverload(skipUnknownArgCheck: boolean, emitNoOverloadFoundError: boolean) {
            // Find the match with the smallest argument match score. If there
            // are more than one with the same score, use the one with the
            // largest index. Later overloads tend to be more general.
            const bestMatch = filteredMatchResults.reduce((previous, current) => {
                if (current.argumentMatchScore === previous.argumentMatchScore) {
                    return current.overloadIndex > previous.overloadIndex ? current : previous;
                }
                return current.argumentMatchScore < previous.argumentMatchScore ? current : previous;
            });

            // If there is more than one filtered match, report that no match
            // was possible and emit a diagnostic that provides the most likely.
            if (emitNoOverloadFoundError) {
                const functionName = bestMatch.overload.shared.name || '<anonymous function>';
                const diagnostic = addDiagnostic(
                    DiagnosticRule.reportCallIssue,
                    LocMessage.noOverload().format({ name: functionName }),
                    errorNode
                );

                const overrideDecl = bestMatch.overload.shared.declaration;
                if (diagnostic && overrideDecl) {
                    diagnostic.addRelatedInfo(
                        LocAddendum.overloadIndex().format({ index: bestMatch.overloadIndex + 1 }),
                        overrideDecl.uri,
                        overrideDecl.range
                    );
                }
            }

            const effectiveConstraints = constraints ?? new ConstraintTracker();

            return validateArgTypesWithContext(
                errorNode,
                bestMatch,
                effectiveConstraints,
                skipUnknownArgCheck,
                inferenceContext
            );
        }

        // If there is only one possible arg/param match among the overloads,
        // use the normal type matching mechanism because it is faster and
        // will provide a clearer error message.
        if (filteredMatchResults.length === 1) {
            return evaluateUsingBestMatchingOverload(
                /* skipUnknownArgCheck */ false,
                /* emitNoOverloadFoundError */ false
            );
        }

        let expandedArgTypes: (Type | undefined)[][] | undefined = [argList.map((arg) => undefined)];

        while (true) {
            const callResult = validateOverloadsWithExpandedTypes(
                errorNode,
                expandedArgTypes,
                filteredMatchResults,
                constraints,
                skipUnknownArgCheck,
                inferenceContext
            );

            if (callResult.isTypeIncomplete) {
                isTypeIncomplete = true;
            }

            if (!callResult.argumentErrors) {
                return callResult;
            }

            // We didn't find an overload match. Try to expand the next union
            // argument type into individual types and retry with the expanded types.
            if (!contextFreeArgTypes) {
                useSpeculativeMode(getSpeculativeNodeForCall(errorNode), () => {
                    // Evaluate the types of each argument expression without regard to
                    // the context. We'll use this to determine whether we need to do
                    // union expansion.
                    contextFreeArgTypes = argList.map((arg) => {
                        if (arg.typeResult) {
                            return arg.typeResult.type;
                        }

                        if (arg.valueExpression) {
                            const valueExpressionNode = arg.valueExpression;
                            return useSpeculativeMode(valueExpressionNode, () => {
                                return getTypeOfExpression(valueExpressionNode).type;
                            });
                        }

                        return AnyType.create();
                    });
                });
            }

            expandedArgTypes = expandArgTypes(contextFreeArgTypes!, expandedArgTypes);

            // Check for combinatoric explosion and break out of loop.
            if (!expandedArgTypes || expandedArgTypes.length > maxTotalOverloadArgTypeExpansionCount) {
                break;
            }
        }

        // We couldn't find any valid overloads. Skip the error message if we're
        // in speculative mode because it's very expensive, and we're going to
        // suppress the diagnostic anyway.
        if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
            const result = evaluateUsingBestMatchingOverload(
                /* skipUnknownArgCheck */ true,
                /* emitNoOverloadFoundError */ true
            );

            // Replace the result with an unknown type since we don't know
            // what overload should have been used.
            result.returnType = UnknownType.create();
            return { ...result, argumentErrors: true };
        }

        return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall: [] };
    }

    // Replaces each item in the expandedArgTypes with n items where n is
    // the number of subtypes in a union or other expandable type.
    // The contextFreeArgTypes parameter represents the types of the arguments
    // evaluated with no bidirectional type inference (i.e. without the help of
    // the corresponding parameter's expected type). If the function returns
    // undefined, that indicates that all types have been expanded, and no
    // more expansion is possible.
    function expandArgTypes(
        contextFreeArgTypes: Type[],
        expandedArgTypes: (Type | undefined)[][]
    ): (Type | undefined)[][] | undefined {
        // Find the rightmost already-expanded argument.
        let indexToExpand = contextFreeArgTypes.length - 1;
        while (indexToExpand >= 0 && !expandedArgTypes[0][indexToExpand]) {
            indexToExpand--;
        }

        // Move to the next candidate for expansion.
        indexToExpand++;

        if (indexToExpand >= contextFreeArgTypes.length) {
            return undefined;
        }

        let expandedTypes: Type[] | undefined;
        while (indexToExpand < contextFreeArgTypes.length) {
            // Is this a union type? If so, we can expand it.
            const argType = contextFreeArgTypes[indexToExpand];

            expandedTypes = expandArgType(argType);
            if (expandedTypes) {
                break;
            }
            indexToExpand++;
        }

        // We have nothing left to expand.
        if (!expandedTypes) {
            return undefined;
        }

        // Expand entry indexToExpand.
        const newExpandedArgTypes: (Type | undefined)[][] = [];

        expandedArgTypes.forEach((preExpandedTypes) => {
            expandedTypes.forEach((subtype) => {
                const expandedTypes = [...preExpandedTypes];
                expandedTypes[indexToExpand] = subtype;
                newExpandedArgTypes.push(expandedTypes);
            });
        });

        return newExpandedArgTypes;
    }

    function expandArgType(type: Type): Type[] | undefined {
        const expandedTypes: Type[] = [];

        // Expand any top-level type variables with constraints.
        type = makeTopLevelTypeVarsConcrete(type);

        doForEachSubtype(type, (subtype) => {
            if (isClassInstance(subtype)) {
                // Expand any bool or Enum literals.
                const expandedLiteralTypes = enumerateLiteralsForType(evaluatorInterface, subtype);
                if (expandedLiteralTypes && expandedLiteralTypes.length <= maxSingleOverloadArgTypeExpansionCount) {
                    appendArray(expandedTypes, expandedLiteralTypes);
                    return;
                }

                // Expand any fixed-size tuples.
                const expandedTuples = expandTuple(subtype, maxSingleOverloadArgTypeExpansionCount);
                if (expandedTuples) {
                    appendArray(expandedTypes, expandedTuples);
                    return;
                }
            }

            expandedTypes.push(subtype);
        });

        return expandedTypes.length > 1 ? expandedTypes : undefined;
    }

    // Validates that the arguments can be assigned to the call's parameter
    // list, specializes the call based on arg types, and returns the
    // specialized type of the return value. If it detects an error along
    // the way, it emits a diagnostic and sets argumentErrors to true.
    function validateCallArgs(
        errorNode: ExpressionNode,
        argList: Arg[],
        callTypeResult: TypeResult,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined,
        recursionCount = 0
    ): CallResult {
        let argumentErrors = false;
        let isTypeIncomplete = false;
        let specializedInitSelfType: Type | undefined;
        const overloadsUsedForCall: FunctionType[] = [];

        if (recursionCount > maxTypeRecursionCount) {
            return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall };
        }
        recursionCount++;

        // Special forms are not callable.
        if (callTypeResult.type.props?.specialForm) {
            const exprNode = errorNode.nodeType === ParseNodeType.Call ? errorNode.d.leftExpr : errorNode;
            addDiagnostic(
                DiagnosticRule.reportCallIssue,
                LocMessage.objectNotCallable().format({
                    type: printType(callTypeResult.type.props.specialForm, { expandTypeAlias: true }),
                }),
                exprNode
            );
            return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall };
        }

        let returnType = mapSubtypesExpandTypeVars(
            callTypeResult.type,
            { sortSubtypes: true },
            (expandedSubtype, unexpandedSubtype, isLastIteration) => {
                return useSpeculativeMode(
                    isLastIteration ? undefined : getSpeculativeNodeForCall(errorNode),
                    () => {
                        const callResult = validateCallArgsForSubtype(
                            errorNode,
                            argList,
                            expandedSubtype,
                            unexpandedSubtype,
                            !!callTypeResult.isIncomplete,
                            constraints,
                            skipUnknownArgCheck,
                            inferenceContext,
                            recursionCount
                        );

                        if (callResult.argumentErrors) {
                            argumentErrors = true;
                        }

                        if (callResult.isTypeIncomplete) {
                            isTypeIncomplete = true;
                        }

                        if (callResult.overloadsUsedForCall) {
                            appendArray(overloadsUsedForCall, callResult.overloadsUsedForCall);
                        }

                        specializedInitSelfType = callResult.specializedInitSelfType;

                        return callResult.returnType;
                    },
                    {
                        allowDiagnostics: true,
                    }
                );
            }
        );

        // If we ended up with a "Never" type because all code paths returned
        // undefined due to argument errors, transform the result into an Unknown
        // to avoid subsequent false positives.
        if (argumentErrors && isNever(returnType) && !returnType.priv.isNoReturn) {
            returnType = UnknownType.create();
        }

        return {
            argumentErrors,
            returnType,
            isTypeIncomplete,
            specializedInitSelfType,
            overloadsUsedForCall,
        };
    }

    function validateCallArgsForSubtype(
        errorNode: ExpressionNode,
        argList: Arg[],
        expandedCallType: Type,
        unexpandedCallType: Type,
        isCallTypeIncomplete: boolean,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined,
        recursionCount: number
    ): CallResult {
        function touchArgTypes() {
            if (!isCallTypeIncomplete) {
                argList.forEach((arg) => {
                    if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) {
                        getTypeOfArg(arg, /* inferenceContext */ undefined);
                    }
                });
            }
        }

        switch (expandedCallType.category) {
            case TypeCategory.Never:
            case TypeCategory.Unknown:
            case TypeCategory.Any: {
                // Create a dummy callable that accepts all arguments and validate
                // that the argument expressions are valid.
                const dummyFunctionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.None);
                FunctionType.addDefaultParams(dummyFunctionType);

                const dummyCallResult = validateCallForFunction(
                    errorNode,
                    argList,
                    dummyFunctionType,
                    isCallTypeIncomplete,
                    constraints,
                    skipUnknownArgCheck,
                    inferenceContext
                );

                return { ...dummyCallResult, returnType: expandedCallType };
            }

            case TypeCategory.Function: {
                return validateCallForFunction(
                    errorNode,
                    argList,
                    expandedCallType,
                    isCallTypeIncomplete,
                    constraints,
                    skipUnknownArgCheck,
                    inferenceContext
                );
            }

            case TypeCategory.Overloaded: {
                return validateCallForOverloaded(
                    errorNode,
                    argList,
                    expandedCallType,
                    isCallTypeIncomplete,
                    constraints,
                    skipUnknownArgCheck,
                    inferenceContext
                );
            }

            case TypeCategory.Class: {
                if (isNoneInstance(expandedCallType)) {
                    addDiagnostic(DiagnosticRule.reportOptionalCall, LocMessage.noneNotCallable(), errorNode);

                    touchArgTypes();
                    return { argumentErrors: true };
                }

                if (TypeBase.isInstantiable(expandedCallType)) {
                    return validateCallForInstantiableClass(
                        errorNode,
                        argList,
                        expandedCallType,
                        unexpandedCallType,
                        skipUnknownArgCheck,
                        inferenceContext
                    );
                }

                return validateCallForClassInstance(
                    errorNode,
                    argList,
                    expandedCallType,
                    unexpandedCallType,
                    constraints,
                    skipUnknownArgCheck,
                    inferenceContext,
                    recursionCount
                );
            }

            // TypeVars should have been expanded in most cases,
            // but we still need to handle the case of Type[T] where
            // T is a constrained type that contains a union. We also
            // need to handle recursive type aliases.
            case TypeCategory.TypeVar: {
                return validateCallArgs(
                    errorNode,
                    argList,
                    { type: transformPossibleRecursiveTypeAlias(expandedCallType), isIncomplete: isCallTypeIncomplete },
                    constraints,
                    skipUnknownArgCheck,
                    inferenceContext,
                    recursionCount
                );
            }

            case TypeCategory.Module: {
                addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.moduleNotCallable(), errorNode);

                touchArgTypes();
                return { argumentErrors: true };
            }
        }

        touchArgTypes();
        return { argumentErrors: true };
    }

    function validateCallForFunction(
        errorNode: ExpressionNode,
        argList: Arg[],
        type: FunctionType,
        isCallTypeIncomplete: boolean,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        if (TypeBase.isInstantiable(type)) {
            addDiagnostic(
                DiagnosticRule.reportCallIssue,
                LocMessage.callableNotInstantiable().format({
                    type: printType(type),
                }),
                errorNode
            );
            return { returnType: undefined, argumentErrors: true };
        }

        // The stdlib collections/__init__.pyi stub file defines namedtuple
        // as a function rather than a class, so we need to check for it here.
        if (FunctionType.isBuiltIn(type, 'namedtuple')) {
            addDiagnostic(DiagnosticRule.reportUntypedNamedTuple, LocMessage.namedTupleNoTypes(), errorNode);

            const result: CallResult = {
                returnType: createNamedTupleType(evaluatorInterface, errorNode, argList, /* includesTypes */ false),
            };

            validateArgs(errorNode, argList, { type: type }, constraints, skipUnknownArgCheck, inferenceContext);

            return result;
        }

        // Handle the NewType specially, replacing the normal return type.
        if (FunctionType.isBuiltIn(type, 'NewType')) {
            return { returnType: createNewType(errorNode, argList) };
        }

        const functionResult = validateArgs(
            errorNode,
            argList,
            { type, isIncomplete: isCallTypeIncomplete },
            constraints,
            skipUnknownArgCheck,
            inferenceContext
        );

        let isTypeIncomplete = !!functionResult.isTypeIncomplete;
        let returnType = functionResult.returnType;

        let argumentErrors = !!functionResult.argumentErrors;
        if (!argumentErrors) {
            // Call the function transform logic to handle special-cased functions.
            const transformed = applyFunctionTransform(evaluatorInterface, errorNode, argList, type, {
                argumentErrors: !!functionResult.argumentErrors,
                returnType: functionResult.returnType ?? UnknownType.create(isTypeIncomplete),
                isTypeIncomplete,
            });

            returnType = transformed.returnType;
            if (transformed.isTypeIncomplete) {
                isTypeIncomplete = true;
            }
            if (transformed.argumentErrors) {
                argumentErrors = true;
            }
        }

        if (FunctionType.isBuiltIn(type, '__import__')) {
            // For the special __import__ type, we'll override the return type to be "Any".
            // This is required because we don't know what module was imported, and we don't
            // want to fail type checks when accessing members of the resulting module type.
            returnType = AnyType.create();
        }

        return {
            returnType,
            isTypeIncomplete,
            argumentErrors,
            overloadsUsedForCall: functionResult.overloadsUsedForCall,
            specializedInitSelfType: functionResult.specializedInitSelfType,
        };
    }

    // Determines whether a symbol is abstract. In an ABC class, this means a function
    // is specifically decorated with @abstractmethod. In a protocol class, the rules
    // are more complicated and depend on whether the method is defined in a stub file.
    function getAbstractSymbolInfo(classType: ClassType, symbolName: string): AbstractSymbol | undefined {
        const isProtocolClass = ClassType.isProtocolClass(classType);

        const symbol = ClassType.getSymbolTable(classType).get(symbolName);
        if (!symbol) {
            return undefined;
        }

        // Ignore instance variables. Also, ignore named tuple members, which are
        // modeled in pyright as instance variables, but their runtime implementation
        // uses a descriptor object.
        if (!symbol.isClassMember() && !symbol.isNamedTupleMemberMember()) {
            return undefined;
        }

        const lastDecl = getLastTypedDeclarationForSymbol(symbol);
        if (!lastDecl) {
            return undefined;
        }

        // Handle protocol variables specially.
        if (isProtocolClass && lastDecl.type === DeclarationType.Variable) {
            // If none of the declarations involve assignments, assume it's
            // not implemented in the protocol.
            const allDecls = symbol.getDeclarations();
            if (!allDecls.some((decl) => decl.type === DeclarationType.Variable && !!decl.inferredTypeSource)) {
                return { symbol, symbolName, classType, hasImplementation: false };
            }
        }

        if (lastDecl.type !== DeclarationType.Function) {
            return undefined;
        }

        let isAbstract = false;
        const lastFunctionInfo = getFunctionInfoFromDecorators(evaluatorInterface, lastDecl.node, /* isInClass */ true);
        if ((lastFunctionInfo.flags & FunctionTypeFlags.AbstractMethod) !== 0) {
            isAbstract = true;
        }

        const isStubFile = AnalyzerNodeInfo.getFileInfo(lastDecl.node).isStubFile;

        // In an overloaded method, the first overload can also be marked abstract.
        // In stub files, there is no implementation, so this is the only way to mark
        // an overloaded method as abstract.
        const firstDecl = symbol.getDeclarations()[0];
        let firstFunctionInfo: FunctionDecoratorInfo | undefined;

        if (firstDecl !== lastDecl && firstDecl.type === DeclarationType.Function) {
            firstFunctionInfo = getFunctionInfoFromDecorators(evaluatorInterface, firstDecl.node, /* isInClass */ true);
            if ((firstFunctionInfo.flags & FunctionTypeFlags.AbstractMethod) !== 0) {
                isAbstract = true;
            }

            // If there's no implementation, assume it's unimplemented.
            if (isProtocolClass && (lastFunctionInfo.flags & FunctionTypeFlags.Overloaded) !== 0) {
                // If this is a protocol class method defined in a stub file and
                // it's not marked abstract, assume it's not abstract and implemented.
                if (isProtocolClass && !isAbstract && isStubFile) {
                    return undefined;
                }

                return { symbol, symbolName, classType, hasImplementation: false };
            }
        }

        // In a non-protocol class, if the method isn't explicitly marked abstract,
        // then it's not abstract.
        if (!isProtocolClass && !isAbstract) {
            return undefined;
        }

        const hasImplementation =
            !ParseTreeUtils.isSuiteEmpty(lastDecl.node.d.suite) && !methodAlwaysRaisesNotImplemented(lastDecl);

        // If this is a protocol class, the method isn't explicitly marked
        // as abstract, and there is an implementation, then it's a default
        // implementation, and it's not considered abstract. If it's in a stub
        // file, assume it's implemented in this case.
        if (isProtocolClass && !isAbstract) {
            if (hasImplementation || isStubFile) {
                return undefined;
            }
        }

        return { symbol, symbolName, classType, hasImplementation };
    }

    function validateCallForOverloaded(
        errorNode: ExpressionNode,
        argList: Arg[],
        expandedCallType: OverloadedType,
        isCallTypeIncomplete: boolean,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        const overloads = OverloadedType.getOverloads(expandedCallType);
        // Handle the 'cast' call as a special case.
        if (
            overloads.length > 0 &&
            FunctionType.isBuiltIn(overloads[0], ['typing.cast', 'typing_extensions.cast']) &&
            argList.length === 2
        ) {
            return { returnType: evaluateCastCall(argList, errorNode) };
        }

        const callResult = validateOverloadedArgTypes(
            errorNode,
            argList,
            { type: expandedCallType, isIncomplete: isCallTypeIncomplete },
            constraints,
            skipUnknownArgCheck,
            inferenceContext
        );

        let returnType = callResult.returnType ?? UnknownType.create();
        let isTypeIncomplete = !!callResult.isTypeIncomplete;
        let argumentErrors = !!callResult.argumentErrors;

        if (!argumentErrors) {
            // Call the function transform logic to handle special-cased functions.
            const transformed = applyFunctionTransform(evaluatorInterface, errorNode, argList, expandedCallType, {
                argumentErrors: !!callResult.argumentErrors,
                returnType: callResult.returnType ?? UnknownType.create(isTypeIncomplete),
                isTypeIncomplete,
            });

            returnType = transformed.returnType;
            if (transformed.isTypeIncomplete) {
                isTypeIncomplete = true;
            }

            if (transformed.argumentErrors) {
                argumentErrors = true;
            }
        }

        return {
            returnType,
            isTypeIncomplete,
            argumentErrors,
            overloadsUsedForCall: callResult.overloadsUsedForCall,
            specializedInitSelfType: callResult.specializedInitSelfType,
        };
    }

    function validateCallForInstantiableClass(
        errorNode: ExpressionNode,
        argList: Arg[],
        expandedCallType: ClassType,
        unexpandedCallType: Type,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        if (expandedCallType.priv.literalValue !== undefined) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.literalNotCallable(), errorNode);

            return { returnType: UnknownType.create(), argumentErrors: true };
        }

        if (ClassType.isBuiltIn(expandedCallType)) {
            const className = expandedCallType.priv.aliasName ?? expandedCallType.shared.name;

            // Handle a call to a metaclass explicitly.
            if (isInstantiableMetaclass(expandedCallType)) {
                if (expandedCallType.priv.typeArgs && expandedCallType.priv.isTypeArgExplicit) {
                    addDiagnostic(
                        DiagnosticRule.reportCallIssue,
                        LocMessage.objectNotCallable().format({
                            type: printType(expandedCallType),
                        }),
                        errorNode
                    );
                    return { returnType: UnknownType.create(), argumentErrors: true };
                }

                // Validate the constructor arguments.
                validateConstructorArgs(
                    evaluatorInterface,
                    errorNode,
                    argList,
                    expandedCallType,
                    skipUnknownArgCheck,
                    inferenceContext
                );

                // The one-parameter form of "type" returns the class
                // for the specified object.
                if (expandedCallType.shared.name === 'type' && argList.length === 1) {
                    const argTypeResult = getTypeOfArg(argList[0], /* inferenceContext */ undefined);
                    const argType = argTypeResult.type;
                    const returnType = mapSubtypes(argType, (subtype) => {
                        if (isNever(subtype)) {
                            return subtype;
                        }

                        if (isClass(subtype)) {
                            // Specifically handle the case where the subtype is a class-like
                            // object created by calling NewType. At runtime, it's actually
                            // a FunctionType object.
                            if (
                                isClassInstance(subtype) &&
                                ClassType.isNewTypeClass(subtype) &&
                                !subtype.priv.includeSubclasses
                            ) {
                                if (prefetched?.functionClass) {
                                    return prefetched.functionClass;
                                }
                            }

                            return convertToInstantiable(stripLiteralValue(subtype));
                        }

                        if (TypeBase.isInstance(subtype)) {
                            if (isFunction(subtype) || isTypeVar(subtype)) {
                                return convertToInstantiable(subtype);
                            }
                        }

                        return ClassType.specialize(ClassType.cloneAsInstance(expandedCallType), [
                            UnknownType.create(),
                        ]);
                    });

                    return { returnType, isTypeIncomplete: argTypeResult.isIncomplete };
                }

                if (argList.length >= 2) {
                    // The two-parameter form of a call to a metaclass returns a new class
                    // built from the specified base types.
                    return {
                        returnType: createClassFromMetaclass(errorNode, argList, expandedCallType) || AnyType.create(),
                    };
                }

                // If the parameter to type() is not statically known,
                // fall back to Any.
                return { returnType: AnyType.create() };
            }

            if (className === 'TypeVar') {
                return {
                    returnType: createTypeVarType(errorNode, expandedCallType, argList),
                };
            }

            if (className === 'TypeVarTuple') {
                return {
                    returnType: createTypeVarTupleType(errorNode, expandedCallType, argList),
                };
            }

            if (className === 'ParamSpec') {
                return {
                    returnType: createParamSpecType(errorNode, expandedCallType, argList),
                };
            }

            if (className === 'TypeAliasType') {
                const newTypeAlias = createTypeAliasType(errorNode, argList);
                if (newTypeAlias) {
                    return { returnType: newTypeAlias };
                }
            }

            if (className === 'NamedTuple') {
                const result: CallResult = {
                    returnType: createNamedTupleType(evaluatorInterface, errorNode, argList, /* includesTypes */ true),
                };

                const initTypeResult = getBoundInitMethod(
                    evaluatorInterface,
                    errorNode,
                    ClassType.cloneAsInstance(expandedCallType),
                    /* diag */ undefined,
                    /* additionalFlags */ MemberAccessFlags.Default
                );

                if (initTypeResult && isOverloaded(initTypeResult.type)) {
                    validateOverloadedArgTypes(
                        errorNode,
                        argList,
                        { type: initTypeResult.type },
                        /* constraints */ undefined,
                        skipUnknownArgCheck,
                        /* inferenceContext */ undefined
                    );
                }

                return result;
            }

            if (className === 'NewType') {
                return { returnType: createNewType(errorNode, argList) };
            }

            // Handle sentinel calls specially.
            if (
                expandedCallType.shared.fullName === 'builtins.sentinel' ||
                expandedCallType.shared.fullName === 'typing_extensions.sentinel' ||
                expandedCallType.shared.fullName === 'typing_extensions.Sentinel'
            ) {
                return { returnType: createSentinelType(evaluatorInterface, errorNode, argList) };
            }

            if (ClassType.isSpecialFormClass(expandedCallType)) {
                addDiagnostic(
                    DiagnosticRule.reportCallIssue,
                    LocMessage.typeNotIntantiable().format({ type: className }),
                    errorNode
                );

                return { returnType: UnknownType.create(), argumentErrors: true };
            }

            if (className === 'TypedDict') {
                return { returnType: createTypedDictType(evaluatorInterface, errorNode, expandedCallType, argList) };
            }

            if (className === 'auto' && argList.length === 0) {
                return { returnType: getEnumAutoValueType(evaluatorInterface, errorNode) };
            }
        }

        // Is it a call to an Enum class factory?
        if (
            isClass(expandedCallType) &&
            expandedCallType.shared.effectiveMetaclass &&
            isClass(expandedCallType.shared.effectiveMetaclass) &&
            isEnumMetaclass(expandedCallType.shared.effectiveMetaclass) &&
            !isEnumClassWithMembers(evaluatorInterface, expandedCallType)
        ) {
            return {
                returnType:
                    createEnumType(evaluatorInterface, errorNode, expandedCallType, argList) ??
                    convertToInstance(unexpandedCallType),
            };
        }

        if (ClassType.supportsAbstractMethods(expandedCallType)) {
            const abstractSymbols = getAbstractSymbols(expandedCallType);

            if (
                abstractSymbols.length > 0 &&
                !expandedCallType.priv.includeSubclasses &&
                !isTypeVar(unexpandedCallType)
            ) {
                // If the class is abstract, it can't be instantiated.
                const diagAddendum = new DiagnosticAddendum();
                const errorsToDisplay = 2;

                abstractSymbols.forEach((abstractMethod, index) => {
                    if (index === errorsToDisplay) {
                        diagAddendum.addMessage(
                            LocAddendum.memberIsAbstractMore().format({
                                count: abstractSymbols.length - errorsToDisplay,
                            })
                        );
                    } else if (index < errorsToDisplay) {
                        if (isInstantiableClass(abstractMethod.classType)) {
                            const className = abstractMethod.classType.shared.name;
                            diagAddendum.addMessage(
                                LocAddendum.memberIsAbstract().format({
                                    type: className,
                                    name: abstractMethod.symbolName,
                                })
                            );
                        }
                    }
                });

                addDiagnostic(
                    DiagnosticRule.reportAbstractUsage,
                    LocMessage.instantiateAbstract().format({
                        type: expandedCallType.shared.name,
                    }) + diagAddendum.getString(),
                    errorNode
                );
            }
        }

        if (ClassType.isProtocolClass(expandedCallType) && !expandedCallType.priv.includeSubclasses) {
            // If the class is a protocol, it can't be instantiated.
            addDiagnostic(
                DiagnosticRule.reportAbstractUsage,
                LocMessage.instantiateProtocol().format({
                    type: expandedCallType.shared.name,
                }),
                errorNode
            );
        }

        // Assume this is a call to the constructor.
        const constructorResult = validateConstructorArgs(
            evaluatorInterface,
            errorNode,
            argList,
            expandedCallType,
            skipUnknownArgCheck,
            inferenceContext
        );

        const overloadsUsedForCall = constructorResult.overloadsUsedForCall;
        const argumentErrors = constructorResult.argumentErrors;
        const isTypeIncomplete = constructorResult.isTypeIncomplete;

        let returnType = constructorResult.returnType;

        // If the expandedCallType originated from a TypeVar, convert
        // the constructed type back to the TypeVar. For example, if
        // we have `cls: Type[_T]` followed by `_T()`.
        if (isTypeVar(unexpandedCallType)) {
            returnType = convertToInstance(unexpandedCallType);
        }

        // If we instantiated the "deprecated" class, attach the deprecation
        // message to the instance.
        if (
            errorNode.nodeType === ParseNodeType.Call &&
            returnType &&
            isClassInstance(returnType) &&
            ClassType.isBuiltIn(returnType, 'deprecated')
        ) {
            returnType = ClassType.cloneForDeprecatedInstance(returnType, getDeprecatedMessageFromCall(errorNode));
        }

        // If we instantiated a type, transform it into a class.
        // This can happen if someone directly instantiates a metaclass
        // deriving from type.
        if (
            returnType &&
            isClassInstance(returnType) &&
            returnType.shared.mro.some(
                (baseClass) => isInstantiableClass(baseClass) && ClassType.isBuiltIn(baseClass, 'type')
            )
        ) {
            let newClassName = '__class_' + returnType.shared.name;
            if (argList.length === 3) {
                const firstArgType = getTypeOfArg(argList[0], /* inferenceContext */ undefined).type;

                if (
                    isClassInstance(firstArgType) &&
                    ClassType.isBuiltIn(firstArgType, 'str') &&
                    typeof firstArgType.priv.literalValue === 'string'
                ) {
                    newClassName = firstArgType.priv.literalValue;
                }
            }

            const newClassType = ClassType.createInstantiable(
                newClassName,
                '',
                '',
                AnalyzerNodeInfo.getFileInfo(errorNode).fileUri,
                ClassTypeFlags.None,
                ParseTreeUtils.getTypeSourceId(errorNode),
                ClassType.cloneAsInstantiable(returnType),
                ClassType.cloneAsInstantiable(returnType)
            );
            newClassType.shared.baseClasses.push(getBuiltInType(errorNode, 'object'));
            newClassType.shared.effectiveMetaclass = expandedCallType;
            newClassType.shared.declaration = returnType.shared.declaration;

            computeMroLinearization(newClassType);
            returnType = newClassType;
        }

        return { returnType, overloadsUsedForCall, argumentErrors, isTypeIncomplete };
    }

    function validateCallForClassInstance(
        errorNode: ExpressionNode,
        argList: Arg[],
        expandedCallType: ClassType,
        unexpandedCallType: Type,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck: boolean | undefined,
        inferenceContext: InferenceContext | undefined,
        recursionCount: number
    ): CallResult {
        const callDiag = new DiagnosticAddendum();
        const callMethodResult = getTypeOfBoundMember(
            errorNode,
            expandedCallType,
            '__call__',
            /* usage */ undefined,
            callDiag,
            MemberAccessFlags.SkipInstanceMembers | MemberAccessFlags.SkipAttributeAccessOverride,
            /* selfType */ undefined,
            recursionCount
        );
        const callMethodType = callMethodResult?.type;

        if (!callMethodType || callMethodResult.typeErrors) {
            addDiagnostic(
                DiagnosticRule.reportCallIssue,
                LocMessage.objectNotCallable().format({
                    type: printType(expandedCallType),
                }) + callDiag.getString(),
                errorNode
            );

            return { returnType: UnknownType.create(), argumentErrors: true };
        }

        const callResult = validateCallArgs(
            errorNode,
            argList,
            { type: callMethodType },
            constraints,
            skipUnknownArgCheck,
            inferenceContext,
            recursionCount
        );

        let returnType = callResult.returnType ?? UnknownType.create();
        if (
            isTypeVar(unexpandedCallType) &&
            TypeBase.isInstantiable(unexpandedCallType) &&
            isClass(expandedCallType) &&
            ClassType.isBuiltIn(expandedCallType, 'type')
        ) {
            // Handle the case where a type[T] is being called. We presume this
            // will instantiate an object of type T.
            returnType = convertToInstance(unexpandedCallType);
        }

        return {
            returnType,
            argumentErrors: callResult.argumentErrors,
            overloadsUsedForCall: callResult.overloadsUsedForCall,
        };
    }

    // Evaluates the type of the "cast" call.
    function evaluateCastCall(argList: Arg[], errorNode: ExpressionNode) {
        if (argList[0].argCategory !== ArgCategory.Simple && argList[0].valueExpression) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.unpackInAnnotation(),
                argList[0].valueExpression
            );
        }

        // Verify that the cast is necessary.
        let castToType = getTypeOfArgExpectingType(argList[0], { typeExpression: true }).type;

        const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(errorNode);
        castToType = makeTypeVarsBound(castToType, liveScopeIds);

        let castFromType = getTypeOfArg(argList[1], /* inferenceContext */ undefined).type;

        if (castFromType.props?.specialForm) {
            castFromType = castFromType.props.specialForm;
        }

        if (TypeBase.isInstantiable(castToType) && !isUnknown(castToType)) {
            if (
                isTypeSame(convertToInstance(castToType), castFromType, {
                    ignorePseudoGeneric: true,
                })
            ) {
                addDiagnostic(
                    DiagnosticRule.reportUnnecessaryCast,
                    LocMessage.unnecessaryCast().format({
                        type: printType(castFromType),
                    }),
                    errorNode
                );
            }
        }

        return convertToInstance(castToType);
    }

    // Expands any unpacked tuples within an argument list.
    function expandArgList(argList: Arg[]): Arg[] {
        const expandedArgList: Arg[] = [];

        for (const arg of argList) {
            if (arg.argCategory === ArgCategory.UnpackedList) {
                const argType = getTypeOfArg(arg, /* inferenceContext */ undefined).type;

                // If this is a tuple with specified element types, use those
                // specified types rather than using the more generic iterator
                // type which will be a union of all element types.
                const combinedArgType = combineSameSizedTuples(
                    makeTopLevelTypeVarsConcrete(argType),
                    prefetched?.tupleClass
                );

                if (isClassInstance(combinedArgType) && isTupleClass(combinedArgType)) {
                    const tupleTypeArgs = combinedArgType.priv.tupleTypeArgs ?? [];

                    if (tupleTypeArgs.length !== 1 || !tupleTypeArgs[0].isUnbounded) {
                        for (const tupleTypeArg of tupleTypeArgs) {
                            if (tupleTypeArg.isUnbounded) {
                                expandedArgList.push({
                                    ...arg,
                                    argCategory: ArgCategory.UnpackedList,
                                    valueExpression: undefined,
                                    typeResult: {
                                        type: makeTupleObject(evaluatorInterface, [tupleTypeArg]),
                                    },
                                });
                            } else {
                                expandedArgList.push({
                                    ...arg,
                                    argCategory: ArgCategory.Simple,
                                    valueExpression: undefined,
                                    typeResult: {
                                        type: tupleTypeArg.type,
                                    },
                                });
                            }
                        }
                        continue;
                    }
                }
            }

            expandedArgList.push(arg);
        }

        return expandedArgList;
    }

    // Matches the arguments passed to a function to the corresponding parameters in that
    // function. This matching is done based on positions and keywords. Type evaluation and
    // validation is left to the caller.
    // This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/
    function matchArgsToParams(
        errorNode: ExpressionNode,
        argList: Arg[],
        typeResult: TypeResult<FunctionType>,
        overloadIndex: number
    ): MatchArgsToParamsResult {
        const overload = typeResult.type;
        const paramDetails = getParamListDetails(overload, { disallowExtraKwargsForTd: true });
        const paramSpec = FunctionType.getParamSpecFromArgsKwargs(overload);

        let argIndex = 0;
        let unpackedArgOfUnknownLength = false;
        let unpackedArgMapsToVariadic = false;
        let reportedArgError = false;
        let isTypeIncomplete = !!typeResult.isIncomplete;
        let isTypeVarTupleFullyMatched = false;

        // Expand any unpacked tuples in the arg list.
        argList = expandArgList(argList);

        // Construct an object that racks which parameters have been assigned arguments.
        const paramTracker = new ParamAssignmentTracker(paramDetails.params);

        let positionalOnlyLimitIndex = paramDetails.positionOnlyParamCount;
        let positionParamLimitIndex = paramDetails.firstKeywordOnlyIndex ?? paramDetails.params.length;

        const varArgListParamIndex = paramDetails.argsIndex;
        const varArgDictParamIndex = paramDetails.kwargsIndex;

        // Is this an function that uses the *args and **kwargs
        // from a param spec? If so, we need to treat all positional parameters
        // prior to the *args as positional-only according to PEP 612.
        let paramSpecArgList: Arg[] | undefined;
        let paramSpecTarget: ParamSpecType | undefined;
        let hasParamSpecArgsKwargs = false;

        // Determine how many positional args are being passed before
        // we see a keyword arg.
        let positionalArgCount = argList.findIndex(
            (arg) => arg.argCategory === ArgCategory.UnpackedDictionary || arg.name !== undefined
        );
        if (positionalArgCount < 0) {
            positionalArgCount = argList.length;
        }

        if (varArgListParamIndex !== undefined && varArgDictParamIndex !== undefined) {
            assert(paramDetails.params[varArgListParamIndex], 'varArgListParamIndex params entry is undefined');
            const varArgListParamType = paramDetails.params[varArgListParamIndex].type;
            assert(paramDetails.params[varArgDictParamIndex], 'varArgDictParamIndex params entry is undefined');
            const varArgDictParamType = paramDetails.params[varArgDictParamIndex].type;

            if (
                isParamSpec(varArgListParamType) &&
                varArgListParamType.priv.paramSpecAccess === 'args' &&
                isParamSpec(varArgDictParamType) &&
                varArgDictParamType.priv.paramSpecAccess === 'kwargs' &&
                varArgListParamType.shared.name === varArgDictParamType.shared.name
            ) {
                hasParamSpecArgsKwargs = true;

                // Does this function define the param spec, or is it an inner
                // function nested within another function that defines the param
                // spec? We need to handle these two cases differently.
                const paramSpecScopeId = varArgListParamType.priv.scopeId;

                if (getTypeVarScopeIds(overload).some((id) => id === paramSpecScopeId)) {
                    paramSpecArgList = [];
                    paramSpecTarget = TypeVarType.cloneForParamSpecAccess(varArgListParamType, /* access */ undefined);
                } else {
                    positionalOnlyLimitIndex = varArgListParamIndex;
                    positionalArgCount = Math.min(varArgListParamIndex, positionalArgCount);
                    positionParamLimitIndex = varArgListParamIndex;
                }
            }
        } else if (paramSpec) {
            if (getTypeVarScopeIds(overload).some((id) => id === paramSpec.priv.scopeId)) {
                hasParamSpecArgsKwargs = true;
                paramSpecArgList = [];
                paramSpecTarget = paramSpec;
            }
        }

        // If there are keyword arguments present after a *args argument,
        // the keyword arguments may target one or more parameters that are positional.
        // In this case, we will limit the number of positional parameters so the
        // *args doesn't consume them all.
        if (argList.some((arg) => arg.argCategory === ArgCategory.UnpackedList)) {
            argList.forEach((arg) => {
                if (arg.name) {
                    const keywordParamIndex = paramDetails.params.findIndex((paramInfo) => {
                        assert(paramInfo, 'paramInfo entry is undefined fork kwargs check');
                        return (
                            paramInfo.param.name === arg.name!.d.value &&
                            paramInfo.param.category === ParamCategory.Simple
                        );
                    });

                    // Is this a parameter that can be interpreted as either a keyword or a positional?
                    // If so, we'll treat it as a keyword parameter in this case because it's being
                    // targeted by a keyword argument.
                    if (keywordParamIndex >= 0 && keywordParamIndex >= positionalOnlyLimitIndex) {
                        if (positionParamLimitIndex < 0 || keywordParamIndex < positionParamLimitIndex) {
                            positionParamLimitIndex = keywordParamIndex;
                        }
                    }
                }
            });
        }

        // If we didn't see any special cases, then all parameters are positional.
        if (positionParamLimitIndex < 0) {
            positionParamLimitIndex = paramDetails.params.length;
        }

        let validateArgTypeParams: ValidateArgTypeParams[] = [];

        let activeParam: FunctionParam | undefined;
        function trySetActive(arg: Arg, param: FunctionParam) {
            if (arg.active) {
                activeParam = param;
            }
        }

        const foundUnpackedListArg = argList.find((arg) => arg.argCategory === ArgCategory.UnpackedList) !== undefined;

        // Map the positional args to parameters.
        let paramIndex = 0;

        while (argIndex < positionalArgCount) {
            if (argIndex < positionalOnlyLimitIndex && argList[argIndex].name) {
                const nameNode = argList[argIndex].name;
                if (nameNode) {
                    addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.argPositional(), nameNode);
                    reportedArgError = true;
                }
            }

            const remainingArgCount = positionalArgCount - argIndex;
            const remainingParamCount = positionParamLimitIndex - paramIndex - 1;

            if (paramIndex >= positionParamLimitIndex) {
                if (paramSpecArgList) {
                    // Push the remaining positional args onto the param spec arg list.
                    while (argIndex < positionalArgCount) {
                        paramSpecArgList.push(argList[argIndex]);
                        argIndex++;
                    }
                } else {
                    let tooManyPositionals = false;

                    if (argList[argIndex].argCategory === ArgCategory.UnpackedList) {
                        // If this is an unpacked iterable, we will conservatively assume that it
                        // might have zero iterations unless we can tell from its type that it
                        // definitely has at least one iterable value.
                        const argType = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined).type;

                        if (
                            isClassInstance(argType) &&
                            isTupleClass(argType) &&
                            !isUnboundedTupleClass(argType) &&
                            argType.priv.tupleTypeArgs !== undefined &&
                            argType.priv.tupleTypeArgs.length > 0
                        ) {
                            tooManyPositionals = true;
                        } else {
                            unpackedArgOfUnknownLength = true;
                        }
                    } else {
                        tooManyPositionals = true;
                    }

                    if (tooManyPositionals) {
                        if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                            addDiagnostic(
                                DiagnosticRule.reportCallIssue,
                                positionParamLimitIndex === 1
                                    ? LocMessage.argPositionalExpectedOne()
                                    : LocMessage.argPositionalExpectedCount().format({
                                          expected: positionParamLimitIndex,
                                      }),
                                argList[argIndex].valueExpression ?? errorNode
                            );
                        }
                        reportedArgError = true;
                    }
                }
                break;
            }

            if (paramIndex >= paramDetails.params.length) {
                break;
            }

            assert(paramDetails.params[paramIndex], 'paramIndex params entry is undefined');
            const paramInfo = paramDetails.params[paramIndex];
            const paramType = paramInfo.type;
            const paramName = paramInfo.param.name;

            const isParamVariadic = paramInfo.param.category === ParamCategory.ArgsList && isUnpacked(paramType);

            if (argList[argIndex].argCategory === ArgCategory.UnpackedList) {
                let isArgCompatibleWithVariadic = false;

                const argTypeResult = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined);

                let listElementType: Type | undefined;
                let enforceIterable = false;
                let advanceToNextArg = false;

                // Handle the case where *args is being passed to a function defined
                // with a ParamSpec and a Concatenate operator. PEP 612 indicates that
                // all positional parameters specified in the Concatenate must be
                // filled explicitly.
                if (paramIndex < positionParamLimitIndex) {
                    if (
                        isParamSpec(argTypeResult.type) &&
                        argTypeResult.type.priv.paramSpecAccess === 'args' &&
                        paramInfo.param.category !== ParamCategory.ArgsList
                    ) {
                        if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                            addDiagnostic(
                                DiagnosticRule.reportCallIssue,
                                positionParamLimitIndex === 1
                                    ? LocMessage.argPositionalExpectedOne()
                                    : LocMessage.argPositionalExpectedCount().format({
                                          expected: positionParamLimitIndex,
                                      }),
                                argList[argIndex].valueExpression ?? errorNode
                            );
                        }
                        reportedArgError = true;
                    }
                }

                const argType = argTypeResult.type;

                if (isParamVariadic && isUnpackedTypeVarTuple(argType)) {
                    // Allow an unpacked TypeVarTuple arg to satisfy an
                    // unpacked TypeVarTuple param.
                    listElementType = argType;
                    isArgCompatibleWithVariadic = true;
                    advanceToNextArg = true;
                    isTypeVarTupleFullyMatched = true;
                } else if (
                    isClassInstance(argType) &&
                    isTupleClass(argType) &&
                    argType.priv.tupleTypeArgs &&
                    argType.priv.tupleTypeArgs.length === 1 &&
                    isUnpackedTypeVarTuple(argType.priv.tupleTypeArgs[0].type)
                ) {
                    // Handle the case where an unpacked TypeVarTuple has
                    // been packaged into a tuple.
                    listElementType = argType.priv.tupleTypeArgs[0].type;
                    isArgCompatibleWithVariadic = true;
                    advanceToNextArg = true;
                    isTypeVarTupleFullyMatched = true;
                } else if (isParamVariadic && isClassInstance(argType) && isTupleClass(argType)) {
                    // Handle the case where an unpacked tuple argument is
                    // matched to a TypeVarTuple parameter.
                    isArgCompatibleWithVariadic = true;
                    advanceToNextArg = true;

                    // Determine whether we should treat the variadic type as fully matched.
                    // This depends on how many args and unmatched parameters exist.
                    if (remainingArgCount < remainingParamCount) {
                        isTypeVarTupleFullyMatched = true;
                    }

                    listElementType = ClassType.cloneForUnpacked(argType);
                } else if (isParamSpec(argType) && argType.priv.paramSpecAccess === 'args') {
                    listElementType = undefined;
                } else {
                    listElementType = getTypeOfIterator(
                        { type: argType, isIncomplete: argTypeResult.isIncomplete },
                        /* isAsync */ false,
                        errorNode,
                        /* emitNotIterableError */ false
                    )?.type;

                    if (!listElementType) {
                        enforceIterable = true;
                    }

                    unpackedArgOfUnknownLength = true;

                    if (paramInfo.param.category === ParamCategory.ArgsList) {
                        unpackedArgMapsToVariadic = true;
                    }

                    if (isParamVariadic && listElementType) {
                        isArgCompatibleWithVariadic = true;
                        listElementType = makeTupleObject(
                            evaluatorInterface,
                            [{ type: listElementType, isUnbounded: true }],
                            /* isUnpacked */ true
                        );
                    }
                }

                const funcArg: Arg | undefined = listElementType
                    ? {
                          argCategory: ArgCategory.Simple,
                          typeResult: { type: listElementType, isIncomplete: argTypeResult.isIncomplete },
                      }
                    : { ...argList[argIndex], enforceIterable };

                if (argTypeResult.isIncomplete) {
                    isTypeIncomplete = true;
                }

                // It's not allowed to use unpacked arguments with a variadic *args
                // parameter unless the argument is a variadic arg as well.
                if (isParamVariadic && !isArgCompatibleWithVariadic) {
                    if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                        addDiagnostic(
                            DiagnosticRule.reportCallIssue,
                            LocMessage.unpackedArgWithVariadicParam(),
                            argList[argIndex].valueExpression || errorNode
                        );
                    }
                    reportedArgError = true;
                } else {
                    if (paramSpecArgList && paramInfo.param.category !== ParamCategory.Simple) {
                        paramSpecArgList.push(argList[argIndex]);
                    }

                    if (funcArg) {
                        validateArgTypeParams.push({
                            paramCategory: paramInfo.param.category,
                            paramType,
                            requiresTypeVarMatching: requiresSpecialization(paramType),
                            argument: funcArg,
                            errorNode: argList[argIndex].valueExpression ?? errorNode,
                            paramName,
                            isParamNameSynthesized: FunctionParam.isNameSynthesized(paramInfo.param),
                            mapsToVarArgList: isParamVariadic && remainingArgCount > remainingParamCount,
                        });
                    }
                }

                trySetActive(argList[argIndex], paramDetails.params[paramIndex].param);

                // Note that the parameter has received an argument.
                if (paramName && paramDetails.params[paramIndex].param.category === ParamCategory.Simple) {
                    paramTracker.markArgReceived(paramInfo);
                }

                if (advanceToNextArg || paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList) {
                    argIndex++;
                }

                if (
                    isTypeVarTupleFullyMatched ||
                    paramDetails.params[paramIndex].param.category !== ParamCategory.ArgsList
                ) {
                    paramIndex++;
                }
            } else if (paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList) {
                trySetActive(argList[argIndex], paramDetails.params[paramIndex].param);

                if (paramSpecArgList) {
                    paramSpecArgList.push(argList[argIndex]);
                    argIndex++;
                } else {
                    let paramCategory = paramDetails.params[paramIndex].param.category;
                    let effectiveParamType = paramType;
                    const paramName = paramDetails.params[paramIndex].param.name;

                    if (
                        isUnpackedClass(paramType) &&
                        paramType.priv.tupleTypeArgs &&
                        paramType.priv.tupleTypeArgs.length > 0
                    ) {
                        effectiveParamType = paramType.priv.tupleTypeArgs[0].type;
                    }

                    paramCategory = isUnpacked(effectiveParamType) ? ParamCategory.ArgsList : ParamCategory.Simple;

                    if (remainingArgCount <= remainingParamCount) {
                        if (remainingArgCount < remainingParamCount) {
                            if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                // Have we run out of arguments and still have parameters left to fill?
                                addDiagnostic(
                                    DiagnosticRule.reportCallIssue,
                                    remainingArgCount === 1
                                        ? LocMessage.argMorePositionalExpectedOne()
                                        : LocMessage.argMorePositionalExpectedCount().format({
                                              expected: remainingArgCount,
                                          }),
                                    argList[argIndex].valueExpression || errorNode
                                );
                            }
                            reportedArgError = true;
                        }

                        paramIndex++;
                    } else {
                        validateArgTypeParams.push({
                            paramCategory,
                            paramType: effectiveParamType,
                            requiresTypeVarMatching: requiresSpecialization(paramType),
                            argument: argList[argIndex],
                            errorNode: argList[argIndex].valueExpression || errorNode,
                            paramName,
                            isParamNameSynthesized: FunctionParam.isNameSynthesized(
                                paramDetails.params[paramIndex].param
                            ),
                            mapsToVarArgList: true,
                        });

                        argIndex++;
                    }
                }
            } else {
                const paramInfo = paramDetails.params[paramIndex];
                const paramName = paramInfo.param.name;

                validateArgTypeParams.push({
                    paramCategory: paramInfo.param.category,
                    paramType,
                    requiresTypeVarMatching: requiresSpecialization(paramType),
                    argument: argList[argIndex],
                    errorNode: argList[argIndex].valueExpression || errorNode,
                    paramName,
                    isParamNameSynthesized: FunctionParam.isNameSynthesized(paramInfo.param),
                });
                trySetActive(argList[argIndex], paramInfo.param);

                // Note that the parameter has received an argument.
                paramTracker.markArgReceived(paramInfo);

                argIndex++;
                paramIndex++;
            }
        }

        // If there weren't enough positional arguments to populate all of the
        // positional-only parameters and the next positional-only parameter is
        // an unbounded tuple, skip past it.
        let skippedArgsParam = false;
        if (
            positionalOnlyLimitIndex >= 0 &&
            paramIndex < positionalOnlyLimitIndex &&
            paramIndex < paramDetails.params.length &&
            paramDetails.params[paramIndex].param.category === ParamCategory.ArgsList &&
            !isParamSpec(paramDetails.params[paramIndex].type)
        ) {
            paramIndex++;
            skippedArgsParam = true;
        }

        // Check if there weren't enough positional arguments to populate all of
        // the positional-only parameters.
        if (
            positionalOnlyLimitIndex >= 0 &&
            paramIndex < positionalOnlyLimitIndex &&
            (!foundUnpackedListArg || hasParamSpecArgsKwargs)
        ) {
            const firstParamWithDefault = paramDetails.params.findIndex((paramInfo) => !!paramInfo.defaultType);
            const positionOnlyWithoutDefaultsCount =
                firstParamWithDefault >= 0 && firstParamWithDefault < positionalOnlyLimitIndex
                    ? firstParamWithDefault
                    : positionalOnlyLimitIndex;

            // Calculate the number of remaining positional parameters to report.
            let argsRemainingCount = positionOnlyWithoutDefaultsCount - positionalArgCount;
            if (skippedArgsParam) {
                // If we skipped an args parameter above, reduce the count by one
                // because it's permitted to pass zero arguments to *args.
                argsRemainingCount--;
            }

            const firstArgsParam = paramDetails.params.findIndex(
                (paramInfo) => paramInfo.param.category === ParamCategory.ArgsList && !isParamSpec(paramInfo.type)
            );
            if (firstArgsParam >= paramIndex && firstArgsParam < positionalOnlyLimitIndex) {
                // If there is another args parameter beyond the current param index,
                // reduce the count by one because it's permitted to pass zero arguments
                // to *args.
                argsRemainingCount--;
            }

            if (argsRemainingCount > 0) {
                if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                    addDiagnostic(
                        DiagnosticRule.reportCallIssue,
                        argsRemainingCount === 1
                            ? LocMessage.argMorePositionalExpectedOne()
                            : LocMessage.argMorePositionalExpectedCount().format({
                                  expected: argsRemainingCount,
                              }),
                        argList.length > positionalArgCount
                            ? argList[positionalArgCount].valueExpression || errorNode
                            : errorNode
                    );
                }
                reportedArgError = true;
            }
        }

        if (!reportedArgError) {
            let unpackedDictKeyNames: string[] | undefined;
            let unpackedDictArgType: Type | undefined;

            // Now consume any keyword arguments.
            while (argIndex < argList.length) {
                if (argList[argIndex].argCategory === ArgCategory.UnpackedDictionary) {
                    // Verify that the type used in this expression is a SupportsKeysAndGetItem[str, T].
                    const argTypeResult = getTypeOfArg(
                        argList[argIndex],
                        makeInferenceContext(paramDetails.unpackedKwargsTypedDictType)
                    );
                    const argType = argTypeResult.type;

                    if (argTypeResult.isIncomplete) {
                        isTypeIncomplete = true;
                    }

                    if (isAnyOrUnknown(argType)) {
                        unpackedDictArgType = argType;
                    } else if (isClassInstance(argType) && ClassType.isTypedDictClass(argType)) {
                        // Handle the special case where it is a TypedDict and we know which
                        // keys are present.
                        const tdEntries = getTypedDictMembersForClass(evaluatorInterface, argType);
                        const diag = new DiagnosticAddendum();

                        tdEntries.knownItems.forEach((entry, name) => {
                            const paramEntry = paramTracker.lookupName(name);
                            if (paramEntry) {
                                if (paramEntry.argsReceived > 0) {
                                    diag.addMessage(LocMessage.paramAlreadyAssigned().format({ name }));
                                } else {
                                    paramEntry.argsReceived++;

                                    const paramInfoIndex = paramDetails.params.findIndex(
                                        (paramInfo) => paramInfo.param.name === name
                                    );
                                    assert(paramInfoIndex >= 0);
                                    const paramType = paramDetails.params[paramInfoIndex].type;

                                    validateArgTypeParams.push({
                                        paramCategory: ParamCategory.Simple,
                                        paramType,
                                        requiresTypeVarMatching: requiresSpecialization(paramType),
                                        argument: {
                                            argCategory: ArgCategory.Simple,
                                            typeResult: { type: entry.valueType },
                                        },
                                        errorNode: argList[argIndex].valueExpression ?? errorNode,
                                        paramName: name,
                                    });
                                }
                            } else if (paramDetails.kwargsIndex !== undefined) {
                                const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
                                validateArgTypeParams.push({
                                    paramCategory: ParamCategory.KwargsDict,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argument: {
                                        argCategory: ArgCategory.Simple,
                                        typeResult: { type: entry.valueType },
                                    },
                                    errorNode: argList[argIndex].valueExpression ?? errorNode,
                                    paramName: name,
                                });

                                // Remember that this parameter has already received a value.
                                paramTracker.addKeywordParam(name, paramDetails.params[paramDetails.kwargsIndex]);
                            } else {
                                // If the function doesn't have a **kwargs parameter, we need to emit an error.
                                // However, it's possible that there was a **kwargs but it was eliminated by
                                // getParamListDetails because it was associated with an unpacked TypedDict.
                                // In this case, we can skip the error.
                                if (!paramDetails.hasUnpackedTypedDict) {
                                    diag.addMessage(LocMessage.paramNameMissing().format({ name }));
                                }
                            }
                        });

                        const extraItemsType = tdEntries.extraItems?.valueType ?? getObjectType();
                        if (!isNever(extraItemsType)) {
                            if (paramDetails.kwargsIndex !== undefined) {
                                const kwargsParam = paramDetails.params[paramDetails.kwargsIndex];

                                validateArgTypeParams.push({
                                    paramCategory: ParamCategory.KwargsDict,
                                    paramType: kwargsParam.type,
                                    requiresTypeVarMatching: requiresSpecialization(kwargsParam.type),
                                    argument: {
                                        argCategory: ArgCategory.UnpackedDictionary,
                                        typeResult: { type: extraItemsType },
                                    },
                                    errorNode: argList[argIndex].valueExpression ?? errorNode,
                                    paramName: kwargsParam.param.name,
                                });
                            }
                        }

                        if (!diag.isEmpty()) {
                            if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                addDiagnostic(
                                    DiagnosticRule.reportCallIssue,
                                    LocMessage.unpackedTypedDictArgument() + diag.getString(),
                                    argList[argIndex].valueExpression || errorNode
                                );
                            }
                            reportedArgError = true;
                        }
                    } else if (paramSpec && isParamSpecKwargs(paramSpec, argType)) {
                        unpackedDictArgType = AnyType.create();

                        if (!paramSpecArgList) {
                            validateArgTypeParams.push({
                                paramCategory: ParamCategory.KwargsDict,
                                paramType: paramSpec,
                                requiresTypeVarMatching: false,
                                argument: argList[argIndex],
                                argType: isParamSpec(argType) ? undefined : AnyType.create(),
                                errorNode: argList[argIndex].valueExpression || errorNode,
                            });
                        }
                    } else {
                        const strObjType = getBuiltInObject(errorNode, 'str');

                        if (
                            prefetched?.supportsKeysAndGetItemClass &&
                            isInstantiableClass(prefetched.supportsKeysAndGetItemClass) &&
                            strObjType &&
                            isClassInstance(strObjType)
                        ) {
                            const mappingConstraints = new ConstraintTracker();
                            let isValidMappingType = false;

                            // If this was a TypeVar (e.g. for pseudo-generic classes),
                            // don't emit this error.
                            if (isTypeVar(argType)) {
                                isValidMappingType = true;
                            } else if (
                                assignType(
                                    ClassType.cloneAsInstance(prefetched.supportsKeysAndGetItemClass),
                                    argType,
                                    /* diag */ undefined,
                                    mappingConstraints
                                )
                            ) {
                                const specializedMapping = solveAndApplyConstraints(
                                    prefetched.supportsKeysAndGetItemClass,
                                    mappingConstraints
                                ) as ClassType;
                                const typeArgs = specializedMapping.priv.typeArgs;
                                if (typeArgs && typeArgs.length >= 2) {
                                    if (assignType(strObjType, typeArgs[0])) {
                                        isValidMappingType = true;
                                    }

                                    unpackedDictKeyNames = [];
                                    doForEachSubtype(typeArgs[0], (keyType) => {
                                        if (isClassInstance(keyType) && typeof keyType.priv.literalValue === 'string') {
                                            unpackedDictKeyNames?.push(keyType.priv.literalValue);
                                        } else {
                                            unpackedDictKeyNames = undefined;
                                        }
                                    });

                                    unpackedDictArgType = typeArgs[1];
                                } else {
                                    isValidMappingType = true;
                                    unpackedDictArgType = UnknownType.create();
                                }
                            }

                            unpackedArgOfUnknownLength = true;

                            if (paramDetails.kwargsIndex !== undefined && unpackedDictArgType) {
                                const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
                                validateArgTypeParams.push({
                                    paramCategory: ParamCategory.Simple,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argType: unpackedDictArgType,
                                    argument: argList[argIndex],
                                    errorNode: argList[argIndex].valueExpression || errorNode,
                                    paramName: paramDetails.params[paramDetails.kwargsIndex].param.name,
                                });

                                unpackedArgMapsToVariadic = true;
                            }

                            if (!isValidMappingType) {
                                if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                    addDiagnostic(
                                        DiagnosticRule.reportCallIssue,
                                        LocMessage.unpackedDictArgumentNotMapping(),
                                        argList[argIndex].valueExpression || errorNode
                                    );
                                }
                                reportedArgError = true;
                            }
                        }
                    }

                    if (paramSpecArgList) {
                        paramSpecArgList.push(argList[argIndex]);
                    }
                } else {
                    // Protect against the case where a non-keyword argument appears after
                    // a keyword argument. This will have already been reported as a parse
                    // error, but we need to protect against it here.
                    const paramName = argList[argIndex].name;
                    if (paramName) {
                        const paramNameValue = paramName.d.value;
                        const paramEntry = paramTracker.lookupName(paramNameValue);

                        if (paramEntry) {
                            if (paramEntry.argsReceived > 0) {
                                if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                    addDiagnostic(
                                        DiagnosticRule.reportCallIssue,
                                        LocMessage.paramAlreadyAssigned().format({ name: paramNameValue }),
                                        paramName
                                    );
                                }
                                reportedArgError = true;
                            } else {
                                paramEntry.argsReceived++;

                                const paramInfoIndex = paramDetails.params.findIndex(
                                    (paramInfo) =>
                                        paramInfo.param.name === paramNameValue &&
                                        paramInfo.kind !== ParamKind.Positional
                                );
                                assert(paramInfoIndex >= 0);
                                const paramType = paramDetails.params[paramInfoIndex].type;

                                validateArgTypeParams.push({
                                    paramCategory: ParamCategory.Simple,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argument: argList[argIndex],
                                    errorNode: argList[argIndex].valueExpression ?? errorNode,
                                    paramName: paramNameValue,
                                });
                                trySetActive(argList[argIndex], paramDetails.params[paramInfoIndex].param);
                            }
                        } else if (paramSpecArgList) {
                            paramSpecArgList.push(argList[argIndex]);
                        } else if (paramDetails.kwargsIndex !== undefined) {
                            const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
                            if (isParamSpec(paramType)) {
                                if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                    addDiagnostic(
                                        DiagnosticRule.reportCallIssue,
                                        LocMessage.paramNameMissing().format({ name: paramName.d.value }),
                                        paramName
                                    );
                                }
                                reportedArgError = true;
                            } else {
                                validateArgTypeParams.push({
                                    paramCategory: ParamCategory.KwargsDict,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argument: argList[argIndex],
                                    errorNode: argList[argIndex].valueExpression ?? errorNode,
                                    paramName: paramNameValue,
                                });

                                assert(
                                    paramDetails.params[paramDetails.kwargsIndex],
                                    'paramDetails.kwargsIndex params entry is undefined'
                                );

                                // Remember that this parameter has already received a value.
                                paramTracker.addKeywordParam(
                                    paramNameValue,
                                    paramDetails.params[paramDetails.kwargsIndex]
                                );
                            }
                            trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param);
                        } else {
                            if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                addDiagnostic(
                                    DiagnosticRule.reportCallIssue,
                                    LocMessage.paramNameMissing().format({ name: paramName.d.value }),
                                    paramName
                                );
                            }
                            reportedArgError = true;
                        }
                    } else if (argList[argIndex].argCategory === ArgCategory.Simple) {
                        if (paramSpecArgList) {
                            paramSpecArgList.push(argList[argIndex]);
                        } else {
                            if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                addDiagnostic(
                                    DiagnosticRule.reportCallIssue,
                                    positionParamLimitIndex === 1
                                        ? LocMessage.argPositionalExpectedOne()
                                        : LocMessage.argPositionalExpectedCount().format({
                                              expected: positionParamLimitIndex,
                                          }),
                                    argList[argIndex].valueExpression || errorNode
                                );
                            }
                            reportedArgError = true;
                        }
                    } else if (argList[argIndex].argCategory === ArgCategory.UnpackedList) {
                        // Handle the case where a *args: P.args (or *args: Any) is passed as an
                        // argument to a function that accepts a ParamSpec.
                        if (paramSpec) {
                            const argTypeResult = getTypeOfArg(argList[argIndex], /* inferenceContext */ undefined);
                            const argType = argTypeResult.type;

                            if (argTypeResult.isIncomplete) {
                                isTypeIncomplete = true;
                            }

                            if (isParamSpecArgs(paramSpec, argType)) {
                                validateArgTypeParams.push({
                                    paramCategory: ParamCategory.ArgsList,
                                    paramType: paramSpec,
                                    requiresTypeVarMatching: false,
                                    argument: argList[argIndex],
                                    argType: isParamSpec(argType) ? undefined : AnyType.create(),
                                    errorNode: argList[argIndex].valueExpression ?? errorNode,
                                });
                            }
                        }
                    }
                }

                argIndex++;
            }

            // If there are keyword-only parameters that haven't been matched but we
            // have an unpacked dictionary arg, assume that it applies to them.
            if (unpackedDictArgType && (!foundUnpackedListArg || paramDetails.argsIndex !== undefined)) {
                // Don't consider any position-only parameters, since they cannot be matched to
                // **kwargs arguments. Consider parameters that are either positional or keyword
                // if there is no *args argument.
                paramDetails.params.forEach((paramInfo, paramIndex) => {
                    const param = paramInfo.param;
                    if (
                        paramIndex >= paramDetails.firstPositionOrKeywordIndex &&
                        param.category === ParamCategory.Simple &&
                        param.name &&
                        paramTracker.lookupDetails(paramInfo).argsReceived === 0
                    ) {
                        const paramType = paramDetails.params[paramIndex].type;

                        if (!unpackedDictKeyNames || unpackedDictKeyNames.includes(param.name)) {
                            validateArgTypeParams.push({
                                paramCategory: ParamCategory.Simple,
                                paramType,
                                requiresTypeVarMatching: requiresSpecialization(paramType),
                                argument: {
                                    argCategory: ArgCategory.Simple,
                                    typeResult: { type: unpackedDictArgType! },
                                },
                                errorNode:
                                    argList.find((arg) => arg.argCategory === ArgCategory.UnpackedDictionary)
                                        ?.valueExpression ?? errorNode,
                                paramName: param.name,
                                isParamNameSynthesized: FunctionParam.isNameSynthesized(param),
                            });

                            paramTracker.markArgReceived(paramDetails.params[paramIndex]);
                        }
                    }
                });
            }

            // Determine whether there are any parameters that require arguments
            // but have not yet received them. If we received a dictionary argument
            // (i.e. an arg starting with a "**"), we will assume that all parameters
            // are matched.
            if (!unpackedDictArgType && !FunctionType.isDefaultParamCheckDisabled(overload)) {
                const unassignedParams = paramTracker.getUnassignedParams();

                if (unassignedParams.length > 0) {
                    if (!canSkipDiagnosticForNode(errorNode)) {
                        const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', ');
                        if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                            addDiagnostic(
                                DiagnosticRule.reportCallIssue,
                                unassignedParams.length === 1
                                    ? LocMessage.argMissingForParam().format({ name: missingParamNames })
                                    : LocMessage.argMissingForParams().format({ names: missingParamNames }),
                                errorNode
                            );
                        }
                    }
                    reportedArgError = true;
                }

                // Add any implicit (default) arguments that are needed for resolving
                // generic types. For example, if the function is defined as
                // def foo(v1: _T = 'default')
                // and _T is a TypeVar, we need to match the TypeVar to the default
                // value's type if it's not provided by the caller.
                paramDetails.params.forEach((paramInfo) => {
                    const param = paramInfo.param;
                    if (param.category === ParamCategory.Simple && param.name) {
                        const entry = paramTracker.lookupDetails(paramInfo);

                        if (entry.argsNeeded === 0 && entry.argsReceived === 0) {
                            const defaultArgType = paramInfo.defaultType;

                            if (
                                defaultArgType &&
                                !isEllipsisType(defaultArgType) &&
                                requiresSpecialization(paramInfo.declaredType, { ignorePseudoGeneric: true })
                            ) {
                                validateArgTypeParams.push({
                                    paramCategory: param.category,
                                    paramType: paramInfo.type,
                                    requiresTypeVarMatching: true,
                                    argument: {
                                        argCategory: ArgCategory.Simple,
                                        typeResult: { type: defaultArgType },
                                    },
                                    isDefaultArg: true,
                                    errorNode,
                                    paramName: param.name,
                                    isParamNameSynthesized: FunctionParam.isNameSynthesized(param),
                                });
                            }
                        }
                    }
                });
            }
        }

        // If we're in speculative mode and an arg/param mismatch has already been reported, don't
        // bother doing the extra work here. This occurs frequently when attempting to find the
        // correct overload.
        if (!reportedArgError || !isSpeculativeModeInUse(undefined)) {
            // If there are arguments that map to a variadic *args parameter that hasn't
            // already been matched, see if the type of that *args parameter is a
            // TypeVarTuple. If so, we'll preprocess those arguments and combine them
            // into a tuple.
            assert(
                paramDetails.argsIndex === undefined || paramDetails.argsIndex < paramDetails.params.length,
                'paramDetails.argsIndex params entry is invalid'
            );
            if (
                paramDetails.argsIndex !== undefined &&
                paramDetails.argsIndex >= 0 &&
                FunctionParam.isTypeDeclared(paramDetails.params[paramDetails.argsIndex].param) &&
                !isTypeVarTupleFullyMatched
            ) {
                const paramType = paramDetails.params[paramDetails.argsIndex].type;
                const variadicArgs = validateArgTypeParams.filter((argParam) => argParam.mapsToVarArgList);

                if (isUnpacked(paramType) && (!isTypeVarTuple(paramType) || !paramType.priv.isInUnion)) {
                    const tupleTypeArgs: TupleTypeArg[] = variadicArgs.map((argParam) => {
                        const argType = getTypeOfArg(argParam.argument, /* inferenceContext */ undefined).type;

                        const containsTypeVarTuple =
                            isUnpackedTypeVarTuple(argType) ||
                            (isClassInstance(argType) &&
                                isTupleClass(argType) &&
                                argType.priv.tupleTypeArgs &&
                                argType.priv.tupleTypeArgs.length === 1 &&
                                isUnpackedTypeVarTuple(argType.priv.tupleTypeArgs[0].type));

                        if (
                            containsTypeVarTuple &&
                            argParam.argument.argCategory !== ArgCategory.UnpackedList &&
                            !argParam.mapsToVarArgList
                        ) {
                            if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
                                addDiagnostic(
                                    DiagnosticRule.reportCallIssue,
                                    LocMessage.typeVarTupleMustBeUnpacked(),
                                    argParam.argument.valueExpression ?? errorNode
                                );
                            }
                            reportedArgError = true;
                        }

                        return {
                            type: argType,
                            isUnbounded: argParam.argument.argCategory === ArgCategory.UnpackedList,
                        };
                    });

                    let specializedTuple: Type | undefined;
                    if (tupleTypeArgs.length === 1 && !tupleTypeArgs[0].isUnbounded) {
                        const entryType = tupleTypeArgs[0].type;

                        if (isUnpacked(entryType)) {
                            specializedTuple = makePacked(entryType);
                        }
                    }

                    if (!specializedTuple) {
                        specializedTuple = makeTupleObject(evaluatorInterface, tupleTypeArgs, /* isUnpacked */ false);
                    }

                    const combinedArg: ValidateArgTypeParams = {
                        paramCategory: ParamCategory.Simple,
                        paramType: makePacked(paramType),
                        requiresTypeVarMatching: true,
                        argument: {
                            argCategory: ArgCategory.Simple,
                            typeResult: { type: specializedTuple },
                        },
                        errorNode,
                        paramName: paramDetails.params[paramDetails.argsIndex].param.name,
                        isParamNameSynthesized: FunctionParam.isNameSynthesized(
                            paramDetails.params[paramDetails.argsIndex].param
                        ),
                        mapsToVarArgList: true,
                    };

                    validateArgTypeParams = [
                        ...validateArgTypeParams.filter((argParam) => !argParam.mapsToVarArgList),
                        combinedArg,
                    ];
                }
            }
        }

        // Special-case the builtin isinstance and issubclass functions.
        if (FunctionType.isBuiltIn(overload, ['isinstance', 'issubclass']) && validateArgTypeParams.length === 2) {
            validateArgTypeParams[1].isinstanceParam = true;
        }

        return {
            overload,
            overloadIndex,
            argumentErrors: reportedArgError,
            isTypeIncomplete,
            argParams: validateArgTypeParams,
            paramSpecTarget,
            paramSpecArgList,
            activeParam,
            unpackedArgOfUnknownLength,
            unpackedArgMapsToVariadic,
            argumentMatchScore: 0,
        };
    }

    // After having matched arguments with parameters, this function evaluates the
    // types of each argument expression and validates that the resulting type is
    // compatible with the declared type of the corresponding parameter.
    function validateArgTypesWithContext(
        errorNode: ExpressionNode,
        matchResults: MatchArgsToParamsResult,
        constraints: ConstraintTracker,
        skipUnknownArgCheck = false,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        const type = matchResults.overload;

        let expectedType: Type | undefined = inferenceContext?.expectedType;

        // Can we safely ignore the inference context, either because it's not provided
        // or will have no effect? If so, avoid the extra work.
        const returnType = inferenceContext?.returnTypeOverride ?? getEffectiveReturnType(type);
        if (!returnType || !requiresSpecialization(returnType)) {
            expectedType = undefined;
        }

        const tryExpectedType = (expectedSubtype: Type): number => {
            const clonedConstraints = constraints.clone();
            const callResult = validateArgTypesWithExpectedType(
                errorNode,
                matchResults,
                clonedConstraints,
                /* skipUnknownArgCheck */ true,
                expectedSubtype,
                returnType
            );

            // Use a heuristic to pick a subtype that is most likely to be correct.
            // We'll look for a subtype that produces no argument errors and has
            // no Unknowns in the return type.
            if (!callResult.argumentErrors && callResult.returnType) {
                const returnType = inferenceContext?.returnTypeOverride
                    ? solveAndApplyConstraints(inferenceContext.returnTypeOverride, clonedConstraints)
                    : callResult.returnType;

                if (
                    assignType(
                        expectedSubtype,
                        returnType,
                        /* diag */ undefined,
                        /* constraints */ undefined,
                        AssignTypeFlags.Default
                    )
                ) {
                    const anyOrUnknown = containsAnyOrUnknown(callResult.returnType, /* recurse */ true);
                    // Prefer return types that have no unknown or Any.
                    if (!anyOrUnknown) {
                        return 3;
                    }

                    // Prefer Any over Unknown.
                    return isAny(anyOrUnknown) ? 2 : 1;
                }
            }

            return 0;
        };

        // Refine the expected type by speculatively evaluating arg types. If the
        // expected type is a union, we may need to perform multiple evaluations
        // to determine whether one of the subtypes works.
        if (expectedType) {
            expectedType = useSpeculativeMode(getSpeculativeNodeForCall(errorNode), () => {
                let validExpectedSubtype: Type | undefined;
                let bestSubtypeScore = -1;

                // If the expected type is a union, we don't know which type is expected.
                // We may or may not be able to make use of the expected type. We'll evaluate
                // speculatively to see if using one of the expected subtypes works.
                if (isUnion(expectedType!)) {
                    doForEachSubtype(
                        expectedType!,
                        (expectedSubtype) => {
                            if (bestSubtypeScore < 3) {
                                const score = tryExpectedType(expectedSubtype);
                                if (score > 0 && score > bestSubtypeScore) {
                                    validExpectedSubtype = expectedSubtype;
                                    bestSubtypeScore = score;
                                }
                            }
                        },
                        /* sortSubtypes */ true
                    );
                }

                if (bestSubtypeScore < 3) {
                    const score = tryExpectedType(expectedType!);
                    if (score > 0 && score > bestSubtypeScore) {
                        validExpectedSubtype = expectedType;
                    }
                }

                return validExpectedSubtype;
            });
        }

        // If there is no expected type, or the expected type is Any or Unknown,
        // there's nothing left to do here.
        if (!expectedType || isAnyOrUnknown(expectedType) || isNever(expectedType)) {
            return validateArgTypes(errorNode, matchResults, constraints, skipUnknownArgCheck);
        }

        return validateArgTypesWithExpectedType(
            errorNode,
            matchResults,
            constraints,
            skipUnknownArgCheck,
            expectedType,
            returnType
        );
    }

    function validateArgTypesWithExpectedType(
        errorNode: ExpressionNode,
        matchResults: MatchArgsToParamsResult,
        constraints: ConstraintTracker,
        skipUnknownArgCheck = false,
        expectedType: Type,
        returnType: Type
    ): CallResult {
        const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode);
        let assignFlags = AssignTypeFlags.PopulateExpectedType;
        if (containsLiteralType(expectedType, /* includeTypeArgs */ true)) {
            assignFlags |= AssignTypeFlags.RetainLiteralsForTypeVar;
        }

        // Prepopulate the constraints based on the specialized expected type.
        // This will allow us to more closely match the expected type if possible.
        if (isClassInstance(returnType) && isClassInstance(expectedType) && !isTypeSame(returnType, expectedType)) {
            const tempConstraints = new ConstraintTracker();
            if (
                addConstraintsForExpectedType(
                    evaluatorInterface,
                    returnType,
                    expectedType,
                    tempConstraints,
                    liveTypeVarScopes,
                    errorNode.start
                )
            ) {
                const genericReturnType = selfSpecializeClass(returnType, {
                    overrideTypeArgs: true,
                });

                expectedType = solveAndApplyConstraints(genericReturnType, tempConstraints, {
                    replaceUnsolved: {
                        scopeIds: getTypeVarScopeIds(returnType),
                        useUnknown: true,
                        tupleClassType: getTupleClassType(),
                    },
                });

                assignFlags |= AssignTypeFlags.SkipPopulateUnknownExpectedType;
            }
        }

        expectedType = transformExpectedType(expectedType, liveTypeVarScopes, errorNode.start);

        assignType(returnType, expectedType, /* diag */ undefined, constraints, assignFlags);

        return validateArgTypes(errorNode, matchResults, constraints, skipUnknownArgCheck);
    }

    function validateArgTypes(
        errorNode: ExpressionNode,
        matchResults: MatchArgsToParamsResult,
        constraints: ConstraintTracker,
        skipUnknownArgCheck: boolean | undefined
    ): CallResult {
        const type = matchResults.overload;
        let isTypeIncomplete = matchResults.isTypeIncomplete;
        let argumentErrors = false;
        let argumentMatchScore = 0;
        let specializedInitSelfType: Type | undefined;
        let anyOrUnknownArg: UnknownType | AnyType | undefined;
        const speculativeNode = getSpeculativeNodeForCall(errorNode);
        const typeCondition = getTypeCondition(type);
        const paramSpec = FunctionType.getParamSpecFromArgsKwargs(type);

        // Check for an attempt to invoke an unimplemented abstract method.
        if (type.priv.boundToType && !type.priv.boundToType.priv.includeSubclasses && type.shared.methodClass) {
            const abstractSymbolInfo = getAbstractSymbolInfo(type.shared.methodClass, type.shared.name);

            if (abstractSymbolInfo && !abstractSymbolInfo.hasImplementation) {
                addDiagnostic(
                    DiagnosticRule.reportAbstractUsage,
                    LocMessage.abstractMethodInvocation().format({
                        method: type.shared.name,
                    }),
                    errorNode.nodeType === ParseNodeType.Call ? errorNode.d.leftExpr : errorNode
                );
            }
        }

        // The type annotation for the "self" parameter in an __init__ method to
        // can influence the type being constructed.
        if (
            type.shared.name === '__init__' &&
            type.priv.strippedFirstParamType &&
            type.priv.boundToType &&
            isClassInstance(type.priv.strippedFirstParamType) &&
            isClassInstance(type.priv.boundToType) &&
            ClassType.isSameGenericClass(type.priv.strippedFirstParamType, type.priv.boundToType) &&
            type.priv.strippedFirstParamType.priv.typeArgs
        ) {
            const typeParams = type.priv.strippedFirstParamType.shared.typeParams;
            specializedInitSelfType = type.priv.strippedFirstParamType;
            type.priv.strippedFirstParamType.priv.typeArgs.forEach((typeArg, index) => {
                if (index < typeParams.length) {
                    const typeParam = typeParams[index];
                    if (!isTypeSame(typeParam, typeArg, { ignorePseudoGeneric: true })) {
                        constraints.setBounds(typeParams[index], typeArg);
                    }
                }
            });
        }

        // Special-case a few built-in calls that are often used for
        // casting or checking for unknown types.
        if (
            FunctionType.isBuiltIn(type, [
                'typing.cast',
                'typing_extensions.cast',
                'builtins.isinstance',
                'builtins.issubclass',
            ])
        ) {
            skipUnknownArgCheck = true;
        }

        // Run through all args and validate them against their matched parameter.
        // We'll do two phases. The first one establishes constraints for type
        // variables. The second perform type validation using the solved
        // types. We can skip the first pass if there are no type vars to solve.
        const typeVarCount = matchResults.argParams.filter((arg) => arg.requiresTypeVarMatching).length;
        if (typeVarCount > 0) {
            // Do up to two passes.
            let passCount = Math.min(typeVarCount, 2);

            for (let i = 0; i < passCount; i++) {
                useSpeculativeMode(speculativeNode, () => {
                    matchResults.argParams.forEach((argParam) => {
                        if (!argParam.requiresTypeVarMatching) {
                            return;
                        }

                        const argResult = validateArgType(
                            argParam,
                            constraints,
                            { type, isIncomplete: matchResults.isTypeIncomplete },
                            {
                                skipUnknownArgCheck,
                                isArgFirstPass: passCount > 1 && i === 0,
                                conditionFilter: typeCondition,
                                skipReportError: true,
                            }
                        );

                        if (argResult.isTypeIncomplete) {
                            isTypeIncomplete = true;
                        }

                        // If we skipped a bare type var during the first pass, add
                        // another pass to ensure that we handle all of the type variables.
                        if (i === 0 && passCount < 2 && argResult.skippedBareTypeVarExpectedType) {
                            passCount++;
                        }
                    });
                });
            }
        }

        let sawParamSpecArgs = false;
        let sawParamSpecKwargs = false;

        let condition: TypeCondition[] = [];
        const argResults: ArgResult[] = [];

        matchResults.argParams.forEach((argParam, argParamIndex) => {
            const argResult = validateArgType(
                argParam,
                constraints,
                { type, isIncomplete: matchResults.isTypeIncomplete },
                {
                    skipUnknownArgCheck,
                    conditionFilter: typeCondition,
                }
            );

            argResults.push(argResult);

            if (!argResult.isCompatible) {
                argumentErrors = true;

                // Add the inverse index so earlier parameters represent larger errors.
                // This will help the heuristics in the overload error paths to pick the
                // most likely intended overload if none of them match.
                argumentMatchScore += 1 + (matchResults.argParams.length - argParamIndex);
            }

            if (argResult.isTypeIncomplete) {
                isTypeIncomplete = true;
            }

            if (argResult.condition) {
                condition = TypeCondition.combine(condition, argResult.condition) ?? [];
            }

            if (isAnyOrUnknown(argResult.argType)) {
                anyOrUnknownArg = anyOrUnknownArg
                    ? preserveUnknown(argResult.argType, anyOrUnknownArg)
                    : argResult.argType;
            }

            if (paramSpec) {
                if (argParam.argument.argCategory === ArgCategory.UnpackedList) {
                    if (isParamSpecArgs(paramSpec, argResult.argType)) {
                        if (sawParamSpecArgs) {
                            addDiagnostic(
                                DiagnosticRule.reportCallIssue,
                                LocMessage.paramSpecArgsKwargsDuplicate().format({ type: printType(paramSpec) }),
                                argParam.errorNode
                            );
                        }

                        sawParamSpecArgs = true;
                    }
                }

                if (argParam.argument.argCategory === ArgCategory.UnpackedDictionary) {
                    if (isParamSpecKwargs(paramSpec, argResult.argType)) {
                        if (sawParamSpecKwargs) {
                            addDiagnostic(
                                DiagnosticRule.reportCallIssue,
                                LocMessage.paramSpecArgsKwargsDuplicate().format({ type: printType(paramSpec) }),
                                argParam.errorNode
                            );
                        }

                        sawParamSpecKwargs = true;
                    }
                }
            }
        });

        let paramSpecConstraints: (ConstraintTracker | undefined)[] = [];

        // Handle the assignment of additional arguments that map to a param spec.
        if (matchResults.paramSpecArgList && matchResults.paramSpecTarget) {
            const paramSpecArgResult = validateArgTypesForParamSpec(
                errorNode,
                matchResults.paramSpecArgList,
                matchResults.paramSpecTarget,
                constraints
            );

            if (paramSpecArgResult.argumentErrors) {
                argumentErrors = true;
                argumentMatchScore += 1;
            }

            paramSpecConstraints = paramSpecArgResult.constraintTrackers;
        } else if (paramSpec) {
            if (!sawParamSpecArgs || !sawParamSpecKwargs) {
                if (!isTypeIncomplete) {
                    addDiagnostic(
                        DiagnosticRule.reportCallIssue,
                        LocMessage.paramSpecArgsMissing().format({ type: printType(paramSpec) }),
                        errorNode
                    );
                }
                argumentErrors = true;
                argumentMatchScore += 1;
            }
        }

        // Calculate the return type.
        const returnTypeResult = getEffectiveReturnTypeResult(type, {
            callSiteInfo: { args: matchResults.argParams, errorNode },
        });
        let returnType = returnTypeResult.type;
        if (returnTypeResult.isIncomplete) {
            isTypeIncomplete = true;
        }

        if (condition.length > 0) {
            returnType = TypeBase.cloneForCondition(returnType, condition);
        }

        let eliminateUnsolvedInUnions = true;

        // If the function is returning a callable, don't eliminate unsolved
        // type vars within a union. There are legit uses for unsolved type vars
        // within a callable.
        if (isFunctionOrOverloaded(returnType)) {
            eliminateUnsolvedInUnions = false;
        }

        let specializedReturnType = solveAndApplyConstraints(returnType, constraints, {
            replaceUnsolved: {
                scopeIds: getTypeVarScopeIds(type),
                unsolvedExemptTypeVars: getUnknownExemptTypeVarsForReturnType(type, returnType),
                tupleClassType: getTupleClassType(),
                eliminateUnsolvedInUnions,
            },
        });
        specializedReturnType = addConditionToType(specializedReturnType, typeCondition, { skipBoundTypeVars: true });

        // If the function includes a ParamSpec and the captured signature(s) includes
        // generic types, we may need to apply those solved TypeVars.
        if (paramSpecConstraints.length > 0) {
            paramSpecConstraints.forEach((paramSpecConstraints) => {
                if (paramSpecConstraints) {
                    specializedReturnType = solveAndApplyConstraints(specializedReturnType, paramSpecConstraints);

                    // It's possible that one or more of the TypeVars or ParamSpecs
                    // in the constraints refer to TypeVars that were solved in
                    // the paramSpecConstraints. Apply these solved TypeVars accordingly.
                    applySourceSolutionToConstraints(
                        constraints,
                        solveConstraints(evaluatorInterface, paramSpecConstraints)
                    );
                }
            });
        }

        // If the final return type is an unpacked tuple, turn it into a normal (unpacked) tuple.
        if (isUnpackedClass(specializedReturnType)) {
            specializedReturnType = ClassType.cloneForPacked(specializedReturnType);
        }

        const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode);
        specializedReturnType = adjustCallableReturnType(errorNode, specializedReturnType, liveTypeVarScopes);

        if (specializedInitSelfType) {
            specializedInitSelfType = solveAndApplyConstraints(specializedInitSelfType, constraints);
        }

        matchResults.argumentMatchScore = argumentMatchScore;

        return {
            argumentErrors,
            argResults,
            anyOrUnknownArg,
            returnType: specializedReturnType,
            isTypeIncomplete,
            activeParam: matchResults.activeParam,
            specializedInitSelfType,
            overloadsUsedForCall: argumentErrors ? [] : [type],
        };
    }

    // In general, all in-scope type variables left in a return type should be
    // replaced with Unknown. However, if the return type is a callable that uses
    // type vars that are found nowhere within the function's input parameters,
    // we'll treat these as though they're scoped to the callable and leave them
    // unsolved.
    function getUnknownExemptTypeVarsForReturnType(functionType: FunctionType, returnType: Type): TypeVarType[] {
        if (isFunction(returnType) && !returnType.shared.name) {
            const returnTypeScopeId = returnType.shared.typeVarScopeId;

            // If one or more type vars found within the return type are scoped to
            // the functionType but don't appear anywhere else within the functionType's
            // input parameters, rescope them to the return type callable so they are
            // not replaced with Unknown.
            if (returnTypeScopeId && functionType.shared.typeVarScopeId) {
                let typeVarsInReturnType = getTypeVarArgsRecursive(returnType);

                // Remove any type variables that appear in the function's input parameters.
                functionType.shared.parameters.forEach((param, index) => {
                    if (FunctionParam.isTypeDeclared(param)) {
                        const typeVarsInInputParam = getTypeVarArgsRecursive(
                            FunctionType.getParamType(functionType, index)
                        );
                        typeVarsInReturnType = typeVarsInReturnType.filter(
                            (returnTypeVar) =>
                                !typeVarsInInputParam.some((inputTypeVar) => isTypeSame(returnTypeVar, inputTypeVar))
                        );
                    }
                });

                return typeVarsInReturnType;
            }
        }

        return [];
    }

    // If the return type includes a generic Callable type, set the type var
    // scope to the scope of the function it was originally associated with
    // to allow these type vars to be solved. This won't work with overloads
    // or unions of callables. It's intended for a specific use case. We may
    // need to make this more sophisticated in the future.
    function adjustCallableReturnType(
        callNode: ExpressionNode,
        returnType: Type,
        liveTypeVarScopes: TypeVarScopeId[]
    ): Type {
        if (!isFunction(returnType)) {
            return returnType;
        }

        // What type variables are referenced in the callable return type? Do not include any live type variables.
        const typeParams = getTypeVarArgsRecursive(returnType).filter(
            (t) => !liveTypeVarScopes.some((scopeId) => t.priv.scopeId === scopeId)
        );

        // If there are no unsolved type variables, we're done. If there are
        // unsolved type variables, rescope them to the callable.
        if (typeParams.length === 0) {
            return returnType;
        }

        inferReturnTypeIfNecessary(returnType);

        // Create a new scope ID based on the caller's position. This
        // will guarantee uniqueness. If another caller uses the same
        // call and arguments, the type vars will not conflict.
        const newScopeId = ParseTreeUtils.getScopeIdForNode(callNode);
        const solution = new ConstraintSolution();

        const newTypeParams = typeParams.map((typeVar) => {
            const newTypeParam = TypeVarType.cloneForScopeId(
                typeVar,
                newScopeId,
                typeVar.priv.scopeName,
                TypeVarScopeType.Function
            );
            solution.setType(typeVar, newTypeParam);
            return newTypeParam;
        });

        return applySolvedTypeVars(
            FunctionType.cloneWithNewTypeVarScopeId(
                returnType,
                newScopeId,
                /* constructorTypeVarScopeId */ undefined,
                newTypeParams
            ),
            solution
        );
    }

    // Tries to assign the call arguments to the function parameter
    // list and reports any mismatches in types or counts. Returns the
    // specialized return type of the call.
    function validateArgs(
        errorNode: ExpressionNode,
        argList: Arg[],
        typeResult: TypeResult<FunctionType>,
        constraints: ConstraintTracker | undefined,
        skipUnknownArgCheck = false,
        inferenceContext: InferenceContext | undefined
    ): CallResult {
        const matchResults = matchArgsToParams(errorNode, argList, typeResult, 0);

        if (matchResults.argumentErrors) {
            // Evaluate types of all args. This will ensure that referenced symbols are
            // not reported as unaccessed. Also pass the expected parameter type as
            // inference context to enable proper completions even when there are errors.
            matchResults.argParams.forEach((argParam) => {
                if (argParam.argument.valueExpression && !isSpeculativeModeInUse(argParam.argument.valueExpression)) {
                    getTypeOfExpression(
                        argParam.argument.valueExpression,
                        /* flags */ undefined,
                        makeInferenceContext(argParam.paramType)
                    );
                }
            });

            // Also evaluate any arguments that weren't matched to parameters
            argList.forEach((arg) => {
                if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) {
                    // Check if this argument was already evaluated above
                    const wasEvaluated = matchResults.argParams.some((argParam) => argParam.argument === arg);
                    if (!wasEvaluated) {
                        getTypeOfExpression(arg.valueExpression);
                    }
                }
            });
            // Use a return type of Unknown but attach a "possible type" to it
            // so the completion provider can suggest better completions.
            const possibleType = FunctionType.getEffectiveReturnType(typeResult.type);
            return {
                returnType:
                    possibleType && !isAnyOrUnknown(possibleType)
                        ? UnknownType.createPossibleType(possibleType, /* isIncomplete */ false)
                        : undefined,
                argumentErrors: true,
                activeParam: matchResults.activeParam,
                overloadsUsedForCall: [],
            };
        }

        return validateArgTypesWithContext(
            errorNode,
            matchResults,
            constraints ?? new ConstraintTracker(),
            skipUnknownArgCheck,
            makeInferenceContext(
                inferenceContext?.expectedType,
                inferenceContext?.isTypeIncomplete,
                inferenceContext?.returnTypeOverride
            )
        );
    }

    // Determines whether the specified argument list satisfies the function
    // signature bound to the specified ParamSpec. Return value indicates success.
    function validateArgTypesForParamSpec(
        errorNode: ExpressionNode,
        argList: Arg[],
        paramSpec: ParamSpecType,
        destConstraints: ConstraintTracker
    ): ParamSpecArgResult {
        const sets = destConstraints.getConstraintSets();

        // Handle the common case where there is only one signature context.
        if (sets.length === 1) {
            return validateArgTypesForParamSpecSignature(errorNode, argList, paramSpec, sets[0]);
        }

        const filteredSets: ConstraintSet[] = [];
        const constraintTrackers: (ConstraintTracker | undefined)[] = [];
        const speculativeNode = getSpeculativeNodeForCall(errorNode);

        sets.forEach((context) => {
            // Use speculative mode to avoid emitting errors or caching types.
            useSpeculativeMode(speculativeNode, () => {
                const paramSpecArgResult = validateArgTypesForParamSpecSignature(
                    errorNode,
                    argList,
                    paramSpec,
                    context
                );

                if (!paramSpecArgResult.argumentErrors) {
                    filteredSets.push(context);
                }

                appendArray(constraintTrackers, paramSpecArgResult.constraintTrackers);
            });
        });

        // Copy back any compatible signature contexts if any were compatible.
        if (filteredSets.length > 0) {
            destConstraints.addConstraintSets(filteredSets);
        }

        // Evaluate non-speculatively to produce a final result and cache types.
        const paramSpecArgResult = validateArgTypesForParamSpecSignature(
            errorNode,
            argList,
            paramSpec,
            filteredSets.length > 0 ? filteredSets[0] : sets[0]
        );

        return { argumentErrors: paramSpecArgResult.argumentErrors, constraintTrackers: constraintTrackers };
    }

    function validateArgTypesForParamSpecSignature(
        errorNode: ExpressionNode,
        argList: Arg[],
        paramSpec: ParamSpecType,
        constraintSet: ConstraintSet
    ): ParamSpecArgResult {
        const solutionSet = solveConstraintSet(evaluatorInterface, constraintSet);
        let paramSpecType = solutionSet.getType(paramSpec);
        paramSpecType = convertTypeToParamSpecValue(paramSpecType ?? paramSpec);

        const matchResults = matchArgsToParams(errorNode, argList, { type: paramSpecType }, 0);
        const functionType = matchResults.overload;
        const constraints = new ConstraintTracker();

        if (matchResults.argumentErrors) {
            // Evaluate types of all args. This will ensure that referenced symbols are
            // not reported as unaccessed.
            argList.forEach((arg) => {
                if (arg.valueExpression && !isSpeculativeModeInUse(arg.valueExpression)) {
                    getTypeOfExpression(arg.valueExpression);
                }
            });

            return { argumentErrors: true, constraintTrackers: [constraints] };
        }

        const functionParamSpec = FunctionType.getParamSpecFromArgsKwargs(functionType);
        const functionWithoutParamSpec = FunctionType.cloneRemoveParamSpecArgsKwargs(functionType);

        // Handle the recursive case where we're passing (*args: P.args, **kwargs: P.args)
        // a remaining function of type (*P).
        if (
            functionParamSpec &&
            functionWithoutParamSpec.shared.parameters.length === 0 &&
            isTypeSame(functionParamSpec, paramSpec)
        ) {
            // If there are any arguments other than *args: P.args or **kwargs: P.kwargs,
            // report an error.
            let argsCount = 0;
            let kwargsCount = 0;
            let argumentErrors = false;
            let argErrorNode: ExpressionNode | undefined;

            for (const arg of argList) {
                const argType = getTypeOfArg(arg, /* inferenceContext */ undefined)?.type;

                if (arg.argCategory === ArgCategory.UnpackedList) {
                    if (isParamSpecArgs(paramSpec, argType)) {
                        argsCount++;
                    }
                } else if (arg.argCategory === ArgCategory.UnpackedDictionary) {
                    if (isParamSpecKwargs(paramSpec, argType)) {
                        kwargsCount++;
                    }
                } else {
                    argErrorNode = argErrorNode ?? arg.valueExpression;
                    argumentErrors = true;
                }
            }

            if (argsCount !== 1 || kwargsCount !== 1) {
                argumentErrors = true;
            }

            if (argumentErrors) {
                addDiagnostic(
                    DiagnosticRule.reportCallIssue,
                    LocMessage.paramSpecArgsMissing().format({
                        type: printType(functionParamSpec),
                    }),
                    argErrorNode ?? errorNode
                );
            }

            return { argumentErrors, constraintTrackers: [constraints] };
        }

        const result = validateArgTypes(errorNode, matchResults, constraints, /* skipUnknownArgCheck */ undefined);
        return { argumentErrors: !!result.argumentErrors, constraintTrackers: [constraints] };
    }

    function validateArgType(
        argParam: ValidateArgTypeParams,
        constraints: ConstraintTracker,
        typeResult: TypeResult<FunctionType> | undefined,
        options: ValidateArgTypeOptions
    ): ArgResult {
        let argType: Type | undefined;
        let expectedTypeDiag: DiagnosticAddendum | undefined;
        let isTypeIncomplete = !!typeResult?.isIncomplete;
        let isCompatible = true;
        const functionName = typeResult?.type.shared.name;
        let skippedBareTypeVarExpectedType = false;

        if (argParam.argument.valueExpression) {
            let expectedType: Type | undefined;

            // Is the expected type a "bare" in-scope TypeVar or a union of bare in-scope TypeVars?
            let isExpectedTypeBareTypeVar = true;
            doForEachSubtype(argParam.paramType, (subtype) => {
                if (!isTypeVar(subtype) || subtype.priv.scopeId !== typeResult?.type.shared.typeVarScopeId) {
                    isExpectedTypeBareTypeVar = false;
                }
            });

            if (!options.isArgFirstPass || !isExpectedTypeBareTypeVar) {
                expectedType = argParam.paramType;

                // If the parameter type is a function with a ParamSpec, don't apply
                // the solved TypeVars if the constraint tracker has more than one signature.
                // This will expand the ParamSpec into an overload, which will cause problems.
                const skipApplySolvedTypeVars =
                    isFunction(argParam.paramType) &&
                    FunctionType.getParamSpecFromArgsKwargs(argParam.paramType) &&
                    constraints.getConstraintSets().length > 1;

                if (!skipApplySolvedTypeVars) {
                    expectedType = solveAndApplyConstraints(expectedType, constraints, /* applyOptions */ undefined, {
                        useLowerBoundOnly: !!options.isArgFirstPass,
                    });
                }
            } else {
                skippedBareTypeVarExpectedType = true;
            }

            // If the expected type is unknown, don't use an expected type. Instead,
            // use default rules for evaluating the expression type.
            if (expectedType && isUnknown(expectedType)) {
                expectedType = undefined;
            }

            // Was the argument's type precomputed by the caller?
            if (argParam.argType) {
                argType = argParam.argType;
            } else {
                const flags = argParam.isinstanceParam
                    ? EvalFlags.IsInstanceArgDefaults
                    : EvalFlags.NoFinal | EvalFlags.NoSpecialize;
                const exprTypeResult = getTypeOfExpression(
                    argParam.argument.valueExpression,
                    flags,
                    makeInferenceContext(expectedType, !!typeResult?.isIncomplete)
                );

                argType = exprTypeResult.type;

                // If the argument is unpacked and we are supposed to enforce
                // that it's an iterator, do so now.
                if (argParam.argument.argCategory === ArgCategory.UnpackedList && argParam.argument.enforceIterable) {
                    const iteratorType = getTypeOfIterator(
                        exprTypeResult,
                        /* isAsync */ false,
                        argParam.argument.valueExpression
                    );
                    // Try to prevent cascading errors if it was not iterable.
                    argType = iteratorType?.type ?? UnknownType.create();
                }

                if (exprTypeResult.isIncomplete) {
                    isTypeIncomplete = true;
                }

                if (expectedType && requiresSpecialization(expectedType)) {
                    // Assign the argument type back to the expected type to assign
                    // values to any unification variables.
                    const clonedConstraints = constraints.clone();
                    if (
                        assignType(
                            expectedType,
                            argType,
                            /* diag */ undefined,
                            clonedConstraints,
                            options?.isArgFirstPass ? AssignTypeFlags.ArgAssignmentFirstPass : AssignTypeFlags.Default
                        )
                    ) {
                        constraints.copyFromClone(clonedConstraints);
                    } else {
                        isCompatible = false;
                    }
                }

                expectedTypeDiag = exprTypeResult.expectedTypeDiagAddendum;
            }

            if (argParam.argument && argParam.argument.name && !isSpeculativeModeInUse(argParam.errorNode)) {
                writeTypeCache(
                    argParam.argument.name,
                    { type: expectedType ?? argType, isIncomplete: isTypeIncomplete },
                    EvalFlags.None
                );
            }
        } else {
            // Was the argument's type precomputed by the caller?
            if (argParam.argType) {
                argType = argParam.argType;
            } else {
                const argTypeResult = getTypeOfArg(
                    argParam.argument,
                    makeInferenceContext(argParam.paramType, isTypeIncomplete)
                );
                argType = argTypeResult.type;
                if (argTypeResult.isIncomplete) {
                    isTypeIncomplete = true;
                }
            }

            // If the argument came from a parameter's default argument value,
            // we may need to specialize the type.
            if (argParam.isDefaultArg) {
                argType = solveAndApplyConstraints(argType, constraints);
            }
        }

        // If we're assigning to a var arg dictionary with a TypeVar type,
        // strip literals before performing the assignment. This is used in
        // places like a dict constructor.
        if (argParam.paramCategory === ParamCategory.KwargsDict && isTypeVar(argParam.paramType)) {
            argType = stripLiteralValue(argType);
        }

        // If there's a constraint filter, apply it to top-level type variables
        // if appropriate. This doesn't properly handle non-top-level constrained
        // type variables.
        if (options.conditionFilter) {
            argType = mapSubtypesExpandTypeVars(
                argType,
                { conditionFilter: options.conditionFilter },
                (expandedSubtype) => {
                    return expandedSubtype;
                }
            );
        }

        const condition = argType.props?.condition;

        let diag = options?.skipReportError ? undefined : new DiagnosticAddendum();

        if (isParamSpec(argParam.paramType)) {
            // Handle the case where we're assigning a *args or **kwargs argument
            // to a *P.args or **P.kwargs parameter.
            if (argParam.paramType.priv.paramSpecAccess !== undefined) {
                return { isCompatible, argType, isTypeIncomplete, condition };
            }

            // Handle the case where we're assigning a *P.args or **P.kwargs argument
            // to a *P.args or **P.kwargs parameter.
            if (isParamSpec(argType) && argType.priv.paramSpecAccess !== undefined) {
                return { isCompatible, argType, isTypeIncomplete, condition };
            }
        }

        let assignTypeFlags = AssignTypeFlags.Default;

        if (argParam.isinstanceParam) {
            assignTypeFlags |= AssignTypeFlags.AllowIsinstanceSpecialForms;
        }

        if (options?.isArgFirstPass) {
            assignTypeFlags |= AssignTypeFlags.ArgAssignmentFirstPass;
        }

        if (!assignType(argParam.paramType, argType, diag?.createAddendum(), constraints, assignTypeFlags)) {
            if (!options?.skipReportError) {
                // Mismatching parameter types are common in untyped code; don't bother spending time
                // printing types if the diagnostic is disabled.
                const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode);
                if (
                    fileInfo.diagnosticRuleSet.reportArgumentType !== 'none' &&
                    !canSkipDiagnosticForNode(argParam.errorNode) &&
                    !isTypeIncomplete
                ) {
                    const argTypeText = printType(argType);
                    const paramTypeText = printType(argParam.paramType);

                    let message: string;
                    if (argParam.paramName && !argParam.isParamNameSynthesized) {
                        if (functionName) {
                            message = LocMessage.argAssignmentParamFunction().format({
                                argType: argTypeText,
                                paramType: paramTypeText,
                                functionName,
                                paramName: argParam.paramName,
                            });
                        } else {
                            message = LocMessage.argAssignmentParam().format({
                                argType: argTypeText,
                                paramType: paramTypeText,
                                paramName: argParam.paramName,
                            });
                        }
                    } else {
                        if (functionName) {
                            message = LocMessage.argAssignmentFunction().format({
                                argType: argTypeText,
                                paramType: paramTypeText,
                                functionName,
                            });
                        } else {
                            message = LocMessage.argAssignment().format({
                                argType: argTypeText,
                                paramType: paramTypeText,
                            });
                        }
                    }

                    // If we have an expected type diagnostic addendum, use that
                    // instead of the local diagnostic addendum because it will
                    // be more informative.
                    if (expectedTypeDiag) {
                        diag = expectedTypeDiag;
                    }

                    addDiagnostic(
                        DiagnosticRule.reportArgumentType,
                        message + diag?.getString(),
                        argParam.errorNode,
                        diag?.getEffectiveTextRange() ?? argParam.errorNode
                    );
                }
            }

            return { isCompatible: false, argType, isTypeIncomplete, skippedBareTypeVarExpectedType, condition };
        }

        if (!options.skipUnknownArgCheck) {
            const simplifiedType = makeTopLevelTypeVarsConcrete(removeUnbound(argType));
            const fileInfo = AnalyzerNodeInfo.getFileInfo(argParam.errorNode);

            function getDiagAddendum() {
                const diagAddendum = new DiagnosticAddendum();
                if (argParam.paramName) {
                    diagAddendum.addMessage(
                        (functionName
                            ? LocAddendum.argParamFunction().format({
                                  paramName: argParam.paramName,
                                  functionName,
                              })
                            : LocAddendum.argParam().format({ paramName: argParam.paramName })) +
                            diagAddendum.getString()
                    );
                }
                return diagAddendum;
            }

            // Do not check for unknown types if the expected type is "Any".
            // Don't print types if reportUnknownArgumentType is disabled for performance.
            if (
                fileInfo.diagnosticRuleSet.reportUnknownArgumentType !== 'none' &&
                !isAny(argParam.paramType) &&
                !isTypeIncomplete
            ) {
                if (isUnknown(simplifiedType)) {
                    const diagAddendum = getDiagAddendum();
                    addDiagnostic(
                        DiagnosticRule.reportUnknownArgumentType,
                        LocMessage.argTypeUnknown() + diagAddendum.getString(),
                        argParam.errorNode
                    );
                } else if (isPartlyUnknown(simplifiedType)) {
                    // If the parameter type is also partially unknown, don't report
                    // the error because it's likely that the partially-unknown type
                    // arose due to bidirectional type matching.
                    if (!isPartlyUnknown(argParam.paramType)) {
                        const diagAddendum = getDiagAddendum();
                        diagAddendum.addMessage(
                            LocAddendum.argumentType().format({
                                type: printType(simplifiedType, { expandTypeAlias: true }),
                            })
                        );
                        addDiagnostic(
                            DiagnosticRule.reportUnknownArgumentType,
                            LocMessage.argTypePartiallyUnknown() + diagAddendum.getString(),
                            argParam.errorNode
                        );
                    }
                }
            }
        }

        return { isCompatible, argType, isTypeIncomplete, skippedBareTypeVarExpectedType, condition };
    }

    function createTypeVarType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined {
        let typeVarName = '';
        let firstConstraintArg: Arg | undefined;
        let defaultValueNode: ExpressionNode | undefined;

        if (argList.length === 0) {
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeVarFirstArg(), errorNode);
            return undefined;
        }

        const firstArg = argList[0];
        if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) {
            typeVarName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join('');
        } else {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeVarFirstArg(),
                firstArg.valueExpression || errorNode
            );
        }

        const typeVar = TypeBase.cloneAsSpecialForm(
            TypeVarType.createInstantiable(typeVarName),
            ClassType.cloneAsInstance(classType)
        );

        // Parse the remaining parameters.
        const paramNameMap = new Map<string, string>();
        for (let i = 1; i < argList.length; i++) {
            const paramNameNode = argList[i].name;
            const paramName = paramNameNode ? paramNameNode.d.value : undefined;

            if (paramName) {
                if (paramNameMap.get(paramName)) {
                    addDiagnostic(
                        DiagnosticRule.reportCallIssue,
                        LocMessage.duplicateParam().format({ name: paramName }),
                        argList[i].valueExpression || errorNode
                    );
                }

                if (paramName === 'bound') {
                    if (TypeVarType.hasConstraints(typeVar)) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarBoundAndConstrained(),
                            argList[i].valueExpression || errorNode
                        );
                    } else {
                        const argType =
                            argList[i].typeResult?.type ??
                            getTypeOfExpressionExpectingType(argList[i].valueExpression!, {
                                noNonTypeSpecialForms: true,
                                typeExpression: true,
                                parsesStringLiteral: true,
                            }).type;
                        if (
                            requiresSpecialization(argType, { ignorePseudoGeneric: true, ignoreImplicitTypeArgs: true })
                        ) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typeVarBoundGeneric(),
                                argList[i].valueExpression || errorNode
                            );
                        }
                        typeVar.shared.boundType = convertToInstance(argType);
                    }
                } else if (paramName === 'covariant') {
                    if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) {
                        if (
                            typeVar.shared.declaredVariance === Variance.Contravariant ||
                            typeVar.shared.declaredVariance === Variance.Auto
                        ) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typeVarVariance(),
                                argList[i].valueExpression!
                            );
                        } else {
                            typeVar.shared.declaredVariance = Variance.Covariant;
                        }
                    }
                } else if (paramName === 'contravariant') {
                    if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) {
                        if (
                            typeVar.shared.declaredVariance === Variance.Covariant ||
                            typeVar.shared.declaredVariance === Variance.Auto
                        ) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typeVarVariance(),
                                argList[i].valueExpression!
                            );
                        } else {
                            typeVar.shared.declaredVariance = Variance.Contravariant;
                        }
                    }
                } else if (paramName === 'infer_variance') {
                    if (argList[i].valueExpression && getBooleanValue(argList[i].valueExpression!)) {
                        if (
                            typeVar.shared.declaredVariance === Variance.Covariant ||
                            typeVar.shared.declaredVariance === Variance.Contravariant
                        ) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typeVarVariance(),
                                argList[i].valueExpression!
                            );
                        } else {
                            typeVar.shared.declaredVariance = Variance.Auto;
                        }
                    }
                } else if (paramName === 'default') {
                    defaultValueNode = argList[i].valueExpression;
                    const argType =
                        argList[i].typeResult?.type ??
                        getTypeOfExpressionExpectingType(defaultValueNode!, {
                            allowTypeVarsWithoutScopeId: true,
                            typeExpression: true,
                        }).type;
                    typeVar.shared.defaultType = convertToInstance(argType);
                    typeVar.shared.isDefaultExplicit = true;

                    const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
                    if (
                        !fileInfo.isStubFile &&
                        PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) &&
                        classType.shared.moduleName !== 'typing_extensions'
                    ) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarDefaultIllegal(),
                            defaultValueNode!
                        );
                    }
                } else {
                    addDiagnostic(
                        DiagnosticRule.reportCallIssue,
                        LocMessage.typeVarUnknownParam().format({ name: paramName }),
                        argList[i].node?.d.name || argList[i].valueExpression || errorNode
                    );
                }

                paramNameMap.set(paramName, paramName);
            } else {
                if (TypeVarType.hasBound(typeVar)) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typeVarBoundAndConstrained(),
                        argList[i].valueExpression || errorNode
                    );
                } else {
                    const argType =
                        argList[i].typeResult?.type ??
                        getTypeOfExpressionExpectingType(argList[i].valueExpression!, {
                            typeExpression: true,
                        }).type;

                    if (requiresSpecialization(argType, { ignorePseudoGeneric: true })) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarConstraintGeneric(),
                            argList[i].valueExpression || errorNode
                        );
                    }
                    TypeVarType.addConstraint(typeVar, convertToInstance(argType));
                    if (firstConstraintArg === undefined) {
                        firstConstraintArg = argList[i];
                    }
                }
            }
        }

        if (typeVar.shared.constraints.length === 1 && firstConstraintArg) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeVarSingleConstraint(),
                firstConstraintArg.valueExpression || errorNode
            );
        }

        // If a default is provided, make sure it is compatible with the bound
        // or constraint.
        if (typeVar.shared.isDefaultExplicit && defaultValueNode) {
            verifyTypeVarDefaultIsCompatible(typeVar, defaultValueNode);
        }

        return typeVar;
    }

    function verifyTypeVarDefaultIsCompatible(typeVar: TypeVarType, defaultValueNode: ExpressionNode) {
        assert(typeVar.shared.isDefaultExplicit);

        const constraints = new ConstraintTracker();
        const concreteDefaultType = makeTopLevelTypeVarsConcrete(
            solveAndApplyConstraints(typeVar.shared.defaultType, constraints, {
                replaceUnsolved: {
                    scopeIds: getTypeVarScopeIds(typeVar),
                    tupleClassType: getTupleClassType(),
                },
            })
        );

        if (typeVar.shared.boundType) {
            if (!assignType(typeVar.shared.boundType, concreteDefaultType)) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarDefaultBoundMismatch(),
                    defaultValueNode
                );
            }
        } else if (TypeVarType.hasConstraints(typeVar)) {
            let isConstraintCompatible = true;

            // If the default type is a constrained TypeVar, make sure all of its constraints
            // are also constraints in typeVar. If the default type is not a constrained TypeVar,
            // use its concrete type to compare against the constraints.
            if (isTypeVar(typeVar.shared.defaultType) && TypeVarType.hasConstraints(typeVar.shared.defaultType)) {
                for (const constraint of typeVar.shared.defaultType.shared.constraints) {
                    if (!typeVar.shared.constraints.some((c) => isTypeSame(c, constraint))) {
                        isConstraintCompatible = false;
                    }
                }
            } else if (
                !typeVar.shared.constraints.some((constraint) =>
                    isTypeSame(constraint, concreteDefaultType, { ignoreConditions: true })
                )
            ) {
                isConstraintCompatible = false;
            }

            if (!isConstraintCompatible) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarDefaultConstraintMismatch(),
                    defaultValueNode
                );
            }
        }
    }

    function createTypeVarTupleType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined {
        let typeVarName = '';

        if (argList.length === 0) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.typeVarFirstArg(), errorNode);
            return undefined;
        }

        const firstArg = argList[0];
        if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) {
            typeVarName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join('');
        } else {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeVarFirstArg(),
                firstArg.valueExpression || errorNode
            );
        }

        const typeVar = TypeBase.cloneAsSpecialForm(
            TypeVarType.createInstantiable(typeVarName, TypeVarKind.TypeVarTuple),
            ClassType.cloneAsInstance(classType)
        );
        typeVar.shared.defaultType = makeTupleObject(evaluatorInterface, [
            { type: UnknownType.create(), isUnbounded: true },
        ]);

        // Parse the remaining parameters.
        for (let i = 1; i < argList.length; i++) {
            const paramNameNode = argList[i].name;
            const paramName = paramNameNode ? paramNameNode.d.value : undefined;

            if (paramName) {
                if (paramName === 'default') {
                    const expr = argList[i].valueExpression;
                    if (expr) {
                        const defaultType = getTypeVarTupleDefaultType(expr, /* isPep695Syntax */ false);
                        if (defaultType) {
                            typeVar.shared.defaultType = defaultType;
                            typeVar.shared.isDefaultExplicit = true;
                        }
                    }

                    const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
                    if (
                        !fileInfo.isStubFile &&
                        PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) &&
                        classType.shared.moduleName !== 'typing_extensions'
                    ) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarDefaultIllegal(),
                            expr!
                        );
                    }
                } else {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typeVarTupleUnknownParam().format({ name: argList[i].name?.d.value || '?' }),
                        argList[i].node?.d.name || argList[i].valueExpression || errorNode
                    );
                }
            } else {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarTupleConstraints(),
                    argList[i].valueExpression || errorNode
                );
            }
        }

        return typeVar;
    }

    function getTypeVarTupleDefaultType(node: ExpressionNode, isPep695Syntax: boolean): Type | undefined {
        const argType = getTypeOfExpressionExpectingType(node, {
            allowUnpackedTuple: true,
            allowTypeVarsWithoutScopeId: true,
            forwardRefs: isPep695Syntax,
            typeExpression: true,
        }).type;
        const isUnpackedTuple = isClass(argType) && isTupleClass(argType) && argType.priv.isUnpacked;
        const isUnpackedTypeVar = isUnpackedTypeVarTuple(argType);

        if (!isUnpackedTuple && !isUnpackedTypeVar) {
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeVarTupleDefaultNotUnpacked(), node);
            return undefined;
        }

        return convertToInstance(argType);
    }

    function createParamSpecType(errorNode: ExpressionNode, classType: ClassType, argList: Arg[]): Type | undefined {
        if (argList.length === 0) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.paramSpecFirstArg(), errorNode);
            return undefined;
        }

        const firstArg = argList[0];
        let paramSpecName = '';
        if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) {
            paramSpecName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join('');
        } else {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.paramSpecFirstArg(),
                firstArg.valueExpression || errorNode
            );
        }

        const paramSpec = TypeBase.cloneAsSpecialForm(
            TypeVarType.createInstantiable(paramSpecName, TypeVarKind.ParamSpec),
            ClassType.cloneAsInstance(classType)
        );

        paramSpec.shared.defaultType = ParamSpecType.getUnknown();

        // Parse the remaining parameters.
        for (let i = 1; i < argList.length; i++) {
            const paramNameNode = argList[i].name;
            const paramName = paramNameNode ? paramNameNode.d.value : undefined;

            if (paramName) {
                if (paramName === 'default') {
                    const expr = argList[i].valueExpression;
                    if (expr) {
                        const defaultType = getParamSpecDefaultType(expr, /* isPep695Syntax */ false);
                        if (defaultType) {
                            paramSpec.shared.defaultType = defaultType;
                            paramSpec.shared.isDefaultExplicit = true;
                        }
                    }

                    const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
                    if (
                        !fileInfo.isStubFile &&
                        PythonVersion.isLessThan(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_13) &&
                        classType.shared.moduleName !== 'typing_extensions'
                    ) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarDefaultIllegal(),
                            expr!
                        );
                    }
                } else {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.paramSpecUnknownParam().format({ name: paramName }),
                        paramNameNode || argList[i].valueExpression || errorNode
                    );
                }
            } else {
                addDiagnostic(
                    DiagnosticRule.reportCallIssue,
                    LocMessage.paramSpecUnknownArg(),
                    argList[i].valueExpression || errorNode
                );
                break;
            }
        }

        return paramSpec;
    }

    function getParamSpecDefaultType(node: ExpressionNode, isPep695Syntax: boolean): Type | undefined {
        const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue);

        if (node.nodeType === ParseNodeType.Ellipsis) {
            FunctionType.addDefaultParams(functionType);
            functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
            return functionType;
        }

        if (node.nodeType === ParseNodeType.List) {
            node.d.items.forEach((paramExpr, index) => {
                const typeResult = getTypeOfExpressionExpectingType(paramExpr, {
                    allowTypeVarsWithoutScopeId: true,
                    forwardRefs: isPep695Syntax,
                    typeExpression: true,
                });

                FunctionType.addParam(
                    functionType,
                    FunctionParam.create(
                        ParamCategory.Simple,
                        convertToInstance(typeResult.type),
                        FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                        `__p${index}`
                    )
                );
            });

            if (node.d.items.length > 0) {
                FunctionType.addPositionOnlyParamSeparator(functionType);
            }

            // Update the type cache so we don't attempt to re-evaluate this node.
            // The type doesn't matter, so use Any.
            writeTypeCache(node, { type: AnyType.create() }, /* flags */ undefined);
            return functionType;
        } else {
            const typeResult = getTypeOfExpressionExpectingType(node, {
                allowParamSpec: true,
                allowTypeVarsWithoutScopeId: true,
                allowEllipsis: true,
                typeExpression: true,
            });

            if (typeResult.typeErrors) {
                return undefined;
            }

            if (isParamSpec(typeResult.type)) {
                FunctionType.addParamSpecVariadics(functionType, typeResult.type);
                return functionType;
            }

            if (
                isClassInstance(typeResult.type) &&
                ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis'])
            ) {
                FunctionType.addDefaultParams(functionType);
                return functionType;
            }
        }

        addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.paramSpecDefaultNotTuple(), node);

        return undefined;
    }

    // Handles a call to TypeAliasType(). This special form allows a caller to programmatically
    // create a type alias as defined in PEP 695. If one or more of the arguments is incorrect,
    // it returns undefined so the normal constructor evaluation can be performed (and type errors
    // emitted).
    function createTypeAliasType(errorNode: ExpressionNode, argList: Arg[]): Type | undefined {
        if (errorNode.nodeType !== ParseNodeType.Call || !errorNode.parent || argList.length < 2) {
            return undefined;
        }

        if (
            errorNode.parent.nodeType !== ParseNodeType.Assignment ||
            errorNode.parent.d.rightExpr !== errorNode ||
            errorNode.parent.d.leftExpr.nodeType !== ParseNodeType.Name
        ) {
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.typeAliasTypeMustBeAssigned(), errorNode);
            return undefined;
        }

        const scope = ScopeUtils.getScopeForNode(errorNode);
        if (scope) {
            if (scope.type !== ScopeType.Class && scope.type !== ScopeType.Module && scope.type !== ScopeType.Builtin) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeAliasTypeBadScope(),
                    errorNode.parent.d.leftExpr
                );
            }
        }

        const nameNode = errorNode.parent.d.leftExpr;

        const firstArg = argList[0];
        if (firstArg.valueExpression && firstArg.valueExpression.nodeType === ParseNodeType.StringList) {
            const typeAliasName = firstArg.valueExpression.d.strings.map((s) => s.d.value).join('');
            if (typeAliasName !== nameNode.d.value) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeAliasTypeNameMismatch(),
                    firstArg.valueExpression
                );
            }
        } else {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeAliasTypeNameArg(),
                firstArg.valueExpression || errorNode
            );
            return undefined;
        }

        let valueExpr: ExpressionNode | undefined;
        let typeParamsExpr: ExpressionNode | undefined;

        // Parse the remaining parameters.
        for (let i = 1; i < argList.length; i++) {
            const paramNameNode = argList[i].name;
            const paramName = paramNameNode ? paramNameNode.d.value : undefined;

            if (paramName) {
                if (paramName === 'type_params' && !typeParamsExpr) {
                    typeParamsExpr = argList[i].valueExpression;
                } else if (paramName === 'value' && !valueExpr) {
                    valueExpr = argList[i].valueExpression;
                } else {
                    return undefined;
                }
            } else if (i === 1) {
                valueExpr = argList[i].valueExpression;
            } else {
                return undefined;
            }
        }

        // The value expression is not optional, so bail if it's not present.
        if (!valueExpr) {
            return undefined;
        }

        let typeParams: TypeVarType[] | undefined;
        if (typeParamsExpr) {
            if (typeParamsExpr.nodeType !== ParseNodeType.Tuple) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeAliasTypeParamInvalid(),
                    typeParamsExpr
                );
                return undefined;
            }

            typeParams = [];
            let isTypeParamListValid = true;
            typeParamsExpr.d.items.map((expr) => {
                let entryType = getTypeOfExpression(
                    expr,
                    EvalFlags.InstantiableType | EvalFlags.AllowTypeVarWithoutScopeId
                ).type;

                if (isTypeVar(entryType)) {
                    if (entryType.priv.scopeId || (isTypeVarTuple(entryType) && entryType.priv.isUnpacked)) {
                        isTypeParamListValid = false;
                    } else {
                        entryType = TypeVarType.cloneForScopeId(
                            entryType,
                            ParseTreeUtils.getScopeIdForNode(nameNode),
                            nameNode.d.value,
                            TypeVarScopeType.TypeAlias
                        );
                    }

                    typeParams!.push(entryType);
                } else {
                    isTypeParamListValid = false;
                }
            });

            if (!isTypeParamListValid) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeAliasTypeParamInvalid(),
                    typeParamsExpr
                );
                return undefined;
            }
        }

        return getTypeOfTypeAliasCommon(
            nameNode,
            nameNode,
            valueExpr,
            /* isPep695Syntax */ false,
            /* typeParamNodes */ undefined,
            () => typeParams
        );
    }

    function getBooleanValue(node: ExpressionNode): boolean {
        if (node.nodeType === ParseNodeType.Constant) {
            if (node.d.constType === KeywordType.False) {
                return false;
            } else if (node.d.constType === KeywordType.True) {
                return true;
            }
        }

        addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.expectedBoolLiteral(), node);
        return false;
    }

    function getFunctionFullName(functionNode: ParseNode, moduleName: string, functionName: string): string {
        const nameParts: string[] = [functionName];

        let curNode: ParseNode | undefined = functionNode;

        // Walk the parse tree looking for classes or functions.
        while (curNode) {
            curNode = ParseTreeUtils.getEnclosingClassOrFunction(curNode);
            if (curNode) {
                nameParts.push(curNode.d.name.d.value);
            }
        }

        nameParts.push(moduleName);

        return nameParts.reverse().join('.');
    }

    // Implements the semantics of the NewType call as documented
    // in the Python specification: The static type checker will treat
    // the new type as if it were a subclass of the original type.
    function createNewType(errorNode: ExpressionNode, argList: Arg[]): ClassType | undefined {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
        let className = '';

        if (argList.length !== 2) {
            addDiagnostic(DiagnosticRule.reportCallIssue, LocMessage.newTypeParamCount(), errorNode);
            return undefined;
        }

        const nameArg = argList[0];
        if (
            nameArg.argCategory === ArgCategory.Simple &&
            nameArg.valueExpression &&
            nameArg.valueExpression.nodeType === ParseNodeType.StringList
        ) {
            className = nameArg.valueExpression.d.strings.map((s) => s.d.value).join('');
        }

        if (!className) {
            addDiagnostic(DiagnosticRule.reportArgumentType, LocMessage.newTypeBadName(), argList[0].node ?? errorNode);
            return undefined;
        }

        if (
            errorNode.parent?.nodeType === ParseNodeType.Assignment &&
            errorNode.parent.d.leftExpr.nodeType === ParseNodeType.Name &&
            errorNode.parent.d.leftExpr.d.value !== className
        ) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.newTypeNameMismatch(),
                errorNode.parent.d.leftExpr
            );
            return undefined;
        }

        let baseClass = getTypeOfArgExpectingType(argList[1]).type;
        let isBaseClassAny = false;

        if (isAnyOrUnknown(baseClass)) {
            baseClass = prefetched?.objectClass ?? UnknownType.create();

            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.newTypeAnyOrUnknown(),
                argList[1].node ?? errorNode
            );

            isBaseClassAny = true;
        }

        // Specifically disallow Annotated.
        if (
            baseClass.props?.specialForm &&
            isClassInstance(baseClass.props.specialForm) &&
            ClassType.isBuiltIn(baseClass.props.specialForm, 'Annotated')
        ) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.newTypeNotAClass(),
                argList[1].node || errorNode
            );
            return undefined;
        }

        if (!isInstantiableClass(baseClass)) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.newTypeNotAClass(),
                argList[1].node || errorNode
            );
            return undefined;
        }

        if (ClassType.isProtocolClass(baseClass) || ClassType.isTypedDictClass(baseClass)) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.newTypeProtocolClass(),
                argList[1].node || errorNode
            );
        } else if (baseClass.priv.literalValue !== undefined) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.newTypeLiteral(),
                argList[1].node || errorNode
            );
        }

        const classType = ClassType.createInstantiable(
            className,
            ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
            fileInfo.moduleName,
            fileInfo.fileUri,
            ClassTypeFlags.Final | ClassTypeFlags.NewTypeClass | ClassTypeFlags.ValidTypeAliasClass,
            ParseTreeUtils.getTypeSourceId(errorNode),
            /* declaredMetaclass */ undefined,
            baseClass.shared.effectiveMetaclass
        );
        classType.shared.baseClasses.push(isBaseClassAny ? AnyType.create() : baseClass);
        computeMroLinearization(classType);

        if (!isBaseClassAny) {
            // Synthesize an __init__ method that accepts only the specified type.
            const initType = FunctionType.createSynthesizedInstance('__init__');
            FunctionType.addParam(
                initType,
                FunctionParam.create(ParamCategory.Simple, AnyType.create(), FunctionParamFlags.TypeDeclared, 'self')
            );
            FunctionType.addParam(
                initType,
                FunctionParam.create(
                    ParamCategory.Simple,
                    ClassType.cloneAsInstance(baseClass),
                    FunctionParamFlags.TypeDeclared,
                    '_x'
                )
            );
            initType.shared.declaredReturnType = getNoneType();
            ClassType.getSymbolTable(classType).set(
                '__init__',
                Symbol.createWithType(SymbolFlags.ClassMember, initType)
            );

            // Synthesize a trivial __new__ method.
            const newType = FunctionType.createSynthesizedInstance('__new__', FunctionTypeFlags.ConstructorMethod);
            FunctionType.addParam(
                newType,
                FunctionParam.create(ParamCategory.Simple, AnyType.create(), FunctionParamFlags.TypeDeclared, 'cls')
            );
            FunctionType.addDefaultParams(newType);
            newType.shared.declaredReturnType = ClassType.cloneAsInstance(classType);
            newType.priv.constructorTypeVarScopeId = getTypeVarScopeId(classType);
            ClassType.getSymbolTable(classType).set('__new__', Symbol.createWithType(SymbolFlags.ClassMember, newType));
        }

        return classType;
    }

    // Implements the semantics of the multi-parameter variant of the "type" call.
    function createClassFromMetaclass(
        errorNode: ExpressionNode,
        argList: Arg[],
        metaclass: ClassType
    ): ClassType | undefined {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
        const arg0Type = getTypeOfArg(argList[0], /* inferenceContext */ undefined).type;
        if (!isClassInstance(arg0Type) || !ClassType.isBuiltIn(arg0Type, 'str')) {
            return undefined;
        }
        const className = (arg0Type.priv.literalValue as string) || '_';

        const arg1Type = getTypeOfArg(argList[1], /* inferenceContext */ undefined).type;

        // TODO - properly handle case where tuple of base classes is provided.
        if (!isClassInstance(arg1Type) || !isTupleClass(arg1Type) || arg1Type.priv.tupleTypeArgs === undefined) {
            return undefined;
        }

        const classType = ClassType.createInstantiable(
            className,
            ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
            fileInfo.moduleName,
            fileInfo.fileUri,
            ClassTypeFlags.ValidTypeAliasClass,
            ParseTreeUtils.getTypeSourceId(errorNode),
            metaclass,
            arg1Type.shared.effectiveMetaclass
        );
        arg1Type.priv.tupleTypeArgs.forEach((typeArg) => {
            const specializedType = makeTopLevelTypeVarsConcrete(typeArg.type);

            if (isEffectivelyInstantiable(specializedType)) {
                classType.shared.baseClasses.push(specializedType);
            } else {
                classType.shared.baseClasses.push(UnknownType.create());
            }
        });

        if (!computeMroLinearization(classType)) {
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), errorNode);
        }

        return classType;
    }

    function getTypeOfConstant(node: ConstantNode, flags: EvalFlags): TypeResult {
        let type: Type | undefined;

        if (node.d.constType === KeywordType.None) {
            if (prefetched?.noneTypeClass) {
                type =
                    (flags & EvalFlags.InstantiableType) !== 0
                        ? prefetched.noneTypeClass
                        : convertToInstance(prefetched.noneTypeClass);

                type = TypeBase.cloneWithTypeForm(type, convertToInstance(type));
            }
        } else if (
            node.d.constType === KeywordType.True ||
            node.d.constType === KeywordType.False ||
            node.d.constType === KeywordType.Debug
        ) {
            type = getBuiltInObject(node, 'bool');

            // For True and False, we can create truthy and falsy
            // versions of 'bool'.
            if (type && isClassInstance(type)) {
                if (node.d.constType === KeywordType.True) {
                    type = ClassType.cloneWithLiteral(type, /* value */ true);
                } else if (node.d.constType === KeywordType.False) {
                    type = ClassType.cloneWithLiteral(type, /* value */ false);
                }
            }
        }

        return { type: type ?? UnknownType.create() };
    }

    function getTypeOfMagicMethodCall(
        objType: Type,
        methodName: string,
        argList: TypeResult[],
        errorNode: ExpressionNode,
        inferenceContext?: InferenceContext,
        diag?: DiagnosticAddendum
    ): TypeResult | undefined {
        let magicMethodSupported = true;
        let isIncomplete = false;
        let deprecationInfo: MagicMethodDeprecationInfo | undefined;
        const overloadsUsedForCall: FunctionType[] = [];

        // Create a helper lambda for object subtypes.
        const handleSubtype = (subtype: ClassType | TypeVarType) => {
            let magicMethodType: Type | undefined;
            const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isClass(concreteSubtype)) {
                magicMethodType = getBoundMagicMethod(concreteSubtype, methodName, subtype, errorNode, diag);
            }

            if (magicMethodType) {
                const functionArgs: Arg[] = argList.map((arg) => {
                    return {
                        argCategory: ArgCategory.Simple,
                        typeResult: arg,
                    };
                });

                let callResult: CallResult | undefined;

                callResult = useSpeculativeMode(errorNode, () => {
                    assert(magicMethodType !== undefined);
                    return validateCallArgs(
                        errorNode,
                        functionArgs,
                        { type: magicMethodType },
                        /* constraints */ undefined,
                        /* skipUnknownArgCheck */ true,
                        inferenceContext
                    );
                });

                // If there were errors with the expected type, try
                // to evaluate without the expected type.
                if (callResult.argumentErrors && inferenceContext) {
                    callResult = useSpeculativeMode(errorNode, () => {
                        assert(magicMethodType !== undefined);
                        return validateCallArgs(
                            errorNode,
                            functionArgs,
                            { type: magicMethodType },
                            /* constraints */ undefined,
                            /* skipUnknownArgCheck */ true,
                            /* inferenceContext */ undefined
                        );
                    });
                }

                if (callResult.argumentErrors) {
                    magicMethodSupported = false;
                } else if (callResult.overloadsUsedForCall) {
                    callResult.overloadsUsedForCall.forEach((overload) => {
                        overloadsUsedForCall.push(overload);

                        // If one of the overloads is deprecated, note the message.
                        if (overload.shared.deprecatedMessage && isClass(concreteSubtype)) {
                            deprecationInfo = {
                                deprecatedMessage: overload.shared.deprecatedMessage,
                                className: concreteSubtype.shared.name,
                                methodName,
                            };
                        }
                    });
                }

                if (callResult.isTypeIncomplete) {
                    isIncomplete = true;
                }

                return callResult.returnType;
            }

            magicMethodSupported = false;
            return undefined;
        };

        const returnType = mapSubtypes(objType, (subtype) => {
            if (isAnyOrUnknown(subtype)) {
                return subtype;
            }

            if (isClassInstance(subtype) || isInstantiableClass(subtype) || isTypeVar(subtype)) {
                return handleSubtype(subtype);
            }

            if (isNoneInstance(subtype)) {
                if (prefetched?.objectClass && isInstantiableClass(prefetched.objectClass)) {
                    // Use 'object' for 'None'.
                    return handleSubtype(ClassType.cloneAsInstance(prefetched.objectClass));
                }
            }

            if (isNoneTypeClass(subtype)) {
                if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) {
                    // Use 'type' for 'type[None]'.
                    return handleSubtype(ClassType.cloneAsInstance(prefetched.typeClass));
                }
            }

            magicMethodSupported = false;
            return undefined;
        });

        if (!magicMethodSupported) {
            return undefined;
        }

        return { type: returnType, isIncomplete, magicMethodDeprecationInfo: deprecationInfo, overloadsUsedForCall };
    }

    function getTypeOfDictionary(
        node: DictionaryNode,
        flags: EvalFlags,
        inferenceContext: InferenceContext | undefined
    ): TypeResult {
        if ((flags & EvalFlags.TypeExpression) !== 0 && node.parent?.nodeType !== ParseNodeType.Argument) {
            const diag = new DiagnosticAddendum();
            diag.addMessage(LocAddendum.useDictInstead());
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.dictInAnnotation() + diag.getString(), node);
        }

        // If the expected type is a union, analyze for each of the subtypes
        // to find one that matches.
        let expectedType = inferenceContext?.expectedType;

        if (inferenceContext && isUnion(inferenceContext.expectedType)) {
            let matchingSubtype: Type | undefined;
            let matchingSubtypeResult: TypeResult | undefined;

            doForEachSubtype(
                inferenceContext.expectedType,
                (subtype) => {
                    // Use shortcut if we've already found a match.
                    if (matchingSubtypeResult && !matchingSubtypeResult.typeErrors) {
                        return;
                    }

                    const subtypeResult = useSpeculativeMode(node, () => {
                        return getTypeOfDictionaryWithContext(node, flags, makeInferenceContext(subtype));
                    });

                    if (subtypeResult && assignType(subtype, subtypeResult.type)) {
                        // If this is the first result we're seeing or it's the first result
                        // without errors, select it as the match.
                        if (!matchingSubtypeResult || (matchingSubtypeResult.typeErrors && !subtypeResult.typeErrors)) {
                            matchingSubtype = subtype;
                            matchingSubtypeResult = subtypeResult;
                        }
                    }
                },
                /* sortSubtypes */ true
            );

            expectedType = matchingSubtype;
        }

        let expectedTypeDiagAddendum = undefined;
        if (expectedType) {
            expectedTypeDiagAddendum = new DiagnosticAddendum();
            const result = getTypeOfDictionaryWithContext(
                node,
                flags,
                makeInferenceContext(expectedType),
                expectedTypeDiagAddendum
            );
            if (result) {
                return result;
            }
        }

        // Don't force strict inference when the expected type is a TypeVar that
        // couldn't constrain the dict's key/value structure. A TypeVar like _T
        // from sorted(Iterable[_T], key=...) doesn't provide useful type
        // information for dict inference, and forcing strict inference would
        // widen heterogeneous value types into a union that can cause false
        // positives (e.g., dict[str, Path | str | dict[Any, Any]] when the
        // user meant each key to have a specific type).
        // Note: bounded/constrained TypeVars with dict-like bounds are handled
        // by getTypeOfDictionaryWithContext above (via makeTopLevelTypeVarsConcrete),
        // so they don't reach this fallback.
        const hasUsefulExpectedType = !!inferenceContext?.expectedType && !isTypeVar(inferenceContext.expectedType);
        const result = getTypeOfDictionaryInferred(node, flags, /* hasExpectedType */ hasUsefulExpectedType);
        return { ...result, expectedTypeDiagAddendum };
    }

    function getTypeOfDictionaryWithContext(
        node: DictionaryNode,
        flags: EvalFlags,
        inferenceContext: InferenceContext,
        expectedDiagAddendum?: DiagnosticAddendum
    ): TypeResult | undefined {
        inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType);
        let concreteExpectedType = makeTopLevelTypeVarsConcrete(inferenceContext.expectedType);

        if (!isClassInstance(concreteExpectedType)) {
            return undefined;
        }

        const keyTypes: TypeResultWithNode[] = [];
        const valueTypes: TypeResultWithNode[] = [];
        let isIncomplete = false;
        let typeErrors = false;

        // Handle TypedDict's as a special case.
        if (ClassType.isTypedDictClass(concreteExpectedType)) {
            // Remove any conditions associated with the type so the resulting type isn't
            // considered compatible with a bound TypeVar.
            concreteExpectedType = TypeBase.cloneForCondition(concreteExpectedType, undefined);

            const expectedTypedDictEntries = getTypedDictMembersForClass(evaluatorInterface, concreteExpectedType);

            // Infer the key and value types if possible.
            const keyValueTypeResult = getKeyAndValueTypesFromDictionary(
                node,
                flags,
                keyTypes,
                valueTypes,
                /* forceStrictInference */ true,
                /* isValueTypeInvariant */ true,
                /* expectedKeyType */ undefined,
                /* expectedValueType */ undefined,
                expectedTypedDictEntries,
                expectedDiagAddendum
            );

            if (keyValueTypeResult.isIncomplete) {
                isIncomplete = true;
            }

            if (keyValueTypeResult.typeErrors) {
                typeErrors = true;
            }

            const resultTypedDict = assignToTypedDict(
                evaluatorInterface,
                concreteExpectedType,
                keyTypes,
                valueTypes,
                // Don't overwrite existing expectedDiagAddendum messages if they were
                // already provided by getKeyValueTypesFromDictionary.
                expectedDiagAddendum?.isEmpty() ? expectedDiagAddendum : undefined
            );
            if (resultTypedDict) {
                return {
                    type: resultTypedDict,
                    isIncomplete,
                };
            }

            return undefined;
        }

        let expectedKeyType: Type;
        let expectedValueType: Type;

        if (isAnyOrUnknown(inferenceContext.expectedType)) {
            expectedKeyType = inferenceContext.expectedType;
            expectedValueType = inferenceContext.expectedType;
        } else {
            const builtInDict = getBuiltInObject(node, 'dict');
            if (!isClassInstance(builtInDict)) {
                return undefined;
            }

            const dictConstraints = new ConstraintTracker();
            if (
                !addConstraintsForExpectedType(
                    evaluatorInterface,
                    builtInDict,
                    inferenceContext.expectedType,
                    dictConstraints,
                    ParseTreeUtils.getTypeVarScopesForNode(node),
                    node.start
                )
            ) {
                return undefined;
            }

            const specializedDict = solveAndApplyConstraints(
                ClassType.cloneAsInstantiable(builtInDict),
                dictConstraints
            ) as ClassType;
            if (!specializedDict.priv.typeArgs || specializedDict.priv.typeArgs.length !== 2) {
                return undefined;
            }

            expectedKeyType = specializedDict.priv.typeArgs[0];
            expectedValueType = specializedDict.priv.typeArgs[1];
        }

        // Dict and MutableMapping types have invariant value types, so they
        // cannot be narrowed further. Other super-types like Mapping, Collection,
        // and Iterable use covariant value types, so they can be narrowed.
        let isValueTypeInvariant = false;
        if (isClassInstance(inferenceContext.expectedType)) {
            if (inferenceContext.expectedType.shared.typeParams.length >= 2) {
                const valueTypeParam = inferenceContext.expectedType.shared.typeParams[1];
                if (TypeVarType.getVariance(valueTypeParam) === Variance.Invariant) {
                    isValueTypeInvariant = true;
                }
            }
        }

        // Infer the key and value types if possible.
        const keyValueResult = getKeyAndValueTypesFromDictionary(
            node,
            flags,
            keyTypes,
            valueTypes,
            /* forceStrictInference */ true,
            isValueTypeInvariant,
            expectedKeyType,
            expectedValueType,
            undefined,
            expectedDiagAddendum
        );

        if (keyValueResult.isIncomplete) {
            isIncomplete = true;
        }

        if (keyValueResult.typeErrors) {
            typeErrors = true;
        }

        const specializedKeyType = inferTypeArgFromExpectedEntryType(
            makeInferenceContext(expectedKeyType),
            keyTypes.map((result) => result.type),
            /* isNarrowable */ false
        );
        const specializedValueType = inferTypeArgFromExpectedEntryType(
            makeInferenceContext(expectedValueType),
            valueTypes.map((result) => result.type),
            !isValueTypeInvariant
        );
        if (!specializedKeyType || !specializedValueType) {
            return undefined;
        }

        const type = getBuiltInObject(node, 'dict', [specializedKeyType, specializedValueType]);
        return { type, isIncomplete, typeErrors };
    }

    // Attempts to infer the type of a dictionary statement. If hasExpectedType
    // is true, strict inference is used for the subexpressions.
    function getTypeOfDictionaryInferred(node: DictionaryNode, flags: EvalFlags, hasExpectedType: boolean): TypeResult {
        const fallbackType = hasExpectedType ? AnyType.create() : UnknownType.create();
        let keyType: Type = fallbackType;
        let valueType: Type = fallbackType;

        const keyTypeResults: TypeResultWithNode[] = [];
        const valueTypeResults: TypeResultWithNode[] = [];

        let isEmptyContainer = false;
        let isIncomplete = false;
        let typeErrors = false;

        // Infer the key and value types if possible.
        const keyValueResult = getKeyAndValueTypesFromDictionary(
            node,
            flags,
            keyTypeResults,
            valueTypeResults,
            /* forceStrictInference */ hasExpectedType,
            /* isValueTypeInvariant */ false
        );

        if (keyValueResult.isIncomplete) {
            isIncomplete = true;
        }

        if (keyValueResult.typeErrors) {
            typeErrors = true;
        }

        // Strip any literal values and TypeForm types.
        const keyTypes = keyTypeResults.map((t) =>
            stripTypeForm(convertSpecialFormToRuntimeValue(stripLiteralValue(t.type), flags, !hasExpectedType))
        );
        const valueTypes = valueTypeResults.map((t) =>
            stripTypeForm(convertSpecialFormToRuntimeValue(stripLiteralValue(t.type), flags, !hasExpectedType))
        );

        if (keyTypes.length > 0) {
            if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.strictDictionaryInference || hasExpectedType) {
                keyType = combineTypes(keyTypes);
            } else {
                keyType = areTypesSame(keyTypes, { ignorePseudoGeneric: true }) ? keyTypes[0] : fallbackType;
            }
        } else {
            keyType = fallbackType;
        }

        // If the value type differs and we're not using "strict inference mode",
        // we need to back off because we can't properly represent the mappings
        // between different keys and associated value types. If all the values
        // are the same type, we'll assume that all values in this dictionary should
        // be the same.
        if (valueTypes.length > 0) {
            if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.strictDictionaryInference || hasExpectedType) {
                valueType = combineTypes(valueTypes);
            } else {
                valueType = areTypesSame(valueTypes, { ignorePseudoGeneric: true }) ? valueTypes[0] : fallbackType;
            }
        } else {
            valueType = fallbackType;
            isEmptyContainer = true;
        }

        const dictClass = getBuiltInType(node, 'dict');
        const type = isInstantiableClass(dictClass)
            ? ClassType.cloneAsInstance(
                  ClassType.specialize(
                      dictClass,
                      [keyType, valueType],
                      /* isTypeArgExplicit */ true,
                      /* includeSubclasses */ undefined,
                      /* tupleTypeArgs */ undefined,
                      isEmptyContainer
                  )
              )
            : UnknownType.create();

        if (isIncomplete) {
            if (getContainerDepth(type) > maxInferredContainerDepth) {
                return { type: UnknownType.create() };
            }
        }

        return { type, isIncomplete, typeErrors };
    }

    function getKeyAndValueTypesFromDictionary(
        node: DictionaryNode,
        flags: EvalFlags,
        keyTypes: TypeResultWithNode[],
        valueTypes: TypeResultWithNode[],
        forceStrictInference: boolean,
        isValueTypeInvariant: boolean,
        expectedKeyType?: Type,
        expectedValueType?: Type,
        expectedTypedDictEntries?: TypedDictEntries,
        expectedDiagAddendum?: DiagnosticAddendum
    ): TypeResult {
        let isIncomplete = false;
        let typeErrors = false;

        // Mask out some of the flags that are not applicable for a dictionary key
        // even if it appears within an inlined TypedDict annotation.
        const keyFlags = flags & ~(EvalFlags.TypeExpression | EvalFlags.StrLiteralAsType | EvalFlags.InstantiableType);

        // Infer the key and value types if possible.
        node.d.items.forEach((entryNode, index) => {
            let addUnknown = true;

            if (entryNode.nodeType === ParseNodeType.DictionaryKeyEntry) {
                const keyTypeResult = getTypeOfExpression(
                    entryNode.d.keyExpr,
                    keyFlags | EvalFlags.StripTupleLiterals,
                    makeInferenceContext(
                        expectedKeyType ?? (forceStrictInference ? NeverType.createNever() : undefined)
                    )
                );

                if (keyTypeResult.isIncomplete) {
                    isIncomplete = true;
                }

                if (keyTypeResult.typeErrors) {
                    typeErrors = true;
                }

                const keyType = keyTypeResult.type;

                if (!keyTypeResult.isIncomplete && !keyTypeResult.typeErrors) {
                    verifySetEntryOrDictKeyIsHashable(entryNode.d.keyExpr, keyType, /* isDictKey */ true);
                }

                if (expectedDiagAddendum && keyTypeResult.expectedTypeDiagAddendum) {
                    expectedDiagAddendum.addAddendum(keyTypeResult.expectedTypeDiagAddendum);
                }

                let valueTypeResult: TypeResult;
                let entryInferenceContext: InferenceContext | undefined;

                if (
                    expectedTypedDictEntries &&
                    isClassInstance(keyType) &&
                    ClassType.isBuiltIn(keyType, 'str') &&
                    isLiteralType(keyType) &&
                    (expectedTypedDictEntries.knownItems.has(keyType.priv.literalValue as string) ||
                        expectedTypedDictEntries.extraItems)
                ) {
                    let effectiveValueType =
                        expectedTypedDictEntries.knownItems.get(keyType.priv.literalValue as string)?.valueType ??
                        expectedTypedDictEntries.extraItems?.valueType;
                    if (effectiveValueType) {
                        const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node);
                        effectiveValueType = transformExpectedType(effectiveValueType, liveTypeVarScopes, node.start);
                    }
                    entryInferenceContext = makeInferenceContext(effectiveValueType);
                    valueTypeResult = getTypeOfExpression(
                        entryNode.d.valueExpr,
                        flags | EvalFlags.StripTupleLiterals,
                        entryInferenceContext
                    );
                } else {
                    let effectiveValueType =
                        expectedValueType ?? (forceStrictInference ? NeverType.createNever() : undefined);
                    if (effectiveValueType) {
                        const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node);
                        effectiveValueType = transformExpectedType(effectiveValueType, liveTypeVarScopes, node.start);
                    }
                    entryInferenceContext = makeInferenceContext(effectiveValueType);
                    valueTypeResult = getTypeOfExpression(
                        entryNode.d.valueExpr,
                        flags | EvalFlags.StripTupleLiterals,
                        entryInferenceContext
                    );
                }

                if (entryInferenceContext && !valueTypeResult.typeErrors) {
                    const fromExpectedType = inferTypeArgFromExpectedEntryType(
                        entryInferenceContext,
                        [valueTypeResult.type],
                        !isValueTypeInvariant
                    );

                    if (fromExpectedType) {
                        valueTypeResult = { ...valueTypeResult, type: fromExpectedType };
                    }
                }

                if (expectedDiagAddendum && valueTypeResult.expectedTypeDiagAddendum) {
                    expectedDiagAddendum.addAddendum(valueTypeResult.expectedTypeDiagAddendum);
                }

                const valueType = valueTypeResult.type;
                if (valueTypeResult.isIncomplete) {
                    isIncomplete = true;
                }

                if (valueTypeResult.typeErrors) {
                    typeErrors = true;
                }

                if (forceStrictInference || index < maxEntriesToUseForInference) {
                    // If an existing key has the same literal type, delete the previous
                    // key since we're overwriting it here.
                    if (isClass(keyType) && isLiteralType(keyType)) {
                        const existingIndex = keyTypes.findIndex((kt) => isTypeSame(keyType, kt.type));
                        if (existingIndex >= 0) {
                            keyTypes.splice(existingIndex, 1);
                            valueTypes.splice(existingIndex, 1);
                        }
                    }

                    keyTypes.push({ node: entryNode.d.keyExpr, type: keyType });
                    valueTypes.push({ node: entryNode.d.valueExpr, type: valueType });
                }

                addUnknown = false;
            } else if (entryNode.nodeType === ParseNodeType.DictionaryExpandEntry) {
                let expectedType: Type | undefined;
                if (expectedKeyType && expectedValueType) {
                    if (
                        prefetched?.supportsKeysAndGetItemClass &&
                        isInstantiableClass(prefetched.supportsKeysAndGetItemClass)
                    ) {
                        expectedType = ClassType.cloneAsInstance(
                            ClassType.specialize(prefetched.supportsKeysAndGetItemClass, [
                                expectedKeyType,
                                expectedValueType,
                            ])
                        );
                    }
                }

                const entryInferenceContext = makeInferenceContext(expectedType);
                let unexpandedTypeResult = getTypeOfExpression(
                    entryNode.d.expr,
                    flags | EvalFlags.StripTupleLiterals,
                    entryInferenceContext
                );

                if (entryInferenceContext && !unexpandedTypeResult.typeErrors) {
                    const fromExpectedType = inferTypeArgFromExpectedEntryType(
                        entryInferenceContext,
                        [unexpandedTypeResult.type],
                        !isValueTypeInvariant
                    );

                    if (fromExpectedType) {
                        unexpandedTypeResult = { ...unexpandedTypeResult, type: fromExpectedType };
                    }
                }

                if (unexpandedTypeResult.isIncomplete) {
                    isIncomplete = true;
                }

                if (unexpandedTypeResult.typeErrors) {
                    typeErrors = true;
                }

                const unexpandedType = unexpandedTypeResult.type;

                if (isAnyOrUnknown(unexpandedType)) {
                    if (forceStrictInference || index < maxEntriesToUseForInference) {
                        keyTypes.push({ node: entryNode, type: unexpandedType });
                        valueTypes.push({ node: entryNode, type: unexpandedType });
                    }
                    addUnknown = false;
                } else if (isClassInstance(unexpandedType) && ClassType.isTypedDictClass(unexpandedType)) {
                    // Handle dictionary expansion for a TypedDict.
                    if (prefetched?.strClass && isInstantiableClass(prefetched.strClass)) {
                        const strObject = ClassType.cloneAsInstance(prefetched.strClass);
                        const tdEntries = getTypedDictMembersForClass(
                            evaluatorInterface,
                            unexpandedType,
                            /* allowNarrowed */ true
                        );

                        tdEntries.knownItems.forEach((entry, name) => {
                            if (entry.isRequired || entry.isProvided) {
                                keyTypes.push({
                                    node: entryNode,
                                    type: ClassType.cloneWithLiteral(strObject, name),
                                });
                                valueTypes.push({ node: entryNode, type: entry.valueType });
                            }
                        });

                        if (!expectedTypedDictEntries) {
                            keyTypes.push({ node: entryNode, type: ClassType.cloneAsInstance(strObject) });
                            valueTypes.push({
                                node: entryNode,
                                type: tdEntries.extraItems?.valueType ?? getObjectType(),
                            });
                        }

                        addUnknown = false;
                    }
                } else if (
                    prefetched?.supportsKeysAndGetItemClass &&
                    isInstantiableClass(prefetched.supportsKeysAndGetItemClass)
                ) {
                    const mappingConstraints = new ConstraintTracker();

                    const supportsKeysAndGetItemClass = selfSpecializeClass(prefetched.supportsKeysAndGetItemClass);

                    if (
                        assignType(
                            ClassType.cloneAsInstance(supportsKeysAndGetItemClass),
                            unexpandedType,
                            /* diag */ undefined,
                            mappingConstraints,
                            AssignTypeFlags.RetainLiteralsForTypeVar
                        )
                    ) {
                        const specializedMapping = solveAndApplyConstraints(
                            supportsKeysAndGetItemClass,
                            mappingConstraints
                        ) as ClassType;
                        const typeArgs = specializedMapping.priv.typeArgs;
                        if (typeArgs && typeArgs.length >= 2) {
                            if (forceStrictInference || index < maxEntriesToUseForInference) {
                                keyTypes.push({ node: entryNode, type: typeArgs[0] });
                                valueTypes.push({ node: entryNode, type: typeArgs[1] });
                            }
                            addUnknown = false;
                        }
                    } else {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.dictUnpackIsNotMapping(),
                            entryNode
                        );
                    }
                }
            } else if (entryNode.nodeType === ParseNodeType.Comprehension) {
                const dictEntryTypeResult = getElementTypeFromComprehension(
                    entryNode,
                    flags | EvalFlags.StripTupleLiterals,
                    expectedValueType,
                    expectedKeyType
                );
                const dictEntryType = dictEntryTypeResult.type;
                if (dictEntryTypeResult.isIncomplete) {
                    isIncomplete = true;
                }

                if (dictEntryTypeResult.typeErrors) {
                    typeErrors = true;
                }

                // The result should be a tuple.
                if (isClassInstance(dictEntryType) && isTupleClass(dictEntryType)) {
                    const typeArgs = dictEntryType.priv.tupleTypeArgs?.map((t) => t.type);
                    if (typeArgs && typeArgs.length === 2) {
                        if (forceStrictInference || index < maxEntriesToUseForInference) {
                            keyTypes.push({ node: entryNode, type: typeArgs[0] });
                            valueTypes.push({ node: entryNode, type: typeArgs[1] });
                        }
                        addUnknown = false;
                    }
                }
            }

            if (addUnknown) {
                if (forceStrictInference || index < maxEntriesToUseForInference) {
                    keyTypes.push({ node: entryNode, type: UnknownType.create() });
                    valueTypes.push({ node: entryNode, type: UnknownType.create() });
                }
            }
        });

        return { type: AnyType.create(), isIncomplete, typeErrors };
    }

    function getTypeOfListOrSet(
        node: ListNode | SetNode,
        flags: EvalFlags,
        inferenceContext: InferenceContext | undefined
    ): TypeResult {
        if (
            (flags & EvalFlags.TypeExpression) !== 0 &&
            node.nodeType === ParseNodeType.List &&
            node.parent?.nodeType !== ParseNodeType.Argument
        ) {
            const diag = new DiagnosticAddendum();
            diag.addMessage(LocAddendum.useListInstead());
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.listInAnnotation() + diag.getString(), node);
        }

        flags &= ~(EvalFlags.TypeExpression | EvalFlags.StrLiteralAsType | EvalFlags.InstantiableType);

        // If the expected type is a union, recursively call for each of the subtypes
        // to find one that matches.
        let expectedType = inferenceContext?.expectedType;

        if (inferenceContext && isUnion(inferenceContext.expectedType)) {
            let matchingSubtype: Type | undefined;
            let matchingSubtypeResult: TypeResult | undefined;

            doForEachSubtype(
                inferenceContext.expectedType,
                (subtype) => {
                    // Use shortcut if we've already found a match.
                    if (matchingSubtypeResult && !matchingSubtypeResult.typeErrors) {
                        return;
                    }

                    const subtypeResult = useSpeculativeMode(node, () => {
                        return getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(subtype));
                    });

                    if (subtypeResult && assignType(subtype, subtypeResult.type)) {
                        // If this is the first result we're seeing or it's the first result
                        // without errors, select it as the match.
                        if (!matchingSubtypeResult || (matchingSubtypeResult.typeErrors && !subtypeResult.typeErrors)) {
                            matchingSubtype = subtype;
                            matchingSubtypeResult = subtypeResult;
                        }
                    }
                },
                /* sortSubtypes */ true
            );

            expectedType = matchingSubtype;
        }

        let expectedTypeDiagAddendum: DiagnosticAddendum | undefined;
        if (expectedType) {
            const result = getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(expectedType));
            if (result && !result.typeErrors) {
                return result;
            }

            expectedTypeDiagAddendum = result?.expectedTypeDiagAddendum;
        }

        const typeResult = getTypeOfListOrSetInferred(
            node,
            flags,
            /* hasExpectedType */ !!inferenceContext?.expectedType
        );
        return { ...typeResult, expectedTypeDiagAddendum };
    }

    // Attempts to determine the type of a list or set statement based on an expected type.
    // Returns undefined if that type cannot be honored.
    function getTypeOfListOrSetWithContext(
        node: ListNode | SetNode,
        flags: EvalFlags,
        inferenceContext: InferenceContext
    ): TypeResult | undefined {
        const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set';
        inferenceContext.expectedType = transformPossibleRecursiveTypeAlias(inferenceContext.expectedType);

        let isIncomplete = false;
        let typeErrors = false;
        const verifyHashable = node.nodeType === ParseNodeType.Set;

        const expectedEntryType = getExpectedEntryTypeForIterable(
            node,
            getBuiltInType(node, builtInClassName),
            inferenceContext
        );
        if (!expectedEntryType) {
            return undefined;
        }

        const entryTypes: Type[] = [];
        const expectedTypeDiagAddendum = new DiagnosticAddendum();
        node.d.items.forEach((entry) => {
            let entryTypeResult: TypeResult;

            if (entry.nodeType === ParseNodeType.Comprehension) {
                entryTypeResult = getElementTypeFromComprehension(
                    entry,
                    flags | EvalFlags.StripTupleLiterals,
                    expectedEntryType
                );
            } else {
                entryTypeResult = getTypeOfExpression(
                    entry,
                    flags | EvalFlags.StripTupleLiterals,
                    makeInferenceContext(expectedEntryType)
                );
            }

            entryTypes.push(entryTypeResult.type);

            if (entryTypeResult.isIncomplete) {
                isIncomplete = true;
            }

            if (entryTypeResult.typeErrors) {
                typeErrors = true;
            }

            if (entryTypeResult.expectedTypeDiagAddendum) {
                expectedTypeDiagAddendum.addAddendum(entryTypeResult.expectedTypeDiagAddendum);
            }

            if (verifyHashable && !entryTypeResult.isIncomplete && !entryTypeResult.typeErrors) {
                verifySetEntryOrDictKeyIsHashable(entry, entryTypeResult.type, /* isDictKey */ false);
            }
        });

        let isTypeInvariant = false;

        if (isClassInstance(inferenceContext.expectedType)) {
            inferVarianceForClass(inferenceContext.expectedType);

            if (
                inferenceContext.expectedType.shared.typeParams.some(
                    (t) => TypeVarType.getVariance(t) === Variance.Invariant
                )
            ) {
                isTypeInvariant = true;
            }
        }

        const specializedEntryType = inferTypeArgFromExpectedEntryType(
            makeInferenceContext(expectedEntryType),
            entryTypes,
            !isTypeInvariant
        );
        if (!specializedEntryType) {
            return { type: UnknownType.create(), isIncomplete, typeErrors: true, expectedTypeDiagAddendum };
        }

        const type = getBuiltInObject(node, builtInClassName, [specializedEntryType]);
        return { type, isIncomplete, typeErrors, expectedTypeDiagAddendum };
    }

    function getExpectedEntryTypeForIterable(
        node: ListNode | SetNode | ComprehensionNode,
        expectedClassType: Type | undefined,
        inferenceContext?: InferenceContext
    ): Type | undefined {
        if (!inferenceContext) {
            return undefined;
        }

        if (!expectedClassType || !isInstantiableClass(expectedClassType)) {
            return undefined;
        }

        if (isAnyOrUnknown(inferenceContext.expectedType)) {
            return inferenceContext.expectedType;
        }

        if (!isClassInstance(inferenceContext.expectedType)) {
            return undefined;
        }

        const constraints = new ConstraintTracker();
        if (
            !addConstraintsForExpectedType(
                evaluatorInterface,
                ClassType.cloneAsInstance(expectedClassType),
                inferenceContext.expectedType,
                constraints,
                ParseTreeUtils.getTypeVarScopesForNode(node),
                node.start
            )
        ) {
            return undefined;
        }

        const specializedListOrSet = solveAndApplyConstraints(expectedClassType, constraints) as ClassType;
        if (!specializedListOrSet.priv.typeArgs) {
            return undefined;
        }

        return specializedListOrSet.priv.typeArgs[0];
    }

    // Attempts to infer the type of a list or set statement with no "expected type".
    function getTypeOfListOrSetInferred(
        node: ListNode | SetNode,
        flags: EvalFlags,
        hasExpectedType: boolean
    ): TypeResult {
        const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set';
        const verifyHashable = node.nodeType === ParseNodeType.Set;
        let isEmptyContainer = false;
        let isIncomplete = false;
        let typeErrors = false;

        let entryTypes: Type[] = [];
        node.d.items.forEach((entry, index) => {
            let entryTypeResult: TypeResult;

            if (entry.nodeType === ParseNodeType.Comprehension && !entry.d.isGenerator) {
                entryTypeResult = getElementTypeFromComprehension(entry, flags | EvalFlags.StripTupleLiterals);
            } else {
                entryTypeResult = getTypeOfExpression(entry, flags | EvalFlags.StripTupleLiterals);
            }

            entryTypeResult.type = stripTypeForm(
                convertSpecialFormToRuntimeValue(entryTypeResult.type, flags, !hasExpectedType)
            );

            if (entryTypeResult.isIncomplete) {
                isIncomplete = true;
            }

            if (entryTypeResult.typeErrors) {
                typeErrors = true;
            }

            if (hasExpectedType || index < maxEntriesToUseForInference) {
                entryTypes.push(entryTypeResult.type);
            }

            if (verifyHashable && !entryTypeResult.isIncomplete && !entryTypeResult.typeErrors) {
                verifySetEntryOrDictKeyIsHashable(entry, entryTypeResult.type, /* isDictKey */ false);
            }
        });

        entryTypes = entryTypes.map((t) => stripLiteralValue(t));

        let inferredEntryType: Type = hasExpectedType ? AnyType.create() : UnknownType.create();
        if (entryTypes.length > 0) {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            // If there was an expected type or we're using strict list inference,
            // combine the types into a union.
            if (
                (builtInClassName === 'list' && fileInfo.diagnosticRuleSet.strictListInference) ||
                (builtInClassName === 'set' && fileInfo.diagnosticRuleSet.strictSetInference) ||
                hasExpectedType
            ) {
                inferredEntryType = combineTypes(entryTypes, { maxSubtypeCount: maxSubtypesForInferredType });
            } else {
                // Is the list or set homogeneous? If so, use stricter rules. Otherwise relax the rules.
                inferredEntryType = areTypesSame(entryTypes, { ignorePseudoGeneric: true })
                    ? entryTypes[0]
                    : inferredEntryType;
            }
        } else {
            isEmptyContainer = true;
        }

        const listOrSetClass = getBuiltInType(node, builtInClassName);
        const type = isInstantiableClass(listOrSetClass)
            ? ClassType.cloneAsInstance(
                  ClassType.specialize(
                      listOrSetClass,
                      [inferredEntryType],
                      /* isTypeArgExplicit */ true,
                      /* includeSubclasses */ undefined,
                      /* tupleTypeArgs */ undefined,
                      isEmptyContainer
                  )
              )
            : UnknownType.create();

        if (isIncomplete) {
            if (getContainerDepth(type) > maxInferredContainerDepth) {
                return { type: UnknownType.create() };
            }
        }

        return { type, isIncomplete, typeErrors };
    }

    function verifySetEntryOrDictKeyIsHashable(entry: ExpressionNode, type: Type, isDictKey: boolean) {
        // Verify that the type is hashable.
        if (!isTypeHashable(type)) {
            const diag = new DiagnosticAddendum();
            diag.addMessage(LocAddendum.unhashableType().format({ type: printType(type) }));

            const message = isDictKey ? LocMessage.unhashableDictKey() : LocMessage.unhashableSetEntry();

            addDiagnostic(DiagnosticRule.reportUnhashable, message + diag.getString(), entry);
        }
    }

    function inferTypeArgFromExpectedEntryType(
        inferenceContext: InferenceContext,
        entryTypes: Type[],
        isNarrowable: boolean
    ): Type | undefined {
        // If the expected type is Any, the resulting type becomes Any.
        if (isAny(inferenceContext.expectedType)) {
            return inferenceContext.expectedType;
        }

        const constraints = new ConstraintTracker();
        const expectedType = inferenceContext.expectedType;
        let isCompatible = true;

        entryTypes.forEach((entryType) => {
            if (isCompatible && !assignType(expectedType, entryType, /* diag */ undefined, constraints)) {
                isCompatible = false;
            }
        });

        if (!isCompatible) {
            return undefined;
        }

        if (isNarrowable && entryTypes.length > 0) {
            const combinedTypes = combineTypes(entryTypes);
            return containsLiteralType(inferenceContext.expectedType)
                ? combinedTypes
                : stripLiteralValue(combinedTypes);
        }

        return mapSubtypes(
            solveAndApplyConstraints(inferenceContext.expectedType, constraints, {
                replaceUnsolved: {
                    scopeIds: [],
                    tupleClassType: getTupleClassType(),
                },
            }),
            (subtype) => {
                if (entryTypes.length !== 1) {
                    return subtype;
                }
                const entryType = entryTypes[0];

                // If the entry type is a TypedDict instance, clone it with additional information.
                if (
                    isTypeSame(subtype, entryType, { ignoreTypedDictNarrowEntries: true }) &&
                    isClass(subtype) &&
                    isClass(entryType) &&
                    ClassType.isTypedDictClass(entryType)
                ) {
                    return ClassType.cloneForNarrowedTypedDictEntries(subtype, entryType.priv.typedDictNarrowedEntries);
                }

                return subtype;
            }
        );
    }

    function getTypeOfYield(node: YieldNode): TypeResult {
        let expectedYieldType: Type | undefined;
        let sentType: Type | undefined;
        let isIncomplete = false;

        const enclosingFunction = ParseTreeUtils.getEnclosingFunction(node);
        if (enclosingFunction) {
            const functionTypeInfo = getTypeOfFunction(enclosingFunction);
            if (functionTypeInfo) {
                let returnType = FunctionType.getEffectiveReturnType(functionTypeInfo.functionType);
                if (returnType) {
                    const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node);
                    returnType = makeTypeVarsBound(returnType, liveScopeIds);

                    expectedYieldType = getGeneratorYieldType(returnType, !!enclosingFunction.d.isAsync);

                    const generatorTypeArgs = getGeneratorTypeArgs(returnType);
                    if (generatorTypeArgs && generatorTypeArgs.length >= 2) {
                        sentType = makeTypeVarsBound(generatorTypeArgs[1], liveScopeIds);
                    }
                }
            }
        }

        if (node.d.expr) {
            const exprResult = getTypeOfExpression(
                node.d.expr,
                /* flags */ undefined,
                makeInferenceContext(expectedYieldType)
            );
            if (exprResult.isIncomplete) {
                isIncomplete = true;
            }
        }

        return { type: sentType || UnknownType.create(), isIncomplete };
    }

    function getTypeOfYieldFrom(node: YieldFromNode): TypeResult {
        const yieldFromTypeResult = getTypeOfExpression(node.d.expr);
        const yieldFromType = yieldFromTypeResult.type;

        const returnedType = mapSubtypes(yieldFromType, (yieldFromSubtype) => {
            // Is the expression a Generator type?
            let generatorTypeArgs = getGeneratorTypeArgs(yieldFromSubtype);
            if (generatorTypeArgs) {
                return generatorTypeArgs.length >= 2 ? generatorTypeArgs[2] : UnknownType.create();
            }

            // Handle old-style (pre-await) Coroutines as a special case.
            if (
                isClassInstance(yieldFromSubtype) &&
                ClassType.isBuiltIn(yieldFromSubtype, ['Coroutine', 'CoroutineType'])
            ) {
                return UnknownType.create();
            }

            // Handle simple iterables.
            const iterableType =
                getTypeOfIterable(yieldFromTypeResult, /* isAsync */ false, node)?.type ?? UnknownType.create();

            // Does the iterable return a Generator?
            generatorTypeArgs = getGeneratorTypeArgs(iterableType);
            return generatorTypeArgs && generatorTypeArgs.length >= 2 ? generatorTypeArgs[2] : UnknownType.create();
        });

        return { type: returnedType };
    }

    function getTypeOfLambda(node: LambdaNode, inferenceContext: InferenceContext | undefined): TypeResult {
        let expectedFunctionTypes: FunctionType[] = [];
        if (inferenceContext) {
            mapSubtypes(inferenceContext.expectedType, (subtype) => {
                if (isFunction(subtype)) {
                    expectedFunctionTypes.push(subtype);
                }

                if (isClassInstance(subtype)) {
                    const boundMethod = getBoundMagicMethod(subtype, '__call__');
                    if (boundMethod && isFunction(boundMethod)) {
                        expectedFunctionTypes.push(boundMethod as FunctionType);
                    }
                }

                return undefined;
            });
        }

        let expectedSubtype: FunctionType | undefined;

        // If there's more than one type, try each in turn until we find one that works.
        if (expectedFunctionTypes.length > 1) {
            // Sort the expected types for deterministic results.
            expectedFunctionTypes = sortTypes(expectedFunctionTypes) as FunctionType[];

            for (const subtype of expectedFunctionTypes) {
                const result = getTypeOfLambdaWithExpectedType(
                    node,
                    subtype,
                    inferenceContext,
                    /* forceSpeculative */ true
                );

                if (!result.typeErrors) {
                    expectedSubtype = subtype;
                    break;
                }
            }
        }

        if (!expectedSubtype && expectedFunctionTypes.length > 0) {
            expectedSubtype = expectedFunctionTypes[0];
        }

        return getTypeOfLambdaWithExpectedType(node, expectedSubtype, inferenceContext, /* forceSpeculative */ false);
    }

    function getTypeOfLambdaWithExpectedType(
        node: LambdaNode,
        expectedType: FunctionType | undefined,
        inferenceContext: InferenceContext | undefined,
        forceSpeculative: boolean
    ): TypeResult {
        let isIncomplete = !!inferenceContext?.isTypeIncomplete;
        let paramsArePositionOnly = true;

        let expectedReturnType: Type | undefined;
        let expectedParamDetails: ParamListDetails | undefined;

        if (expectedType) {
            const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node);
            expectedType = transformExpectedType(expectedType, liveTypeVarScopes, node.start) as FunctionType;

            expectedParamDetails = getParamListDetails(expectedType);
            expectedReturnType = getEffectiveReturnType(expectedType);
        }

        let functionType = FunctionType.createInstance('', '', '', FunctionTypeFlags.PartiallyEvaluated);
        functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node);

        return invalidateTypeCacheIfCanceled(() => {
            // Pre-cache the incomplete function type in case the evaluation of the
            // lambda depends on itself.
            writeTypeCache(node, { type: functionType, isIncomplete: true }, EvalFlags.None);

            // We assume for simplicity that the parameter signature of the lambda is
            // the same as the expected type. If this isn't the case, we'll use
            // object for any lambda parameters that don't match. We could make this
            // more sophisticated in the future, but it becomes very complex to handle
            // all of the permutations.
            let sawParamMismatch = false;

            node.d.params.forEach((param, index) => {
                let paramType: Type | undefined;

                if (expectedParamDetails && !sawParamMismatch) {
                    if (index < expectedParamDetails.params.length) {
                        const expectedParam = expectedParamDetails.params[index];

                        // If the parameter category matches and both of the parameters are
                        // either separators (/ or *) or not separators, copy the type
                        // from the expected parameter.
                        if (
                            expectedParam.param.category === param.d.category &&
                            !param.d.name === !expectedParam.param.name
                        ) {
                            paramType = expectedParam.type;
                        } else {
                            sawParamMismatch = true;
                        }
                    } else if (param.d.defaultValue) {
                        // If the lambda param has a default value but there is no associated
                        // parameter in the expected type, assume that the default value is
                        // being used to explicitly capture a value from an outer scope. Infer
                        // its type from the default value expression.
                        paramType = getTypeOfExpression(param.d.defaultValue, undefined, inferenceContext).type;
                    }
                } else if (param.d.defaultValue) {
                    // If there is no inference context but we have a default value,
                    // use the default value to infer the parameter's type.
                    paramType = inferParamTypeFromDefaultValue(param.d.defaultValue);
                }

                if (param.d.name) {
                    writeTypeCache(
                        param.d.name,
                        {
                            type: transformVariadicParamType(node, param.d.category, paramType ?? UnknownType.create()),
                        },
                        EvalFlags.None
                    );
                }

                if (param.d.defaultValue) {
                    // Evaluate the default value if it's present.
                    getTypeOfExpression(param.d.defaultValue, EvalFlags.ConvertEllipsisToAny);
                }

                // Determine whether we need to insert an implied position-only parameter.
                // This is needed when a function's parameters are named using the old-style
                // way of specifying position-only parameters.
                if (index >= 0) {
                    let isImplicitPositionOnlyParam = false;

                    if (param.d.category === ParamCategory.Simple && param.d.name) {
                        if (isPrivateName(param.d.name.d.value)) {
                            isImplicitPositionOnlyParam = true;
                        }
                    } else {
                        paramsArePositionOnly = false;
                    }

                    if (
                        paramsArePositionOnly &&
                        !isImplicitPositionOnlyParam &&
                        functionType.shared.parameters.length > 0
                    ) {
                        FunctionType.addPositionOnlyParamSeparator(functionType);
                    }

                    if (!isImplicitPositionOnlyParam) {
                        paramsArePositionOnly = false;
                    }
                }

                const functionParam = FunctionParam.create(
                    param.d.category,
                    paramType ?? UnknownType.create(),
                    FunctionParamFlags.TypeDeclared,
                    param.d.name ? param.d.name.d.value : undefined,
                    param.d.defaultValue ? AnyType.create(/* isEllipsis */ true) : undefined,
                    param.d.defaultValue
                );

                FunctionType.addParam(functionType, functionParam);
            });

            if (paramsArePositionOnly && functionType.shared.parameters.length > 0) {
                FunctionType.addPositionOnlyParamSeparator(functionType);
            }

            let typeErrors = false;

            // If we're speculatively evaluating the lambda, create another speculative
            // evaluation scope for the return expression and do not allow retention
            // of the cached types.
            // We need to set allowCacheRetention to false because we don't want to
            // cache the type of the lambda return expression because it depends on
            // the parameter types that we set above, and the speculative type cache
            // doesn't know about that context.
            useSpeculativeMode(
                forceSpeculative || isSpeculativeModeInUse(node) || inferenceContext?.isTypeIncomplete
                    ? node.d.expr
                    : undefined,
                () => {
                    const returnTypeResult = getTypeOfExpression(
                        node.d.expr,
                        /* flags */ undefined,
                        makeInferenceContext(expectedReturnType)
                    );

                    functionType.shared.inferredReturnType = {
                        type: returnTypeResult.type,
                    };
                    if (returnTypeResult.isIncomplete) {
                        isIncomplete = true;
                    }

                    if (returnTypeResult.typeErrors) {
                        typeErrors = true;
                    } else if (expectedReturnType) {
                        // If the expectedReturnType is generic, see if the actual return type
                        // provides types for some or all type variables.
                        if (requiresSpecialization(expectedReturnType)) {
                            const constraints = new ConstraintTracker();
                            if (
                                assignType(expectedReturnType, returnTypeResult.type, /* diag */ undefined, constraints)
                            ) {
                                functionType = solveAndApplyConstraints(functionType, constraints, {
                                    replaceUnsolved: {
                                        scopeIds: [],
                                        tupleClassType: getTupleClassType(),
                                    },
                                }) as FunctionType;
                            }
                        }
                    }
                },
                {
                    dependentType: inferenceContext?.expectedType,
                    allowDiagnostics:
                        !forceSpeculative && !canSkipDiagnosticForNode(node) && !inferenceContext?.isTypeIncomplete,
                }
            );

            // Mark the function type as no longer being evaluated.
            functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated;

            // Is the resulting function compatible with the expected type?
            if (expectedType && !assignType(expectedType, functionType)) {
                typeErrors = true;
            }

            return { type: functionType, isIncomplete, typeErrors };
        });
    }

    function getTypeOfComprehension(
        node: ComprehensionNode,
        flags: EvalFlags,
        inferenceContext?: InferenceContext
    ): TypeResult {
        let isIncomplete = false;
        let typeErrors = false;

        // If any of the "for" clauses are marked async or any of the "if" clauses
        // or any clause other than the leftmost "for" contain an "await" operator,
        // it is treated as an async generator.
        let isAsync = node.d.forIfNodes.some((comp, index) => {
            if (comp.nodeType === ParseNodeType.ComprehensionFor && comp.d.isAsync) {
                return true;
            }
            return index > 0 && ParseTreeUtils.containsAwaitNode(comp);
        });
        let type: Type = UnknownType.create();

        if (ParseTreeUtils.containsAwaitNode(node.d.expr)) {
            isAsync = true;
        }

        const builtInIteratorType = getTypingType(node, isAsync ? 'AsyncGenerator' : 'Generator');

        const expectedEntryType = getExpectedEntryTypeForIterable(node, builtInIteratorType, inferenceContext);
        const elementTypeResult = getElementTypeFromComprehension(
            node,
            flags | EvalFlags.StripTupleLiterals,
            expectedEntryType
        );

        if (elementTypeResult.isIncomplete) {
            isIncomplete = true;
        }

        if (elementTypeResult.typeErrors) {
            typeErrors = true;
        }

        let elementType = elementTypeResult.type;
        if (!expectedEntryType || !containsLiteralType(expectedEntryType)) {
            elementType = stripLiteralValue(elementType);
        }

        if (builtInIteratorType && isInstantiableClass(builtInIteratorType)) {
            type = ClassType.cloneAsInstance(
                ClassType.specialize(
                    builtInIteratorType,
                    isAsync ? [elementType, getNoneType()] : [elementType, getNoneType(), getNoneType()]
                )
            );
        }

        return { type, isIncomplete, typeErrors };
    }

    function reportPossibleUnknownAssignment(
        diagLevel: DiagnosticLevel,
        rule: DiagnosticRule,
        target: NameNode,
        type: Type,
        errorNode: ExpressionNode,
        ignoreEmptyContainers: boolean
    ) {
        // Don't bother if the feature is disabled.
        if (diagLevel === 'none') {
            return;
        }

        const nameValue = target.d.value;

        // Sometimes variables contain an "unbound" type if they're
        // assigned only within conditional statements. Remove this
        // to avoid confusion.
        const simplifiedType = removeUnbound(type);

        if (isUnknown(simplifiedType)) {
            addDiagnostic(rule, LocMessage.typeUnknown().format({ name: nameValue }), errorNode);
        } else if (isPartlyUnknown(simplifiedType)) {
            // If ignoreEmptyContainers is true, don't report the problem for
            // empty containers (lists or dictionaries). We'll report the problem
            // only if the assigned value is used later.
            if (!ignoreEmptyContainers || !isClassInstance(type) || !type.priv.isEmptyContainer) {
                const diagAddendum = new DiagnosticAddendum();
                diagAddendum.addMessage(
                    LocAddendum.typeOfSymbol().format({
                        name: nameValue,
                        type: printType(simplifiedType, { expandTypeAlias: true }),
                    })
                );
                addDiagnostic(
                    rule,
                    LocMessage.typePartiallyUnknown().format({ name: nameValue }) + diagAddendum.getString(),
                    errorNode
                );
            }
        }
    }

    function evaluateComprehensionForIf(node: ComprehensionForIfNode) {
        let isIncomplete = false;

        if (node.nodeType === ParseNodeType.ComprehensionFor) {
            const iterableTypeResult = getTypeOfExpression(node.d.iterableExpr);
            if (iterableTypeResult.isIncomplete) {
                isIncomplete = true;
            }
            const iterableType = stripLiteralValue(iterableTypeResult.type);
            const itemTypeResult = getTypeOfIterator(
                { type: iterableType, isIncomplete: iterableTypeResult.isIncomplete },
                !!node.d.isAsync,
                node.d.iterableExpr
            ) ?? { type: UnknownType.create(), isIncomplete: iterableTypeResult.isIncomplete };

            const targetExpr = node.d.targetExpr;
            assignTypeToExpression(targetExpr, itemTypeResult, node.d.iterableExpr);
        } else {
            assert(node.nodeType === ParseNodeType.ComprehensionIf);

            // Evaluate the test expression to validate it and mark symbols
            // as referenced. This doesn't affect the type of the evaluated
            // comprehension, but it is important for evaluating intermediate
            // expressions such as assignment expressions that can affect other
            // subexpressions.
            getTypeOfExpression(node.d.testExpr);
        }

        return isIncomplete;
    }

    // Returns the type of one entry returned by the comprehension.
    function getElementTypeFromComprehension(
        node: ComprehensionNode,
        flags: EvalFlags,
        expectedValueOrElementType?: Type,
        expectedKeyType?: Type
    ): TypeResult {
        let isIncomplete = false;
        let typeErrors = false;

        // "Execute" the list comprehensions from start to finish.
        for (const forIfNode of node.d.forIfNodes) {
            if (evaluateComprehensionForIf(forIfNode)) {
                isIncomplete = true;
            }
        }

        let type: Type = UnknownType.create();
        if (node.d.expr.nodeType === ParseNodeType.DictionaryKeyEntry) {
            // Create a tuple with the key/value types.
            const keyTypeResult = getTypeOfExpression(
                node.d.expr.d.keyExpr,
                flags,
                makeInferenceContext(expectedKeyType)
            );
            if (keyTypeResult.isIncomplete) {
                isIncomplete = true;
            }
            if (keyTypeResult.typeErrors) {
                typeErrors = true;
            }
            let keyType = keyTypeResult.type;
            if (!expectedKeyType || !containsLiteralType(expectedKeyType)) {
                keyType = stripLiteralValue(keyType);
            }

            const valueTypeResult = getTypeOfExpression(
                node.d.expr.d.valueExpr,
                flags,
                makeInferenceContext(expectedValueOrElementType)
            );
            if (valueTypeResult.isIncomplete) {
                isIncomplete = true;
            }
            if (valueTypeResult.typeErrors) {
                typeErrors = true;
            }
            let valueType = valueTypeResult.type;
            if (!expectedValueOrElementType || !containsLiteralType(expectedValueOrElementType)) {
                valueType = stripLiteralValue(valueType);
            }

            type = makeTupleObject(evaluatorInterface, [
                { type: keyType, isUnbounded: false },
                { type: valueType, isUnbounded: false },
            ]);
        } else if (node.d.expr.nodeType === ParseNodeType.DictionaryExpandEntry) {
            // The parser should have reported an error in this case because it's not allowed.
            getTypeOfExpression(node.d.expr.d.expr, flags, makeInferenceContext(expectedValueOrElementType));
        } else if (isExpressionNode(node)) {
            const exprTypeResult = getTypeOfExpression(
                node.d.expr as ExpressionNode,
                flags,
                makeInferenceContext(expectedValueOrElementType)
            );
            if (exprTypeResult.isIncomplete) {
                isIncomplete = true;
            }
            if (exprTypeResult.typeErrors) {
                typeErrors = true;
            }
            type = exprTypeResult.type;
        }

        return { type, isIncomplete, typeErrors };
    }

    function getTypeOfSlice(node: SliceNode): TypeResult {
        const noneType = getNoneType();
        let startType = noneType;
        let endType = noneType;
        let stepType = noneType;
        let isIncomplete = false;

        // Evaluate the expressions to report errors and record symbol
        // references.
        if (node.d.startValue) {
            const startTypeResult = getTypeOfExpression(node.d.startValue);
            startType = startTypeResult.type;
            if (startTypeResult.isIncomplete) {
                isIncomplete = true;
            }
        }

        if (node.d.endValue) {
            const endTypeResult = getTypeOfExpression(node.d.endValue);
            endType = endTypeResult.type;
            if (endTypeResult.isIncomplete) {
                isIncomplete = true;
            }
        }

        if (node.d.stepValue) {
            const stepTypeResult = getTypeOfExpression(node.d.stepValue);
            stepType = stepTypeResult.type;
            if (stepTypeResult.isIncomplete) {
                isIncomplete = true;
            }
        }

        const sliceType = getBuiltInObject(node, 'slice');

        if (!isClassInstance(sliceType)) {
            return { type: sliceType };
        }

        return { type: ClassType.specialize(sliceType, [startType, endType, stepType]), isIncomplete };
    }

    // Verifies that a type argument's type is not disallowed.
    function validateTypeArg(argResult: TypeResultWithNode, options?: ValidateTypeArgsOptions): boolean {
        if (argResult.typeList) {
            if (!options?.allowTypeArgList) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeArgListNotAllowed(), argResult.node);
                return false;
            } else {
                argResult.typeList.forEach((typeArg) => {
                    validateTypeArg(typeArg);
                });
            }
        }

        if (isEllipsisType(argResult.type)) {
            if (!options?.allowTypeArgList) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), argResult.node);
                return false;
            }
        }

        if (isModule(argResult.type)) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.moduleAsType(), argResult.node);
            return false;
        }

        if (isParamSpec(argResult.type)) {
            if (!options?.allowParamSpec) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), argResult.node);
                return false;
            }
        }

        if (isTypeVarTuple(argResult.type) && !argResult.type.priv.isInUnion) {
            if (!options?.allowTypeVarTuple) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeVarTupleContext(), argResult.node);
                return false;
            } else {
                validateTypeVarTupleIsUnpacked(argResult.type, argResult.node);
            }
        }

        if (!options?.allowEmptyTuple && argResult.isEmptyTupleShorthand) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.zeroLengthTupleNotAllowed(), argResult.node);
            return false;
        }

        if (isUnpackedClass(argResult.type)) {
            if (!options?.allowUnpackedTuples) {
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeForm,
                    LocMessage.unpackedArgInTypeArgument(),
                    argResult.node
                );
                return false;
            }
        }

        return true;
    }

    // Evaluates the type arguments for a Callable type. It should have zero
    // to two arguments.The first argument, if present, should be an ellipsis,
    // a ParamSpec, a Concatenate, or a list of positional parameter types.
    // The second argument, if present, should specify the return type.
    function createCallableType(
        classType: ClassType,
        typeArgs: TypeResultWithNode[] | undefined,
        errorNode: ParseNode
    ): FunctionType {
        let functionType = FunctionType.createInstantiable(FunctionTypeFlags.None);
        let paramSpec: ParamSpecType | undefined;
        let isValidTypeForm = true;

        TypeBase.setSpecialForm(functionType, ClassType.cloneAsInstance(classType));
        functionType.shared.declaredReturnType = UnknownType.create();
        functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(errorNode);

        if (typeArgs && typeArgs.length > 0) {
            functionType.priv.isCallableWithTypeArgs = true;

            if (typeArgs[0].typeList) {
                const typeList = typeArgs[0].typeList;
                let sawUnpacked = false;
                let reportedUnpackedError = false;
                const noteSawUnpacked = (entry: TypeResultWithNode) => {
                    // Make sure we have at most one unpacked TypeVarTuple.
                    if (sawUnpacked) {
                        if (!reportedUnpackedError) {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.variadicTypeArgsTooMany(),
                                entry.node
                            );
                            reportedUnpackedError = true;
                            isValidTypeForm = false;
                        }
                    }
                    sawUnpacked = true;
                };

                typeList.forEach((entry, index) => {
                    let entryType = entry.type;
                    let paramCategory: ParamCategory = ParamCategory.Simple;
                    const paramName = `__p${index.toString()}`;

                    if (isTypeVarTuple(entryType)) {
                        validateTypeVarTupleIsUnpacked(entryType, entry.node);
                        paramCategory = ParamCategory.ArgsList;
                        noteSawUnpacked(entry);
                    } else if (validateTypeArg(entry, { allowUnpackedTuples: true })) {
                        if (isUnpackedClass(entryType)) {
                            paramCategory = ParamCategory.ArgsList;

                            if (
                                entryType.priv.tupleTypeArgs?.some(
                                    (typeArg) => isTypeVarTuple(typeArg.type) || typeArg.isUnbounded
                                )
                            ) {
                                noteSawUnpacked(entry);
                            }
                        }
                    } else {
                        entryType = UnknownType.create();
                    }

                    FunctionType.addParam(
                        functionType,
                        FunctionParam.create(
                            paramCategory,
                            convertToInstance(entryType),
                            FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                            paramName
                        )
                    );
                });

                if (typeList.length > 0) {
                    FunctionType.addPositionOnlyParamSeparator(functionType);
                }
            } else if (isEllipsisType(typeArgs[0].type)) {
                FunctionType.addDefaultParams(functionType);
                functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
            } else if (isParamSpec(typeArgs[0].type)) {
                paramSpec = typeArgs[0].type;
            } else {
                if (isInstantiableClass(typeArgs[0].type) && ClassType.isBuiltIn(typeArgs[0].type, 'Concatenate')) {
                    const concatTypeArgs = typeArgs[0].type.priv.typeArgs;
                    if (concatTypeArgs && concatTypeArgs.length > 0) {
                        concatTypeArgs.forEach((typeArg, index) => {
                            if (index === concatTypeArgs.length - 1) {
                                FunctionType.addPositionOnlyParamSeparator(functionType);

                                if (isParamSpec(typeArg)) {
                                    paramSpec = typeArg;
                                } else if (isEllipsisType(typeArg)) {
                                    FunctionType.addDefaultParams(functionType);
                                    functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
                                }
                            } else {
                                FunctionType.addParam(
                                    functionType,
                                    FunctionParam.create(
                                        ParamCategory.Simple,
                                        typeArg,
                                        FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                                        `__p${index}`
                                    )
                                );
                            }
                        });
                    }
                } else {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.callableFirstArg(),
                        typeArgs[0].node
                    );
                    isValidTypeForm = false;
                }
            }

            if (typeArgs.length > 1) {
                let typeArg1Type = typeArgs[1].type;
                if (!validateTypeArg(typeArgs[1])) {
                    typeArg1Type = UnknownType.create();
                }
                functionType.shared.declaredReturnType = convertToInstance(typeArg1Type);
            } else {
                addDiagnostic(DiagnosticRule.reportMissingTypeArgument, LocMessage.callableSecondArg(), errorNode);

                functionType.shared.declaredReturnType = UnknownType.create();
                isValidTypeForm = false;
            }

            if (typeArgs.length > 2) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.callableExtraArgs(), typeArgs[2].node);
                isValidTypeForm = false;
            }
        } else {
            FunctionType.addDefaultParams(functionType, /* useUnknown */ true);
            functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;

            if (typeArgs && typeArgs.length === 0) {
                isValidTypeForm = false;
            }
        }

        if (paramSpec) {
            FunctionType.addParamSpecVariadics(functionType, convertToInstance(paramSpec));
        }

        if (isValidTypeForm) {
            functionType = TypeBase.cloneWithTypeForm(functionType, convertToInstance(functionType));
        }

        return functionType;
    }

    // Creates an Optional[X] type.
    function createOptionalType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        if (!typeArgs) {
            // If no type arguments are provided, the resulting type
            // depends on whether we're evaluating a type annotation or
            // we're in some other context.
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.optionalExtraArgs(), errorNode);
                return UnknownType.create();
            }

            return classType;
        }

        if (typeArgs.length !== 1) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.optionalExtraArgs(), errorNode);
            return UnknownType.create();
        }

        let typeArg0Type = typeArgs[0].type;
        if (!validateTypeArg(typeArgs[0])) {
            typeArg0Type = UnknownType.create();
        }

        let optionalType = combineTypes([typeArg0Type, prefetched?.noneTypeClass ?? UnknownType.create()]);
        if (prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) {
            optionalType = TypeBase.cloneAsSpecialForm(
                optionalType,
                ClassType.cloneAsInstance(prefetched.unionTypeClass)
            );
        }

        if (typeArg0Type.props?.typeForm) {
            const typeFormType = combineTypes([
                typeArg0Type.props.typeForm,
                convertToInstance(prefetched?.noneTypeClass ?? UnknownType.create()),
            ]);
            optionalType = TypeBase.cloneWithTypeForm(optionalType, typeFormType);
        }

        return optionalType;
    }

    function cloneBuiltinObjectWithLiteral(node: ParseNode, builtInName: string, value: LiteralValue): Type {
        const type = getBuiltInObject(node, builtInName);
        if (isClassInstance(type)) {
            return ClassType.cloneWithLiteral(ClassType.cloneRemoveTypePromotions(type), value);
        }

        return UnknownType.create();
    }

    function cloneBuiltinClassWithLiteral(
        node: ParseNode,
        literalClassType: ClassType,
        builtInName: string,
        value: LiteralValue
    ): Type {
        const type = getBuiltInType(node, builtInName);
        if (isInstantiableClass(type)) {
            const literalType = ClassType.cloneWithLiteral(type, value);
            TypeBase.setSpecialForm(literalType, literalClassType);
            return literalType;
        }

        return UnknownType.create();
    }

    // Creates a type that represents a Literal.
    function createLiteralType(classType: ClassType, node: IndexNode, flags: EvalFlags): Type {
        if (node.d.items.length === 0) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalEmptyArgs(), node.d.leftExpr);
            return UnknownType.create();
        }

        // As per the specification, we support None, int, bool, str, bytes literals
        // plus enum values.
        const literalTypes: Type[] = [];
        let isValidTypeForm = true;

        for (const item of node.d.items) {
            let type: Type | undefined;
            const itemExpr = item.d.valueExpr;

            if (item.d.argCategory !== ArgCategory.Simple) {
                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.unpackedArgInTypeArgument(),
                        itemExpr
                    );
                    type = UnknownType.create();
                    isValidTypeForm = false;
                }
            } else if (item.d.name) {
                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.keywordArgInTypeArgument(),
                        itemExpr
                    );
                    type = UnknownType.create();
                    isValidTypeForm = false;
                }
            } else if (itemExpr.nodeType === ParseNodeType.StringList) {
                const isBytes = (itemExpr.d.strings[0].d.token.flags & StringTokenFlags.Bytes) !== 0;
                const value = itemExpr.d.strings.map((s) => s.d.value).join('');
                if (isBytes) {
                    type = cloneBuiltinClassWithLiteral(node, classType, 'bytes', value);
                } else {
                    type = cloneBuiltinClassWithLiteral(node, classType, 'str', value);
                }

                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    itemExpr.d.strings.forEach((stringNode) => {
                        if ((stringNode.d.token.flags & StringTokenFlags.NamedUnicodeEscape) !== 0) {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.literalNamedUnicodeEscape(),
                                stringNode
                            );
                            isValidTypeForm = false;
                        }
                    });
                }
            } else if (itemExpr.nodeType === ParseNodeType.Number) {
                if (!itemExpr.d.isImaginary && itemExpr.d.isInteger) {
                    type = cloneBuiltinClassWithLiteral(node, classType, 'int', itemExpr.d.value);
                }
            } else if (itemExpr.nodeType === ParseNodeType.Constant) {
                if (itemExpr.d.constType === KeywordType.True) {
                    type = cloneBuiltinClassWithLiteral(node, classType, 'bool', true);
                } else if (itemExpr.d.constType === KeywordType.False) {
                    type = cloneBuiltinClassWithLiteral(node, classType, 'bool', false);
                } else if (itemExpr.d.constType === KeywordType.None) {
                    type = prefetched?.noneTypeClass ?? UnknownType.create();
                }
            } else if (itemExpr.nodeType === ParseNodeType.UnaryOperation) {
                if (itemExpr.d.operator === OperatorType.Subtract || itemExpr.d.operator === OperatorType.Add) {
                    if (itemExpr.d.expr.nodeType === ParseNodeType.Number) {
                        if (!itemExpr.d.expr.d.isImaginary && itemExpr.d.expr.d.isInteger) {
                            type = cloneBuiltinClassWithLiteral(
                                node,
                                classType,
                                'int',
                                itemExpr.d.operator === OperatorType.Subtract
                                    ? -itemExpr.d.expr.d.value
                                    : itemExpr.d.expr.d.value
                            );
                        }
                    }
                }
            }

            if (!type) {
                const exprType = getTypeOfExpression(
                    itemExpr,
                    (flags & (EvalFlags.ForwardRefs | EvalFlags.TypeExpression)) | EvalFlags.NoConvertSpecialForm
                );

                // Is this an enum type?
                if (
                    isClassInstance(exprType.type) &&
                    ClassType.isEnumClass(exprType.type) &&
                    exprType.type.priv.literalValue !== undefined
                ) {
                    type = ClassType.cloneAsInstantiable(exprType.type);
                } else {
                    // Is this a type alias to an existing literal type?
                    let isLiteralType = true;

                    doForEachSubtype(exprType.type, (subtype) => {
                        if (!isInstantiableClass(subtype) || subtype.priv.literalValue === undefined) {
                            if (!isNoneTypeClass(subtype)) {
                                isLiteralType = false;
                            }
                        }
                    });

                    if (isLiteralType) {
                        type = exprType.type;
                    }
                }
            }

            if (!type) {
                if ((flags & EvalFlags.TypeExpression) !== 0) {
                    addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalUnsupportedType(), item);
                    type = UnknownType.create();
                    isValidTypeForm = false;
                } else {
                    return ClassType.cloneAsInstance(classType);
                }
            }

            literalTypes.push(type);
        }

        let result = combineTypes(literalTypes, { skipElideRedundantLiterals: true });

        if (isUnion(result) && prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) {
            result = TypeBase.cloneAsSpecialForm(result, ClassType.cloneAsInstance(prefetched.unionTypeClass));
        }

        if (isValidTypeForm) {
            result = TypeBase.cloneWithTypeForm(result, convertToInstance(result));
        }

        return result;
    }

    // Creates a ClassVar type.
    function createClassVarType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        if (flags & EvalFlags.NoClassVar) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarNotAllowed(), errorNode);
            return AnyType.create();
        }

        if (!typeArgs) {
            return classType;
        } else if (typeArgs.length === 0) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarFirstArgMissing(), errorNode);
            return UnknownType.create();
        } else if (typeArgs.length > 1) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.classVarTooManyArgs(), typeArgs[1].node);
            return UnknownType.create();
        }

        const type = typeArgs[0].type;

        // A ClassVar should not allow TypeVars or generic types parameterized
        // by TypeVars.
        if (requiresSpecialization(type, { ignorePseudoGeneric: true, ignoreSelf: true })) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.classVarWithTypeVar(),
                typeArgs[0].node ?? errorNode
            );
        }

        return type;
    }

    function createTypeFormType(
        classType: ClassType,
        errorNode: ExpressionNode,
        typeArgs: TypeResultWithNode[] | undefined
    ): Type {
        if (!typeArgs || typeArgs.length === 0) {
            return ClassType.specialize(classType, [UnknownType.create()]);
        }

        if (typeArgs.length > 1) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.typeArgsTooMany().format({
                    name: classType.priv.aliasName || classType.shared.name,
                    expected: 1,
                    received: typeArgs.length,
                }),
                typeArgs[1].node
            );
            return UnknownType.create();
        }

        const convertedTypeArgs = typeArgs.map((typeArg) => {
            return convertToInstance(validateTypeArg(typeArg) ? typeArg.type : UnknownType.create());
        });
        let resultType = ClassType.specialize(classType, convertedTypeArgs);

        resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));

        return resultType;
    }

    // Creates a "TypeGuard" and "TypeIs" type.
    function createTypeGuardType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        // If no type arguments are provided, the resulting type
        // depends on whether we're evaluating a type annotation or
        // we're in some other context.
        if (!typeArgs) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeGuardArgCount(), errorNode);
            }

            return classType;
        } else if (typeArgs.length !== 1) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeGuardArgCount(), errorNode);
            return UnknownType.create();
        }

        const convertedTypeArgs = typeArgs.map((typeArg) => {
            return convertToInstance(validateTypeArg(typeArg) ? typeArg.type : UnknownType.create());
        });

        let resultType = ClassType.specialize(classType, convertedTypeArgs);

        resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));

        return resultType;
    }

    function createSelfType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ) {
        // Self doesn't support any type arguments.
        if (typeArgs && typeArgs.length > 0) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeArguments,
                LocMessage.typeArgsExpectingNone().format({
                    name: classType.shared.name,
                }),
                typeArgs[0].node ?? errorNode
            );
        }

        let enclosingClass = ParseTreeUtils.getEnclosingClass(errorNode);

        // If `Self` appears anywhere outside of the class body (e.g. a decorator,
        // base class list, metaclass argument, type parameter list), it is
        // considered illegal.
        if (enclosingClass && !ParseTreeUtils.isNodeContainedWithin(errorNode, enclosingClass.d.suite)) {
            enclosingClass = undefined;
        }

        const enclosingClassTypeResult = enclosingClass ? getTypeOfClass(enclosingClass) : undefined;
        if (!enclosingClassTypeResult) {
            if ((flags & (EvalFlags.TypeExpression | EvalFlags.InstantiableType)) !== 0) {
                addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeContext(), errorNode);
            }

            return UnknownType.create();
        } else if (isInstantiableMetaclass(enclosingClassTypeResult.classType)) {
            // If `Self` appears within a metaclass, it is considered illegal.
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeMetaclass(), errorNode);

            return UnknownType.create();
        }

        const enclosingFunction = ParseTreeUtils.getEnclosingFunction(errorNode);
        if (enclosingFunction) {
            const functionInfo = getFunctionInfoFromDecorators(
                evaluatorInterface,
                enclosingFunction,
                /* isInClass */ true
            );

            const isInnerFunction = !!ParseTreeUtils.getEnclosingFunction(enclosingFunction);
            if (!isInnerFunction) {
                // Check for static methods.
                if (functionInfo.flags & FunctionTypeFlags.StaticMethod) {
                    addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.selfTypeContext(), errorNode);

                    return UnknownType.create();
                }

                if (enclosingFunction.d.params.length > 0) {
                    const firstParamTypeAnnotation = ParseTreeUtils.getTypeAnnotationForParam(enclosingFunction, 0);
                    if (
                        firstParamTypeAnnotation &&
                        !ParseTreeUtils.isNodeContainedWithin(errorNode, firstParamTypeAnnotation)
                    ) {
                        const annotationType = getTypeOfAnnotation(firstParamTypeAnnotation, {
                            typeVarGetsCurScope: true,
                        });
                        if (!isTypeVar(annotationType) || !TypeVarType.isSelf(annotationType)) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.selfTypeWithTypedSelfOrCls(),
                                errorNode
                            );
                        }
                    }
                }
            }
        }

        let result = synthesizeTypeVarForSelfCls(enclosingClassTypeResult.classType, /* isClsParam */ true);

        if (enclosingClass) {
            // If "Self" is used as a type expression within a function suite, it needs
            // to be marked as bound.
            const enclosingSuite = ParseTreeUtils.getEnclosingClassOrFunctionSuite(errorNode);

            if (enclosingSuite && ParseTreeUtils.isNodeContainedWithin(enclosingSuite, enclosingClass)) {
                if (enclosingClass.d.suite !== enclosingSuite) {
                    result = TypeVarType.cloneAsBound(result);
                }
            }
        }

        return result;
    }

    function createRequiredOrReadOnlyType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): TypeResult {
        // If no type arguments are provided, the resulting type
        // depends on whether we're evaluating a type annotation or
        // we're in some other context.
        if (!typeArgs && (flags & EvalFlags.TypeExpression) === 0) {
            return { type: classType };
        }

        if (!typeArgs || typeArgs.length !== 1) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeForm,
                    classType.shared.name === 'ReadOnly'
                        ? LocMessage.readOnlyArgCount()
                        : classType.shared.name === 'Required'
                        ? LocMessage.requiredArgCount()
                        : LocMessage.notRequiredArgCount(),
                    errorNode
                );
            }

            return { type: classType };
        }

        const typeArgType = typeArgs[0].type;

        // Make sure this is used only in a dataclass.
        const containingClassNode = ParseTreeUtils.getEnclosingClass(errorNode, /* stopAtFunction */ true);
        const classTypeInfo = containingClassNode ? getTypeOfClass(containingClassNode) : undefined;

        let isUsageLegal = false;

        if (
            classTypeInfo &&
            isInstantiableClass(classTypeInfo.classType) &&
            ClassType.isTypedDictClass(classTypeInfo.classType)
        ) {
            // The only legal usage is when used in a type annotation statement.
            if (ParseTreeUtils.isNodeContainedWithinNodeType(errorNode, ParseNodeType.TypeAnnotation)) {
                isUsageLegal = true;
            }
        }

        let isReadOnly = typeArgs[0].isReadOnly;
        let isRequired = typeArgs[0].isRequired;
        let isNotRequired = typeArgs[0].isNotRequired;

        if (classType.shared.name === 'ReadOnly') {
            if ((flags & EvalFlags.AllowReadOnly) !== 0) {
                isUsageLegal = true;
            }

            // Nested ReadOnly are not allowed.
            if (typeArgs[0].isReadOnly) {
                isUsageLegal = false;
            }

            isReadOnly = true;
        } else {
            if ((flags & EvalFlags.AllowRequired) !== 0) {
                isUsageLegal = true;
            }

            // Nested Required/NotRequired are not allowed.
            if (typeArgs[0].isRequired || typeArgs[0].isNotRequired) {
                isUsageLegal = false;
            }

            isRequired = classType.shared.name === 'Required';
            isNotRequired = classType.shared.name === 'NotRequired';
        }

        if (!isUsageLegal) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeForm,
                    classType.shared.name === 'ReadOnly'
                        ? LocMessage.readOnlyNotInTypedDict()
                        : classType.shared.name === 'Required'
                        ? LocMessage.requiredNotInTypedDict()
                        : LocMessage.notRequiredNotInTypedDict(),
                    errorNode
                );
            }

            return { type: classType };
        }

        return { type: typeArgType, isReadOnly, isRequired, isNotRequired };
    }

    function createUnpackType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        if (!typeArgs || typeArgs.length !== 1) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unpackArgCount(), errorNode);
            }
            return classType;
        }

        const typeArgType = typeArgs[0].type;

        if ((flags & EvalFlags.AllowUnpackedTuple) !== 0) {
            const unpackedType = applyUnpackToTupleLike(typeArgType);
            if (unpackedType) {
                return unpackedType;
            }

            if ((flags & EvalFlags.TypeExpression) === 0) {
                return classType;
            }
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackExpectedTypeVarTuple(), errorNode);
            return UnknownType.create();
        }

        if ((flags & EvalFlags.AllowUnpackedTypedDict) !== 0) {
            if (isInstantiableClass(typeArgType) && ClassType.isTypedDictClass(typeArgType)) {
                return ClassType.cloneForUnpacked(typeArgType);
            }

            if ((flags & EvalFlags.TypeExpression) === 0) {
                return classType;
            }
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackExpectedTypedDict(), errorNode);
            return UnknownType.create();
        }

        if ((flags & EvalFlags.TypeExpression) === 0) {
            return classType;
        }
        addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.unpackNotAllowed(), errorNode);
        return UnknownType.create();
    }

    // Creates a "Final" type.
    function createFinalType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        if (flags & EvalFlags.NoFinal) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.finalContext(), errorNode);
            }
            return classType;
        }

        if ((flags & EvalFlags.TypeExpression) === 0 || !typeArgs || typeArgs.length === 0) {
            return classType;
        }

        if (typeArgs.length > 1) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.finalTooManyArgs(), errorNode);
        }

        return TypeBase.cloneAsSpecialForm(typeArgs[0].type, classType);
    }

    function createConcatenateType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        if ((flags & EvalFlags.AllowConcatenate) === 0) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateContext(), errorNode);
            }
            return classType;
        }

        if (!typeArgs || typeArgs.length === 0) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateTypeArgsMissing(), errorNode);
        } else {
            typeArgs.forEach((typeArg, index) => {
                if (index === typeArgs.length - 1) {
                    if (!isParamSpec(typeArg.type) && !isEllipsisType(typeArg.type)) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.concatenateParamSpecMissing(),
                            typeArg.node
                        );
                    }
                } else {
                    if (isParamSpec(typeArg.type)) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.paramSpecContext(),
                            typeArg.node
                        );
                    } else if (isUnpackedTypeVarTuple(typeArg.type)) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.typeVarTupleContext(),
                            typeArg.node
                        );
                    } else if (isUnpackedClass(typeArg.type)) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.unpackedArgInTypeArgument(),
                            typeArg.node
                        );
                    }
                }
            });
        }

        return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true);
    }

    function createAnnotatedType(
        classType: ClassType,
        errorNode: ExpressionNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): TypeResult {
        let type: Type | undefined;

        const typeExprFlags = EvalFlags.TypeExpression | EvalFlags.NoConvertSpecialForm;
        if ((flags & typeExprFlags) === 0) {
            type = ClassType.cloneAsInstance(classType);

            if (typeArgs && typeArgs.length >= 1 && typeArgs[0].type.props?.typeForm) {
                type = TypeBase.cloneWithTypeForm(type, typeArgs[0].type.props.typeForm);
            }

            return { type };
        }

        if (typeArgs && typeArgs.length > 0) {
            type = typeArgs[0].type;

            if (typeArgs.length < 2) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.annotatedTypeArgMissing(), errorNode);
            } else {
                type = validateAnnotatedMetadata(errorNode, typeArgs[0].type, typeArgs.slice(1));
            }
        }

        if (!type || !typeArgs || typeArgs.length === 0) {
            return { type: AnyType.create() };
        }

        if (typeArgs[0].typeList) {
            addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.typeArgListNotAllowed(), typeArgs[0].node);
        }

        return {
            type: TypeBase.cloneAsSpecialForm(type, ClassType.cloneAsInstance(classType)),
            isReadOnly: typeArgs[0].isReadOnly,
            isRequired: typeArgs[0].isRequired,
            isNotRequired: typeArgs[0].isNotRequired,
        };
    }

    // Enforces metadata consistency as specified in PEP 746.
    function validateAnnotatedMetadata(
        errorNode: ExpressionNode,
        baseType: Type,
        metaArgs: TypeResultWithNode[]
    ): Type {
        for (const metaArg of metaArgs) {
            validateTypeMetadata(errorNode, baseType, metaArg);
        }

        return baseType;
    }

    // Determines whether the metadata object is compatible with the base type.
    function validateTypeMetadata(errorNode: ExpressionNode, baseType: Type, metaArg: TypeResultWithNode): boolean {
        // This function was added for draft PEP 746, but the functionality
        // has been removed for now while the PEP is being revised.
        return true;
    }

    // Creates one of several "special" types that are defined in typing.pyi
    // but not declared in their entirety. This includes the likes of "Tuple",
    // "Dict", etc.
    function createSpecialType(
        classType: ClassType,
        typeArgs: TypeResultWithNode[] | undefined,
        paramLimit?: number,
        allowParamSpec = false,
        isSpecialForm = true
    ): Type {
        const isTupleTypeParam = ClassType.isTupleClass(classType);

        if (typeArgs) {
            if (isTupleTypeParam && typeArgs.length === 1 && typeArgs[0].isEmptyTupleShorthand) {
                typeArgs = [];
            } else {
                let sawUnpacked = false;
                const noteSawUnpacked = (typeArg: TypeResultWithNode) => {
                    if (sawUnpacked) {
                        if (!reportedUnpackedError) {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.variadicTypeArgsTooMany(),
                                typeArg.node
                            );
                            reportedUnpackedError = true;
                        }
                    }
                    sawUnpacked = true;
                };
                let reportedUnpackedError = false;

                // Verify that we didn't receive any inappropriate types.
                typeArgs.forEach((typeArg, index) => {
                    assert(typeArgs !== undefined);
                    if (isEllipsisType(typeArg.type)) {
                        if (!isTupleTypeParam) {
                            if (!allowParamSpec) {
                                addDiagnostic(
                                    DiagnosticRule.reportInvalidTypeForm,
                                    LocMessage.ellipsisContext(),
                                    typeArg.node
                                );
                            }
                        } else if (typeArgs.length !== 2 || index !== 1) {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.ellipsisSecondArg(),
                                typeArg.node
                            );
                        } else {
                            if (isTypeVarTuple(typeArgs[0].type) && !typeArgs[0].type.priv.isInUnion) {
                                addDiagnostic(
                                    DiagnosticRule.reportInvalidTypeForm,
                                    LocMessage.typeVarTupleContext(),
                                    typeArgs[0].node
                                );
                            } else if (isUnpackedClass(typeArgs[0].type)) {
                                addDiagnostic(
                                    DiagnosticRule.reportInvalidTypeForm,
                                    LocMessage.ellipsisAfterUnpacked(),
                                    typeArg.node
                                );
                            }
                        }
                    } else if (isParamSpec(typeArg.type) && allowParamSpec) {
                        // Nothing to do - this is allowed.
                    } else if (paramLimit === undefined && isTypeVarTuple(typeArg.type)) {
                        if (!typeArg.type.priv.isInUnion) {
                            noteSawUnpacked(typeArg);
                        }
                        validateTypeVarTupleIsUnpacked(typeArg.type, typeArg.node);
                    } else if (paramLimit === undefined && isUnpackedClass(typeArg.type)) {
                        if (isUnboundedTupleClass(typeArg.type)) {
                            noteSawUnpacked(typeArg);
                        }
                        validateTypeArg(typeArg, { allowUnpackedTuples: true });
                    } else {
                        validateTypeArg(typeArg);
                    }
                });
            }
        }

        let typeArgTypes = typeArgs ? typeArgs.map((t) => convertToInstance(t.type)) : [];

        // Make sure the argument list count is correct.
        if (paramLimit !== undefined) {
            if (typeArgs && typeArgTypes.length > paramLimit) {
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeForm,
                    LocMessage.typeArgsTooMany().format({
                        name: classType.priv.aliasName || classType.shared.name,
                        expected: paramLimit,
                        received: typeArgTypes.length,
                    }),
                    typeArgs[paramLimit].node
                );
                typeArgTypes = typeArgTypes.slice(0, paramLimit);
            } else if (typeArgTypes.length < paramLimit) {
                // Fill up the remainder of the slots with unknown types.
                while (typeArgTypes.length < paramLimit) {
                    typeArgTypes.push(UnknownType.create());
                }
            }
        }

        // Handle tuple type params as a special case.
        let returnType: Type;
        if (isTupleTypeParam) {
            const tupleTypeArgTypes: TupleTypeArg[] = [];

            // If no type args are provided and it's a tuple, default to [Unknown, ...].
            if (!typeArgs) {
                tupleTypeArgTypes.push({ type: UnknownType.create(), isUnbounded: true });
            } else {
                typeArgs.forEach((typeArg, index) => {
                    if (index === 1 && isEllipsisType(typeArgTypes[index])) {
                        if (tupleTypeArgTypes.length === 1 && !tupleTypeArgTypes[0].isUnbounded) {
                            tupleTypeArgTypes[0] = { type: tupleTypeArgTypes[0].type, isUnbounded: true };
                        }
                    } else if (isUnpackedClass(typeArg.type) && typeArg.type.priv.tupleTypeArgs) {
                        appendArray(tupleTypeArgTypes, typeArg.type.priv.tupleTypeArgs);
                    } else {
                        tupleTypeArgTypes.push({ type: typeArgTypes[index], isUnbounded: false });
                    }
                });
            }

            returnType = specializeTupleClass(classType, tupleTypeArgTypes, typeArgs !== undefined);
        } else {
            returnType = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined);
        }

        if (isSpecialForm) {
            returnType = TypeBase.cloneAsSpecialForm(returnType, classType);
        }

        return returnType;
    }

    // Unpacks the index expression for a "Union[X, Y, Z]" type annotation.
    function createUnionType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
        const types: Type[] = [];
        let allowSingleTypeArg = false;
        let isValidTypeForm = true;

        if (!typeArgs) {
            // If no type arguments are provided, the resulting type
            // depends on whether we're evaluating a type annotation or
            // we're in some other context.
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.unionTypeArgCount(), errorNode);
                return NeverType.createNever();
            }

            return classType;
        }

        for (const typeArg of typeArgs) {
            let typeArgType = typeArg.type;

            // This is an experimental feature because Unions of unpacked TypeVarTuples are not officially supported.
            if (
                !validateTypeArg(typeArg, {
                    allowTypeVarTuple: fileInfo.diagnosticRuleSet.enableExperimentalFeatures,
                })
            ) {
                typeArgType = UnknownType.create();
            }

            if (isTypeVar(typeArgType) && isUnpackedTypeVarTuple(typeArgType)) {
                // This is an experimental feature because Unions of unpacked TypeVarTuples are not officially supported.
                if (fileInfo.diagnosticRuleSet.enableExperimentalFeatures) {
                    // If this is an unpacked TypeVar, note that it is in a union so we can
                    // differentiate between Unpack[Vs] and Union[Unpack[Vs]].
                    typeArgType = TypeVarType.cloneForUnpacked(typeArgType, /* isInUnion */ true);
                    allowSingleTypeArg = true;
                } else {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.unionUnpackedTypeVarTuple(),
                        errorNode
                    );

                    typeArgType = UnknownType.create();
                    isValidTypeForm = false;
                }
            }

            types.push(typeArgType);
        }

        // Validate that we received at least two type arguments. One type argument
        // is allowed if it's an unpacked TypeVarTuple or tuple. None is also allowed
        // since it is used to define NoReturn in typeshed stubs).
        if (types.length === 1 && !allowSingleTypeArg && !isNoneInstance(types[0])) {
            if ((flags & EvalFlags.TypeExpression) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeArguments, LocMessage.unionTypeArgCount(), errorNode);
            }
            isValidTypeForm = false;
        }

        let unionType = combineTypes(types, { skipElideRedundantLiterals: true });
        if (prefetched?.unionTypeClass && isInstantiableClass(prefetched.unionTypeClass)) {
            unionType = TypeBase.cloneAsSpecialForm(unionType, ClassType.cloneAsInstance(prefetched.unionTypeClass));
        }

        if (!isValidTypeForm || types.some((t) => !t.props?.typeForm)) {
            if (unionType.props?.typeForm) {
                unionType = TypeBase.cloneWithTypeForm(unionType, undefined);
            }
        } else {
            const typeFormType = combineTypes(types.map((t) => t.props!.typeForm!));
            unionType = TypeBase.cloneWithTypeForm(unionType, typeFormType);
        }

        return unionType;
    }

    // Creates a type that represents "Generic[T1, T2, ...]", used in the
    // definition of a generic class.
    function createGenericType(
        classType: ClassType,
        errorNode: ParseNode,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags
    ): Type {
        if (!typeArgs) {
            // If no type arguments are provided, the resulting type
            // depends on whether we're evaluating a type annotation or
            // we're in some other context.
            if ((flags & (EvalFlags.TypeExpression | EvalFlags.NoNakedGeneric)) !== 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.genericTypeArgMissing(), errorNode);
            }

            return classType;
        }

        const uniqueTypeVars: TypeVarType[] = [];
        if (typeArgs) {
            // Make sure there's at least one type arg.
            if (typeArgs.length === 0) {
                addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.genericTypeArgMissing(), errorNode);
            }

            // Make sure that all of the type args are typeVars and are unique.
            typeArgs.forEach((typeArg) => {
                if (!isTypeVar(typeArg.type)) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.genericTypeArgTypeVar(),
                        typeArg.node
                    );
                } else {
                    if (uniqueTypeVars.some((t) => isTypeSame(t, typeArg.type))) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.genericTypeArgUnique(),
                            typeArg.node
                        );
                    }

                    uniqueTypeVars.push(typeArg.type);
                }
            });
        }

        return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true);
    }

    function transformTypeForTypeAlias(
        type: Type,
        errorNode: ExpressionNode,
        typeAliasPlaceholder: TypeVarType,
        isPep695TypeVarType: boolean,
        typeParamNodes?: TypeParameterNode[]
    ): Type {
        // If this is a recursive type alias that hasn't yet been fully resolved
        // (i.e. there is no boundType associated with it), don't apply the transform.
        if (isTypeAliasPlaceholder(type)) {
            return type;
        }

        const sharedInfo = typeAliasPlaceholder.shared.recursiveAlias;
        assert(sharedInfo !== undefined);

        let typeParams: TypeVarType[] | undefined = sharedInfo.typeParams;
        if (!typeParams) {
            // Determine if there are any generic type parameters associated
            // with this type alias.
            typeParams = [];

            addTypeVarsToListIfUnique(typeParams, getTypeVarArgsRecursive(type));

            // Don't include any synthesized type variables.
            typeParams = typeParams.filter((typeVar) => !typeVar.shared.isSynthesized);
        }

        // Convert all type variables to instances.
        typeParams = typeParams.map((typeVar) => {
            if (TypeBase.isInstance(typeVar)) {
                return typeVar;
            }
            return convertToInstance(typeVar);
        });

        // See if the type alias includes a TypeVarTuple followed by a TypeVar
        // with a default value. This isn't allowed.
        const firstTypeVarTupleIndex = typeParams.findIndex((typeVar) => isTypeVarTuple(typeVar));
        if (firstTypeVarTupleIndex >= 0) {
            const typeVarWithDefaultIndex = typeParams.findIndex(
                (typeVar, index) =>
                    index > firstTypeVarTupleIndex && !isParamSpec(typeVar) && typeVar.shared.isDefaultExplicit
            );

            if (typeVarWithDefaultIndex >= 0) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarWithDefaultFollowsVariadic().format({
                        typeVarName: typeParams[typeVarWithDefaultIndex].shared.name,
                        variadicName: typeParams[firstTypeVarTupleIndex].shared.name,
                    }),
                    typeParamNodes ? typeParamNodes[typeVarWithDefaultIndex].d.name : errorNode
                );
            }
        }

        // Validate the default types for all type parameters.
        typeParams.forEach((typeParam, index) => {
            assert(typeParams !== undefined);
            let bestErrorNode = errorNode;
            if (typeParamNodes && index < typeParamNodes.length) {
                bestErrorNode = typeParamNodes[index].d.defaultExpr ?? typeParamNodes[index].d.name;
            }
            validateTypeParamDefault(bestErrorNode, typeParam, typeParams.slice(0, index), sharedInfo.typeVarScopeId);
        });

        // Verify that we have at most one TypeVarTuple.
        const variadics = typeParams.filter((param) => isTypeVarTuple(param));
        if (variadics.length > 1) {
            addDiagnostic(
                DiagnosticRule.reportInvalidTypeForm,
                LocMessage.variadicTypeParamTooManyAlias().format({
                    names: variadics.map((v) => `"${v.shared.name}"`).join(', '),
                }),
                errorNode
            );
        }

        if (!sharedInfo.isTypeAliasType && !isPep695TypeVarType) {
            const boundTypeVars = typeParams.filter(
                (typeVar) =>
                    typeVar.priv.scopeId !== sharedInfo.typeVarScopeId &&
                    typeVar.priv.scopeType === TypeVarScopeType.Class
            );

            if (boundTypeVars.length > 0) {
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeForm,
                    LocMessage.genericTypeAliasBoundTypeVar().format({
                        names: boundTypeVars.map((t) => `${t.shared.name}`).join(', '),
                    }),
                    errorNode
                );
            }
        }

        if (!TypeBase.isInstantiable(type)) {
            return type;
        }

        sharedInfo.typeParams = typeParams.length > 0 ? typeParams : undefined;

        let typeAlias = TypeBase.cloneForTypeAlias(type, {
            shared: sharedInfo,
            typeArgs: undefined,
        });

        // All PEP 695 type aliases are special forms because they are
        // TypeAliasType objects at runtime.
        if (sharedInfo.isTypeAliasType || isPep695TypeVarType) {
            const typeAliasTypeClass = getTypingType(errorNode, 'TypeAliasType');
            if (typeAliasTypeClass && isInstantiableClass(typeAliasTypeClass)) {
                typeAlias = TypeBase.cloneAsSpecialForm(typeAlias, ClassType.cloneAsInstance(typeAliasTypeClass));
            }
        }

        // Delete the TypeForm info. The type alias serves as its own TypeForm info.
        if (typeAlias.props?.typeForm) {
            typeAlias = TypeBase.cloneWithTypeForm(typeAlias, undefined);
        }

        return typeAlias;
    }

    function createSpecialBuiltInClass(node: ParseNode, assignedName: string, aliasMapEntry: AliasMapEntry): ClassType {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        let specialClassType = ClassType.createInstantiable(
            assignedName,
            ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, assignedName),
            fileInfo.moduleName,
            fileInfo.fileUri,
            ClassTypeFlags.BuiltIn | ClassTypeFlags.SpecialBuiltIn,
            /* typeSourceId */ 0,
            /* declaredMetaclass */ undefined,
            /* effectiveMetaclass */ undefined
        );

        if (aliasMapEntry.isSpecialForm) {
            specialClassType.shared.flags |= ClassTypeFlags.SpecialFormClass;
        }

        if (aliasMapEntry.isIllegalInIsinstance) {
            specialClassType.shared.flags |= ClassTypeFlags.IllegalIsinstanceClass;
        }

        // Synthesize a single type parameter with the specified variance if
        // specified in the alias map entry.
        if (aliasMapEntry.typeParamVariance !== undefined) {
            let typeParam = TypeVarType.createInstance('T');
            typeParam = TypeVarType.cloneForScopeId(
                typeParam,
                ParseTreeUtils.getScopeIdForNode(node),
                assignedName,
                TypeVarScopeType.Class
            );
            typeParam.shared.declaredVariance = aliasMapEntry.typeParamVariance;
            specialClassType.shared.typeParams.push(typeParam);
        }

        const specialBuiltInClassDeclaration = (AnalyzerNodeInfo.getDeclaration(node) ??
            (node.parent ? AnalyzerNodeInfo.getDeclaration(node.parent) : undefined)) as
            | SpecialBuiltInClassDeclaration
            | undefined;

        specialClassType.shared.declaration = specialBuiltInClassDeclaration;

        if (fileInfo.isTypingExtensionsStubFile) {
            specialClassType.shared.flags |= ClassTypeFlags.TypingExtensionClass;
        }

        const baseClassName = aliasMapEntry.implicitBaseClass || aliasMapEntry.alias || 'object';

        let baseClass: Type | undefined;
        if (aliasMapEntry.module === 'builtins') {
            baseClass = getBuiltInType(node, baseClassName);
        } else if (aliasMapEntry.module === 'collections') {
            // The typing.pyi file imports collections.
            baseClass = getTypeOfModule(node, baseClassName, ['collections']);
        } else if (aliasMapEntry.module === 'internals') {
            // Handle TypedDict specially.
            assert(baseClassName === 'TypedDictFallback');
            baseClass = prefetched?.typedDictPrivateClass;
            if (baseClass) {
                // The TypedDictFallback class is marked as abstract, but the
                // methods that are abstract are overridden and shouldn't
                // cause the TypedDict to be marked as abstract.
                if (
                    isInstantiableClass(baseClass) &&
                    ClassType.isBuiltIn(baseClass, ['_TypedDict', 'TypedDictFallback'])
                ) {
                    baseClass = ClassType.cloneWithNewFlags(
                        baseClass,
                        baseClass.shared.flags &
                            ~(ClassTypeFlags.SupportsAbstractMethods | ClassTypeFlags.TypeCheckOnly)
                    );
                }
            }
        }

        if (baseClass && isInstantiableClass(baseClass)) {
            if (aliasMapEntry.alias) {
                specialClassType = ClassType.cloneForTypingAlias(baseClass, assignedName);
            } else {
                specialClassType.shared.baseClasses.push(baseClass);
                specialClassType.shared.effectiveMetaclass = baseClass.shared.effectiveMetaclass;
                computeMroLinearization(specialClassType);
            }
        } else {
            specialClassType.shared.baseClasses.push(UnknownType.create());
            specialClassType.shared.effectiveMetaclass = UnknownType.create();
            computeMroLinearization(specialClassType);
        }

        return specialClassType;
    }

    // Handles some special-case type annotations that are found
    // within the typings.pyi file.
    function handleTypingStubTypeAnnotation(node: ExpressionNode): Type | undefined {
        if (!node.parent || node.parent.nodeType !== ParseNodeType.TypeAnnotation) {
            return undefined;
        }

        if (node.parent.d.valueExpr.nodeType !== ParseNodeType.Name) {
            return undefined;
        }

        const nameNode = node.parent.d.valueExpr;
        const assignedName = nameNode.d.value;

        const specialTypes: Map<string, AliasMapEntry> = new Map([
            ['Tuple', { alias: 'tuple', module: 'builtins' }],
            ['Generic', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Protocol', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Callable', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Type', { alias: 'type', module: 'builtins' }],
            ['ClassVar', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Final', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Literal', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['TypedDict', { alias: 'TypedDictFallback', module: 'internals' }],
            ['Union', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Optional', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Annotated', { alias: '', module: 'builtins', isSpecialForm: true, isIllegalInIsinstance: true }],
            ['TypeAlias', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Concatenate', { alias: '', module: 'builtins', isSpecialForm: true }],
            [
                'TypeGuard',
                {
                    alias: '',
                    module: 'builtins',
                    implicitBaseClass: 'bool',
                    isSpecialForm: true,
                    typeParamVariance: Variance.Covariant,
                },
            ],
            ['Unpack', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Required', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['NotRequired', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Self', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['NoReturn', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['Never', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['LiteralString', { alias: '', module: 'builtins', isSpecialForm: true }],
            ['ReadOnly', { alias: '', module: 'builtins', isSpecialForm: true }],
            [
                'TypeIs',
                {
                    alias: '',
                    module: 'builtins',
                    implicitBaseClass: 'bool',
                    isSpecialForm: true,
                    typeParamVariance: Variance.Invariant,
                },
            ],
            [
                'TypeForm',
                {
                    alias: '',
                    module: 'builtins',
                    isSpecialForm: true,
                    typeParamVariance: Variance.Covariant,
                    isIllegalInIsinstance: true,
                },
            ],
        ]);

        const aliasMapEntry = specialTypes.get(assignedName);

        if (aliasMapEntry) {
            const cachedType = readTypeCache(node, EvalFlags.None);
            if (cachedType) {
                return cachedType;
            }

            let specialType: Type = createSpecialBuiltInClass(node, assignedName, aliasMapEntry);

            // Handle 'LiteralString' specially because we want it to act as
            // though it derives from 'str'.
            if (assignedName === 'LiteralString') {
                specialType.shared.baseClasses.push(prefetched?.strClass ?? AnyType.create());
                computeMroLinearization(specialType);

                specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType));
            }

            // Handle 'Never' and 'NoReturn' specially.
            if (assignedName === 'Never' || assignedName === 'NoReturn') {
                specialType = TypeBase.cloneAsSpecialForm(
                    assignedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn(),
                    specialType
                );

                specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType));
            }

            writeTypeCache(node, { type: specialType }, EvalFlags.None);
            return specialType;
        }

        return undefined;
    }

    // Handles some special-case assignment statements that are found
    // within the typings.pyi file.
    function handleTypingStubAssignment(node: AssignmentNode): Type | undefined {
        if (node.d.leftExpr.nodeType !== ParseNodeType.Name) {
            return undefined;
        }

        const nameNode = node.d.leftExpr;
        const assignedName = nameNode.d.value;

        if (assignedName === 'Any') {
            return AnyType.createSpecialForm();
        }

        const specialTypes: Map<string, AliasMapEntry> = new Map([
            ['overload', { alias: '', module: 'builtins' }],
            ['TypeVar', { alias: '', module: 'builtins' }],
            ['_promote', { alias: '', module: 'builtins' }],
            ['no_type_check', { alias: '', module: 'builtins' }],
            ['NoReturn', { alias: '', module: 'builtins' }],
            ['Never', { alias: '', module: 'builtins' }],
            ['Counter', { alias: 'Counter', module: 'collections' }],
            ['List', { alias: 'list', module: 'builtins' }],
            ['Dict', { alias: 'dict', module: 'builtins' }],
            ['DefaultDict', { alias: 'defaultdict', module: 'collections' }],
            ['Set', { alias: 'set', module: 'builtins' }],
            ['FrozenSet', { alias: 'frozenset', module: 'builtins' }],
            ['Deque', { alias: 'deque', module: 'collections' }],
            ['ChainMap', { alias: 'ChainMap', module: 'collections' }],
            ['OrderedDict', { alias: 'OrderedDict', module: 'collections' }],
        ]);

        const aliasMapEntry = specialTypes.get(assignedName);
        if (aliasMapEntry) {
            // Evaluate the expression so symbols are marked as accessed.
            getTypeOfExpression(node.d.rightExpr);
            return createSpecialBuiltInClass(node, assignedName, aliasMapEntry);
        }

        return undefined;
    }

    function evaluateTypesForAssignmentStatement(node: AssignmentNode): void {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

        // If the entire statement has already been evaluated, don't
        // re-evaluate it.
        if (isTypeCached(node)) {
            return;
        }

        let flags: EvalFlags = EvalFlags.None;
        if (fileInfo.isStubFile) {
            // An assignment of ellipsis means "Any" within a type stub file.
            flags |= EvalFlags.ConvertEllipsisToAny;
        }

        if (
            node.d.rightExpr.nodeType === ParseNodeType.Name ||
            node.d.rightExpr.nodeType === ParseNodeType.MemberAccess
        ) {
            // Don't specialize a generic class on assignment (e.g. "x = list"
            // or "x = collections.OrderedDict") because we may want to later
            // specialize it (e.g. "x[int]").
            flags |= EvalFlags.NoSpecialize;
        }

        // Is this type already cached?
        let rightHandType = readTypeCache(node.d.rightExpr, /* flags */ undefined);
        let isIncomplete = false;
        let expectedTypeDiagAddendum: DiagnosticAddendum | undefined;

        if (!rightHandType) {
            // Special-case the typing.pyi file, which contains some special
            // types that the type analyzer needs to interpret differently.
            if (fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) {
                rightHandType = handleTypingStubAssignment(node);
                if (rightHandType) {
                    writeTypeCache(node.d.rightExpr, { type: rightHandType }, EvalFlags.None);
                }
            }
        }

        if (!rightHandType) {
            let typeAliasNameNode: NameNode | undefined;
            let typeAliasPlaceholder: TypeVarType | undefined;
            let isSpeculativeTypeAlias = false;

            if (isDeclaredTypeAlias(node.d.leftExpr)) {
                flags =
                    EvalFlags.InstantiableType |
                    EvalFlags.TypeExpression |
                    EvalFlags.StrLiteralAsType |
                    EvalFlags.NoParamSpec |
                    EvalFlags.NoTypeVarTuple |
                    EvalFlags.NoClassVar;

                typeAliasNameNode = (node.d.leftExpr as TypeAnnotationNode).d.valueExpr as NameNode;

                if (!isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true)) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.typeAliasIllegalExpressionForm(),
                        node.d.rightExpr
                    );
                }
            } else if (node.d.leftExpr.nodeType === ParseNodeType.Name) {
                const symbolWithScope = lookUpSymbolRecursive(
                    node.d.leftExpr,
                    node.d.leftExpr.d.value,
                    /* honorCodeFlow */ false
                );

                if (symbolWithScope) {
                    const decls = symbolWithScope.symbol.getDeclarations();

                    if (decls.length === 1) {
                        if (isPossibleTypeAliasDeclaration(decls[0])) {
                            typeAliasNameNode = node.d.leftExpr;
                            isSpeculativeTypeAlias = true;
                            flags |= EvalFlags.NoConvertSpecialForm;
                        } else if (isPossibleTypeDictFactoryCall(decls[0])) {
                            // Handle calls to TypedDict factory functions like type
                            // aliases to support recursive field type definitions.
                            typeAliasNameNode = node.d.leftExpr;
                        }
                    }
                }
            }

            if (typeAliasNameNode) {
                typeAliasPlaceholder = synthesizeTypeAliasPlaceholder(typeAliasNameNode);

                writeTypeCache(node, { type: typeAliasPlaceholder }, /* flags */ undefined);
                writeTypeCache(node.d.leftExpr, { type: typeAliasPlaceholder }, /* flags */ undefined);

                if (node.d.leftExpr.nodeType === ParseNodeType.TypeAnnotation) {
                    writeTypeCache(node.d.leftExpr.d.valueExpr, { type: typeAliasPlaceholder }, /* flags */ undefined);
                }
            }

            let declaredType = getDeclaredTypeForExpression(node.d.leftExpr, { method: 'set' });

            if (declaredType) {
                const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node);
                declaredType = makeTypeVarsBound(declaredType, liveTypeVarScopes);
            }

            const srcTypeResult = getTypeOfExpression(node.d.rightExpr, flags, makeInferenceContext(declaredType));

            rightHandType = srcTypeResult.type;
            expectedTypeDiagAddendum = srcTypeResult.expectedTypeDiagAddendum;
            if (srcTypeResult.isIncomplete) {
                isIncomplete = true;
            }

            // If this was a speculative type alias, it becomes a real type alias
            // only if the evaluated type is an instantiable type.
            if (isSpeculativeTypeAlias && !isLegalImplicitTypeAliasType(rightHandType)) {
                typeAliasNameNode = undefined;
            }

            if (typeAliasNameNode) {
                assert(typeAliasPlaceholder !== undefined);

                // If this is a type alias, record its name based on the assignment target.
                rightHandType = transformTypeForTypeAlias(
                    rightHandType,
                    typeAliasNameNode,
                    typeAliasPlaceholder,
                    /* isPep695TypeVarType */ false
                );

                if (isTypeAliasRecursive(typeAliasPlaceholder, rightHandType)) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typeAliasIsRecursiveDirect().format({
                            name: typeAliasNameNode.d.value,
                        }),
                        node.d.rightExpr
                    );

                    rightHandType = UnknownType.create();
                }

                // Set the resulting type to the boundType of the original type alias
                // to support recursive type aliases.
                typeAliasPlaceholder.shared.boundType = rightHandType;

                // Record the type parameters within the recursive type alias so it
                // can be specialized.
                typeAliasPlaceholder.shared.recursiveAlias!.typeParams =
                    rightHandType.props?.typeAliasInfo?.shared.typeParams;
            } else {
                // If the RHS is a constant boolean expression, assign it a literal type.
                const constExprValue = evaluateStaticBoolExpression(
                    node.d.rightExpr,
                    fileInfo.executionEnvironment,
                    fileInfo.definedConstants
                );

                if (constExprValue !== undefined) {
                    const boolType = getBuiltInObject(node, 'bool');
                    if (isClassInstance(boolType)) {
                        rightHandType = ClassType.cloneWithLiteral(boolType, constExprValue);
                    }
                }
            }
        }

        assignTypeToExpression(
            node.d.leftExpr,
            { type: rightHandType, isIncomplete },
            node.d.rightExpr,
            /* ignoreEmptyContainers */ true,
            /* allowAssignmentToFinalVar */ true,
            expectedTypeDiagAddendum
        );

        writeTypeCache(node, { type: rightHandType, isIncomplete }, EvalFlags.None);
    }

    // Synthesize a TypeVar that acts as a placeholder for a type alias. This allows
    // the type alias definition to refer to itself.
    function synthesizeTypeAliasPlaceholder(nameNode: NameNode, isTypeAliasType: boolean = false): TypeVarType {
        const placeholder = TypeVarType.createInstantiable(`__type_alias_${nameNode.d.value}`);
        placeholder.shared.isSynthesized = true;
        const typeVarScopeId = ParseTreeUtils.getScopeIdForNode(nameNode);
        const fileInfo = AnalyzerNodeInfo.getFileInfo(nameNode);

        placeholder.shared.recursiveAlias = {
            name: nameNode.d.value,
            fullName: ParseTreeUtils.getClassFullName(nameNode, fileInfo.moduleName, nameNode.d.value),
            moduleName: fileInfo.moduleName,
            fileUri: fileInfo.fileUri,
            typeVarScopeId,
            isTypeAliasType,
            typeParams: undefined,
            computedVariance: undefined,
        };
        placeholder.priv.scopeId = typeVarScopeId;

        return placeholder;
    }

    // Evaluates the type of a type alias (i.e. "type") statement. This code
    // path does not handle traditional type aliases, which are treated as
    // variables since they use normal variable assignment syntax.
    function getTypeOfTypeAlias(node: TypeAliasNode): Type {
        return getTypeOfTypeAliasCommon(
            node,
            node.d.name,
            node.d.expr,
            /* isPep695Syntax */ true,
            node.d.typeParams?.d.params,
            () => {
                if (node.d.typeParams) {
                    return evaluateTypeParamList(node.d.typeParams);
                }
                return undefined;
            }
        );
    }

    // This function is common to the handling of "type" statements and explicit
    // calls to the TypeAliasType constructor.
    function getTypeOfTypeAliasCommon(
        declNode: ParseNode,
        nameNode: NameNode,
        valueNode: ExpressionNode,
        isPep695Syntax: boolean,
        typeParamNodes: TypeParameterNode[] | undefined,
        getTypeParamCallback: () => TypeVarType[] | undefined
    ) {
        const cachedType = readTypeCache(nameNode, EvalFlags.None);
        if (cachedType) {
            return cachedType;
        }

        // Synthesize a type variable that represents the type alias while we're
        // evaluating it. This allows us to handle recursive definitions.
        const typeAliasTypeVar = synthesizeTypeAliasPlaceholder(nameNode, /* isTypeAliasType */ true);

        // Write the type to the type cache to support recursive type alias definitions.
        writeTypeCache(nameNode, { type: typeAliasTypeVar }, /* flags */ undefined);

        // Set a partial type to handle recursive (self-referential) type aliases.
        const scope = ScopeUtils.getScopeForNode(declNode);
        const typeAliasSymbol = scope?.lookUpSymbolRecursive(nameNode.d.value);
        const typeAliasDecl = AnalyzerNodeInfo.getDeclaration(declNode);
        if (typeAliasDecl && typeAliasSymbol) {
            setSymbolResolutionPartialType(typeAliasSymbol.symbol, typeAliasDecl, typeAliasTypeVar);
        }

        const typeParams = getTypeParamCallback();
        if (typeAliasTypeVar.shared.recursiveAlias) {
            typeAliasTypeVar.shared.recursiveAlias.typeParams = typeParams ?? [];
        }

        let aliasTypeResult: TypeResult;
        if (isPep695Syntax) {
            aliasTypeResult = getTypeOfExpressionExpectingType(valueNode, {
                forwardRefs: true,
                typeExpression: true,
            });
        } else {
            const flags =
                EvalFlags.InstantiableType |
                EvalFlags.TypeExpression |
                EvalFlags.StrLiteralAsType |
                EvalFlags.NoParamSpec |
                EvalFlags.NoTypeVarTuple |
                EvalFlags.NoClassVar;
            aliasTypeResult = getTypeOfExpression(valueNode, flags);
        }

        let isIncomplete = false;
        let aliasType = aliasTypeResult.type;
        if (aliasTypeResult.isIncomplete) {
            isIncomplete = true;
        }

        aliasType = transformTypeForTypeAlias(
            aliasType,
            nameNode,
            typeAliasTypeVar,
            /* isPep695TypeVarType */ true,
            typeParamNodes
        );

        // See if the type alias relies on itself in a way that cannot be resolved.
        if (isTypeAliasRecursive(typeAliasTypeVar, aliasType)) {
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeAliasIsRecursiveDirect().format({
                    name: nameNode.d.value,
                }),
                valueNode
            );

            aliasType = UnknownType.create();
        }

        // Set the resulting type to the boundType of the original type alias
        // to support recursive type aliases.
        typeAliasTypeVar.shared.boundType = aliasType;

        writeTypeCache(nameNode, { type: aliasType, isIncomplete }, EvalFlags.None);

        return aliasType;
    }

    function evaluateTypesForAugmentedAssignment(node: AugmentedAssignmentNode): void {
        if (isTypeCached(node)) {
            return;
        }

        const destTypeResult = getTypeOfAugmentedAssignment(evaluatorInterface, node, /* inferenceContext */ undefined);

        writeTypeCache(node, destTypeResult, EvalFlags.None);
    }

    function getPseudoGenericTypeVarName(paramName: string) {
        return `__type_of_${paramName}`;
    }

    // Creates a new class type that is a subclass of two other specified classes.
    function createSubclass(errorNode: ExpressionNode, type1: ClassType, type2: ClassType): ClassType {
        assert(isInstantiableClass(type1) && isInstantiableClass(type2));

        // If both classes are class objects (type[A] and type[B]), create a new
        // class object (type[A & B]) rather than "type[A] & type[B]".
        let createClassObject = false;
        if (TypeBase.getInstantiableDepth(type1) > 0 && TypeBase.getInstantiableDepth(type2) > 0) {
            type1 = ClassType.cloneAsInstance(type1);
            type2 = ClassType.cloneAsInstance(type2);
            createClassObject = true;
        }

        const className = `<subclass of ${printType(convertToInstance(type1), {
            omitTypeArgsIfUnknown: true,
        })} and ${printType(convertToInstance(type2), { omitTypeArgsIfUnknown: true })}>`;
        const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);

        // The effective metaclass of the intersection is the narrower of the two metaclasses.
        let effectiveMetaclass = type1.shared.effectiveMetaclass;
        if (type2.shared.effectiveMetaclass) {
            if (!effectiveMetaclass || assignType(effectiveMetaclass, type2.shared.effectiveMetaclass)) {
                effectiveMetaclass = type2.shared.effectiveMetaclass;
            }
        }

        let newClassType = ClassType.createInstantiable(
            className,
            ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
            fileInfo.moduleName,
            fileInfo.fileUri,
            ClassTypeFlags.None,
            ParseTreeUtils.getTypeSourceId(errorNode),
            /* declaredMetaclass */ undefined,
            effectiveMetaclass,
            type1.shared.docString
        );

        newClassType.shared.baseClasses = [type1, type2];
        computeMroLinearization(newClassType);

        newClassType = addConditionToType(newClassType, type1.props?.condition);
        newClassType = addConditionToType(newClassType, type2.props?.condition);

        if (createClassObject) {
            newClassType = ClassType.cloneAsInstantiable(newClassType);
        }

        return newClassType;
    }

    function getTypeOfClass(node: ClassNode): ClassTypeResult | undefined {
        initializePrefetchedTypes(node);

        // Is this type already cached?
        const cachedClassType = readTypeCache(node.d.name, EvalFlags.None);

        if (cachedClassType) {
            if (!isInstantiableClass(cachedClassType)) {
                // This can happen in rare circumstances where the class declaration
                // is located in an unreachable code block.
                return undefined;
            }
            return {
                classType: cachedClassType,
                decoratedType: readTypeCache(node, EvalFlags.None) || UnknownType.create(),
            };
        }

        // The type wasn't cached, so we need to create a new one.
        const scope = ScopeUtils.getScopeForNode(node);

        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        let classFlags = ClassTypeFlags.None;
        if (
            scope?.type === ScopeType.Builtin ||
            fileInfo.isTypingStubFile ||
            fileInfo.isTypingExtensionsStubFile ||
            fileInfo.isBuiltInStubFile ||
            fileInfo.isTypeshedStubFile
        ) {
            classFlags |= ClassTypeFlags.BuiltIn;

            if (fileInfo.isTypingExtensionsStubFile) {
                classFlags |= ClassTypeFlags.TypingExtensionClass;
            }

            if (node.d.name.d.value === 'property') {
                classFlags |= ClassTypeFlags.PropertyClass;
            }

            if (node.d.name.d.value === 'tuple') {
                classFlags |= ClassTypeFlags.TupleClass;
            }
        }

        if (fileInfo.isStubFile) {
            classFlags |= ClassTypeFlags.DefinedInStub;
        }

        const classType = ClassType.createInstantiable(
            node.d.name.d.value,
            ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, node.d.name.d.value),
            fileInfo.moduleName,
            fileInfo.fileUri,
            classFlags,
            ParseTreeUtils.getTypeSourceId(node),
            /* declaredMetaclass */ undefined,
            /* effectiveMetaclass */ undefined,
            ParseTreeUtils.getDocString(node.d.suite.d.statements)
        );

        classType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node);

        // Is this a special type that supports type promotions according to PEP 484?
        if (typePromotions.has(classType.shared.fullName)) {
            classType.priv.includePromotions = true;
        }

        // Some classes refer to themselves within type arguments used within
        // base classes. We'll register the partially-constructed class type
        // to allow these to be resolved.
        const classSymbol = scope?.lookUpSymbol(node.d.name.d.value);
        let classDecl: ClassDeclaration | undefined;
        const decl = AnalyzerNodeInfo.getDeclaration(node);
        if (decl) {
            classDecl = decl as ClassDeclaration;
        }
        if (classDecl && classSymbol) {
            setSymbolResolutionPartialType(classSymbol, classDecl, classType);
        }
        classType.shared.flags |= ClassTypeFlags.PartiallyEvaluated;
        classType.shared.declaration = classDecl;

        return invalidateTypeCacheIfCanceled(() => {
            writeTypeCache(node, { type: classType }, /* flags */ undefined);
            writeTypeCache(node.d.name, { type: classType }, /* flags */ undefined);

            // Keep a list of unique type parameters that are used in the
            // base class arguments.
            let typeParams: TypeVarType[] = [];

            if (node.d.typeParams) {
                typeParams = evaluateTypeParamList(node.d.typeParams).map((t) => TypeVarType.cloneAsInstance(t));
            }

            // If the class derives from "Generic" directly, it will provide
            // all of the type parameters in the specified order.
            let genericTypeParams: TypeVarType[] | undefined;
            let protocolTypeParams: TypeVarType[] | undefined;
            let isNamedTupleSubclass = false;

            const initSubclassArgs: Arg[] = [];
            let metaclassNode: ExpressionNode | undefined;
            let exprFlags =
                EvalFlags.InstantiableType |
                EvalFlags.AllowGeneric |
                EvalFlags.NoNakedGeneric |
                EvalFlags.NoTypeVarWithScopeId |
                EvalFlags.TypeVarGetsCurScope |
                EvalFlags.EnforceVarianceConsistency;
            if (fileInfo.isStubFile) {
                exprFlags |= EvalFlags.ForwardRefs;
            }
            let sawClosedOrExtraItems = false;

            node.d.arguments.forEach((arg) => {
                // Ignore unpacked arguments.
                if (arg.d.argCategory === ArgCategory.UnpackedDictionary) {
                    // Evaluate the expression's type so symbols are marked accessed
                    // and errors are reported.
                    getTypeOfExpression(arg.d.valueExpr);
                    return;
                }

                if (!arg.d.name) {
                    let argType: Type;

                    if (arg.d.argCategory === ArgCategory.UnpackedList) {
                        getTypeOfExpression(arg.d.valueExpr);
                        argType = UnknownType.create();
                    } else {
                        argType = getTypeOfExpression(arg.d.valueExpr, exprFlags).type;

                        if (
                            isTypeVar(argType) &&
                            argType.props?.specialForm &&
                            TypeBase.isInstance(argType.props.specialForm)
                        ) {
                            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.baseClassInvalid(), arg);
                            argType = UnknownType.create();
                        }

                        argType = makeTopLevelTypeVarsConcrete(argType);
                    }

                    // In some stub files, classes are conditionally defined (e.g. based
                    // on platform type). We'll assume that the conditional logic is correct
                    // and strip off the "unbound" union.
                    if (isUnion(argType)) {
                        argType = removeUnbound(argType);
                    }

                    // Any is allowed as a base class. Remove its "special form" flag to avoid
                    // false positive errors.
                    if (isAny(argType) && argType.props?.specialForm) {
                        argType = AnyType.create();
                    }

                    argType = stripTypeFormRecursive(argType);

                    if (!isAnyOrUnknown(argType) && !isUnbound(argType)) {
                        // If the specified base class is type(T), use the metaclass
                        // of T if it's known.
                        if (
                            isClass(argType) &&
                            TypeBase.getInstantiableDepth(argType) > 0 &&
                            argType.shared.effectiveMetaclass &&
                            isClass(argType.shared.effectiveMetaclass)
                        ) {
                            argType = argType.shared.effectiveMetaclass;
                        }

                        if (isMetaclassInstance(argType)) {
                            assert(isClassInstance(argType));
                            argType =
                                argType.priv.typeArgs && argType.priv.typeArgs.length > 0
                                    ? argType.priv.typeArgs[0]
                                    : UnknownType.create();
                        } else if (!isInstantiableClass(argType)) {
                            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.baseClassInvalid(), arg);
                            argType = UnknownType.create();
                        } else {
                            if (
                                ClassType.isPartiallyEvaluated(argType) ||
                                argType.shared.mro.some((t) => isClass(t) && ClassType.isPartiallyEvaluated(t))
                            ) {
                                // If the base class is partially evaluated, install a callback
                                // so we can fix up this class (e.g. compute the MRO) when the
                                // dependent class is completed.
                                registerDeferredClassCompletion(node, argType);
                            }

                            if (ClassType.isBuiltIn(argType, 'Protocol')) {
                                if (
                                    !fileInfo.isStubFile &&
                                    !ClassType.isTypingExtensionClass(argType) &&
                                    PythonVersion.isLessThan(
                                        fileInfo.executionEnvironment.pythonVersion,
                                        pythonVersion3_7
                                    )
                                ) {
                                    addDiagnostic(
                                        DiagnosticRule.reportInvalidTypeForm,
                                        LocMessage.protocolIllegal(),
                                        arg.d.valueExpr
                                    );
                                }
                                classType.shared.flags |= ClassTypeFlags.ProtocolClass;
                            }

                            if (ClassType.isBuiltIn(argType, 'property')) {
                                classType.shared.flags |= ClassTypeFlags.PropertyClass;
                            }

                            // If the class directly derives from NamedTuple (in Python 3.6 or
                            // newer), it's considered a (read-only) dataclass.
                            if (
                                PythonVersion.isGreaterOrEqualTo(
                                    fileInfo.executionEnvironment.pythonVersion,
                                    pythonVersion3_6
                                )
                            ) {
                                if (ClassType.isBuiltIn(argType, 'NamedTuple')) {
                                    isNamedTupleSubclass = true;
                                }
                            }

                            // If the class directly derives from TypedDict or from a class that is
                            // a TypedDict, it is considered a TypedDict.
                            if (ClassType.isBuiltIn(argType, 'TypedDict') || ClassType.isTypedDictClass(argType)) {
                                classType.shared.flags |= ClassTypeFlags.TypedDictClass;

                                // Propagate the "effectively closed" flag from base classes.
                                if (ClassType.isTypedDictEffectivelyClosed(argType)) {
                                    classType.shared.flags |= ClassTypeFlags.TypedDictEffectivelyClosed;
                                }
                            }

                            // Validate that the class isn't deriving from itself, creating a
                            // circular dependency.
                            if (derivesFromClassRecursive(argType, classType, /* ignoreUnknown */ true)) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.baseClassCircular(),
                                    arg
                                );
                                argType = UnknownType.create();
                            }

                            // If the class is attempting to derive from a TypeAliasType,
                            // generate an error.
                            if (
                                argType.props?.specialForm &&
                                ClassType.isBuiltIn(argType.props.specialForm, 'TypeAliasType')
                            ) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.typeAliasTypeBaseClass(),
                                    arg
                                );
                                argType = UnknownType.create();
                            }
                        }
                    }

                    if (isUnknown(argType)) {
                        addDiagnostic(DiagnosticRule.reportUntypedBaseClass, LocMessage.baseClassUnknown(), arg);
                    }

                    // Check for a duplicate class.
                    if (
                        classType.shared.baseClasses.some((prevBaseClass) => {
                            return (
                                isInstantiableClass(prevBaseClass) &&
                                isInstantiableClass(argType) &&
                                ClassType.isSameGenericClass(argType, prevBaseClass)
                            );
                        })
                    ) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.duplicateBaseClass(),
                            arg.d.name || arg
                        );
                    }

                    classType.shared.baseClasses.push(argType);
                    if (isInstantiableClass(argType)) {
                        if (ClassType.isEnumClass(argType)) {
                            classType.shared.flags |= ClassTypeFlags.EnumClass;
                        }

                        // Determine if the class is abstract. Protocol classes support abstract methods
                        // because they are constructed by the _ProtocolMeta metaclass, which derives
                        // from ABCMeta.
                        if (ClassType.supportsAbstractMethods(argType) || ClassType.isProtocolClass(argType)) {
                            classType.shared.flags |= ClassTypeFlags.SupportsAbstractMethods;
                        }

                        if (ClassType.isPropertyClass(argType)) {
                            classType.shared.flags |= ClassTypeFlags.PropertyClass;
                        }

                        if (ClassType.isFinal(argType)) {
                            const className = printObjectTypeForClass(argType);
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.baseClassFinal().format({ type: className }),
                                arg.d.valueExpr
                            );
                        }
                    }

                    addTypeVarsToListIfUnique(typeParams, getTypeVarArgsRecursive(argType));
                    if (isInstantiableClass(argType)) {
                        if (ClassType.isBuiltIn(argType, 'Generic')) {
                            // 'Generic' is implicitly added if type parameter syntax is used.
                            if (node.d.typeParams) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.genericBaseClassNotAllowed(),
                                    arg.d.valueExpr
                                );
                            } else {
                                if (!genericTypeParams) {
                                    if (protocolTypeParams) {
                                        addDiagnostic(
                                            DiagnosticRule.reportGeneralTypeIssues,
                                            LocMessage.duplicateGenericAndProtocolBase(),
                                            arg.d.valueExpr
                                        );
                                    }
                                    genericTypeParams = buildTypeParamsFromTypeArgs(argType);
                                }
                            }
                        } else if (
                            ClassType.isBuiltIn(argType, 'Protocol') &&
                            argType.priv.typeArgs &&
                            argType.priv.typeArgs.length > 0
                        ) {
                            if (!protocolTypeParams) {
                                if (genericTypeParams) {
                                    addDiagnostic(
                                        DiagnosticRule.reportGeneralTypeIssues,
                                        LocMessage.duplicateGenericAndProtocolBase(),
                                        arg.d.valueExpr
                                    );
                                }
                                protocolTypeParams = buildTypeParamsFromTypeArgs(argType);

                                if (node.d.typeParams && protocolTypeParams.length > 0) {
                                    addDiagnostic(
                                        DiagnosticRule.reportGeneralTypeIssues,
                                        LocMessage.protocolBaseClassWithTypeArgs(),
                                        arg.d.valueExpr
                                    );
                                    protocolTypeParams = [];
                                }
                            }
                        }
                    }
                } else if (ClassType.isTypedDictClass(classType)) {
                    if (arg.d.name.d.value === 'total' || arg.d.name.d.value === 'closed') {
                        // The "total" and "readonly" parameters apply only for TypedDict classes.
                        // PEP 589 specifies that the parameter must be either True or False.
                        const constArgValue = evaluateStaticBoolExpression(
                            arg.d.valueExpr,
                            fileInfo.executionEnvironment,
                            fileInfo.definedConstants
                        );

                        if (constArgValue === undefined) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typedDictBoolParam().format({ name: arg.d.name.d.value }),
                                arg.d.valueExpr
                            );
                        } else if (arg.d.name.d.value === 'total' && !constArgValue) {
                            classType.shared.flags |= ClassTypeFlags.CanOmitDictValues;
                        } else if (arg.d.name.d.value === 'closed') {
                            if (constArgValue) {
                                classType.shared.flags |=
                                    ClassTypeFlags.TypedDictMarkedClosed | ClassTypeFlags.TypedDictEffectivelyClosed;

                                if (classType.shared.typedDictExtraItemsExpr) {
                                    addDiagnostic(
                                        DiagnosticRule.reportGeneralTypeIssues,
                                        LocMessage.typedDictExtraItemsClosed(),
                                        classType.shared.typedDictExtraItemsExpr
                                    );
                                }
                            } else {
                                // PEP 728: A class that subclasses from a non-open TypedDict
                                // cannot specify closed=False.
                                const nonOpenBase = classType.shared.baseClasses.find(
                                    (base) =>
                                        isInstantiableClass(base) &&
                                        ClassType.isTypedDictClass(base) &&
                                        ClassType.isTypedDictEffectivelyClosed(base)
                                );
                                if (nonOpenBase) {
                                    addDiagnostic(
                                        DiagnosticRule.reportGeneralTypeIssues,
                                        LocMessage.typedDictClosedFalseNonOpenBase().format({
                                            name: (nonOpenBase as ClassType).shared.name,
                                        }),
                                        arg.d.valueExpr
                                    );
                                }
                            }

                            if (sawClosedOrExtraItems) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.typedDictExtraItemsClosed(),
                                    arg.d.valueExpr
                                );
                            }

                            sawClosedOrExtraItems = true;
                        }
                    } else if (arg.d.name.d.value === 'extra_items') {
                        // Record a reference to the expression but don't evaluate it yet.
                        // It may refer to the class itself.
                        classType.shared.typedDictExtraItemsExpr = arg.d.valueExpr;
                        classType.shared.flags |= ClassTypeFlags.TypedDictEffectivelyClosed;

                        if (ClassType.isTypedDictMarkedClosed(classType)) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typedDictExtraItemsClosed(),
                                classType.shared.typedDictExtraItemsExpr
                            );
                        }

                        if (sawClosedOrExtraItems) {
                            addDiagnostic(
                                DiagnosticRule.reportGeneralTypeIssues,
                                LocMessage.typedDictExtraItemsClosed(),
                                arg.d.valueExpr
                            );
                        }

                        sawClosedOrExtraItems = true;
                    } else {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typedDictInitsubclassParameter().format({ name: arg.d.name.d.value }),
                            arg
                        );
                    }
                } else if (arg.d.name.d.value === 'metaclass') {
                    if (metaclassNode) {
                        addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.metaclassDuplicate(), arg);
                    } else {
                        metaclassNode = arg.d.valueExpr;
                    }
                } else {
                    // Collect arguments that will be passed to the `__init_subclass__`
                    // method described in PEP 487.
                    initSubclassArgs.push({
                        argCategory: ArgCategory.Simple,
                        node: arg,
                        name: arg.d.name,
                        valueExpression: arg.d.valueExpr,
                    });
                }
            });

            // Check for NamedTuple multiple inheritance.
            if (classType.shared.baseClasses.length > 1) {
                let derivesFromNamedTuple = false;
                let foundIllegalBaseClass = false;

                classType.shared.baseClasses.forEach((baseClass) => {
                    if (isInstantiableClass(baseClass)) {
                        if (ClassType.isBuiltIn(baseClass, 'NamedTuple')) {
                            derivesFromNamedTuple = true;
                        } else if (!ClassType.isBuiltIn(baseClass, 'Generic')) {
                            foundIllegalBaseClass = true;
                        }
                    }
                });

                if (derivesFromNamedTuple && foundIllegalBaseClass) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.namedTupleMultipleInheritance(),
                        node.d.name
                    );
                }
            }

            // Make sure we don't have 'object' derive from itself. Infinite
            // recursion will result.
            if (
                !ClassType.isBuiltIn(classType, 'object') &&
                classType.shared.baseClasses.filter((baseClass) => isClass(baseClass)).length === 0
            ) {
                // If there are no other (known) base classes, the class implicitly derives from object.
                classType.shared.baseClasses.push(getBuiltInType(node, 'object'));
            }

            // If genericTypeParams or protocolTypeParams are provided,
            // make sure that typeParams is a proper subset.
            genericTypeParams = genericTypeParams ?? protocolTypeParams;
            if (genericTypeParams && !node.d.typeParams) {
                verifyGenericTypeParams(node.d.name, typeParams, genericTypeParams);
            }
            classType.shared.typeParams = genericTypeParams ?? typeParams;

            // Determine if one or more type parameters is autovariance.
            if (
                classType.shared.typeParams.some(
                    (param) =>
                        param.shared.declaredVariance === Variance.Auto && param.priv.computedVariance === undefined
                )
            ) {
                classType.shared.requiresVarianceInference = true;
            }

            // Make sure there's at most one TypeVarTuple.
            const variadics = typeParams.filter((param) => isTypeVarTuple(param));
            if (variadics.length > 1) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.variadicTypeParamTooManyClass().format({
                        names: variadics.map((v) => `"${v.shared.name}"`).join(', '),
                    }),
                    node.d.name,
                    TextRange.combine(node.d.arguments) || node.d.name
                );
            } else if (variadics.length > 0) {
                // Make sure a TypeVar with a default doesn't come after a TypeVarTuple.
                const firstVariadicIndex = typeParams.findIndex((param) => isTypeVarTuple(param));
                const typeVarWithDefaultIndex = typeParams.findIndex(
                    (param, index) =>
                        index > firstVariadicIndex && !isParamSpec(param) && param.shared.isDefaultExplicit
                );

                if (typeVarWithDefaultIndex >= 0) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typeVarWithDefaultFollowsVariadic().format({
                            typeVarName: typeParams[typeVarWithDefaultIndex].shared.name,
                            variadicName: typeParams[firstVariadicIndex].shared.name,
                        }),
                        node.d.typeParams ? node.d.typeParams.d.params[typeVarWithDefaultIndex].d.name : node.d.name
                    );
                }
            }

            // Validate the default types for all type parameters.
            classType.shared.typeParams.forEach((typeParam, index) => {
                let bestErrorNode: ExpressionNode = node.d.name;
                if (node.d.typeParams && index < node.d.typeParams.d.params.length) {
                    const typeParamNode = node.d.typeParams.d.params[index];
                    bestErrorNode = typeParamNode.d.defaultExpr ?? typeParamNode.d.name;
                }
                validateTypeParamDefault(
                    bestErrorNode,
                    typeParam,
                    classType.shared.typeParams.slice(0, index),
                    classType.shared.typeVarScopeId!
                );
            });

            if (!computeMroLinearization(classType)) {
                addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), node.d.name);
            }

            // The scope for this class becomes the "fields" for the corresponding type.
            const innerScope = ScopeUtils.getScopeForNode(node.d.suite);
            classType.shared.fields = innerScope?.symbolTable
                ? new Map<string, Symbol>(innerScope.symbolTable)
                : new Map<string, Symbol>();

            // Determine whether the class should inherit __hash__. If a class defines
            // __eq__ but doesn't define __hash__ then __hash__ is set to None.
            if (classType.shared.fields.has('__eq__') && !classType.shared.fields.has('__hash__')) {
                classType.shared.fields.set(
                    '__hash__',
                    Symbol.createWithType(
                        SymbolFlags.ClassMember |
                            SymbolFlags.ClassVar |
                            SymbolFlags.IgnoredForProtocolMatch |
                            SymbolFlags.IgnoredForOverrideChecks,
                        getNoneType()
                    )
                );
            }

            // Determine whether the class's instance variables are constrained
            // to those defined by __slots__. We need to do this prior to dataclass
            // processing because dataclasses can implicitly add to the slots
            // list.
            const slotsNames = innerScope?.getSlotsNames();
            if (slotsNames) {
                classType.shared.localSlotsNames = slotsNames;
            }

            // Determine if the class should be a "pseudo-generic" class, characterized
            // by having an __init__ method with parameters that lack type annotations.
            // For such classes, we'll treat them as generic, with the type arguments provided
            // by the callers of the constructor.
            if (!fileInfo.isStubFile && classType.shared.typeParams.length === 0) {
                const initMethod = classType.shared.fields.get('__init__');
                if (initMethod) {
                    const initDecls = initMethod.getTypedDeclarations();
                    if (initDecls.length === 1 && initDecls[0].type === DeclarationType.Function) {
                        const initDeclNode = initDecls[0].node;
                        const initParams = initDeclNode.d.params;

                        if (
                            initParams.length > 1 &&
                            !initParams.some(
                                (param, index) => !!ParseTreeUtils.getTypeAnnotationForParam(initDeclNode, index)
                            )
                        ) {
                            const genericParams = initParams.filter(
                                (param, index) =>
                                    index > 0 &&
                                    param.d.name &&
                                    param.d.category === ParamCategory.Simple &&
                                    !param.d.defaultValue
                            );

                            if (genericParams.length > 0) {
                                classType.shared.flags |= ClassTypeFlags.PseudoGenericClass;

                                // Create a type parameter for each simple, named parameter
                                // in the __init__ method.
                                classType.shared.typeParams = genericParams.map((param) => {
                                    const typeVar = TypeVarType.createInstance(
                                        getPseudoGenericTypeVarName(param.d.name!.d.value)
                                    );
                                    typeVar.shared.isSynthesized = true;
                                    typeVar.priv.scopeId = ParseTreeUtils.getScopeIdForNode(initDeclNode);
                                    typeVar.shared.boundType = UnknownType.create();
                                    return TypeVarType.cloneForScopeId(
                                        typeVar,
                                        ParseTreeUtils.getScopeIdForNode(node),
                                        node.d.name.d.value,
                                        TypeVarScopeType.Class
                                    );
                                });
                            }
                        }
                    }
                }
            }

            // Determine if the class has a custom __class_getitem__ method. This applies
            // only to classes that have no type parameters, since those with type parameters
            // are assumed to follow normal subscripting semantics for generic classes.
            if (classType.shared.typeParams.length === 0 && !ClassType.isBuiltIn(classType, 'type')) {
                if (
                    classType.shared.baseClasses.some(
                        (baseClass) => isInstantiableClass(baseClass) && ClassType.hasCustomClassGetItem(baseClass)
                    ) ||
                    classType.shared.fields.has('__class_getitem__')
                ) {
                    classType.shared.flags |= ClassTypeFlags.HasCustomClassGetItem;
                }
            }

            // Determine the effective metaclass.
            if (metaclassNode) {
                let metaclassType = getTypeOfExpression(metaclassNode, exprFlags).type;
                if (isInstantiableClass(metaclassType) || isUnknown(metaclassType)) {
                    if (requiresSpecialization(metaclassType, { ignorePseudoGeneric: true })) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.metaclassIsGeneric(),
                            metaclassNode
                        );
                    }

                    // If the specified metaclass is type(T), use the metaclass
                    // of T if it's known.
                    if (
                        TypeBase.getInstantiableDepth(metaclassType) > 0 &&
                        isClass(metaclassType) &&
                        metaclassType.shared.effectiveMetaclass &&
                        isClass(metaclassType.shared.effectiveMetaclass)
                    ) {
                        metaclassType = metaclassType.shared.effectiveMetaclass;
                    }

                    classType.shared.declaredMetaclass = metaclassType;
                    if (isInstantiableClass(metaclassType)) {
                        if (isEnumMetaclass(metaclassType)) {
                            classType.shared.flags |= ClassTypeFlags.EnumClass;
                        }

                        if (derivesFromStdlibClass(metaclassType, 'ABCMeta')) {
                            classType.shared.flags |= ClassTypeFlags.SupportsAbstractMethods;
                        }
                    }
                }
            }

            const effectiveMetaclass = computeEffectiveMetaclass(classType, node.d.name);

            // Clear the "partially constructed" flag.
            classType.shared.flags &= ~ClassTypeFlags.PartiallyEvaluated;

            // Now determine the decorated type of the class.
            let decoratedType: Type = classType;
            let foundUnknown = false;

            for (let i = node.d.decorators.length - 1; i >= 0; i--) {
                const decorator = node.d.decorators[i];

                const newDecoratedType = useSignatureTracker(node.parent ?? node, () =>
                    applyClassDecorator(evaluatorInterface, decoratedType, classType, decorator)
                );
                const unknownOrAny = containsAnyOrUnknown(newDecoratedType, /* recurse */ false);

                if (unknownOrAny && isUnknown(unknownOrAny)) {
                    // Report this error only on the first unknown type.
                    if (!foundUnknown) {
                        addDiagnostic(
                            DiagnosticRule.reportUntypedClassDecorator,
                            LocMessage.classDecoratorTypeUnknown(),
                            node.d.decorators[i].d.expr
                        );

                        foundUnknown = true;
                    }
                } else {
                    // Apply the decorator only if the type is known.
                    decoratedType = newDecoratedType;
                }
            }

            // Determine whether this class derives from (or has a metaclass) that imbues
            // it with dataclass-like behaviors. If so, we'll apply those here.
            let dataClassBehaviors: DataClassBehaviors | undefined;
            if (isInstantiableClass(effectiveMetaclass) && effectiveMetaclass.shared.classDataClassTransform) {
                dataClassBehaviors = effectiveMetaclass.shared.classDataClassTransform;
            } else {
                const baseClassDataTransform = classType.shared.mro.find((mroClass) => {
                    return (
                        isClass(mroClass) &&
                        mroClass.shared.classDataClassTransform !== undefined &&
                        !ClassType.isSameGenericClass(mroClass, classType)
                    );
                });

                if (baseClassDataTransform) {
                    dataClassBehaviors = (baseClassDataTransform as ClassType).shared.classDataClassTransform;
                }
            }

            if (dataClassBehaviors) {
                applyDataClassClassBehaviorOverrides(
                    evaluatorInterface,
                    node.d.name,
                    classType,
                    initSubclassArgs,
                    dataClassBehaviors
                );
            }

            // Run any deferred class completions that depend on this class.
            runDeferredClassCompletions(classType);

            // If there are any outstanding deferred class completions registered that
            // were not removed by the call to runDeferredClassCompletions, assume that
            // the current class may depend on them and register for deferred completion.
            registerDeferredClassCompletion(node, /* dependsUpon */ undefined);

            // Synthesize TypedDict methods.
            if (ClassType.isTypedDictClass(classType)) {
                // TypedDict classes must derive only from other TypedDict classes.
                let foundInvalidBaseClass = false;
                const diag = new DiagnosticAddendum();

                classType.shared.baseClasses.forEach((baseClass) => {
                    if (
                        isClass(baseClass) &&
                        !ClassType.isTypedDictClass(baseClass) &&
                        !ClassType.isBuiltIn(baseClass, ['_TypedDict', 'TypedDictFallback', 'Generic'])
                    ) {
                        foundInvalidBaseClass = true;
                        diag.addMessage(LocAddendum.typedDictBaseClass().format({ type: baseClass.shared.name }));
                    }
                });

                if (foundInvalidBaseClass) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typedDictBaseClass() + diag.getString(),
                        node.d.name
                    );
                }

                synthesizeTypedDictClassMethods(evaluatorInterface, node, classType);
            }

            // Synthesize dataclass methods.
            if (ClassType.isDataClass(classType) || isNamedTupleSubclass) {
                const skipSynthesizedInit = ClassType.isDataClassSkipGenerateInit(classType);
                let hasExistingInitMethod = skipSynthesizedInit;

                // See if there's already a non-synthesized __init__ method.
                // We shouldn't override it.
                if (!skipSynthesizedInit) {
                    const initSymbol = classType.shared.fields.get('__init__');
                    if (initSymbol && initSymbol.isClassMember()) {
                        hasExistingInitMethod = true;
                    }
                }

                let skipSynthesizeHash = false;
                const hashSymbol = classType.shared.fields.get('__hash__');

                // If there is a hash symbol defined in the class (i.e. one that we didn't
                // synthesize above), then we shouldn't synthesize a new one for the dataclass.
                if (hashSymbol && hashSymbol.isClassMember() && !hashSymbol.getSynthesizedType()) {
                    skipSynthesizeHash = true;
                }

                const synthesizeMethods = () =>
                    synthesizeDataClassMethods(
                        evaluatorInterface,
                        node,
                        classType,
                        isNamedTupleSubclass,
                        skipSynthesizedInit,
                        hasExistingInitMethod,
                        skipSynthesizeHash
                    );

                // If this is a NamedTuple subclass, immediately synthesize dataclass methods
                // because we also need to update the MRO classes in this case. For regular
                // dataclasses, we'll defer the  method synthesis to avoid circular dependencies.
                if (isNamedTupleSubclass) {
                    synthesizeMethods();
                } else {
                    classType.shared.synthesizeMethodsDeferred = () => {
                        delete classType.shared.synthesizeMethodsDeferred;
                        synthesizeMethods();
                    };
                }
            }

            // Build a complete list of all slots names defined by the class hierarchy.
            // This needs to be done after dataclass processing.
            classType.shared.calculateInheritedSlotsNamesDeferred = () => {
                delete classType.shared.calculateInheritedSlotsNamesDeferred;

                if (classType.shared.localSlotsNames) {
                    let isLimitedToSlots = true;
                    const extendedSlotsNames = Array.from(classType.shared.localSlotsNames);

                    classType.shared.baseClasses.forEach((baseClass) => {
                        if (isInstantiableClass(baseClass)) {
                            if (
                                !ClassType.isBuiltIn(baseClass, 'object') &&
                                !ClassType.isBuiltIn(baseClass, 'type') &&
                                !ClassType.isBuiltIn(baseClass, 'Generic')
                            ) {
                                const inheritedSlotsNames = ClassType.getInheritedSlotsNames(baseClass);
                                if (inheritedSlotsNames) {
                                    appendArray(extendedSlotsNames, inheritedSlotsNames);
                                } else {
                                    isLimitedToSlots = false;
                                }
                            }
                        } else {
                            isLimitedToSlots = false;
                        }
                    });

                    if (isLimitedToSlots) {
                        classType.shared.inheritedSlotsNamesCached = extendedSlotsNames;
                    }
                }
            };

            // If Any is defined using a class statement, treat it as a special form.
            if (node.d.name.d.value === 'Any' && fileInfo.isTypingStubFile) {
                decoratedType = AnyType.createSpecialForm();
            }

            // Update the undecorated class type.
            writeTypeCache(node.d.name, { type: classType }, EvalFlags.None);

            // Update the decorated class type.
            writeTypeCache(node, { type: decoratedType }, EvalFlags.None);

            return { classType, decoratedType };
        });
    }

    function buildTypeParamsFromTypeArgs(classType: ClassType): TypeVarType[] {
        const typeParams: TypeVarType[] = [];
        const typeArgs = classType.priv.typeArgs ?? [];

        typeArgs.forEach((typeArg, index) => {
            if (isTypeVar(typeArg)) {
                typeParams.push(typeArg);
                return;
            }

            // Synthesize a dummy type parameter.
            const typeVar = TypeVarType.createInstance(`__P${index}`);
            typeVar.shared.isSynthesized = true;
            typeParams.push(typeVar);
        });

        return typeParams;
    }

    // Determines whether the type parameters has a default that refers to another
    // type parameter. If so, validates that it is in the list of "live" type
    // parameters and updates the scope of the type parameter referred to in the
    // default type expression.
    function validateTypeParamDefault(
        errorNode: ExpressionNode,
        typeParam: TypeVarType,
        otherLiveTypeParams: TypeVarType[],
        scopeId: TypeVarScopeId
    ) {
        if (!typeParam.shared.isDefaultExplicit && !typeParam.shared.isSynthesized && !TypeVarType.isSelf(typeParam)) {
            const typeVarWithDefault = otherLiveTypeParams.find(
                (param) => param.shared.isDefaultExplicit && param.priv.scopeId === scopeId
            );

            if (typeVarWithDefault) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.typeVarWithoutDefault().format({
                        name: typeParam.shared.name,
                        other: typeVarWithDefault.shared.name,
                    }),
                    errorNode
                );
            }
            return;
        }

        const invalidTypeVars = new Set<string>();
        validateTypeVarDefault(typeParam, otherLiveTypeParams, invalidTypeVars);

        // If we found one or more unapplied type variable, report an error.
        if (invalidTypeVars.size > 0) {
            const diag = new DiagnosticAddendum();
            invalidTypeVars.forEach((name) => {
                diag.addMessage(LocAddendum.typeVarDefaultOutOfScope().format({ name }));
            });

            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeVarDefaultInvalidTypeVar().format({
                    name: typeParam.shared.name,
                }) + diag.getString(),
                errorNode
            );
        }
    }

    function inferVarianceForClass(classType: ClassType): void {
        if (!classType.shared.requiresVarianceInference) {
            return;
        }

        // Presumptively mark the variance inference as complete. This
        // prevents potential recursion.
        classType.shared.requiresVarianceInference = false;

        // Presumptively mark the computed variance to "unknown". We'll
        // replace this below once the variance has been inferred.
        classType.shared.typeParams.forEach((param) => {
            if (param.shared.declaredVariance === Variance.Auto) {
                param.priv.computedVariance = Variance.Unknown;
            }
        });

        const dummyTypeObject = ClassType.createInstantiable(
            '__varianceDummy',
            '',
            '',
            Uri.empty(),
            0,
            0,
            undefined,
            undefined
        );

        classType.shared.typeParams.forEach((param, paramIndex) => {
            // Skip TypeVarTuples and ParamSpecs.
            if (isTypeVarTuple(param) || isParamSpec(param)) {
                return;
            }

            // Skip type variables without auto-variance.
            if (param.shared.declaredVariance !== Variance.Auto) {
                return;
            }

            // Replace all type arguments with a dummy type except for the
            // TypeVar of interest, which is replaced with an object instance.
            const srcTypeArgs = classType.shared.typeParams.map((p, i) => {
                if (isTypeVarTuple(p)) {
                    return p;
                }
                return i === paramIndex ? getObjectType() : dummyTypeObject;
            });

            // Replace all type arguments with a dummy type except for the
            // TypeVar of interest, which is replaced with itself.
            const destTypeArgs = classType.shared.typeParams.map((p, i) => {
                return i === paramIndex || isTypeVarTuple(p) ? p : dummyTypeObject;
            });

            const srcType = ClassType.specialize(classType, srcTypeArgs);
            const destType = ClassType.specialize(classType, destTypeArgs);

            const isDestSubtypeOfSrc = assignClassToSelf(
                srcType,
                destType,
                Variance.Covariant,
                /* ignoreBaseClassVariance */ false
            );

            let inferredVariance: Variance;
            if (isDestSubtypeOfSrc) {
                inferredVariance = Variance.Covariant;
            } else {
                const isSrcSubtypeOfDest = assignClassToSelf(
                    destType,
                    srcType,
                    Variance.Contravariant,
                    /* ignoreBaseClassVariance */ false
                );
                if (isSrcSubtypeOfDest) {
                    inferredVariance = Variance.Contravariant;
                } else {
                    inferredVariance = Variance.Invariant;
                }
            }

            // We assume here that we don't need to clone the type var object
            // because it was already cloned when it was associated with this
            // class scope.
            classType.shared.typeParams[paramIndex].priv.computedVariance = inferredVariance;
        });
    }

    function evaluateTypeParamList(node: TypeParameterListNode): TypeVarType[] {
        const paramTypes: TypeVarType[] = [];
        const typeParamScope = AnalyzerNodeInfo.getScope(node);

        node.d.params.forEach((param) => {
            const paramSymbol = typeParamScope?.symbolTable.get(param.d.name.d.value);
            if (!paramSymbol) {
                // This can happen if the code is unreachable.
                return;
            }

            const typeOfParam = getDeclaredTypeOfSymbol(paramSymbol, param.d.name)?.type;
            if (!typeOfParam || !isTypeVar(typeOfParam)) {
                return;
            }

            writeTypeCache(param.d.name, { type: typeOfParam }, EvalFlags.None);
            paramTypes.push(typeOfParam);
        });

        return paramTypes;
    }

    function computeEffectiveMetaclass(classType: ClassType, errorNode: ParseNode) {
        let effectiveMetaclass = classType.shared.declaredMetaclass;
        let reportedMetaclassConflict = false;

        if (!effectiveMetaclass || isInstantiableClass(effectiveMetaclass)) {
            for (const baseClass of classType.shared.baseClasses) {
                if (isInstantiableClass(baseClass)) {
                    const baseClassMeta = baseClass.shared.effectiveMetaclass ?? prefetched?.typeClass;
                    if (baseClassMeta && isInstantiableClass(baseClassMeta)) {
                        // Make sure there is no metaclass conflict.
                        if (!effectiveMetaclass) {
                            effectiveMetaclass = baseClassMeta;
                        } else if (
                            derivesFromClassRecursive(baseClassMeta, effectiveMetaclass, /* ignoreUnknown */ false)
                        ) {
                            effectiveMetaclass = baseClassMeta;
                        } else if (
                            !derivesFromClassRecursive(effectiveMetaclass, baseClassMeta, /* ignoreUnknown */ false)
                        ) {
                            if (!reportedMetaclassConflict) {
                                const diag = new DiagnosticAddendum();

                                diag.addMessage(
                                    LocAddendum.metaclassConflict().format({
                                        metaclass1: printType(convertToInstance(effectiveMetaclass)),
                                        metaclass2: printType(convertToInstance(baseClassMeta)),
                                    })
                                );
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.metaclassConflict() + diag.getString(),
                                    errorNode
                                );

                                // Don't report more than once.
                                reportedMetaclassConflict = true;
                            }
                        }
                    } else {
                        effectiveMetaclass = baseClassMeta ? UnknownType.create() : undefined;
                        break;
                    }
                } else {
                    // If one of the base classes is unknown, then the effective
                    // metaclass is also unknowable.
                    effectiveMetaclass = UnknownType.create();
                    break;
                }
            }
        }

        // If we haven't found an effective metaclass, assume "type", which
        // is the metaclass for "object".
        if (!effectiveMetaclass) {
            const typeMetaclass = getBuiltInType(errorNode, 'type');
            effectiveMetaclass =
                typeMetaclass && isInstantiableClass(typeMetaclass) ? typeMetaclass : UnknownType.create();
        }

        classType.shared.effectiveMetaclass = effectiveMetaclass;

        return effectiveMetaclass;
    }

    // Verifies that the type variables provided outside of "Generic"
    // or "Protocol" are also provided within the "Generic". For example:
    //    class Foo(Mapping[K, V], Generic[V])
    // is illegal because K is not included in Generic.
    function verifyGenericTypeParams(
        errorNode: ExpressionNode,
        typeVars: TypeVarType[],
        genericTypeVars: TypeVarType[]
    ) {
        const missingFromGeneric = typeVars.filter((typeVar) => {
            return !genericTypeVars.some((genericTypeVar) => genericTypeVar.shared.name === typeVar.shared.name);
        });

        if (missingFromGeneric.length > 0) {
            const diag = new DiagnosticAddendum();
            diag.addMessage(
                LocAddendum.typeVarsMissing().format({
                    names: missingFromGeneric.map((typeVar) => `"${typeVar.shared.name}"`).join(', '),
                })
            );
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeVarsNotInGenericOrProtocol() + diag.getString(),
                errorNode
            );
        }
    }

    // Records the fact that the specified class requires "deferred completion" because
    // one of its base classes has not yet been fully evaluated. If the caller passes
    // undefined for "dependsUpon", then the class is added to all outstanding deferred
    // completions.
    function registerDeferredClassCompletion(classToComplete: ClassNode, dependsUpon: ClassType | undefined) {
        if (dependsUpon) {
            // See if there is an existing entry for this dependency.
            const entry = deferredClassCompletions.find((e) =>
                ClassType.isSameGenericClass(e.dependsUpon, dependsUpon)
            );
            if (entry) {
                entry.classesToComplete.push(classToComplete);
            } else {
                deferredClassCompletions.push({ dependsUpon, classesToComplete: [classToComplete] });
            }
        } else {
            deferredClassCompletions.forEach((e) => {
                e.classesToComplete.push(classToComplete);
            });
        }
    }

    // Runs any registered "deferred class completions" that depend on the specified
    // class type. This allows us to complete any work that requires dependent classes
    // to be completed.
    function runDeferredClassCompletions(type: ClassType) {
        deferredClassCompletions.forEach((e) => {
            if (ClassType.isSameGenericClass(e.dependsUpon, type)) {
                e.classesToComplete.forEach((classNode) => {
                    const classType = readTypeCache(classNode.d.name, EvalFlags.None);
                    if (classType) {
                        completeClassTypeDeferred(classType as ClassType, classNode.d.name);
                    }
                });
            }
        });

        // Remove any completions that depend on this type.
        deferredClassCompletions = deferredClassCompletions.filter(
            (e) => !ClassType.isSameGenericClass(e.dependsUpon, type)
        );
    }

    // Recomputes the MRO and effective metaclass for the class after dependent
    // classes have been fully constructed.
    function completeClassTypeDeferred(type: ClassType, errorNode: ParseNode) {
        // Recompute the MRO linearization.
        if (!computeMroLinearization(type)) {
            addDiagnostic(DiagnosticRule.reportGeneralTypeIssues, LocMessage.methodOrdering(), errorNode);
        }

        // Recompute the effective metaclass.
        computeEffectiveMetaclass(type, errorNode);
    }

    function validateInitSubclassArgs(node: ClassNode, classType: ClassType) {
        // Collect arguments that will be passed to the `__init_subclass__`
        // method described in PEP 487 and validate it.
        const argList: Arg[] = [];

        node.d.arguments.forEach((arg) => {
            if (arg.d.name && arg.d.name.d.value !== 'metaclass') {
                argList.push({
                    argCategory: ArgCategory.Simple,
                    node: arg,
                    name: arg.d.name,
                    valueExpression: arg.d.valueExpr,
                });
            }
        });

        let newMethodMember: ClassMember | undefined;

        // See if the class has a metaclass that overrides `__new__`. If so, we
        // will validate the signature of the `__new__` method.
        if (classType.shared.effectiveMetaclass && isClass(classType.shared.effectiveMetaclass)) {
            // If the metaclass is 'type' or 'ABCMeta', we'll assume it will call through to
            // __init_subclass__, so we'll skip the `__new__` method check. We need to exclude
            // TypedDict classes here because _TypedDict uses ABCMeta as its metaclass, but its
            // typeshed definition doesn't override __init_subclass__.
            const metaclassCallsInitSubclass =
                ClassType.isBuiltIn(classType.shared.effectiveMetaclass, ['ABCMeta', 'type']) &&
                !ClassType.isTypedDictClass(classType);

            if (!metaclassCallsInitSubclass) {
                // See if the metaclass has a `__new__` method that accepts keyword parameters.
                newMethodMember = lookUpClassMember(
                    classType.shared.effectiveMetaclass,
                    '__new__',
                    MemberAccessFlags.SkipTypeBaseClass
                );
            }
        }

        if (newMethodMember) {
            const newMethodType = getTypeOfMember(newMethodMember);
            if (isFunction(newMethodType)) {
                const paramListDetails = getParamListDetails(newMethodType);

                if (paramListDetails.firstKeywordOnlyIndex !== undefined) {
                    // Build a map of the keyword-only parameters.
                    const paramMap = new Map<string, number>();
                    for (let i = paramListDetails.firstKeywordOnlyIndex; i < paramListDetails.params.length; i++) {
                        const paramInfo = paramListDetails.params[i];
                        if (
                            paramInfo.param.category === ParamCategory.Simple &&
                            paramInfo.param.name &&
                            paramInfo.kind !== ParamKind.Positional
                        ) {
                            paramMap.set(paramInfo.param.name, i);
                        }
                    }

                    argList.forEach((arg) => {
                        if (arg.argCategory === ArgCategory.Simple && arg.name) {
                            const paramIndex = paramMap.get(arg.name.d.value) ?? paramListDetails.kwargsIndex;

                            if (paramIndex !== undefined) {
                                const paramInfo = paramListDetails.params[paramIndex];
                                const argParam: ValidateArgTypeParams = {
                                    paramCategory: paramInfo.param.category,
                                    paramType: paramInfo.type,
                                    requiresTypeVarMatching: false,
                                    argument: arg,
                                    errorNode: arg.valueExpression ?? node.d.name,
                                };

                                validateArgType(
                                    argParam,
                                    new ConstraintTracker(),
                                    { type: newMethodType },
                                    { skipUnknownArgCheck: true }
                                );
                                paramMap.delete(arg.name.d.value);
                            } else {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.paramNameMissing().format({ name: arg.name.d.value }),
                                    arg.name ?? node.d.name
                                );
                            }
                        }
                    });

                    // See if we have any remaining unmatched parameters without
                    // default values.
                    const unassignedParams: string[] = [];
                    paramMap.forEach((index, paramName) => {
                        const paramInfo = paramListDetails.params[index];
                        if (!paramInfo.defaultType) {
                            unassignedParams.push(paramName);
                        }
                    });

                    if (unassignedParams.length > 0) {
                        const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', ');
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            unassignedParams.length === 1
                                ? LocMessage.argMissingForParam().format({ name: missingParamNames })
                                : LocMessage.argMissingForParams().format({ names: missingParamNames }),
                            node.d.name
                        );
                    }
                }
            }
        } else {
            // If there was no custom metaclass __new__ method, see if there is an __init_subclass__
            // method present somewhere in the class hierarchy.
            const initSubclassMethodInfo = getTypeOfBoundMember(
                node.d.name,
                classType,
                '__init_subclass__',
                /* usage */ undefined,
                /* diag */ undefined,
                MemberAccessFlags.SkipClassMembers |
                    MemberAccessFlags.SkipOriginalClass |
                    MemberAccessFlags.SkipAttributeAccessOverride
            );

            if (initSubclassMethodInfo) {
                const initSubclassMethodType = initSubclassMethodInfo.type;

                if (initSubclassMethodType && initSubclassMethodInfo.classType) {
                    const callResult = validateCallArgs(
                        node.d.name,
                        argList,
                        { type: initSubclassMethodType },
                        /* constraints */ undefined,
                        /* skipUnknownArgCheck */ false,
                        makeInferenceContext(getNoneType())
                    );

                    if (callResult.argumentErrors) {
                        const diag = addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.initSubclassCallFailed(),
                            node.d.name
                        );

                        const initSubclassFunction = isOverloaded(initSubclassMethodType)
                            ? OverloadedType.getOverloads(initSubclassMethodType)[0]
                            : initSubclassMethodType;
                        const initSubclassDecl = isFunction(initSubclassFunction)
                            ? initSubclassFunction.shared.declaration
                            : undefined;

                        if (diag && initSubclassDecl) {
                            diag.addRelatedInfo(
                                LocAddendum.initSubclassLocation().format({
                                    name: printType(convertToInstance(initSubclassMethodInfo.classType)),
                                }),
                                initSubclassDecl.uri,
                                initSubclassDecl.range
                            );
                        }
                    }
                }
            }
        }

        // Evaluate all of the expressions so they are checked and marked referenced.
        argList.forEach((arg) => {
            if (arg.valueExpression) {
                getTypeOfExpression(arg.valueExpression);
            }
        });
    }

    function getTypeOfFunction(node: FunctionNode): FunctionTypeResult | undefined {
        initializePrefetchedTypes(node);

        // Is this predecorated function type cached?
        let functionType = readTypeCache(node.d.name, EvalFlags.None);

        if (functionType) {
            if (!isFunction(functionType)) {
                // This can happen in certain rare circumstances where the
                // function declaration falls within an unreachable code block.
                return undefined;
            }

            if (FunctionType.isPartiallyEvaluated(functionType)) {
                return { functionType, decoratedType: functionType };
            }
        } else {
            functionType = getTypeOfFunctionPredecorated(node);
        }

        // Is the decorated function type cached?
        let decoratedType = readTypeCache(node, EvalFlags.None);
        if (decoratedType) {
            return { functionType, decoratedType };
        }

        // Populate the cache with a temporary value to handle recursion.
        writeTypeCache(node, { type: functionType }, /* flags */ undefined);

        // If it's an async function, wrap the return type in an Awaitable or Generator.
        // Set the "partially evaluated" flag around this logic to detect recursion.
        functionType.shared.flags |= FunctionTypeFlags.PartiallyEvaluated;
        const preDecoratedType = node.d.isAsync ? createAsyncFunction(node, functionType) : functionType;

        // Apply all of the decorators in reverse order.
        decoratedType = preDecoratedType;
        let foundUnknown = false;
        for (let i = node.d.decorators.length - 1; i >= 0; i--) {
            const decorator = node.d.decorators[i];

            const newDecoratedType = useSignatureTracker(node.parent ?? node, () => {
                assert(decoratedType !== undefined);
                return applyFunctionDecorator(evaluatorInterface, decoratedType, functionType, decorator, node);
            });

            const unknownOrAny = containsAnyOrUnknown(newDecoratedType, /* recurse */ false);

            if (unknownOrAny && isUnknown(unknownOrAny)) {
                // Report this error only on the first unknown type.
                if (!foundUnknown) {
                    addDiagnostic(
                        DiagnosticRule.reportUntypedFunctionDecorator,
                        LocMessage.functionDecoratorTypeUnknown(),
                        node.d.decorators[i].d.expr
                    );

                    foundUnknown = true;
                }
            } else {
                // Apply the decorator only if the type is known.
                decoratedType = newDecoratedType;
            }
        }

        // See if there are any overloads provided by previous function declarations.
        if (isFunction(decoratedType)) {
            decoratedType.shared.deprecatedMessage = functionType.shared.deprecatedMessage;

            if (FunctionType.isOverloaded(decoratedType)) {
                // Mark all the parameters as accessed.
                node.d.params.forEach((param) => {
                    markParamAccessed(param);
                });
            }
        }

        decoratedType = addOverloadsToFunctionType(evaluatorInterface, node, decoratedType);

        writeTypeCache(node, { type: decoratedType }, EvalFlags.None);

        // Now that the decorator has been applied, we can clear the
        // "partially evaluated" flag.
        functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated;

        return { functionType, decoratedType };
    }

    // Evaluates the type of a "def" statement without applying an async
    // modifier or any decorators.
    function getTypeOfFunctionPredecorated(node: FunctionNode): FunctionType {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

        // Is this type already cached?
        const cachedFunctionType = readTypeCache(node.d.name, EvalFlags.None);

        if (cachedFunctionType && isFunction(cachedFunctionType)) {
            return cachedFunctionType;
        }

        let functionDecl: FunctionDeclaration | undefined;
        const decl = AnalyzerNodeInfo.getDeclaration(node);
        if (decl) {
            functionDecl = decl as FunctionDeclaration;
        }

        // There was no cached type, so create a new one.
        // Retrieve the containing class node if the function is a method.
        const containingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true);
        let containingClassType: ClassType | undefined;
        if (containingClassNode) {
            containingClassType = getTypeOfClass(containingClassNode)?.classType;
        }

        const functionInfo = getFunctionInfoFromDecorators(evaluatorInterface, node, !!containingClassNode);
        let functionFlags = functionInfo.flags;
        if (functionDecl?.isGenerator) {
            functionFlags |= FunctionTypeFlags.Generator;
        }

        if (fileInfo.isStubFile) {
            functionFlags |= FunctionTypeFlags.StubDefinition;
        } else if (fileInfo.isInPyTypedPackage) {
            functionFlags |= FunctionTypeFlags.PyTypedDefinition;
        }

        if (node.d.isAsync) {
            functionFlags |= FunctionTypeFlags.Async;
        }

        const functionType = FunctionType.createInstance(
            node.d.name.d.value,
            getFunctionFullName(node, fileInfo.moduleName, node.d.name.d.value),
            fileInfo.moduleName,
            functionFlags | FunctionTypeFlags.PartiallyEvaluated,
            ParseTreeUtils.getDocString(node.d.suite.d.statements)
        );

        functionType.shared.typeVarScopeId = ParseTreeUtils.getScopeIdForNode(node);
        functionType.shared.deprecatedMessage = functionInfo.deprecationMessage;
        functionType.shared.methodClass = containingClassType;

        if (node.d.name.d.value === '__init__' || node.d.name.d.value === '__new__') {
            if (containingClassNode) {
                functionType.priv.constructorTypeVarScopeId = ParseTreeUtils.getScopeIdForNode(containingClassNode);
            }
        }

        if (fileInfo.isBuiltInStubFile || fileInfo.isTypingStubFile || fileInfo.isTypingExtensionsStubFile) {
            // Mark the function as a built-in stdlib function.
            functionType.shared.flags |= FunctionTypeFlags.BuiltIn;
        }

        functionType.shared.declaration = functionDecl;

        // Allow recursion by caching and registering the partially-constructed function type.
        const scope = ScopeUtils.getScopeForNode(node);
        const functionSymbol = scope?.lookUpSymbolRecursive(node.d.name.d.value);
        if (functionDecl && functionSymbol) {
            setSymbolResolutionPartialType(functionSymbol.symbol, functionDecl, functionType);
        }

        return invalidateTypeCacheIfCanceled(() => {
            writeTypeCache(node.d.name, { type: functionType }, /* flags */ undefined);

            // Is this an "__init__" method within a pseudo-generic class? If so,
            // we'll add generic types to the constructor's parameters.
            const addGenericParamTypes =
                containingClassType &&
                ClassType.isPseudoGenericClass(containingClassType) &&
                node.d.name.d.value === '__init__';

            const paramTypes: Type[] = [];

            // Determine if the first parameter should be skipped for comment-based
            // function annotations.
            let firstCommentAnnotationIndex = 0;
            if (containingClassType && (functionType.shared.flags & FunctionTypeFlags.StaticMethod) === 0) {
                firstCommentAnnotationIndex = 1;
            }

            // If there is a function annotation comment, validate that it has the correct
            // number of parameter annotations.
            if (node.d.funcAnnotationComment && !node.d.funcAnnotationComment.d.isEllipsis) {
                const expected = node.d.params.length - firstCommentAnnotationIndex;
                const received = node.d.funcAnnotationComment.d.paramAnnotations.length;

                // For methods with "self" or "cls" parameters, the annotation list
                // can either include or exclude the annotation for the first parameter.
                if (firstCommentAnnotationIndex > 0 && received === node.d.params.length) {
                    firstCommentAnnotationIndex = 0;
                } else if (received !== expected) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.annotatedParamCountMismatch().format({
                            expected,
                            received,
                        }),
                        node.d.funcAnnotationComment
                    );
                }
            }

            // If this function uses PEP 695 syntax for type parameters,
            // accumulate the list of type parameters upfront.
            const typeParamsSeen: TypeVarType[] = [];
            if (node.d.typeParams) {
                functionType.shared.typeParams = evaluateTypeParamList(node.d.typeParams).map((typeParam) =>
                    convertToInstance(typeParam)
                );
            } else {
                functionType.shared.typeParams = typeParamsSeen;
            }

            let paramsArePositionOnly = true;
            const isFirstParamClsOrSelf =
                containingClassType &&
                (FunctionType.isClassMethod(functionType) ||
                    FunctionType.isInstanceMethod(functionType) ||
                    FunctionType.isConstructorMethod(functionType));
            const firstNonClsSelfParamIndex = isFirstParamClsOrSelf ? 1 : 0;

            node.d.params.forEach((param, index) => {
                let paramType: Type | undefined;
                let annotatedType: Type | undefined;
                let paramTypeNode: ExpressionNode | undefined;

                if (param.d.name) {
                    if (index === 0 && isFirstParamClsOrSelf) {
                        // Mark "self/cls" as accessed.
                        markParamAccessed(param);
                    } else if (FunctionType.isAbstractMethod(functionType)) {
                        // Mark all parameters in abstract methods as accessed.
                        markParamAccessed(param);
                    } else if (containingClassType && ClassType.isProtocolClass(containingClassType)) {
                        // Mark all parameters in protocol methods as accessed.
                        markParamAccessed(param);
                    }
                }

                if (param.d.annotation) {
                    paramTypeNode = param.d.annotation;
                } else if (param.d.annotationComment) {
                    paramTypeNode = param.d.annotationComment;
                } else if (node.d.funcAnnotationComment && !node.d.funcAnnotationComment.d.isEllipsis) {
                    const adjustedIndex = index - firstCommentAnnotationIndex;
                    if (adjustedIndex >= 0 && adjustedIndex < node.d.funcAnnotationComment.d.paramAnnotations.length) {
                        paramTypeNode = node.d.funcAnnotationComment.d.paramAnnotations[adjustedIndex];
                    }
                }

                if (paramTypeNode) {
                    if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) {
                        annotatedType = UnknownType.create();
                    } else {
                        annotatedType = getTypeOfParamAnnotation(paramTypeNode, param.d.category);
                    }

                    if (annotatedType) {
                        addTypeVarsToListIfUnique(
                            typeParamsSeen,
                            getTypeVarArgsRecursive(annotatedType),
                            functionType.shared.typeVarScopeId
                        );
                    }

                    if (isTypeVarTuple(annotatedType) && !annotatedType.priv.isUnpacked) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.unpackedTypeVarTupleExpected().format({
                                name1: annotatedType.shared.name,
                                name2: annotatedType.shared.name,
                            }),
                            paramTypeNode
                        );
                        annotatedType = UnknownType.create();
                    }
                }

                if (!annotatedType && addGenericParamTypes) {
                    if (
                        index > 0 &&
                        param.d.category === ParamCategory.Simple &&
                        param.d.name &&
                        !param.d.defaultValue
                    ) {
                        const typeParamName = getPseudoGenericTypeVarName(param.d.name.d.value);
                        annotatedType = containingClassType!.shared.typeParams.find(
                            (param) => param.shared.name === typeParamName
                        );
                    }
                }

                if (annotatedType) {
                    const adjustedAnnotatedType = adjustParamAnnotatedType(param, annotatedType);
                    if (adjustedAnnotatedType !== annotatedType) {
                        annotatedType = adjustedAnnotatedType;
                    }
                }

                let defaultValueType: Type | undefined;
                if (param.d.defaultValue) {
                    // If this is a stub file, a protocol, an overload, or a class
                    // whose body is a placeholder implementation, treat a "...", as
                    // an "Any" value.
                    let treatEllipsisAsAny = fileInfo.isStubFile || ParseTreeUtils.isSuiteEmpty(node.d.suite);
                    if (containingClassType && ClassType.isProtocolClass(containingClassType)) {
                        treatEllipsisAsAny = true;
                    }
                    if (FunctionType.isOverloaded(functionType) || FunctionType.isAbstractMethod(functionType)) {
                        treatEllipsisAsAny = true;
                    }

                    defaultValueType = getTypeOfExpression(
                        param.d.defaultValue,
                        treatEllipsisAsAny ? EvalFlags.ConvertEllipsisToAny : EvalFlags.None,
                        makeInferenceContext(annotatedType)
                    ).type;
                }

                if (annotatedType) {
                    // If there was both a type annotation and a default value, verify
                    // that the default value matches the annotation.
                    if (param.d.defaultValue && defaultValueType) {
                        const diagAddendum = new DiagnosticAddendum();

                        if (!assignType(annotatedType, defaultValueType, diagAddendum)) {
                            addDiagnostic(
                                DiagnosticRule.reportArgumentType,
                                LocMessage.paramAssignmentMismatch().format({
                                    sourceType: printType(defaultValueType),
                                    paramType: printType(annotatedType),
                                }) + diagAddendum.getString(),
                                param.d.defaultValue
                            );
                        }
                    }

                    paramType = annotatedType;
                }

                // Determine whether we need to insert an implied position-only parameter.
                // This is needed when a function's parameters are named using the old-style
                // way of specifying position-only parameters.
                if (index >= firstNonClsSelfParamIndex) {
                    let isImplicitPositionOnlyParam = false;

                    if (param.d.category === ParamCategory.Simple && param.d.name) {
                        if (
                            isPrivateName(param.d.name.d.value) &&
                            !node.d.params.some((p) => p.d.category === ParamCategory.Simple && !p.d.name)
                        ) {
                            isImplicitPositionOnlyParam = true;

                            // If the parameter name indicates an implicit position-only parameter
                            // but we have already seen non-position-only parameters, report an error.
                            if (
                                !paramsArePositionOnly &&
                                functionType.shared.parameters.every((p) => p.category === ParamCategory.Simple)
                            ) {
                                addDiagnostic(
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    LocMessage.positionOnlyAfterNon(),
                                    param.d.name
                                );
                            }
                        }
                    } else {
                        paramsArePositionOnly = false;
                    }

                    if (
                        paramsArePositionOnly &&
                        !isImplicitPositionOnlyParam &&
                        functionType.shared.parameters.length > firstNonClsSelfParamIndex
                    ) {
                        FunctionType.addPositionOnlyParamSeparator(functionType);
                    }

                    if (!isImplicitPositionOnlyParam) {
                        paramsArePositionOnly = false;
                    }
                }

                // If there was no annotation for the parameter, infer its type if possible.
                let isTypeInferred = false;
                if (!paramTypeNode) {
                    isTypeInferred = true;
                    const inferredType = inferParamType(node, functionType.shared.flags, index, containingClassType);
                    if (inferredType) {
                        paramType = inferredType;
                    }
                }

                paramType = paramType ?? UnknownType.create();

                const functionParam = FunctionParam.create(
                    param.d.category,
                    paramType,
                    (isTypeInferred ? FunctionParamFlags.TypeInferred : FunctionParamFlags.None) |
                        (paramTypeNode ? FunctionParamFlags.TypeDeclared : FunctionParamFlags.None),
                    param.d.name ? param.d.name.d.value : undefined,
                    defaultValueType,
                    param.d.defaultValue
                );

                FunctionType.addParam(functionType, functionParam);

                if (FunctionParam.isTypeDeclared(functionParam)) {
                    addTypeVarsToListIfUnique(
                        typeParamsSeen,
                        getTypeVarArgsRecursive(paramType),
                        functionType.shared.typeVarScopeId
                    );
                }

                if (param.d.name) {
                    const variadicParamType = transformVariadicParamType(node, param.d.category, paramType);
                    paramTypes.push(variadicParamType);
                } else {
                    paramTypes.push(paramType);
                }
            });

            if (paramsArePositionOnly && functionType.shared.parameters.length > firstNonClsSelfParamIndex) {
                FunctionType.addPositionOnlyParamSeparator(functionType);
            }

            // Update the types for the nodes associated with the parameters.
            const scopeIds = ParseTreeUtils.getTypeVarScopesForNode(node);
            paramTypes.forEach((paramType, index) => {
                const paramNameNode = node.d.params[index].d.name;
                if (paramNameNode) {
                    if (isUnknown(paramType)) {
                        functionType.shared.flags |= FunctionTypeFlags.UnannotatedParams;
                    }

                    paramType = makeTypeVarsBound(paramType, scopeIds);

                    writeTypeCache(paramNameNode, { type: paramType }, EvalFlags.None);
                }
            });

            // If the function ends in P.args and P.kwargs parameters, make it exempt from
            // args/kwargs compatibility checks. This is important for protocol comparisons.
            if (paramTypes.length >= 2) {
                const paramType1 = paramTypes[paramTypes.length - 2];
                const paramType2 = paramTypes[paramTypes.length - 1];
                if (
                    isParamSpec(paramType1) &&
                    paramType1.priv.paramSpecAccess === 'args' &&
                    isParamSpec(paramType2) &&
                    paramType2.priv.paramSpecAccess === 'kwargs'
                ) {
                    functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
                }
            }

            // If the function contains an *args and a **kwargs parameter and both
            // are annotated as Any or are unannotated, make it exempt from
            // args/kwargs compatibility checks.
            const variadicsWithAnyType = functionType.shared.parameters.filter(
                (param, index) =>
                    param.category !== ParamCategory.Simple &&
                    param.name &&
                    isAnyOrUnknown(FunctionType.getParamType(functionType, index))
            );
            if (variadicsWithAnyType.length >= 2) {
                functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
            }

            // If there was a defined return type, analyze that first so when we
            // walk the contents of the function, return statements can be
            // validated against this type.
            const returnTypeAnnotationNode =
                node.d.returnAnnotation ?? node.d.funcAnnotationComment?.d.returnAnnotation;
            if (returnTypeAnnotationNode) {
                // Temporarily set the return type to unknown in case of recursion.
                functionType.shared.declaredReturnType = UnknownType.create();

                const returnType = getTypeOfAnnotation(returnTypeAnnotationNode, {
                    typeVarGetsCurScope: true,
                });
                functionType.shared.declaredReturnType = returnType;
            } else {
                // If there was no return type annotation and this is a type stub,
                // we have no opportunity to infer the return type, so we'll indicate
                // that it's unknown.
                if (fileInfo.isStubFile) {
                    // Special-case the __init__ method, which is commonly left without
                    // an annotated return type, but we can assume it returns None.
                    if (node.d.name.d.value === '__init__') {
                        functionType.shared.declaredReturnType = getNoneType();
                    } else {
                        functionType.shared.declaredReturnType = UnknownType.create();
                    }
                }
            }

            // Accumulate any type parameters used in the return type.
            if (functionType.shared.declaredReturnType && returnTypeAnnotationNode) {
                addTypeVarsToListIfUnique(
                    typeParamsSeen,
                    getTypeVarArgsRecursive(functionType.shared.declaredReturnType),
                    functionType.shared.typeVarScopeId
                );
            }

            // Validate the default types for all type parameters.
            functionType.shared.typeParams.forEach((typeParam, index) => {
                let bestErrorNode: ExpressionNode = node.d.name;
                if (node.d.typeParams && index < node.d.typeParams.d.params.length) {
                    const typeParamNode = node.d.typeParams.d.params[index];
                    bestErrorNode = typeParamNode.d.defaultExpr ?? typeParamNode.d.name;
                }

                validateTypeParamDefault(
                    bestErrorNode,
                    typeParam,
                    functionType.shared.typeParams.slice(0, index),
                    functionType.shared.typeVarScopeId!
                );
            });

            // Clear the "partially evaluated" flag to indicate that the functionType
            // is fully evaluated.
            functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated;

            writeTypeCache(node.d.name, { type: functionType }, EvalFlags.None);

            return functionType;
        });
    }

    function markParamAccessed(param: ParameterNode) {
        if (param.d.name) {
            const symbolWithScope = lookUpSymbolRecursive(
                param.d.name,
                param.d.name.d.value,
                /* honorCodeFlow */ false
            );
            if (symbolWithScope) {
                setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(param), symbolWithScope.symbol, param.d.name);
            }
        }
    }

    function adjustParamAnnotatedType(param: ParameterNode, type: Type): Type {
        // PEP 484 indicates that if a parameter has a default value of 'None'
        // the type checker should assume that the type is optional (i.e. a union
        // of the specified type and 'None'). Skip this step if the type is already
        // optional to avoid losing alias names when combining the types.
        if (
            param.d.defaultValue?.nodeType === ParseNodeType.Constant &&
            param.d.defaultValue.d.constType === KeywordType.None &&
            !isOptionalType(type) &&
            !AnalyzerNodeInfo.getFileInfo(param).diagnosticRuleSet.strictParameterNoneValue
        ) {
            return combineTypes([type, getNoneType()]);
        }

        return type;
    }

    // Attempts to infer an unannotated parameter type from available context.
    function inferParamType(
        functionNode: FunctionNode,
        functionFlags: FunctionTypeFlags,
        paramIndex: number,
        containingClassType: ClassType | undefined
    ) {
        // Is the function a method within a class? If so, see if a base class
        // defines the same method and provides annotations.
        if (containingClassType) {
            if (paramIndex === 0) {
                if ((functionFlags & FunctionTypeFlags.StaticMethod) === 0) {
                    const hasClsParam =
                        (functionFlags & (FunctionTypeFlags.ClassMethod | FunctionTypeFlags.ConstructorMethod)) !== 0;
                    return synthesizeTypeVarForSelfCls(containingClassType, hasClsParam);
                }
            }

            const methodName = functionNode.d.name.d.value;

            const baseClassMemberInfo = lookUpClassMember(
                containingClassType,
                methodName,
                MemberAccessFlags.SkipOriginalClass
            );

            if (baseClassMemberInfo) {
                const memberDecls = baseClassMemberInfo.symbol.getDeclarations();
                if (memberDecls.length === 1 && memberDecls[0].type === DeclarationType.Function) {
                    const baseClassMethodNode = memberDecls[0].node;

                    // Does the signature match exactly with the exception of annotations?
                    if (
                        baseClassMethodNode.d.params.length === functionNode.d.params.length &&
                        baseClassMethodNode.d.params.every((param, index) => {
                            const overrideParam = functionNode.d.params[index];
                            return (
                                overrideParam.d.name?.d.value === param.d.name?.d.value &&
                                overrideParam.d.category === param.d.category
                            );
                        })
                    ) {
                        const baseClassParam = baseClassMethodNode.d.params[paramIndex];
                        const baseClassParamAnnotation =
                            baseClassParam.d.annotation ?? baseClassParam.d.annotationComment;
                        if (baseClassParamAnnotation) {
                            let inferredParamType = getTypeOfParamAnnotation(
                                baseClassParamAnnotation,
                                functionNode.d.params[paramIndex].d.category
                            );

                            // If the parameter type is generic, specialize it in the context
                            // of the child class.
                            if (requiresSpecialization(inferredParamType) && isClass(baseClassMemberInfo.classType)) {
                                const scopeIds: TypeVarScopeId[] = getTypeVarScopeIds(baseClassMemberInfo.classType);
                                const solution = buildSolutionFromSpecializedClass(baseClassMemberInfo.classType);

                                scopeIds.push(ParseTreeUtils.getScopeIdForNode(baseClassMethodNode));

                                // Replace any unsolved TypeVars with Unknown (including all function-scoped TypeVars).
                                inferredParamType = applySolvedTypeVars(inferredParamType, solution, {
                                    replaceUnsolved: {
                                        scopeIds,
                                        tupleClassType: getTupleClassType(),
                                    },
                                });
                            }

                            const fileInfo = AnalyzerNodeInfo.getFileInfo(functionNode);
                            if (fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) {
                                inferredParamType = TypeBase.cloneForAmbiguousType(inferredParamType);
                            }

                            return inferredParamType;
                        }
                    }
                }
            }
        }

        // If the parameter has a default argument value, we may be able to infer its
        // type from this information.
        const paramValueExpr = functionNode.d.params[paramIndex].d.defaultValue;
        if (paramValueExpr) {
            return inferParamTypeFromDefaultValue(paramValueExpr);
        }

        return undefined;
    }

    function inferParamTypeFromDefaultValue(paramValueExpr: ExpressionNode) {
        const defaultValueType = getTypeOfExpression(paramValueExpr, EvalFlags.ConvertEllipsisToAny).type;

        let inferredParamType: Type | undefined;

        // Is the default value a "None", a sentinel, or an instance of some private
        // class (one whose name starts with an underscore)? If so, we will assume
        // that the value is a singleton sentinel. The actual supported type is
        // going to be a union of this type and Unknown.
        if (
            isNoneInstance(defaultValueType) ||
            isSentinelLiteral(defaultValueType) ||
            (isClassInstance(defaultValueType) && isPrivateOrProtectedName(defaultValueType.shared.name))
        ) {
            inferredParamType = combineTypes([defaultValueType, UnknownType.create()]);
        } else {
            let skipInference = false;

            if (isFunctionOrOverloaded(defaultValueType)) {
                // Do not infer parameter types that use a lambda or another function as a
                // default value. We're likely to generate false positives in this case.
                // It's not clear whether parameters should be positional-only or not.
                skipInference = true;
            } else if (
                isClassInstance(defaultValueType) &&
                ClassType.isBuiltIn(defaultValueType, ['tuple', 'list', 'set', 'dict'])
            ) {
                // Do not infer certain types like tuple because it's likely to be
                // more restrictive (narrower) than intended.
                skipInference = true;
            }

            if (!skipInference) {
                inferredParamType = convertSpecialFormToRuntimeValue(
                    defaultValueType,
                    EvalFlags.None,
                    /* convertModule */ true
                );
                inferredParamType = stripTypeForm(inferredParamType);
                inferredParamType = stripLiteralValue(inferredParamType);
            }
        }

        if (inferredParamType) {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(paramValueExpr);
            if (fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) {
                inferredParamType = TypeBase.cloneForAmbiguousType(inferredParamType);
            }
        }

        return inferredParamType;
    }

    // Transforms the parameter type based on its category. If it's a simple parameter,
    // no transform is applied. If it's a var-arg or keyword-arg parameter, the type
    // is wrapped in a List or Dict.
    function transformVariadicParamType(node: ParseNode, paramCategory: ParamCategory, type: Type): Type {
        switch (paramCategory) {
            case ParamCategory.Simple: {
                return type;
            }

            case ParamCategory.ArgsList: {
                if (isParamSpec(type) && type.priv.paramSpecAccess) {
                    return type;
                }

                if (isUnpackedClass(type)) {
                    return ClassType.cloneForPacked(type);
                }

                return makeTupleObject(evaluatorInterface, [{ type, isUnbounded: !isTypeVarTuple(type) }]);
            }

            case ParamCategory.KwargsDict: {
                // Leave a ParamSpec alone.
                if (isParamSpec(type) && type.priv.paramSpecAccess) {
                    return type;
                }

                // Is this an unpacked TypedDict? If so, return its packed version.
                if (isClassInstance(type) && ClassType.isTypedDictClass(type) && type.priv.isUnpacked) {
                    return ClassType.cloneForPacked(type);
                }

                // Wrap the type in a dict with str keys.
                const dictType = getBuiltInType(node, 'dict');
                const strType = getBuiltInObject(node, 'str');

                if (isInstantiableClass(dictType) && isClassInstance(strType)) {
                    return ClassType.cloneAsInstance(ClassType.specialize(dictType, [strType, type]));
                }

                return UnknownType.create();
            }
        }
    }

    function createAsyncFunction(node: FunctionNode, functionType: FunctionType): FunctionType {
        assert(FunctionType.isAsync(functionType));

        // Clone the original function and replace its return type with an
        // Awaitable[<returnType>]. Mark the new function as no longer async.
        const awaitableFunctionType = FunctionType.cloneWithNewFlags(
            functionType,
            functionType.shared.flags & ~(FunctionTypeFlags.Async | FunctionTypeFlags.PartiallyEvaluated)
        );

        if (functionType.shared.declaredReturnType) {
            awaitableFunctionType.shared.declaredReturnType = createAwaitableReturnType(
                node,
                functionType.shared.declaredReturnType,
                FunctionType.isGenerator(functionType)
            );
        } else {
            awaitableFunctionType.shared.inferredReturnType = {
                type: createAwaitableReturnType(
                    node,
                    getInferredReturnType(functionType),
                    FunctionType.isGenerator(functionType)
                ),
            };
        }

        return awaitableFunctionType;
    }

    function createAwaitableReturnType(
        node: ParseNode,
        returnType: Type,
        isGenerator: boolean,
        useCoroutine = true
    ): Type {
        let awaitableReturnType: Type | undefined;

        if (isClassInstance(returnType)) {
            if (ClassType.isBuiltIn(returnType)) {
                if (returnType.shared.name === 'Generator') {
                    // If the return type is a Generator, change it to an AsyncGenerator.
                    const asyncGeneratorType = getTypingType(node, 'AsyncGenerator');
                    if (asyncGeneratorType && isInstantiableClass(asyncGeneratorType)) {
                        const typeArgs: Type[] = [];
                        const generatorTypeArgs = returnType.priv.typeArgs;
                        if (generatorTypeArgs && generatorTypeArgs.length > 0) {
                            typeArgs.push(generatorTypeArgs[0]);
                        }
                        if (generatorTypeArgs && generatorTypeArgs.length > 1) {
                            typeArgs.push(generatorTypeArgs[1]);
                        }
                        awaitableReturnType = ClassType.cloneAsInstance(
                            ClassType.specialize(asyncGeneratorType, typeArgs)
                        );
                    }
                } else if (['AsyncIterator', 'AsyncIterable'].some((name) => name === returnType.shared.name)) {
                    // If it's already an AsyncIterator or AsyncIterable, leave it as is.
                    awaitableReturnType = returnType;
                } else if (returnType.shared.name === 'AsyncGenerator') {
                    // If it's already an AsyncGenerator and the function is a generator,
                    // leave it as is.
                    if (isGenerator) {
                        awaitableReturnType = returnType;
                    }
                }
            }
        }

        if (!awaitableReturnType || !isGenerator) {
            // Wrap in either an Awaitable or a CoroutineType, which is a subclass of Awaitable.
            const awaitableType = useCoroutine ? getTypesType(node, 'CoroutineType') : getTypingType(node, 'Awaitable');
            if (awaitableType && isInstantiableClass(awaitableType)) {
                awaitableReturnType = ClassType.cloneAsInstance(
                    ClassType.specialize(
                        awaitableType,
                        useCoroutine ? [AnyType.create(), AnyType.create(), returnType] : [returnType]
                    )
                );
            } else {
                awaitableReturnType = UnknownType.create();
            }
        }

        return awaitableReturnType;
    }

    function inferFunctionReturnType(
        node: FunctionNode,
        isAbstract: boolean,
        callerNode: ExpressionNode | undefined
    ): TypeResult | undefined {
        const returnAnnotation = node.d.returnAnnotation || node.d.funcAnnotationComment?.d.returnAnnotation;

        // This shouldn't be called if there is a declared return type, but it
        // can happen if there are unexpected cycles between decorators and
        // classes that they decorate. We'll just return an undefined type
        // in this case.
        if (returnAnnotation) {
            return undefined;
        }

        // Is this type already cached?
        let inferredReturnType = readTypeCache(node.d.suite, EvalFlags.None);
        let isIncomplete = false;

        if (inferredReturnType) {
            return { type: inferredReturnType, isIncomplete };
        }

        const recursionEntry = functionRecursionMap.get(node.id) ?? [];

        if (functionRecursionMap.size >= maxInferFunctionReturnRecursionCount) {
            inferredReturnType = UnknownType.create();
            isIncomplete = true;
        } else if (recursionEntry.some((entry) => entry.callerNode === callerNode)) {
            inferredReturnType = UnknownType.create();
            isIncomplete = true;
        } else {
            recursionEntry.push({ callerNode });
            functionRecursionMap.set(node.id, recursionEntry);

            try {
                let functionDecl: FunctionDeclaration | undefined;
                const decl = AnalyzerNodeInfo.getDeclaration(node);
                if (decl) {
                    functionDecl = decl as FunctionDeclaration;
                }

                const functionNeverReturns = !isAfterNodeReachable(node);
                const implicitlyReturnsNone = isAfterNodeReachable(node.d.suite);

                // Infer the return type based on all of the return statements in the function's body.
                if (AnalyzerNodeInfo.getFileInfo(node).isStubFile) {
                    // If a return type annotation is missing in a stub file, assume
                    // it's an "unknown" type. In normal source files, we can infer the
                    // type from the implementation.
                    inferredReturnType = UnknownType.create();
                } else {
                    if (functionNeverReturns) {
                        // If the function always raises and never returns, assume a "NoReturn" type.
                        // Skip this for abstract methods which often are implemented with "raise
                        // NotImplementedError()".
                        if (isAbstract || methodAlwaysRaisesNotImplemented(functionDecl)) {
                            inferredReturnType = UnknownType.create();
                        } else {
                            inferredReturnType = NeverType.createNoReturn();
                        }
                    } else {
                        const inferredReturnTypes: Type[] = [];
                        if (functionDecl?.returnStatements) {
                            functionDecl.returnStatements.forEach((returnNode) => {
                                if (isNodeReachable(returnNode)) {
                                    if (returnNode.d.expr) {
                                        const returnTypeResult = getTypeOfExpression(returnNode.d.expr);
                                        if (returnTypeResult.isIncomplete) {
                                            isIncomplete = true;
                                        }

                                        let returnType = returnTypeResult.type;

                                        // If the type is a special form, use the special form instead.
                                        if (returnType.props?.specialForm) {
                                            returnType = returnType.props.specialForm;
                                        }

                                        // If the return type includes an instance of a class with isEmptyContainer
                                        // set, clear that because we don't want this flag to "leak" into the
                                        // inferred return type.
                                        returnType = mapSubtypes(returnType, (subtype) => {
                                            if (isClassInstance(subtype) && subtype.priv.isEmptyContainer) {
                                                return ClassType.specialize(
                                                    subtype,
                                                    subtype.priv.typeArgs,
                                                    !!subtype.priv.isTypeArgExplicit,
                                                    subtype.priv.includeSubclasses,
                                                    subtype.priv.tupleTypeArgs,
                                                    /* isEmptyContainer */ false
                                                );
                                            }
                                            return subtype;
                                        });

                                        // Do not retain TypeForm types in inferred return types.
                                        returnType = stripTypeForm(returnType);

                                        // If we're returning a function value (or overload), force
                                        // lazy return-type inference before caching this function's
                                        // inferred return. Without this, a partially evaluated
                                        // function can leak a temporary Any return type (for example,
                                        // immediately after an edit), and that Any then gets cached
                                        // as though it were final for the enclosing function.
                                        inferReturnTypeIfNecessary(returnType);

                                        inferredReturnTypes.push(returnType);
                                    } else {
                                        inferredReturnTypes.push(getNoneType());
                                    }
                                }
                            });
                        }

                        if (!functionNeverReturns && implicitlyReturnsNone) {
                            inferredReturnTypes.push(getNoneType());
                        }

                        inferredReturnType = combineTypes(inferredReturnTypes);

                        // Remove any unbound values since those would generate an exception
                        // before being returned.
                        inferredReturnType = removeUnbound(inferredReturnType);
                    }

                    // Is it a generator?
                    if (functionDecl?.isGenerator) {
                        const inferredYieldTypes: Type[] = [];
                        let useAwaitableGenerator = false;
                        let isYieldResultUsed = false;

                        if (functionDecl.yieldStatements) {
                            functionDecl.yieldStatements.forEach((yieldNode) => {
                                if (isNodeReachable(yieldNode)) {
                                    if (yieldNode.nodeType === ParseNodeType.YieldFrom) {
                                        isYieldResultUsed = true;
                                        const iteratorTypeResult = getTypeOfExpression(yieldNode.d.expr);
                                        if (
                                            isClassInstance(iteratorTypeResult.type) &&
                                            ClassType.isBuiltIn(iteratorTypeResult.type, ['Coroutine', 'CoroutineType'])
                                        ) {
                                            const yieldType =
                                                iteratorTypeResult.type.priv.typeArgs &&
                                                iteratorTypeResult.type.priv.typeArgs.length > 0
                                                    ? iteratorTypeResult.type.priv.typeArgs[0]
                                                    : UnknownType.create();

                                            // Handle old-style (pre-await) Coroutines.
                                            inferredYieldTypes.push(yieldType);
                                            useAwaitableGenerator = true;
                                        } else {
                                            const yieldType = getTypeOfIterator(
                                                iteratorTypeResult,
                                                /* isAsync */ false,
                                                yieldNode
                                            )?.type;

                                            inferredYieldTypes.push(yieldType ?? UnknownType.create());
                                        }
                                    } else {
                                        // If the yield expression is not by itself in a statement list,
                                        // assume that its result is consumed.
                                        if (yieldNode?.parent?.nodeType !== ParseNodeType.StatementList) {
                                            isYieldResultUsed = true;
                                        }

                                        if (yieldNode.d.expr) {
                                            const yieldType = getTypeOfExpression(yieldNode.d.expr).type;
                                            inferredYieldTypes.push(yieldType ?? UnknownType.create());
                                        } else {
                                            inferredYieldTypes.push(getNoneType());
                                        }
                                    }
                                }
                            });
                        }

                        const inferredYieldType = combineTypes(inferredYieldTypes);

                        // Inferred yield types need to be wrapped in a Generator or
                        // AwaitableGenerator to produce the final result.
                        const generatorType = useAwaitableGenerator
                            ? getTypeCheckerInternalsType(node, 'AwaitableGenerator') ??
                              getTypingType(node, 'AwaitableGenerator')
                            : getTypingType(node, 'Generator');

                        if (generatorType && isInstantiableClass(generatorType)) {
                            const typeArgs: Type[] = [];

                            // The "send type" for the generator (the second type argument) is
                            // not generally inferrable, but we can assume that it's Any
                            // if the function never uses the value and Unknown if it does.
                            // This eliminates any "partially unknown" errors in strict mode
                            // in the common case.
                            const sendType = isYieldResultUsed ? UnknownType.create() : AnyType.create();

                            typeArgs.push(inferredYieldType, sendType, inferredReturnType);

                            if (useAwaitableGenerator) {
                                typeArgs.push(AnyType.create());
                            }

                            inferredReturnType = ClassType.cloneAsInstance(
                                ClassType.specialize(generatorType, typeArgs)
                            );
                        } else {
                            inferredReturnType = UnknownType.create();
                        }
                    }
                }

                writeTypeCache(node.d.suite, { type: inferredReturnType, isIncomplete }, EvalFlags.None);
            } catch (err) {
                // Attempt to handle a stack overflow without crashing. In rare
                // cases, we can get very deep stacks when inferring return types
                // within untyped code.
                if ((err as any)?.message === 'Maximum call stack size exceeded') {
                    const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
                    console.error(
                        `Overflowed stack when inferring return type for function: ${
                            node.d.name.d.value
                        } in file ${fileInfo.fileUri.toUserVisibleString()}`
                    );
                    return;
                }
                throw err;
            } finally {
                recursionEntry.pop();
                if (recursionEntry.length === 0) {
                    functionRecursionMap.delete(node.id);
                }
            }
        }

        return inferredReturnType ? { type: inferredReturnType, isIncomplete } : undefined;
    }

    // Determines whether the method consists only of a "raise" statement
    // and the exception type raised is a NotImplementedError or a subclass
    // thereof. This is commonly used for abstract methods.
    function methodAlwaysRaisesNotImplemented(functionDecl?: FunctionDeclaration): boolean {
        if (
            !functionDecl ||
            !functionDecl.isMethod ||
            functionDecl.returnStatements ||
            functionDecl.yieldStatements ||
            !functionDecl.raiseStatements
        ) {
            return false;
        }

        const statements = functionDecl.node.d.suite.d.statements;
        if (statements.some((statement) => statement.nodeType !== ParseNodeType.StatementList)) {
            return false;
        }

        for (const raiseStatement of functionDecl.raiseStatements) {
            if (!raiseStatement.d.expr || raiseStatement.d.fromExpr) {
                return false;
            }
            const raiseType = getTypeOfExpression(raiseStatement.d.expr).type;
            const classType = isInstantiableClass(raiseType)
                ? raiseType
                : isClassInstance(raiseType)
                ? raiseType
                : undefined;
            if (!classType || !derivesFromStdlibClass(classType, 'NotImplementedError')) {
                return false;
            }
        }

        return true;
    }

    function evaluateTypesForForStatement(node: ForNode): void {
        if (isTypeCached(node)) {
            return;
        }

        const iteratorTypeResult = getTypeOfExpression(node.d.iterableExpr);
        const iteratedType =
            getTypeOfIterator(iteratorTypeResult, !!node.d.isAsync, node.d.iterableExpr)?.type ?? UnknownType.create();

        assignTypeToExpression(
            node.d.targetExpr,
            { type: iteratedType, isIncomplete: iteratorTypeResult.isIncomplete },
            node.d.targetExpr
        );

        writeTypeCache(node, { type: iteratedType, isIncomplete: !!iteratorTypeResult.isIncomplete }, EvalFlags.None);
    }

    function evaluateTypesForExceptStatement(node: ExceptNode): void {
        // This should be called only if the except node has a target exception.
        assert(node.d.typeExpr !== undefined);

        if (isTypeCached(node)) {
            return;
        }

        const exceptionTypeResult = getTypeOfExpression(node.d.typeExpr!);
        const exceptionTypes = exceptionTypeResult.type;
        let includesBaseException = false;

        function getExceptionType(exceptionType: Type, errorNode: ExpressionNode) {
            exceptionType = makeTopLevelTypeVarsConcrete(exceptionType);

            if (isAnyOrUnknown(exceptionType)) {
                return exceptionType;
            }

            if (isInstantiableClass(exceptionType)) {
                if (ClassType.isBuiltIn(exceptionType, 'BaseException')) {
                    includesBaseException = true;
                }
                return ClassType.cloneAsInstance(exceptionType);
            }

            if (isClassInstance(exceptionType)) {
                const iterableType =
                    getTypeOfIterator(
                        { type: exceptionType, isIncomplete: exceptionTypeResult.isIncomplete },
                        /* isAsync */ false,
                        errorNode,
                        /* emitNotIterableError */ false
                    )?.type ?? UnknownType.create();

                return mapSubtypes(iterableType, (subtype) => {
                    if (isAnyOrUnknown(subtype)) {
                        return subtype;
                    }

                    return UnknownType.create();
                });
            }

            return UnknownType.create();
        }

        let targetType = mapSubtypes(exceptionTypes, (subType) => {
            // If more than one type was specified for the exception, we'll receive
            // a specialized tuple object here.
            const tupleType = getSpecializedTupleType(subType);
            if (tupleType && tupleType.priv.tupleTypeArgs) {
                const entryTypes = tupleType.priv.tupleTypeArgs.map((t) => {
                    return getExceptionType(t.type, node.d.typeExpr!);
                });
                return combineTypes(entryTypes);
            }

            return getExceptionType(subType, node.d.typeExpr!);
        });

        // If this is an except group, wrap the exception type in an ExceptionGroup
        // or BaseExceptionGroup depending on whether the target exception is
        // a BaseException.
        if (node.d.isExceptGroup) {
            targetType = getBuiltInObject(node, includesBaseException ? 'BaseExceptionGroup' : 'ExceptionGroup', [
                targetType,
            ]);
        }

        if (node.d.name) {
            assignTypeToExpression(node.d.name, { type: targetType }, node.d.name);
        }

        writeTypeCache(node, { type: targetType }, EvalFlags.None);
    }

    function evaluateTypesForWithStatement(node: WithItemNode): void {
        if (isTypeCached(node)) {
            return;
        }

        const exprTypeResult = getTypeOfExpression(node.d.expr);
        let isIncomplete = exprTypeResult.isIncomplete;
        let exprType = exprTypeResult.type;
        const isAsync = node.parent && node.parent.nodeType === ParseNodeType.With && !!node.parent.d.isAsync;

        if (isOptionalType(exprType)) {
            addDiagnostic(
                DiagnosticRule.reportOptionalContextManager,
                isAsync ? LocMessage.noneNotUsableWithAsync() : LocMessage.noneNotUsableWith(),
                node.d.expr
            );
            exprType = removeNoneFromUnion(exprType);
        }

        // Verify that the target has an __enter__ or __aenter__ method defined.
        const enterMethodName = isAsync ? '__aenter__' : '__enter__';
        const scopedType = mapSubtypes(exprType, (subtype) => {
            subtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isAnyOrUnknown(subtype)) {
                return subtype;
            }

            const enterDiag = new DiagnosticAddendum();

            if (isClass(subtype)) {
                const enterTypeResult = getTypeOfMagicMethodCall(
                    subtype,
                    enterMethodName,
                    [],
                    node.d.expr,
                    /* inferenceContext */ undefined,
                    enterDiag.createAddendum()
                );

                if (enterTypeResult) {
                    if (isAsync) {
                        if (enterTypeResult.isIncomplete) {
                            isIncomplete = true;
                        }

                        const asyncResult = getTypeOfAwaitable({ type: enterTypeResult.type }, node.d.expr);
                        if (asyncResult.isIncomplete) {
                            isIncomplete = true;
                        }

                        return asyncResult.type;
                    }
                    return enterTypeResult.type;
                }

                if (!isAsync) {
                    if (
                        getTypeOfMagicMethodCall(
                            subtype,
                            '__aenter__',
                            [],
                            node.d.expr,
                            /* inferenceContext */ undefined
                        )?.type
                    ) {
                        enterDiag.addMessage(LocAddendum.asyncHelp());
                    }
                }
            }

            const message = isAsync ? LocMessage.typeNotUsableWithAsync() : LocMessage.typeNotUsableWith();
            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                message.format({ type: printType(subtype), method: enterMethodName }) + enterDiag.getString(),
                node.d.expr
            );
            return UnknownType.create();
        });

        // Verify that the target has an __exit__ or __aexit__ method defined.
        const exitMethodName = isAsync ? '__aexit__' : '__exit__';
        const exitDiag = new DiagnosticAddendum();

        doForEachSubtype(exprType, (subtype) => {
            subtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isAnyOrUnknown(subtype)) {
                return;
            }

            if (isClass(subtype)) {
                const anyArg: TypeResult = { type: AnyType.create() };
                const exitTypeResult = getTypeOfMagicMethodCall(
                    subtype,
                    exitMethodName,
                    [anyArg, anyArg, anyArg],
                    node.d.expr,
                    /* inferenceContext */ undefined,
                    exitDiag
                );

                if (exitTypeResult) {
                    if (exitTypeResult.isIncomplete) {
                        isIncomplete = true;
                    }

                    if (isAsync) {
                        const asyncResult = getTypeOfAwaitable({ type: exitTypeResult.type }, node.d.expr);
                        if (asyncResult.isIncomplete) {
                            isIncomplete = true;
                        }

                        return asyncResult.type;
                    }

                    return exitTypeResult.type;
                }
            }

            addDiagnostic(
                DiagnosticRule.reportGeneralTypeIssues,
                LocMessage.typeNotUsableWith().format({ type: printType(subtype), method: exitMethodName }) +
                    exitDiag.getString(),
                node.d.expr
            );
            return UnknownType.create();
        });

        if (node.d.target) {
            assignTypeToExpression(node.d.target, { type: scopedType, isIncomplete }, node.d.target);
        }

        writeTypeCache(node, { type: scopedType, isIncomplete }, EvalFlags.None);
    }

    function evaluateTypesForImportAs(node: ImportAsNode): void {
        if (isTypeCached(node)) {
            return;
        }

        let symbolNameNode: NameNode;
        if (node.d.alias) {
            // The symbol name is defined by the alias.
            symbolNameNode = node.d.alias;
        } else {
            // There was no alias, so we need to use the first element of
            // the name parts as the symbol.
            symbolNameNode = node.d.module.d.nameParts[0];
        }

        if (!symbolNameNode) {
            // This can happen in certain cases where there are parse errors.
            return;
        }

        // Look up the symbol to find the alias declaration.
        let symbolType = getAliasedSymbolTypeForName(node, symbolNameNode.d.value) ?? UnknownType.create();

        // Is there a cached module type associated with this node? If so, use
        // it instead of the type we just created.
        const cachedModuleType = readTypeCache(node, EvalFlags.None) as ModuleType;
        if (cachedModuleType && isModule(cachedModuleType) && symbolType) {
            if (isTypeSame(symbolType, cachedModuleType)) {
                symbolType = cachedModuleType;
            }
        }

        assignTypeToNameNode(symbolNameNode, { type: symbolType }, /* ignoreEmptyContainers */ false);

        writeTypeCache(node, { type: symbolType }, EvalFlags.None);
    }

    function evaluateTypesForImportFromAs(node: ImportFromAsNode): void {
        if (isTypeCached(node)) {
            return;
        }

        const aliasNode = node.d.alias || node.d.name;
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

        // If this is a redundant form of an import, assume it is an intentional
        // export and mark the symbol as accessed.
        if (node.d.alias?.d.value === node.d.name.d.value) {
            const symbolInScope = lookUpSymbolRecursive(node, node.d.name.d.value, /* honorCodeFlow */ true);
            if (symbolInScope) {
                setSymbolAccessed(fileInfo, symbolInScope.symbol, node);
            }
        }

        // If this is an import into a class scope, mark the symbol as accessed.
        const classNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true);
        if (classNode) {
            const symbolInScope = lookUpSymbolRecursive(node, aliasNode.d.value, /* honorCodeFlow */ true);
            if (symbolInScope) {
                setSymbolAccessed(fileInfo, symbolInScope.symbol, node);
            }
        }

        let symbolType = getAliasedSymbolTypeForName(node, aliasNode.d.value);
        if (!symbolType) {
            const parentNode = node.parent as ImportFromNode;
            assert(parentNode && parentNode.nodeType === ParseNodeType.ImportFrom);
            assert(!parentNode.d.isWildcardImport);

            const importInfo = AnalyzerNodeInfo.getImportInfo(parentNode.d.module);
            if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) {
                const resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];

                const importLookupInfo = importLookup(resolvedPath);
                let reportError = false;

                // If we were able to resolve the import, report the error as
                // an unresolved symbol.
                if (importLookupInfo) {
                    reportError = true;

                    // Handle PEP 562 support for module-level __getattr__ function,
                    // introduced in Python 3.7.
                    if (
                        PythonVersion.isGreaterOrEqualTo(
                            fileInfo.executionEnvironment.pythonVersion,
                            pythonVersion3_7
                        ) ||
                        fileInfo.isStubFile
                    ) {
                        const getAttrSymbol = importLookupInfo.symbolTable.get('__getattr__');
                        if (getAttrSymbol) {
                            const getAttrType = getEffectiveTypeOfSymbol(getAttrSymbol);
                            if (isFunction(getAttrType)) {
                                symbolType = getEffectiveReturnType(getAttrType);
                                reportError = false;
                            }
                        }
                    }
                } else if (resolvedPath.isEmpty()) {
                    // This corresponds to the "from . import a" form.
                    reportError = true;
                }

                if (reportError) {
                    addDiagnostic(
                        DiagnosticRule.reportAttributeAccessIssue,
                        LocMessage.importSymbolUnknown().format({ name: node.d.name.d.value }),
                        node.d.name
                    );
                }
            }

            if (!symbolType) {
                symbolType = UnknownType.create();
            }
        }

        assignTypeToNameNode(aliasNode, { type: symbolType }, /* ignoreEmptyContainers */ false);
        writeTypeCache(node, { type: symbolType }, EvalFlags.None);
    }

    function evaluateTypesForMatchStatement(node: MatchNode): void {
        if (isTypeCached(node)) {
            return;
        }

        const subjectTypeResult = getTypeOfExpression(node.d.expr);
        let subjectType = subjectTypeResult.type;

        // Apply negative narrowing for each of the cases that doesn't have a guard statement.
        for (const caseStatement of node.d.cases) {
            if (!caseStatement.d.guardExpr) {
                subjectType = narrowTypeBasedOnPattern(
                    evaluatorInterface,
                    subjectType,
                    caseStatement.d.pattern,
                    /* isPositiveTest */ false
                );
            }
        }

        writeTypeCache(node, { type: subjectType, isIncomplete: !!subjectTypeResult.isIncomplete }, EvalFlags.None);
    }

    function evaluateTypesForCaseStatement(node: CaseNode): void {
        if (isTypeCached(node)) {
            return;
        }

        if (!node.parent || node.parent.nodeType !== ParseNodeType.Match) {
            fail('Expected parent of case statement to be match statement');
            return;
        }

        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const subjectTypeResult = getTypeOfExpression(node.parent.d.expr);
        let subjectType = subjectTypeResult.type;

        // Apply negative narrowing for each of the cases prior to the current one
        // except for those that have a guard expression.
        for (const caseStatement of node.parent.d.cases) {
            if (caseStatement === node) {
                if (fileInfo.diagnosticRuleSet.reportUnnecessaryComparison !== 'none') {
                    if (!subjectTypeResult.isIncomplete) {
                        checkForUnusedPattern(evaluatorInterface, node.d.pattern, subjectType);
                    }
                }
                break;
            }

            if (!caseStatement.d.guardExpr) {
                subjectType = narrowTypeBasedOnPattern(
                    evaluatorInterface,
                    subjectType,
                    caseStatement.d.pattern,
                    /* isPositiveTest */ false
                );
            }
        }

        const narrowedSubjectType = assignTypeToPatternTargets(
            evaluatorInterface,
            subjectType,
            !!subjectTypeResult.isIncomplete,
            node.d.pattern
        );

        writeTypeCache(
            node,
            { type: narrowedSubjectType, isIncomplete: !!subjectTypeResult.isIncomplete },
            EvalFlags.None
        );
    }

    function evaluateTypesForImportFrom(node: ImportFromNode): void {
        if (isTypeCached(node)) {
            return;
        }

        if (node.d.isWildcardImport) {
            // Write back a dummy type so we don't evaluate this node again.
            writeTypeCache(node, { type: AnyType.create() }, EvalFlags.None);

            const flowNode = AnalyzerNodeInfo.getFlowNode(node);
            if (flowNode && (flowNode.flags & FlowFlags.WildcardImport) !== 0) {
                const wildcardFlowNode = flowNode as FlowWildcardImport;
                wildcardFlowNode.names.forEach((name) => {
                    const importedSymbolType = getAliasedSymbolTypeForName(node, name);

                    if (!importedSymbolType) {
                        return;
                    }

                    const symbolWithScope = lookUpSymbolRecursive(node, name, /* honorCodeFlow */ false);
                    if (!symbolWithScope) {
                        return;
                    }

                    const declaredType = getDeclaredTypeOfSymbol(symbolWithScope.symbol)?.type;
                    if (!declaredType) {
                        return;
                    }

                    const diagAddendum = new DiagnosticAddendum();

                    if (!assignType(declaredType, importedSymbolType, diagAddendum)) {
                        addDiagnostic(
                            DiagnosticRule.reportAssignmentType,
                            LocMessage.typeAssignmentMismatchWildcard().format({
                                ...printSrcDestTypes(importedSymbolType, declaredType),
                                name,
                            }) + diagAddendum.getString(),
                            node,
                            node.d.wildcardToken ?? node
                        );
                    }
                });
            }
        } else {
            // Use the first element of the name parts as the symbol.
            const symbolNameNode = node.d.module.d.nameParts[0];

            // Look up the symbol to find the alias declaration.
            let symbolType = getAliasedSymbolTypeForName(node, symbolNameNode.d.value);
            if (!symbolType) {
                return;
            }

            // Is there a cached module type associated with this node? If so, use
            // it instead of the type we just created.
            const cachedModuleType = readTypeCache(node, EvalFlags.None) as ModuleType;
            if (cachedModuleType && isModule(cachedModuleType) && symbolType) {
                if (isTypeSame(symbolType, cachedModuleType)) {
                    symbolType = cachedModuleType;
                }
            }

            assignTypeToNameNode(symbolNameNode, { type: symbolType }, /* ignoreEmptyContainers */ false);

            writeTypeCache(node, { type: symbolType }, EvalFlags.None);
        }
    }

    function evaluateTypesForTypeAnnotationNode(node: TypeAnnotationNode) {
        // If this node is part of an assignment statement, use specialized
        // logic that performs bidirectional inference and assignment
        // type narrowing.
        if (node.parent?.nodeType === ParseNodeType.Assignment) {
            evaluateTypesForAssignmentStatement(node.parent);
        } else {
            const annotationType = getTypeOfAnnotation(node.d.annotation, {
                varTypeAnnotation: true,
                allowFinal: isFinalAllowedForAssignmentTarget(node.d.valueExpr),
                allowClassVar: isClassVarAllowedForAssignmentTarget(node.d.valueExpr),
            });

            writeTypeCache(node.d.valueExpr, { type: annotationType }, EvalFlags.None);
        }
    }

    function getAliasedSymbolTypeForName(
        node: ImportAsNode | ImportFromAsNode | ImportFromNode,
        name: string
    ): Type | undefined {
        const symbolWithScope = lookUpSymbolRecursive(node, name, /* honorCodeFlow */ true);
        if (!symbolWithScope) {
            return undefined;
        }

        // Normally there will be at most one decl associated with the import node, but
        // there can be multiple in the case of the "from .X import X" statement. In such
        // case, we want to choose the last declaration.
        const filteredDecls = symbolWithScope.symbol
            .getDeclarations()
            .filter(
                (decl) => ParseTreeUtils.isNodeContainedWithin(node, decl.node) && decl.type === DeclarationType.Alias
            );
        let aliasDecl = filteredDecls.length > 0 ? filteredDecls[filteredDecls.length - 1] : undefined;

        // If we didn't find an exact match, look for any alias associated with
        // this symbol. In cases where we have multiple ImportAs nodes that share
        // the same first-part name (e.g. "import asyncio" and "import asyncio.tasks"),
        // we may not find the declaration associated with this node.
        if (!aliasDecl) {
            aliasDecl = symbolWithScope.symbol.getDeclarations().find((decl) => decl.type === DeclarationType.Alias);
        }

        if (!aliasDecl) {
            return undefined;
        }

        assert(aliasDecl.type === DeclarationType.Alias);

        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

        // Try to resolve the alias while honoring external visibility.
        const resolvedAliasInfo = resolveAliasDeclarationWithInfo(aliasDecl, /* resolveLocalNames */ true, {
            allowExternallyHiddenAccess: fileInfo.isStubFile,
        });

        if (!resolvedAliasInfo) {
            return undefined;
        }

        if (!resolvedAliasInfo.declaration) {
            return evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create();
        }

        if (node.nodeType === ParseNodeType.ImportFromAs) {
            if (resolvedAliasInfo.isPrivate) {
                addDiagnostic(
                    DiagnosticRule.reportPrivateUsage,
                    LocMessage.privateUsedOutsideOfModule().format({
                        name: node.d.name.d.value,
                    }),
                    node.d.name
                );
            }

            if (resolvedAliasInfo.privatePyTypedImporter) {
                const diag = new DiagnosticAddendum();
                if (resolvedAliasInfo.privatePyTypedImported) {
                    diag.addMessage(
                        LocAddendum.privateImportFromPyTypedSource().format({
                            module: resolvedAliasInfo.privatePyTypedImported,
                        })
                    );
                }
                addDiagnostic(
                    DiagnosticRule.reportPrivateImportUsage,
                    LocMessage.privateImportFromPyTypedModule().format({
                        name: node.d.name.d.value,
                        module: resolvedAliasInfo.privatePyTypedImporter,
                    }) + diag.getString(),
                    node.d.name
                );
            }
        }

        return getInferredTypeOfDeclaration(symbolWithScope.symbol, aliasDecl);
    }

    // In some cases, an expression must be evaluated in the context of another
    // expression or statement that contains it. This contextual evaluation
    // allows for bidirectional type evaluation.
    function evaluateTypesForExpressionInContext(node: ExpressionNode): void {
        // Check for a couple of special cases where the node is a NameNode but
        // is technically not part of an expression. We'll handle these here so
        // callers don't need to include special-case logic.
        if (node.nodeType === ParseNodeType.Name && node.parent) {
            if (node.parent.nodeType === ParseNodeType.Function && node.parent.d.name === node) {
                getTypeOfFunction(node.parent);
                return;
            }

            if (node.parent.nodeType === ParseNodeType.Class && node.parent.d.name === node) {
                getTypeOfClass(node.parent);
                return;
            }

            if (node.parent.nodeType === ParseNodeType.ImportFromAs) {
                evaluateTypesForImportFromAs(node.parent);
                return;
            }

            if (node.parent.nodeType === ParseNodeType.ImportAs) {
                evaluateTypesForImportAs(node.parent);
                return;
            }

            if (node.parent.nodeType === ParseNodeType.TypeAlias && node.parent.d.name === node) {
                getTypeOfTypeAlias(node.parent);
                return;
            }

            if (node.parent.nodeType === ParseNodeType.Global || node.parent.nodeType === ParseNodeType.Nonlocal) {
                // For global and nonlocal statements, allow forward references so
                // we don't use code flow during symbol lookups.
                getTypeOfExpression(node, EvalFlags.ForwardRefs);
                return;
            }

            if (node.parent.nodeType === ParseNodeType.ModuleName) {
                // A name within a module name isn't an expression,
                // so there's nothing we can evaluate here.
                return;
            }
        }

        // If the expression is part of a type annotation, we need to evaluate
        // it with special evaluation flags.
        const annotationNode = ParseTreeUtils.getParentAnnotationNode(node);
        if (annotationNode) {
            // Annotations need to be evaluated with specialized evaluation flags.
            const annotationParent = annotationNode.parent;
            assert(annotationParent !== undefined);

            if (annotationParent.nodeType === ParseNodeType.Assignment) {
                if (annotationNode === annotationParent.d.annotationComment) {
                    getTypeOfAnnotation(annotationNode, {
                        varTypeAnnotation: true,
                        allowFinal: isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr),
                        allowClassVar: isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr),
                    });
                } else {
                    evaluateTypesForAssignmentStatement(annotationParent);
                }
                return;
            }

            if (annotationParent.nodeType === ParseNodeType.TypeAnnotation) {
                evaluateTypesForTypeAnnotationNode(annotationParent);
                return;
            }

            if (
                annotationParent.nodeType === ParseNodeType.Function &&
                annotationNode === annotationParent.d.returnAnnotation
            ) {
                getTypeOfAnnotation(annotationNode, {
                    typeVarGetsCurScope: true,
                });
                return;
            }

            getTypeOfAnnotation(annotationNode, {
                varTypeAnnotation: annotationNode.parent?.nodeType === ParseNodeType.TypeAnnotation,
                allowUnpackedTuple:
                    annotationParent.nodeType === ParseNodeType.Parameter &&
                    annotationParent.d.category === ParamCategory.ArgsList,
                allowUnpackedTypedDict:
                    annotationParent.nodeType === ParseNodeType.Parameter &&
                    annotationParent.d.category === ParamCategory.KwargsDict,
            });
            return;
        }

        // See if the expression is part of a pattern used in a case statement.
        const possibleCaseNode = ParseTreeUtils.getParentNodeOfType<CaseNode>(node, ParseNodeType.Case);
        if (possibleCaseNode) {
            if (ParseTreeUtils.isNodeContainedWithin(node, possibleCaseNode.d.pattern)) {
                evaluateTypesForCaseStatement(possibleCaseNode);
                return;
            }
        }

        // Scan up the parse tree until we find a node that doesn't
        // require any context to be evaluated.
        let nodeToEvaluate: ExpressionNode = node;
        let flags = EvalFlags.None;

        while (true) {
            // If we're within an argument node in a call or index expression, skip
            // all of the nodes between because the entire argument expression
            // needs to be evaluated contextually.
            const argumentNode = ParseTreeUtils.getParentNodeOfType(nodeToEvaluate, ParseNodeType.Argument);
            if (argumentNode && argumentNode !== nodeToEvaluate) {
                assert(argumentNode.parent !== undefined);

                if (
                    argumentNode.parent.nodeType === ParseNodeType.Call ||
                    argumentNode.parent.nodeType === ParseNodeType.Index
                ) {
                    nodeToEvaluate = argumentNode.parent;
                    continue;
                }

                if (argumentNode.parent.nodeType === ParseNodeType.Class) {
                    // If this is an argument node within a class declaration,
                    // evaluate the full class declaration node.
                    getTypeOfClass(argumentNode.parent);
                    return;
                }
            }

            let parent = nodeToEvaluate.parent;
            if (!parent) {
                break;
            }

            // If this is the target of an assignment expression, evaluate the
            // assignment expression node instead.
            if (parent.nodeType === ParseNodeType.AssignmentExpression && nodeToEvaluate === parent.d.name) {
                nodeToEvaluate = parent;
                continue;
            }

            // Forward-declared type annotation expressions need to be be evaluated
            // in context so they have the appropriate flags set. Most of these cases
            // will have been detected above when calling getParentAnnotationNode,
            // but TypeAlias expressions are not handled there.
            const stringEnclosure = ParseTreeUtils.getParentNodeOfType(parent, ParseNodeType.StringList);
            if (stringEnclosure) {
                nodeToEvaluate = stringEnclosure as StringListNode;
                continue;
            }

            // The left expression of a call or member access expression is not generally contextual.
            if (parent.nodeType === ParseNodeType.Call || parent.nodeType === ParseNodeType.MemberAccess) {
                if (nodeToEvaluate === parent.d.leftExpr) {
                    // Handle the special case where the LHS is a call to super().
                    if (
                        nodeToEvaluate.nodeType === ParseNodeType.Call &&
                        nodeToEvaluate.d.leftExpr.nodeType === ParseNodeType.Name &&
                        nodeToEvaluate.d.leftExpr.d.value === 'super'
                    ) {
                        nodeToEvaluate = parent;
                        continue;
                    }

                    // Handle the special case where the LHS is a call to a lambda.
                    if (parent.nodeType === ParseNodeType.Call && nodeToEvaluate.nodeType === ParseNodeType.Lambda) {
                        nodeToEvaluate = parent;
                        continue;
                    }

                    flags = EvalFlags.CallBaseDefaults;
                    break;
                }
            } else if (parent.nodeType === ParseNodeType.Index) {
                // The base expression of an index expression is not contextual.
                if (nodeToEvaluate === parent.d.leftExpr) {
                    flags = EvalFlags.IndexBaseDefaults;
                }
            }

            if (!isExpressionNode(parent)) {
                // If we've hit a non-expression node, we generally want to
                // stop. However, there are a few special "pass through"
                // node types that we can skip over to get to a known
                // expression node.
                if (
                    parent.nodeType === ParseNodeType.DictionaryKeyEntry ||
                    parent.nodeType === ParseNodeType.DictionaryExpandEntry ||
                    parent.nodeType === ParseNodeType.ComprehensionFor ||
                    parent.nodeType === ParseNodeType.ComprehensionIf
                ) {
                    assert(parent.parent !== undefined && isExpressionNode(parent.parent));
                    parent = parent.parent;
                } else if (parent.nodeType === ParseNodeType.Parameter) {
                    assert(parent.parent !== undefined);

                    // Parameters are contextual for lambdas.
                    if (parent.parent.nodeType === ParseNodeType.Lambda) {
                        parent = parent.parent;
                    } else {
                        break;
                    }
                } else if (parent.nodeType === ParseNodeType.TypeParameter) {
                    // If this is a bound or default expression in a type parameter list,
                    // we need to evaluate it in the context of the type parameter.
                    if (node === parent.d.boundExpr || node === parent.d.defaultExpr) {
                        getTypeOfTypeParam(parent);
                        return;
                    }

                    break;
                } else {
                    break;
                }
            }

            nodeToEvaluate = parent;
        }

        const parent = nodeToEvaluate.parent!;
        assert(parent !== undefined);

        switch (parent.nodeType) {
            case ParseNodeType.Del: {
                verifyDeleteExpression(nodeToEvaluate);
                return;
            }

            case ParseNodeType.TypeParameter: {
                // If this is the name node within a type parameter list, see if it's a type alias
                // definition. If so, we need to evaluate the type alias contextually.
                if (
                    nodeToEvaluate === parent.d.name &&
                    parent.parent?.nodeType === ParseNodeType.TypeParameterList &&
                    parent.parent.parent?.nodeType === ParseNodeType.TypeAlias
                ) {
                    getTypeOfTypeAlias(parent.parent.parent);
                    return;
                }
                break;
            }

            case ParseNodeType.TypeAlias: {
                getTypeOfTypeAlias(parent);
                return;
            }

            case ParseNodeType.Decorator: {
                if (parent.parent?.nodeType === ParseNodeType.Class) {
                    getTypeOfClass(parent.parent);
                } else if (parent.parent?.nodeType === ParseNodeType.Function) {
                    getTypeOfFunction(parent.parent);
                }
                return;
            }

            case ParseNodeType.Parameter: {
                if (nodeToEvaluate !== parent.d.defaultValue) {
                    evaluateTypeOfParam(parent);
                    return;
                }
                break;
            }

            case ParseNodeType.Argument: {
                if (nodeToEvaluate === parent.d.name) {
                    // A name used to specify a named parameter in an argument isn't an
                    // expression, so there's nothing we can evaluate here.
                    return;
                }

                if (parent.parent?.nodeType === ParseNodeType.Class) {
                    // A class argument must be evaluated in the context of the class declaration.
                    getTypeOfClass(parent.parent);
                    return;
                }
                break;
            }

            case ParseNodeType.Return: {
                // Return expressions must be evaluated in the context of the expected return type.
                if (parent.d.expr) {
                    const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(node);
                    let declaredReturnType = enclosingFunctionNode
                        ? getDeclaredReturnType(enclosingFunctionNode)
                        : undefined;
                    if (declaredReturnType) {
                        const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(node);
                        declaredReturnType = makeTypeVarsBound(declaredReturnType, liveScopeIds);
                    }
                    getTypeOfExpression(parent.d.expr, EvalFlags.None, makeInferenceContext(declaredReturnType));
                    return;
                }
                break;
            }

            case ParseNodeType.TypeAnnotation: {
                evaluateTypesForTypeAnnotationNode(parent);
                return;
            }

            case ParseNodeType.Assignment: {
                evaluateTypesForAssignmentStatement(parent);
                return;
            }

            case ParseNodeType.AugmentedAssignment: {
                evaluateTypesForAugmentedAssignment(parent);
                return;
            }
        }

        if (nodeToEvaluate.nodeType === ParseNodeType.TypeAnnotation) {
            evaluateTypesForTypeAnnotationNode(nodeToEvaluate);
            return;
        }

        getTypeOfExpression(nodeToEvaluate, flags);
    }

    function evaluateTypeOfParam(node: ParameterNode): void {
        // If this parameter has no name, we have nothing to do.
        if (!node.d.name) {
            return;
        }

        // We need to handle lambdas differently from functions because
        // the former never have parameter type annotations but can
        // be inferred, whereas the latter sometimes have type annotations
        // but cannot be inferred.
        const parent = node.parent!;
        if (parent.nodeType === ParseNodeType.Lambda) {
            evaluateTypesForExpressionInContext(parent);
            return;
        }

        assert(parent.nodeType === ParseNodeType.Function);
        const functionNode = parent as FunctionNode;

        const paramIndex = functionNode.d.params.findIndex((param) => param === node);
        const typeAnnotation = ParseTreeUtils.getTypeAnnotationForParam(functionNode, paramIndex);

        if (typeAnnotation) {
            const param = functionNode.d.params[paramIndex];
            let annotatedType = getTypeOfParamAnnotation(typeAnnotation, functionNode.d.params[paramIndex].d.category);

            const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(param);
            annotatedType = makeTypeVarsBound(annotatedType, liveTypeVarScopes);

            const adjType = transformVariadicParamType(
                node,
                node.d.category,
                adjustParamAnnotatedType(param, annotatedType)
            );

            writeTypeCache(node.d.name, { type: adjType }, EvalFlags.None);
            return;
        }

        const containingClassNode = ParseTreeUtils.getEnclosingClass(functionNode, /* stopAtFunction */ true);
        const classInfo = containingClassNode ? getTypeOfClass(containingClassNode) : undefined;

        if (
            classInfo &&
            ClassType.isPseudoGenericClass(classInfo?.classType) &&
            functionNode.d.name.d.value === '__init__'
        ) {
            const typeParamName = getPseudoGenericTypeVarName(node.d.name.d.value);
            const paramType = classInfo.classType.shared.typeParams.find(
                (param) => param.shared.name === typeParamName
            );

            if (paramType) {
                writeTypeCache(node.d.name, { type: TypeVarType.cloneAsBound(paramType) }, EvalFlags.None);
                return;
            }
        }

        // See if the function is a method in a child class. We may be able to
        // infer the type of the parameter from a method of the same name in
        // a parent class if it has an annotated type.
        const functionFlags = getFunctionInfoFromDecorators(
            evaluatorInterface,
            functionNode,
            /* isInClass */ true
        ).flags;

        let inferredParamType =
            inferParamType(functionNode, functionFlags, paramIndex, classInfo?.classType) ?? UnknownType.create();
        const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(node);
        inferredParamType = makeTypeVarsBound(inferredParamType, liveTypeVarScopes);

        writeTypeCache(
            node.d.name,
            { type: transformVariadicParamType(node, node.d.category, inferredParamType) },
            EvalFlags.None
        );
    }

    // Evaluates the types that are assigned within the statement that contains
    // the specified parse node. In some cases, a broader statement may need to
    // be evaluated to provide sufficient context for the type. Evaluated types
    // are written back to the type cache for later retrieval.
    function evaluateTypesForStatement(node: ParseNode): void {
        initializePrefetchedTypes(node);

        let curNode: ParseNode | undefined = node;

        while (curNode) {
            switch (curNode.nodeType) {
                case ParseNodeType.Assignment: {
                    // See if the assignment is part of a chain of assignments. If so,
                    // evaluate the entire chain.
                    const isInAssignmentChain =
                        curNode.parent &&
                        (curNode.parent.nodeType === ParseNodeType.Assignment ||
                            curNode.parent.nodeType === ParseNodeType.AssignmentExpression ||
                            curNode.parent.nodeType === ParseNodeType.AugmentedAssignment) &&
                        curNode.parent.d.rightExpr === curNode;
                    if (!isInAssignmentChain) {
                        evaluateTypesForAssignmentStatement(curNode);
                        return;
                    }
                    break;
                }

                case ParseNodeType.TypeAlias: {
                    getTypeOfTypeAlias(curNode);
                    return;
                }

                case ParseNodeType.AssignmentExpression: {
                    evaluateTypesForExpressionInContext(curNode);
                    return;
                }

                case ParseNodeType.AugmentedAssignment: {
                    evaluateTypesForAugmentedAssignment(curNode);
                    return;
                }

                case ParseNodeType.Class: {
                    getTypeOfClass(curNode);
                    return;
                }

                case ParseNodeType.Parameter: {
                    evaluateTypeOfParam(curNode);
                    return;
                }

                case ParseNodeType.Lambda: {
                    evaluateTypesForExpressionInContext(curNode);
                    return;
                }

                case ParseNodeType.Function: {
                    getTypeOfFunction(curNode);
                    return;
                }

                case ParseNodeType.For: {
                    evaluateTypesForForStatement(curNode);
                    return;
                }

                case ParseNodeType.Except: {
                    evaluateTypesForExceptStatement(curNode);
                    return;
                }

                case ParseNodeType.WithItem: {
                    evaluateTypesForWithStatement(curNode);
                    return;
                }

                case ParseNodeType.ComprehensionFor: {
                    const comprehension = curNode.parent as ComprehensionNode;
                    assert(comprehension.nodeType === ParseNodeType.Comprehension);
                    if (curNode === comprehension.d.expr) {
                        evaluateTypesForExpressionInContext(comprehension);
                    } else {
                        // Evaluate the individual iterations starting with the first
                        // up to the curNode.
                        for (const forIfNode of comprehension.d.forIfNodes) {
                            evaluateComprehensionForIf(forIfNode);
                            if (forIfNode === curNode) {
                                break;
                            }
                        }
                    }
                    return;
                }

                case ParseNodeType.ImportAs: {
                    evaluateTypesForImportAs(curNode);
                    return;
                }

                case ParseNodeType.ImportFromAs: {
                    evaluateTypesForImportFromAs(curNode);
                    return;
                }

                case ParseNodeType.ImportFrom: {
                    evaluateTypesForImportFrom(curNode);
                    return;
                }

                case ParseNodeType.Case: {
                    evaluateTypesForCaseStatement(curNode);
                    return;
                }
            }

            curNode = curNode.parent;
        }

        fail('Unexpected statement');
        return undefined;
    }

    // Helper function for cases where we need to evaluate the types
    // for a subtree so we can determine the type of one of the subnodes
    // within that tree. If the type cannot be determined (because it's part
    // of a cyclical dependency), the function returns undefined.
    function evaluateTypeForSubnode(subnode: ParseNode, callback: () => void): TypeResult | undefined {
        // If the type cache is already populated with a complete type,
        // don't bother doing additional work.
        let cacheEntry = readTypeCacheEntry(subnode);
        if (cacheEntry && !cacheEntry.typeResult.isIncomplete) {
            const typeResult = cacheEntry.typeResult;

            // Handle the special case where a function or class is partially evaluated.
            // Indicate that these are not complete types.
            if (isFunction(typeResult.type) && FunctionType.isPartiallyEvaluated(typeResult.type)) {
                return { ...typeResult, isIncomplete: true };
            }

            if (isClass(typeResult.type) && ClassType.isPartiallyEvaluated(typeResult.type)) {
                return { ...typeResult, isIncomplete: true };
            }

            return typeResult;
        }

        callback();
        cacheEntry = readTypeCacheEntry(subnode);
        if (cacheEntry) {
            return cacheEntry.typeResult;
        }

        return undefined;
    }

    function getCodeFlowAnalyzerForNode(
        node: ExecutionScopeNode,
        typeAtStart: TypeResult | undefined
    ): CodeFlowAnalyzer {
        let entries = codeFlowAnalyzerCache.get(node.id);

        if (entries) {
            const cachedEntry = entries.find((entry) => {
                if (!typeAtStart || !entry.typeAtStart) {
                    return !typeAtStart && !entry.typeAtStart;
                }

                if (!typeAtStart.isIncomplete !== !entry.typeAtStart.isIncomplete) {
                    return false;
                }

                return isTypeSame(typeAtStart.type, entry.typeAtStart.type);
            });

            if (cachedEntry) {
                return cachedEntry.codeFlowAnalyzer;
            }
        }

        // Allocate a new code flow analyzer.
        const analyzer = codeFlowEngine.createCodeFlowAnalyzer();
        if (entries) {
            entries.push({ typeAtStart, codeFlowAnalyzer: analyzer });
        } else {
            entries = [{ typeAtStart, codeFlowAnalyzer: analyzer }];
            codeFlowAnalyzerCache.set(node.id, entries);
        }

        return analyzer;
    }

    // Attempts to determine the type of the reference expression at the
    // point in the code. If the code flow analysis has nothing to say
    // about that expression, it returns un undefined type. Normally
    // flow analysis starts from the reference node, but startNode can be
    // specified to override this in a few special cases (functions and
    // lambdas) to support analysis of captured variables.
    function getFlowTypeOfReference(
        reference: CodeFlowReferenceExpressionNode,
        startNode?: ClassNode | FunctionNode | LambdaNode,
        options?: FlowNodeTypeOptions
    ): FlowNodeTypeResult {
        // See if this execution scope requires code flow for this reference expression.
        const referenceKey = createKeyForReference(reference);
        const executionNode = ParseTreeUtils.getExecutionScopeNode(startNode?.parent ?? reference);
        const codeFlowExpressions = AnalyzerNodeInfo.getCodeFlowExpressions(executionNode);

        if (
            !codeFlowExpressions ||
            (!codeFlowExpressions.has(referenceKey) && !codeFlowExpressions.has(wildcardImportReferenceKey))
        ) {
            return FlowNodeTypeResult.create(/* type */ undefined, /* isIncomplete */ false);
        }

        if (checkCodeFlowTooComplex(reference)) {
            return FlowNodeTypeResult.create(
                /* type */ options?.typeAtStart && isUnbound(options.typeAtStart.type)
                    ? UnknownType.create()
                    : undefined,
                /* isIncomplete */ true
            );
        }

        // Is there an code flow analyzer cached for this execution scope?
        let analyzer: CodeFlowAnalyzer | undefined;

        if (isNodeInReturnTypeInferenceContext(executionNode)) {
            // If we're performing the analysis within a temporary
            // context of a function for purposes of inferring its
            // return type for a specified set of arguments, use
            // a temporary analyzer that we'll use only for this context.
            analyzer = getCodeFlowAnalyzerForReturnTypeInferenceContext();
        } else {
            analyzer = getCodeFlowAnalyzerForNode(executionNode, options?.typeAtStart);
        }

        const flowNode = AnalyzerNodeInfo.getFlowNode(startNode ?? reference);
        if (flowNode === undefined) {
            return FlowNodeTypeResult.create(/* type */ undefined, /* isIncomplete */ false);
        }

        return analyzer.getTypeFromCodeFlow(flowNode!, reference, options);
    }

    // Specializes the specified (potentially generic) class type using
    // the specified type arguments, reporting errors as appropriate.
    // Returns the specialized type and a boolean indicating whether
    // the type indicates a class type (true) or an object type (false).
    function createSpecializedClassType(
        classType: ClassType,
        typeArgs: TypeResultWithNode[] | undefined,
        flags: EvalFlags,
        errorNode: ExpressionNode
    ): TypeResult {
        let isValidTypeForm = true;

        // Handle the special-case classes that are not defined
        // in the type stubs.
        if (ClassType.isSpecialBuiltIn(classType)) {
            const aliasedName = classType.priv.aliasName || classType.shared.name;
            switch (aliasedName) {
                case 'Callable': {
                    return { type: createCallableType(classType, typeArgs, errorNode) };
                }

                case 'Never':
                case 'NoReturn': {
                    if (typeArgs && typeArgs.length > 0) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.typeArgsExpectingNone().format({ name: aliasedName }),
                            typeArgs[0].node
                        );
                    }

                    let resultType = aliasedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn();
                    resultType = TypeBase.cloneAsSpecialForm(resultType, classType);
                    resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));

                    return { type: resultType };
                }

                case 'Optional': {
                    return { type: createOptionalType(classType, errorNode, typeArgs, flags) };
                }

                case 'Type': {
                    let typeType = createSpecialType(
                        classType,
                        typeArgs,
                        1,
                        /* allowParamSpec */ undefined,
                        /* isSpecialForm */ false
                    );

                    if (isInstantiableClass(typeType)) {
                        typeType = explodeGenericClass(typeType);
                    }

                    typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType));

                    return { type: typeType };
                }

                case 'ClassVar': {
                    return { type: createClassVarType(classType, errorNode, typeArgs, flags) };
                }

                case 'Protocol': {
                    if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) {
                        addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.protocolNotAllowed(), errorNode);
                    }

                    typeArgs?.forEach((typeArg) => {
                        if (typeArg.typeList || !isTypeVar(typeArg.type)) {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.protocolTypeArgMustBeTypeParam(),
                                typeArg.node
                            );
                        }
                    });

                    return {
                        type: createSpecialType(
                            classType,
                            typeArgs,
                            /* paramLimit */ undefined,
                            /* allowParamSpec */ true
                        ),
                    };
                }

                case 'TypedDict': {
                    if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) {
                        const isInlinedTypedDict =
                            AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.enableExperimentalFeatures &&
                            !!typeArgs;

                        if (!isInlinedTypedDict) {
                            addDiagnostic(
                                DiagnosticRule.reportInvalidTypeForm,
                                LocMessage.typedDictNotAllowed(),
                                errorNode
                            );
                        }
                    }
                    isValidTypeForm = false;
                    break;
                }

                case 'Literal': {
                    if ((flags & (EvalFlags.NoNonTypeSpecialForms | EvalFlags.TypeExpression)) !== 0) {
                        addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.literalNotAllowed(), errorNode);
                    }
                    isValidTypeForm = false;
                    break;
                }

                case 'Tuple': {
                    return {
                        type: createSpecialType(
                            classType,
                            typeArgs,
                            /* paramLimit */ undefined,
                            /* allowParamSpec */ false,
                            /* isSpecialForm */ false
                        ),
                    };
                }

                case 'Union': {
                    return { type: createUnionType(classType, errorNode, typeArgs, flags) };
                }

                case 'Generic': {
                    return { type: createGenericType(classType, errorNode, typeArgs, flags) };
                }

                case 'Final': {
                    return { type: createFinalType(classType, errorNode, typeArgs, flags) };
                }

                case 'Annotated': {
                    return createAnnotatedType(classType, errorNode, typeArgs, flags);
                }

                case 'Concatenate': {
                    return { type: createConcatenateType(classType, errorNode, typeArgs, flags) };
                }

                case 'TypeGuard':
                case 'TypeIs': {
                    return { type: createTypeGuardType(classType, errorNode, typeArgs, flags) };
                }

                case 'Unpack': {
                    return { type: createUnpackType(classType, errorNode, typeArgs, flags) };
                }

                case 'Required':
                case 'NotRequired': {
                    return createRequiredOrReadOnlyType(classType, errorNode, typeArgs, flags);
                }

                case 'ReadOnly': {
                    return createRequiredOrReadOnlyType(classType, errorNode, typeArgs, flags);
                }

                case 'Self': {
                    return { type: createSelfType(classType, errorNode, typeArgs, flags) };
                }

                case 'LiteralString': {
                    return { type: createSpecialType(classType, typeArgs, 0) };
                }

                case 'TypeForm': {
                    return { type: createTypeFormType(classType, errorNode, typeArgs) };
                }
            }
        }

        const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
        if (
            fileInfo.isStubFile ||
            PythonVersion.isGreaterOrEqualTo(fileInfo.executionEnvironment.pythonVersion, pythonVersion3_9) ||
            isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(errorNode)) ||
            (flags & EvalFlags.ForwardRefs) !== 0
        ) {
            // Handle "type" specially, since it needs to act like "Type"
            // in Python 3.9 and newer.
            if (ClassType.isBuiltIn(classType, 'type') && typeArgs) {
                if (typeArgs.length >= 1) {
                    // Treat type[function] as illegal.
                    if (isFunctionOrOverloaded(typeArgs[0].type)) {
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeForm,
                            LocMessage.typeAnnotationWithCallable(),
                            typeArgs[0].node
                        );

                        return { type: UnknownType.create() };
                    }
                }

                if (prefetched?.typeClass && isInstantiableClass(prefetched.typeClass)) {
                    let typeType = createSpecialType(
                        prefetched.typeClass,
                        typeArgs,
                        1,
                        /* allowParamSpec */ undefined,
                        /* isSpecialForm */ false
                    );

                    if (isInstantiableClass(typeType)) {
                        typeType = explodeGenericClass(typeType);
                    }

                    typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType));

                    return { type: typeType };
                }
            }

            // Handle "tuple" specially, since it needs to act like "Tuple"
            // in Python 3.9 and newer.
            if (isTupleClass(classType)) {
                let specializedClass = createSpecialType(
                    classType,
                    typeArgs,
                    /* paramLimit */ undefined,
                    /* allowParamSpec */ undefined,
                    /* isSpecialForm */ false
                );

                specializedClass = TypeBase.cloneWithTypeForm(specializedClass, convertToInstance(specializedClass));

                return { type: specializedClass };
            }
        }

        let typeArgCount = typeArgs ? typeArgs.length : 0;

        // Make sure the argument list count is correct.
        const typeParams = ClassType.isPseudoGenericClass(classType) ? [] : ClassType.getTypeParams(classType);

        // If there are no type parameters or args, the class is already specialized.
        // No need to do any more work.
        if (typeParams.length === 0 && typeArgCount === 0) {
            return { type: classType };
        }

        const variadicTypeParamIndex = typeParams.findIndex((param) => isTypeVarTuple(param));

        if (typeArgs) {
            let minTypeArgCount = typeParams.length;
            const firstDefaultParamIndex = typeParams.findIndex((param) => !!param.shared.isDefaultExplicit);

            if (firstDefaultParamIndex >= 0) {
                minTypeArgCount = firstDefaultParamIndex;
            }

            // Classes that accept inlined type dict type args allow only one.
            if (typeArgs.length > 0 && typeArgs[0].inlinedTypeDict) {
                if (typeArgs.length > 1) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeArguments,
                        LocMessage.typeArgsTooMany().format({
                            name: classType.priv.aliasName || classType.shared.name,
                            expected: 1,
                            received: typeArgCount,
                        }),
                        typeArgs[1].node
                    );
                }

                return { type: typeArgs[0].inlinedTypeDict };
            } else if (typeArgCount > typeParams.length) {
                if (!ClassType.isPartiallyEvaluated(classType) && !ClassType.isTupleClass(classType)) {
                    if (typeParams.length === 0) {
                        isValidTypeForm = false;
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeArguments,
                            LocMessage.typeArgsExpectingNone().format({
                                name: classType.priv.aliasName || classType.shared.name,
                            }),
                            typeArgs[typeParams.length].node
                        );
                    } else if (typeParams.length !== 1 || !isParamSpec(typeParams[0])) {
                        isValidTypeForm = false;
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeArguments,
                            LocMessage.typeArgsTooMany().format({
                                name: classType.priv.aliasName || classType.shared.name,
                                expected: typeParams.length,
                                received: typeArgCount,
                            }),
                            typeArgs[typeParams.length].node
                        );
                    }

                    typeArgCount = typeParams.length;
                }
            } else if (typeArgCount < minTypeArgCount) {
                isValidTypeForm = false;
                addDiagnostic(
                    DiagnosticRule.reportInvalidTypeArguments,
                    LocMessage.typeArgsTooFew().format({
                        name: classType.priv.aliasName || classType.shared.name,
                        expected: minTypeArgCount,
                        received: typeArgCount,
                    }),
                    typeArgs.length > 0 ? typeArgs[0].node.parent! : errorNode
                );
            }

            typeArgs.forEach((typeArg, index) => {
                if (!typeArg.type.props?.typeForm) {
                    isValidTypeForm = false;
                }

                if (index === variadicTypeParamIndex) {
                    // The types that make up the tuple that maps to the
                    // TypeVarTuple have already been validated when the tuple
                    // object was created in adjustTypeArgsForTypeVarTuple.
                    if (isClassInstance(typeArg.type) && isTupleClass(typeArg.type)) {
                        return;
                    }

                    if (isTypeVarTuple(typeArg.type)) {
                        if (!validateTypeVarTupleIsUnpacked(typeArg.type, typeArg.node)) {
                            isValidTypeForm = false;
                        }
                        return;
                    }
                }

                const typeParam = index < typeParams.length ? typeParams[index] : undefined;
                const isParamSpecTarget = typeParam && isParamSpec(typeParam);

                if (
                    !validateTypeArg(typeArg, {
                        allowParamSpec: true,
                        allowTypeArgList: isParamSpecTarget,
                    })
                ) {
                    isValidTypeForm = false;
                }
            });
        }

        // Handle ParamSpec arguments and fill in any missing type arguments with Unknown.
        let typeArgTypes: Type[] = [];
        const fullTypeParams = ClassType.getTypeParams(classType);

        typeArgs = transformTypeArgsForParamSpec(fullTypeParams, typeArgs, errorNode);
        if (!typeArgs) {
            isValidTypeForm = false;
        }

        const constraints = new ConstraintTracker();

        fullTypeParams.forEach((typeParam, index) => {
            if (typeArgs && index < typeArgs.length) {
                if (isParamSpec(typeParam)) {
                    const typeArg = typeArgs[index];
                    const functionType = FunctionType.createSynthesizedInstance('', FunctionTypeFlags.ParamSpecValue);

                    if (isEllipsisType(typeArg.type)) {
                        FunctionType.addDefaultParams(functionType);
                        functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
                        typeArgTypes.push(functionType);
                        constraints.setBounds(typeParam, functionType);
                        return;
                    }

                    if (typeArg.typeList) {
                        typeArg.typeList!.forEach((paramType, paramIndex) => {
                            FunctionType.addParam(
                                functionType,
                                FunctionParam.create(
                                    ParamCategory.Simple,
                                    convertToInstance(paramType.type),
                                    FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                                    `__p${paramIndex}`
                                )
                            );
                        });

                        if (typeArg.typeList.length > 0) {
                            FunctionType.addPositionOnlyParamSeparator(functionType);
                        }

                        typeArgTypes.push(functionType);
                        constraints.setBounds(typeParam, functionType);
                        return;
                    }

                    if (isInstantiableClass(typeArg.type) && ClassType.isBuiltIn(typeArg.type, 'Concatenate')) {
                        const concatTypeArgs = typeArg.type.priv.typeArgs;
                        if (concatTypeArgs && concatTypeArgs.length > 0) {
                            concatTypeArgs.forEach((typeArg, index) => {
                                if (index === concatTypeArgs.length - 1) {
                                    if (isParamSpec(typeArg)) {
                                        FunctionType.addParamSpecVariadics(functionType, typeArg);
                                    } else if (isEllipsisType(typeArg)) {
                                        FunctionType.addDefaultParams(functionType);
                                        functionType.shared.flags |= FunctionTypeFlags.GradualCallableForm;
                                    }
                                } else {
                                    FunctionType.addParam(
                                        functionType,
                                        FunctionParam.create(
                                            ParamCategory.Simple,
                                            typeArg,
                                            FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                                            `__p${index}`
                                        )
                                    );
                                }
                            });
                        }

                        typeArgTypes.push(functionType);
                        return;
                    }
                }

                const typeArgType = convertToInstance(typeArgs[index].type);
                typeArgTypes.push(typeArgType);
                constraints.setBounds(typeParam, typeArgType);
                return;
            }

            const solvedDefaultType = solveAndApplyConstraints(typeParam, constraints, {
                replaceUnsolved: {
                    scopeIds: getTypeVarScopeIds(classType),
                    tupleClassType: getTupleClassType(),
                },
            });
            typeArgTypes.push(solvedDefaultType);
            constraints.setBounds(typeParam, solvedDefaultType);
        });

        typeArgTypes = typeArgTypes.map((typeArgType, index) => {
            if (index < typeArgCount) {
                const diag = new DiagnosticAddendum();
                let adjustedTypeArgType = applyTypeArgToTypeVar(typeParams[index], typeArgType, diag);

                // Determine if the variance must match.
                if (adjustedTypeArgType && (flags & EvalFlags.EnforceVarianceConsistency) !== 0) {
                    const destType = typeParams[index];
                    const declaredVariance = destType.shared.declaredVariance;

                    if (!isVarianceOfTypeArgCompatible(adjustedTypeArgType, declaredVariance)) {
                        diag.addMessage(
                            LocAddendum.varianceMismatchForClass().format({
                                typeVarName: printType(adjustedTypeArgType),
                                className: classType.shared.name,
                            })
                        );
                        adjustedTypeArgType = undefined;
                    }
                }

                if (adjustedTypeArgType) {
                    typeArgType = adjustedTypeArgType;
                } else {
                    // Avoid emitting this error for a partially-constructed class.
                    if (!isClassInstance(typeArgType) || !ClassType.isPartiallyEvaluated(typeArgType)) {
                        assert(typeArgs !== undefined);
                        isValidTypeForm = false;
                        addDiagnostic(
                            DiagnosticRule.reportInvalidTypeArguments,
                            LocMessage.typeVarAssignmentMismatch().format({
                                type: printType(typeArgType),
                                name: TypeVarType.getReadableName(typeParams[index]),
                            }) + diag.getString(),
                            typeArgs[index].node
                        );
                    }
                }
            }

            return typeArgType;
        });

        // If the class is partially constructed and doesn't yet have
        // type parameters, assume that the number and types of supplied type
        // arguments are correct.
        if (typeArgs && classType.shared.typeParams.length === 0 && ClassType.isPartiallyEvaluated(classType)) {
            typeArgTypes = typeArgs.map((t) => convertToInstance(t.type));
        }

        let specializedClass = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined);

        specializedClass = TypeBase.cloneWithTypeForm(
            specializedClass,
            isValidTypeForm ? convertToInstance(specializedClass) : undefined
        );

        return { type: specializedClass };
    }

    // PEP 612 says that if the class has only one type parameter consisting
    // of a ParamSpec, the list of arguments does not need to be enclosed in
    // a list. We'll handle that case specially here.
    function transformTypeArgsForParamSpec(
        typeParams: TypeVarType[],
        typeArgs: TypeResultWithNode[] | undefined,
        errorNode: ExpressionNode
    ): TypeResultWithNode[] | undefined {
        if (typeParams.length !== 1 || !isParamSpec(typeParams[0]) || !typeArgs) {
            return typeArgs;
        }

        if (typeArgs.length > 1) {
            for (const typeArg of typeArgs) {
                if (isParamSpec(typeArg.type)) {
                    addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.paramSpecContext(), typeArg.node);
                    return undefined;
                }

                if (isEllipsisType(typeArg.type)) {
                    addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.ellipsisContext(), typeArg.node);
                    return undefined;
                }

                if (isInstantiableClass(typeArg.type) && ClassType.isBuiltIn(typeArg.type, 'Concatenate')) {
                    addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.concatenateContext(), typeArg.node);
                    return undefined;
                }

                if (typeArg.typeList) {
                    addDiagnostic(
                        DiagnosticRule.reportInvalidTypeForm,
                        LocMessage.typeArgListNotAllowed(),
                        typeArg.node
                    );
                    return undefined;
                }
            }
        }

        if (typeArgs.length === 1) {
            // Don't transform a type list.
            if (typeArgs[0].typeList) {
                return typeArgs;
            }

            const typeArgType = typeArgs[0].type;

            // Don't transform a single ParamSpec or ellipsis.
            if (isParamSpec(typeArgType) || isEllipsisType(typeArgType)) {
                return typeArgs;
            }

            // Don't transform a Concatenate.
            if (isInstantiableClass(typeArgType) && ClassType.isBuiltIn(typeArgType, 'Concatenate')) {
                return typeArgs;
            }
        }

        // Package up the type arguments into a type list.
        return [
            {
                type: UnknownType.create(),
                node: typeArgs.length > 0 ? typeArgs[0].node : errorNode,
                typeList: typeArgs,
            },
        ];
    }

    function getTypeOfArg(arg: Arg, inferenceContext: InferenceContext | undefined): TypeResult {
        if (arg.typeResult) {
            const type = arg.typeResult.type;
            return { type: type?.props?.specialForm ?? type, isIncomplete: arg.typeResult.isIncomplete };
        }

        if (!arg.valueExpression) {
            // We shouldn't ever get here, but just in case.
            return { type: UnknownType.create() };
        }

        // If there was no defined type provided, there should always
        // be a value expression from which we can retrieve the type.
        return getTypeOfExpression(arg.valueExpression, /* flags */ undefined, inferenceContext);
    }

    // This function is like getTypeOfArg except that it is
    // used in cases where the argument is expected to be a type
    // and therefore follows the normal rules of types (e.g. they
    // can be forward-declared in stubs, etc.).
    function getTypeOfArgExpectingType(arg: Arg, options?: ExpectedTypeOptions): TypeResult {
        if (arg.typeResult) {
            return { type: arg.typeResult.type, isIncomplete: arg.typeResult.isIncomplete };
        }

        // If there was no defined type provided, there should always
        // be a value expression from which we can retrieve the type.
        assert(arg.valueExpression !== undefined);
        return getTypeOfExpressionExpectingType(arg.valueExpression, options);
    }

    function getTypeOfExpressionExpectingType(node: ExpressionNode, options?: ExpectedTypeOptions): TypeResult {
        let flags = EvalFlags.InstantiableType | EvalFlags.StrLiteralAsType;

        if (options?.allowTypeVarsWithoutScopeId) {
            flags |= EvalFlags.AllowTypeVarWithoutScopeId;
        }

        if (options?.typeVarGetsCurScope) {
            flags |= EvalFlags.TypeVarGetsCurScope;
        }

        if (options?.enforceClassTypeVarScope) {
            flags |= EvalFlags.EnforceClassTypeVarScope;
        }

        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        if ((isAnnotationEvaluationPostponed(fileInfo) || options?.forwardRefs) && !options?.runtimeTypeExpression) {
            flags |= EvalFlags.ForwardRefs;
        } else if (options?.parsesStringLiteral) {
            flags |= EvalFlags.ParsesStringLiteral;
        }

        if (!options?.allowFinal) {
            flags |= EvalFlags.NoFinal;
        }

        if (options?.allowRequired) {
            flags |= EvalFlags.AllowRequired | EvalFlags.TypeExpression;
        }

        if (options?.allowReadOnly) {
            flags |= EvalFlags.AllowReadOnly | EvalFlags.TypeExpression;
        }

        if (options?.allowUnpackedTuple) {
            flags |= EvalFlags.AllowUnpackedTuple;
        } else {
            flags |= EvalFlags.NoTypeVarTuple;
        }

        if (options?.allowUnpackedTypedDict) {
            flags |= EvalFlags.AllowUnpackedTypedDict;
        }

        if (!options?.allowParamSpec) {
            flags |= EvalFlags.NoParamSpec;
        }

        if (options?.typeExpression) {
            flags |= EvalFlags.TypeExpression;
        }

        if (options?.convertEllipsisToAny) {
            flags |= EvalFlags.ConvertEllipsisToAny;
        }

        if (options?.allowEllipsis) {
            flags |= EvalFlags.AllowEllipsis;
        }

        if (options?.noNonTypeSpecialForms) {
            flags |= EvalFlags.NoNonTypeSpecialForms;
        }

        if (!options?.allowClassVar) {
            flags |= EvalFlags.NoClassVar;
        }

        if (options?.varTypeAnnotation) {
            flags |= EvalFlags.VarTypeAnnotation;
        }

        if (options?.notParsed) {
            flags |= EvalFlags.NotParsed;
        }

        if (options?.typeFormArg) {
            flags |= EvalFlags.TypeFormArg;
        }

        return getTypeOfExpression(node, flags);
    }

    function getBuiltInType(node: ParseNode, name: string): Type {
        const scope = ScopeUtils.getScopeForNode(node);
        if (scope) {
            const builtInScope = ScopeUtils.getBuiltInScope(scope);
            const nameType = builtInScope.lookUpSymbol(name);
            if (nameType) {
                return getEffectiveTypeOfSymbol(nameType);
            }
        }

        return UnknownType.create();
    }

    function getBuiltInObject(node: ParseNode, name: string, typeArgs?: Type[]) {
        const nameType = getBuiltInType(node, name);
        if (isInstantiableClass(nameType)) {
            let classType = nameType;
            if (typeArgs) {
                classType = ClassType.specialize(classType, typeArgs);
            }

            return ClassType.cloneAsInstance(classType);
        }

        return nameType;
    }

    function lookUpSymbolRecursive(
        node: ParseNode,
        name: string,
        honorCodeFlow: boolean,
        preferGlobalScope = false
    ): SymbolWithScope | undefined {
        const scopeNodeInfo = ParseTreeUtils.getEvaluationScopeNode(node);
        const scope = AnalyzerNodeInfo.getScope(scopeNodeInfo.node);

        let symbolWithScope = scope?.lookUpSymbolRecursive(name, {
            useProxyScope: !!scopeNodeInfo.useProxyScope,
            useChainedModuleLevelScopes: !!scopeNodeInfo.useChainedModuleLevelScopes,
        });
        const scopeType = scope?.type ?? ScopeType.Module;

        // Functions and list comprehensions don't allow access to implicitly
        // aliased symbols in outer scopes if they haven't yet been assigned
        // within the local scope.
        let scopeTypeHonorsCodeFlow = scopeType !== ScopeType.Function && scopeType !== ScopeType.Comprehension;

        // Type parameter scopes don't honor code flow.
        if (symbolWithScope?.scope.type === ScopeType.TypeParameter) {
            scopeTypeHonorsCodeFlow = false;
        }

        if (symbolWithScope && honorCodeFlow && scopeTypeHonorsCodeFlow) {
            // Filter the declarations based on flow reachability.
            const reachableDecl = symbolWithScope.symbol.getDeclarations().find((decl) => {
                if (decl.type !== DeclarationType.Alias && decl.type !== DeclarationType.Intrinsic) {
                    // Determine if the declaration is in the same execution scope as the "usageNode" node.
                    let usageScopeNode = ParseTreeUtils.getExecutionScopeNode(node);
                    const declNode: ParseNode =
                        decl.type === DeclarationType.Class ||
                        decl.type === DeclarationType.Function ||
                        decl.type === DeclarationType.TypeAlias
                            ? decl.node.d.name
                            : decl.node;
                    const declScopeNode = ParseTreeUtils.getExecutionScopeNode(declNode);

                    // If this is a type parameter scope, it will be a proxy for its
                    // containing scope, so we need to use that instead.
                    const usageScope = AnalyzerNodeInfo.getScope(usageScopeNode);
                    if (usageScope?.proxy) {
                        const typeParamScope = AnalyzerNodeInfo.getScope(usageScopeNode);
                        if (!typeParamScope?.symbolTable.has(name) && usageScopeNode.parent) {
                            usageScopeNode = ParseTreeUtils.getExecutionScopeNode(usageScopeNode.parent);
                        }
                    }

                    if (usageScopeNode === declScopeNode) {
                        if (!isFlowPathBetweenNodes(declNode, node)) {
                            // If there was no control flow path from the usage back
                            // to the source, see if the usage node is reachable by
                            // any path.
                            const flowNode = AnalyzerNodeInfo.getFlowNode(node);
                            const isReachable =
                                flowNode &&
                                codeFlowEngine.getFlowNodeReachability(
                                    flowNode,
                                    /* sourceFlowNode */ undefined,
                                    /* ignoreNoReturn */ true
                                ) === Reachability.Reachable;
                            return !isReachable;
                        }
                    }
                }
                return true;
            });

            // If none of the declarations are reachable from the current node,
            // search for the symbol in outer scopes.
            if (!reachableDecl) {
                if (symbolWithScope.scope.type !== ScopeType.Function) {
                    let nextScopeToSearch = symbolWithScope.scope.parent;
                    const isOutsideCallerModule =
                        symbolWithScope.isOutsideCallerModule || symbolWithScope.scope.type === ScopeType.Module;
                    let isBeyondExecutionScope =
                        symbolWithScope.isBeyondExecutionScope || symbolWithScope.scope.isIndependentlyExecutable();

                    if (symbolWithScope.scope.type === ScopeType.Class) {
                        // There is an odd documented behavior for classes in that
                        // symbol resolution skips to the global scope rather than
                        // the next scope in the chain.
                        const globalScopeResult = symbolWithScope.scope.getGlobalScope();
                        nextScopeToSearch = globalScopeResult.scope;
                        if (globalScopeResult.isBeyondExecutionScope) {
                            isBeyondExecutionScope = true;
                        }
                    }

                    if (nextScopeToSearch) {
                        symbolWithScope = nextScopeToSearch.lookUpSymbolRecursive(name, {
                            isOutsideCallerModule,
                            isBeyondExecutionScope,
                        });
                    } else {
                        symbolWithScope = undefined;
                    }
                } else {
                    symbolWithScope = undefined;
                }
            }
        }

        // PEP 563 indicates that if a forward reference can be resolved in the module
        // scope (or, by implication, in the builtins scope), it should prefer that
        // resolution over local resolutions.
        if (symbolWithScope && preferGlobalScope) {
            let curSymbolWithScope: SymbolWithScope | undefined = symbolWithScope;
            while (
                curSymbolWithScope.scope.type !== ScopeType.Module &&
                curSymbolWithScope.scope.type !== ScopeType.Builtin &&
                curSymbolWithScope.scope.type !== ScopeType.TypeParameter &&
                curSymbolWithScope.scope.parent
            ) {
                curSymbolWithScope = curSymbolWithScope.scope.parent.lookUpSymbolRecursive(name, {
                    isOutsideCallerModule: curSymbolWithScope.isOutsideCallerModule,
                    isBeyondExecutionScope:
                        curSymbolWithScope.isBeyondExecutionScope ||
                        curSymbolWithScope.scope.isIndependentlyExecutable(),
                });
                if (!curSymbolWithScope) {
                    break;
                }
            }

            if (
                curSymbolWithScope?.scope.type === ScopeType.Module ||
                curSymbolWithScope?.scope.type === ScopeType.Builtin
            ) {
                symbolWithScope = curSymbolWithScope;
            }
        }

        return symbolWithScope;
    }

    // Disables recording of errors and warnings.
    function suppressDiagnostics<T>(
        node: ParseNode,
        callback: () => T,
        diagCallback?: (suppressedDiags: string[]) => void
    ) {
        suppressedNodeStack.push({ node, suppressedDiags: diagCallback ? [] : undefined });

        try {
            const result = callback();
            const poppedNode = suppressedNodeStack.pop();
            if (diagCallback && poppedNode?.suppressedDiags) {
                diagCallback(poppedNode.suppressedDiags);
            }
            return result;
        } catch (e) {
            // We don't use finally here because the TypeScript debugger doesn't
            // handle finally well when single stepping.
            suppressedNodeStack.pop();
            throw e;
        }
    }

    function getSignatureTrackerForNode(node: ParseNode): UniqueSignatureTracker | undefined {
        for (let i = signatureTrackerStack.length - 1; i >= 0; i--) {
            const rootNode = signatureTrackerStack[i].rootNode;
            if (ParseTreeUtils.isNodeContainedWithin(node, rootNode)) {
                return signatureTrackerStack[i].tracker;
            }
        }

        return undefined;
    }

    function useSignatureTracker<T>(node: ParseNode, callback: () => T): T {
        const tracker = getSignatureTrackerForNode(node);

        try {
            // If a signature tracker doesn't already exist, allocate one.
            if (!tracker) {
                signatureTrackerStack.push({
                    tracker: new UniqueSignatureTracker(),
                    rootNode: node,
                });
            }

            const result = callback();

            if (!tracker) {
                signatureTrackerStack.pop();
            }

            return result;
        } catch (e) {
            // We don't use finally here because the TypeScript debugger doesn't
            // handle finally well when single stepping.
            if (!tracker) {
                signatureTrackerStack.pop();
            }

            throw e;
        }
    }

    function ensureSignatureIsUnique<T extends Type>(type: T, node: ParseNode): T {
        const tracker = getSignatureTrackerForNode(node);
        if (!tracker) {
            return type;
        }

        if (isFunctionOrOverloaded(type)) {
            return ensureSignaturesAreUnique(type, tracker, node.start);
        }

        return type;
    }

    // Disables recording of errors and warnings and disables any caching of
    // types, under the assumption that we're performing speculative evaluations.
    // If speculativeNode is undefined, speculative mode is not used. This is
    // useful in cases where we conditionally want to use speculative mode.
    function useSpeculativeMode<T>(
        speculativeNode: ParseNode | undefined,
        callback: () => T,
        options?: SpeculativeModeOptions
    ) {
        if (!speculativeNode) {
            return callback();
        }

        speculativeTypeTracker.enterSpeculativeContext(speculativeNode, options);

        try {
            const result = callback();
            speculativeTypeTracker.leaveSpeculativeContext();
            return result;
        } catch (e) {
            // We don't use finally here because the TypeScript debugger doesn't
            // handle finally well when single stepping.
            speculativeTypeTracker.leaveSpeculativeContext();
            throw e;
        }
    }

    function disableSpeculativeMode(callback: () => void) {
        const stack = speculativeTypeTracker.disableSpeculativeMode();

        try {
            callback();
            speculativeTypeTracker.enableSpeculativeMode(stack);
        } catch (e) {
            // We don't use finally here because the TypeScript debugger doesn't
            // handle finally well when single stepping.
            speculativeTypeTracker.enableSpeculativeMode(stack);
            throw e;
        }
    }

    // Indicates whether the specified node is within a context that
    // is currently being evaluated speculative. If node is undefined,
    // returns true if any node is being evaluated speculatively.
    function isSpeculativeModeInUse(node: ParseNode | undefined) {
        return speculativeTypeTracker.isSpeculative(node);
    }

    function getDeclarationFromKeywordParam(type: FunctionType, paramName: string): Declaration | undefined {
        if (isFunction(type)) {
            if (type.shared.declaration) {
                const functionDecl = type.shared.declaration;
                if (functionDecl.type === DeclarationType.Function) {
                    const functionNode = functionDecl.node;
                    const functionScope = AnalyzerNodeInfo.getScope(functionNode);
                    if (functionScope) {
                        const paramSymbol = functionScope.lookUpSymbol(paramName)!;
                        if (paramSymbol) {
                            return paramSymbol.getDeclarations().find((decl) => decl.type === DeclarationType.Param);
                        }

                        const parameterDetails = getParamListDetails(type);
                        if (parameterDetails.unpackedKwargsTypedDictType) {
                            const lookupResults = lookUpClassMember(
                                parameterDetails.unpackedKwargsTypedDictType,
                                paramName
                            );
                            if (lookupResults) {
                                return lookupResults.symbol
                                    .getDeclarations()
                                    .find((decl) => decl.type === DeclarationType.Variable);
                            }
                        }
                    }
                }
            }
        }

        return undefined;
    }

    function _shouldFallBackToClassEntryForKeywordArg(baseType: ClassType, paramName: string) {
        return (
            ClassType.isDataClass(baseType) ||
            ClassType.isTypedDictClass(baseType) ||
            ClassType.hasNamedTupleEntry(baseType, paramName)
        );
    }

    function _addSymbolDeclInfo(
        symbol: Symbol,
        decls: Declaration[],
        synthesizedTypes: SynthesizedTypeInfo[],
        preferTypedDeclarations = false
    ) {
        const declCountBeforeAdd = decls.length;
        const typedDecls = preferTypedDeclarations ? symbol.getTypedDeclarations() : undefined;
        appendArray(decls, typedDecls?.length ? typedDecls : symbol.getDeclarations());

        const synthTypeInfo = symbol.getSynthesizedType();
        if (synthTypeInfo) {
            synthesizedTypes.push(synthTypeInfo);

            // Some module members are represented only by a synthesized module type,
            // with no concrete declaration node to navigate to. Synthesize a matching
            // alias declaration so definition/declaration/reference features can still
            // bind to the submodule's file.
            if (
                decls.length === declCountBeforeAdd &&
                isModule(synthTypeInfo.type) &&
                !synthTypeInfo.type.priv.fileUri.isEmpty()
            ) {
                decls.push(synthesizeAliasDeclaration(synthTypeInfo.type.priv.fileUri));
            }
        }
    }

    function _addClassEntryDeclsForKeywordArgIfPresent(
        baseType: ClassType,
        paramName: string,
        decls: Declaration[],
        synthesizedTypes: SynthesizedTypeInfo[]
    ) {
        const lookupResults = lookUpClassMember(baseType, paramName);
        if (!lookupResults) {
            return;
        }

        _addSymbolDeclInfo(lookupResults.symbol, decls, synthesizedTypes);
    }

    // In general, string nodes don't have any declarations associated with them, but
    // we need to handle the special case of string literals used as keys within a
    // dictionary expression where those keys are associated with a known TypedDict.
    function getDeclInfoForStringNode(node: StringNode): SymbolDeclInfo | undefined {
        const decls: Declaration[] = [];
        const synthesizedTypes: SynthesizedTypeInfo[] = [];
        const expectedType = getExpectedType(node)?.type;

        if (expectedType) {
            doForEachSubtype(expectedType, (subtype) => {
                // If the expected type is a TypedDict then the node is either a key expression
                // or a single entry in a set. We then need to check that the value of the node
                // is a valid entry in the TypedDict to avoid resolving declarations for
                // synthesized symbols such as 'get'.
                if (isClassInstance(subtype) && ClassType.isTypedDictClass(subtype)) {
                    const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value);
                    if (entry) {
                        const symbol = lookUpObjectMember(subtype, node.d.value)?.symbol;

                        if (symbol) {
                            _addSymbolDeclInfo(symbol, decls, synthesizedTypes);
                        }
                    }
                }
            });
        }

        return decls.length === 0 ? undefined : { decls, synthesizedTypes };
    }

    function getAliasFromImport(node: NameNode): NameNode | undefined {
        if (
            node.parent &&
            node.parent.nodeType === ParseNodeType.ImportFromAs &&
            node.parent.d.alias &&
            node === node.parent.d.name
        ) {
            return node.parent.d.alias;
        }
        return undefined;
    }

    function getDeclInfoForNameNode(node: NameNode, skipUnreachableCode = true): SymbolDeclInfo | undefined {
        if (skipUnreachableCode && AnalyzerNodeInfo.isCodeUnreachable(node)) {
            return undefined;
        }

        const decls: Declaration[] = [];
        const synthesizedTypes: SynthesizedTypeInfo[] = [];

        // If the node is part of a "from X import Y as Z" statement and the node
        // is the "Y" (non-aliased) name, we need to look up the alias symbol
        // since the non-aliased name is not in the symbol table.
        const alias = getAliasFromImport(node);
        if (alias) {
            const scope = ScopeUtils.getScopeForNode(node);
            if (scope) {
                // Look up the alias symbol.
                const symbolInScope = scope.lookUpSymbolRecursive(alias.d.value);
                if (symbolInScope) {
                    // The alias could have more decls that don't refer to this import. Filter
                    // out the one(s) that specifically associated with this import statement.
                    const declsForThisImport = symbolInScope.symbol.getDeclarations().filter((decl) => {
                        return decl.type === DeclarationType.Alias && decl.node === node.parent;
                    });

                    appendArray(decls, getDeclarationsWithUsesLocalNameRemoved(declsForThisImport));
                }
            }
        } else if (
            node.parent &&
            node.parent.nodeType === ParseNodeType.MemberAccess &&
            node === node.parent.d.member
        ) {
            let baseType = getType(node.parent.d.leftExpr);
            if (baseType) {
                baseType = makeTopLevelTypeVarsConcrete(baseType);
                const memberName = node.parent.d.member.d.value;
                doForEachSubtype(baseType, (subtype) => {
                    let symbol: Symbol | undefined;

                    subtype = makeTopLevelTypeVarsConcrete(subtype);

                    if (isInstantiableClass(subtype)) {
                        // Try to find a member that has a declared type. If so, that
                        // overrides any inferred types.
                        let member = lookUpClassMember(subtype, memberName, MemberAccessFlags.DeclaredTypesOnly);
                        if (!member) {
                            member = lookUpClassMember(subtype, memberName);
                        }

                        if (!member) {
                            const metaclass = subtype.shared.effectiveMetaclass;
                            if (metaclass && isInstantiableClass(metaclass)) {
                                member = lookUpClassMember(metaclass, memberName);
                            }
                        }

                        if (member) {
                            symbol = member.symbol;
                        }
                    } else if (isClassInstance(subtype)) {
                        // Try to find a member that has a declared type. If so, that
                        // overrides any inferred types.
                        let member = lookUpObjectMember(subtype, memberName, MemberAccessFlags.DeclaredTypesOnly);
                        if (!member) {
                            member = lookUpObjectMember(subtype, memberName);
                        }
                        if (member) {
                            symbol = member.symbol;
                        }
                    } else if (isModule(subtype)) {
                        symbol = ModuleType.getField(subtype, memberName);
                    }

                    if (symbol) {
                        // By default, report only the declarations that have type annotations.
                        // If there are none, then report all of the unannotated declarations,
                        // which includes every assignment of that symbol.
                        _addSymbolDeclInfo(symbol, decls, synthesizedTypes, /* preferTypedDeclarations */ true);
                    }
                });
            }
        } else if (node.parent && node.parent.nodeType === ParseNodeType.ModuleName) {
            const namePartIndex = node.parent.d.nameParts.findIndex((part) => part === node);
            const importInfo = AnalyzerNodeInfo.getImportInfo(node.parent);
            if (
                namePartIndex >= 0 &&
                importInfo &&
                !importInfo.isNativeLib &&
                namePartIndex < importInfo.resolvedUris.length
            ) {
                if (importInfo.resolvedUris[namePartIndex]) {
                    evaluateTypesForStatement(node);

                    // Synthesize an alias declaration for this name part. The only
                    // time this case is used is for IDE services such as
                    // the find all references, hover provider and etc.
                    decls.push(synthesizeAliasDeclaration(importInfo.resolvedUris[namePartIndex]));
                }
            }
        } else if (node.parent && node.parent.nodeType === ParseNodeType.Argument && node === node.parent.d.name) {
            // The target node is the name in a keyword argument. We need to determine whether
            // the corresponding keyword parameter can be determined from the context.
            const argNode = node.parent;
            const paramName = node.d.value;
            if (argNode.parent?.nodeType === ParseNodeType.Call) {
                const baseType = getType(argNode.parent.d.leftExpr);

                if (baseType) {
                    if (isFunction(baseType) && baseType.shared.declaration) {
                        const paramDecl = getDeclarationFromKeywordParam(baseType, paramName);
                        if (paramDecl) {
                            decls.push(paramDecl);
                        }
                    } else if (isOverloaded(baseType)) {
                        OverloadedType.getOverloads(baseType).forEach((f) => {
                            const paramDecl = getDeclarationFromKeywordParam(f, paramName);
                            if (paramDecl) {
                                decls.push(paramDecl);
                            }
                        });
                    } else if (isInstantiableClass(baseType)) {
                        const initMethodType = getBoundInitMethod(
                            evaluatorInterface,
                            argNode.parent.d.leftExpr,
                            ClassType.cloneAsInstance(baseType)
                        )?.type;

                        if (initMethodType && isFunction(initMethodType)) {
                            const paramDecl = getDeclarationFromKeywordParam(initMethodType, paramName);
                            if (paramDecl) {
                                decls.push(paramDecl);
                            } else if (_shouldFallBackToClassEntryForKeywordArg(baseType, paramName)) {
                                _addClassEntryDeclsForKeywordArgIfPresent(baseType, paramName, decls, synthesizedTypes);
                            }
                        } else if (_shouldFallBackToClassEntryForKeywordArg(baseType, paramName)) {
                            // Some synthesized callables (notably TypedDict "constructors") don't have a
                            // meaningful __init__ signature we can map keyword arguments to. In these cases,
                            // treat the keyword as referring to the class entry so IDE features like
                            // go-to-definition and rename can bind to the field declaration.
                            _addClassEntryDeclsForKeywordArgIfPresent(baseType, paramName, decls, synthesizedTypes);
                        }
                    }
                }
            } else if (argNode.parent?.nodeType === ParseNodeType.Class) {
                const classTypeResult = getTypeOfClass(argNode.parent);

                // Validate the init subclass args for this class so we can properly
                // evaluate its custom keyword parameters.
                if (classTypeResult) {
                    validateInitSubclassArgs(argNode.parent, classTypeResult.classType);
                }
            }
        } else {
            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

            // Determine if this node is within a quoted type annotation.
            const isWithinTypeAnnotation = ParseTreeUtils.isWithinTypeAnnotation(
                node,
                !isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node))
            );

            // Determine if this is part of a "type" statement.
            const isWithinTypeAliasStatement = !!ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeAlias);
            const allowForwardReferences = isWithinTypeAnnotation || isWithinTypeAliasStatement || fileInfo.isStubFile;

            const symbolWithScope = lookUpSymbolRecursive(
                node,
                node.d.value,
                !allowForwardReferences,
                isWithinTypeAnnotation
            );

            if (symbolWithScope) {
                _addSymbolDeclInfo(symbolWithScope.symbol, decls, synthesizedTypes);
            }
        }

        return { decls, synthesizedTypes };
    }

    function getTypeForDeclaration(declaration: Declaration): DeclaredSymbolTypeInfo {
        switch (declaration.type) {
            case DeclarationType.Intrinsic: {
                if (declaration.intrinsicType === 'Any') {
                    return { type: AnyType.create() };
                }

                if (declaration.intrinsicType === '__class__') {
                    const classNode = ParseTreeUtils.getEnclosingClass(declaration.node) as ClassNode;
                    const classTypeInfo = getTypeOfClass(classNode);
                    return {
                        type: classTypeInfo
                            ? specializeWithUnknownTypeArgs(classTypeInfo.classType, getTupleClassType())
                            : UnknownType.create(),
                    };
                }

                const strType = getBuiltInObject(declaration.node, 'str');
                const intType = getBuiltInObject(declaration.node, 'int');
                if (isClassInstance(intType) && isClassInstance(strType)) {
                    if (declaration.intrinsicType === 'str') {
                        return { type: strType };
                    }

                    if (declaration.intrinsicType === 'str | None') {
                        return { type: combineTypes([strType, getNoneType()]) };
                    }

                    if (declaration.intrinsicType === 'int') {
                        return { type: intType };
                    }

                    if (declaration.intrinsicType === 'MutableSequence[str]') {
                        const sequenceType = getBuiltInType(declaration.node, 'MutableSequence');
                        if (isInstantiableClass(sequenceType)) {
                            return {
                                type: ClassType.cloneAsInstance(ClassType.specialize(sequenceType, [strType])),
                            };
                        }
                    }

                    if (declaration.intrinsicType === 'dict[str, Any]') {
                        const dictType = getBuiltInType(declaration.node, 'dict');
                        if (isInstantiableClass(dictType)) {
                            return {
                                type: ClassType.cloneAsInstance(
                                    ClassType.specialize(dictType, [strType, AnyType.create()])
                                ),
                            };
                        }
                    }
                }

                return { type: UnknownType.create() };
            }

            case DeclarationType.Class: {
                const classTypeInfo = getTypeOfClass(declaration.node);
                return { type: classTypeInfo?.decoratedType };
            }

            case DeclarationType.SpecialBuiltInClass: {
                return { type: getTypeOfAnnotation(declaration.node.d.annotation) };
            }

            case DeclarationType.Function: {
                const functionTypeInfo = getTypeOfFunction(declaration.node);
                return { type: functionTypeInfo?.decoratedType };
            }

            case DeclarationType.TypeAlias: {
                return { type: getTypeOfTypeAlias(declaration.node) };
            }

            case DeclarationType.Param: {
                let typeAnnotationNode = declaration.node.d.annotation ?? declaration.node.d.annotationComment;

                // If there wasn't an annotation, see if the parent function
                // has a function-level annotation comment that provides
                // this parameter's annotation type.
                if (!typeAnnotationNode) {
                    if (declaration.node.parent?.nodeType === ParseNodeType.Function) {
                        const functionNode = declaration.node.parent;
                        if (
                            functionNode.d.funcAnnotationComment &&
                            !functionNode.d.funcAnnotationComment.d.isEllipsis
                        ) {
                            const paramIndex = functionNode.d.params.findIndex((param) => param === declaration.node);
                            typeAnnotationNode = ParseTreeUtils.getTypeAnnotationForParam(functionNode, paramIndex);
                        }
                    }
                }

                if (typeAnnotationNode) {
                    let declaredType = getTypeOfParamAnnotation(typeAnnotationNode, declaration.node.d.category);

                    const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(declaration.node);
                    declaredType = makeTypeVarsBound(declaredType, liveTypeVarScopes);

                    return {
                        type: transformVariadicParamType(
                            declaration.node,
                            declaration.node.d.category,
                            adjustParamAnnotatedType(declaration.node, declaredType)
                        ),
                    };
                }

                return { type: undefined };
            }

            case DeclarationType.TypeParam: {
                return { type: getTypeOfTypeParam(declaration.node) };
            }

            case DeclarationType.Variable: {
                const typeAnnotationNode = declaration.typeAnnotationNode;

                if (typeAnnotationNode) {
                    let declaredType: Type | undefined;

                    if (declaration.isRuntimeTypeExpression) {
                        declaredType = convertToInstance(
                            getTypeOfExpressionExpectingType(typeAnnotationNode, {
                                allowFinal: true,
                                allowRequired: true,
                                allowReadOnly: true,
                                runtimeTypeExpression: true,
                            }).type
                        );
                    } else {
                        const declNode =
                            declaration.isDefinedByMemberAccess &&
                            declaration.node.parent?.nodeType === ParseNodeType.MemberAccess
                                ? declaration.node.parent
                                : declaration.node;
                        const allowClassVar = isClassVarAllowedForAssignmentTarget(declNode);
                        const allowFinal = isFinalAllowedForAssignmentTarget(declNode);
                        const allowRequired =
                            ParseTreeUtils.isRequiredAllowedForAssignmentTarget(declNode) ||
                            !!declaration.isInInlinedTypedDict;

                        declaredType = getTypeOfAnnotation(typeAnnotationNode, {
                            varTypeAnnotation: true,
                            allowClassVar,
                            allowFinal,
                            allowRequired,
                            allowReadOnly: allowRequired,
                            enforceClassTypeVarScope: declaration.isDefinedByMemberAccess,
                        });
                    }

                    if (declaredType) {
                        // If this is a declaration for a member variable within a method,
                        // we need to convert any bound TypeVars associated with the
                        // class to their free counterparts.
                        if (declaration.isDefinedByMemberAccess) {
                            const enclosingClass = ParseTreeUtils.getEnclosingClass(declaration.node);

                            if (enclosingClass) {
                                declaredType = makeTypeVarsFree(declaredType, [
                                    ParseTreeUtils.getScopeIdForNode(enclosingClass),
                                ]);
                            }
                        }

                        if (isClassInstance(declaredType) && ClassType.isBuiltIn(declaredType, 'TypeAlias')) {
                            return { type: undefined, isTypeAlias: true };
                        }

                        return { type: declaredType };
                    }
                }

                return { type: undefined };
            }

            case DeclarationType.Alias: {
                return { type: undefined };
            }
        }
    }

    function getTypeOfTypeParam(node: TypeParameterNode): TypeVarType {
        // Is this type already cached?
        const cachedTypeVarType = readTypeCache(node.d.name, EvalFlags.None) as FunctionType;
        if (cachedTypeVarType && isTypeVar(cachedTypeVarType)) {
            return cachedTypeVarType;
        }

        let runtimeClassName = 'TypeVar';
        let kind: TypeVarKind = TypeVarKind.TypeVar;
        if (node.d.typeParamKind === TypeParamKind.TypeVarTuple) {
            runtimeClassName = 'TypeVarTuple';
            kind = TypeVarKind.TypeVarTuple;
        } else if (node.d.typeParamKind === TypeParamKind.ParamSpec) {
            runtimeClassName = 'ParamSpec';
            kind = TypeVarKind.ParamSpec;
        }
        const runtimeType = getTypingType(node, runtimeClassName);
        const runtimeClass = runtimeType && isInstantiableClass(runtimeType) ? runtimeType : undefined;

        let typeVar = TypeVarType.createInstantiable(node.d.name.d.value, kind);
        if (runtimeClass) {
            typeVar = TypeBase.cloneAsSpecialForm(typeVar, ClassType.cloneAsInstance(runtimeClass));
        }
        typeVar.shared.isTypeParamSyntax = true;

        // Cache the value before we evaluate the bound or the default type in
        // case it refers to itself in a circular manner.
        writeTypeCache(node, { type: typeVar }, /* flags */ undefined);
        writeTypeCache(node.d.name, { type: typeVar }, /* flags */ undefined);

        if (node.d.boundExpr) {
            if (node.d.boundExpr.nodeType === ParseNodeType.Tuple) {
                const constraints = node.d.boundExpr.d.items.map((constraint) => {
                    const constraintType = getTypeOfExpressionExpectingType(constraint, {
                        noNonTypeSpecialForms: true,
                        forwardRefs: true,
                        typeExpression: true,
                    }).type;

                    if (
                        requiresSpecialization(constraintType, {
                            ignorePseudoGeneric: true,
                            ignoreImplicitTypeArgs: true,
                        })
                    ) {
                        addDiagnostic(
                            DiagnosticRule.reportGeneralTypeIssues,
                            LocMessage.typeVarBoundGeneric(),
                            constraint
                        );
                    }

                    return convertToInstance(constraintType);
                });

                if (constraints.length < 2) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typeVarSingleConstraint(),
                        node.d.boundExpr
                    );
                } else if (node.d.typeParamKind === TypeParamKind.TypeVar) {
                    typeVar.shared.constraints = constraints;
                }
            } else {
                const boundType = getTypeOfExpressionExpectingType(node.d.boundExpr, {
                    noNonTypeSpecialForms: true,
                    forwardRefs: true,
                    typeExpression: true,
                }).type;

                if (requiresSpecialization(boundType, { ignorePseudoGeneric: true })) {
                    addDiagnostic(
                        DiagnosticRule.reportGeneralTypeIssues,
                        LocMessage.typeVarConstraintGeneric(),
                        node.d.boundExpr
                    );
                }

                if (node.d.typeParamKind === TypeParamKind.TypeVar) {
                    typeVar.shared.boundType = convertToInstance(boundType);
                }
            }
        }

        if (node.d.typeParamKind === TypeParamKind.ParamSpec) {
            const defaultType = node.d.defaultExpr
                ? getParamSpecDefaultType(node.d.defaultExpr, /* isPep695Syntax */ true)
                : undefined;

            if (defaultType) {
                typeVar.shared.defaultType = defaultType;
                typeVar.shared.isDefaultExplicit = true;
            } else {
                typeVar.shared.defaultType = ParamSpecType.getUnknown();
            }
        } else if (node.d.typeParamKind === TypeParamKind.TypeVarTuple) {
            const defaultType = node.d.defaultExpr
                ? getTypeVarTupleDefaultType(node.d.defaultExpr, /* isPep695Syntax */ true)
                : undefined;

            if (defaultType) {
                typeVar.shared.defaultType = defaultType;
                typeVar.shared.isDefaultExplicit = true;
            } else {
                typeVar.shared.defaultType = makeTupleObject(evaluatorInterface, [
                    { type: UnknownType.create(), isUnbounded: true },
                ]);
            }
        } else {
            const defaultType = node.d.defaultExpr
                ? convertToInstance(
                      getTypeOfExpressionExpectingType(node.d.defaultExpr, {
                          forwardRefs: true,
                          typeExpression: true,
                      }).type
                  )
                : undefined;

            if (defaultType) {
                typeVar.shared.defaultType = defaultType;
                typeVar.shared.isDefaultExplicit = true;
            } else {
                typeVar.shared.defaultType = UnknownType.create();
            }
        }

        // If a default is provided, make sure it is compatible with the bound
        // or constraint.
        if (typeVar.shared.isDefaultExplicit && node.d.defaultExpr) {
            verifyTypeVarDefaultIsCompatible(typeVar, node.d.defaultExpr);
        }

        // Associate the type variable with the owning scope.
        const scopeNode = ParseTreeUtils.getTypeVarScopeNode(node);
        if (scopeNode) {
            let scopeType: TypeVarScopeType;
            if (scopeNode.nodeType === ParseNodeType.Class) {
                scopeType = TypeVarScopeType.Class;

                // Set the variance to "auto" for class-scoped TypeVars.
                typeVar.shared.declaredVariance =
                    isParamSpec(typeVar) || isTypeVarTuple(typeVar) ? Variance.Invariant : Variance.Auto;
            } else if (scopeNode.nodeType === ParseNodeType.Function) {
                scopeType = TypeVarScopeType.Function;
            } else {
                assert(scopeNode.nodeType === ParseNodeType.TypeAlias);
                scopeType = TypeVarScopeType.TypeAlias;
                typeVar.shared.declaredVariance =
                    isParamSpec(typeVar) || isTypeVarTuple(typeVar) ? Variance.Invariant : Variance.Auto;
            }

            typeVar = TypeVarType.cloneForScopeId(
                typeVar,
                ParseTreeUtils.getScopeIdForNode(
                    scopeNode.nodeType === ParseNodeType.TypeAlias ? scopeNode.d.name : scopeNode
                ),
                scopeNode.d.name.d.value,
                scopeType
            );
        }

        writeTypeCache(node, { type: typeVar }, /* flags */ undefined);
        writeTypeCache(node.d.name, { type: typeVar }, /* flags */ undefined);

        return typeVar;
    }

    function getInferredTypeOfDeclaration(symbol: Symbol, decl: Declaration): Type | undefined {
        const resolvedDecl = resolveAliasDeclaration(decl, /* resolveLocalNames */ true, {
            allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(decl.node).isStubFile,
        });

        // We couldn't resolve the alias. Substitute an unknown
        // type in this case.
        if (!resolvedDecl) {
            return evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create();
        }

        function applyLoaderActionsToModuleType(
            moduleType: ModuleType,
            loaderActions: ModuleLoaderActions,
            importLookup: ImportLookup
        ): Type {
            if (!loaderActions.uri.isEmpty() && loaderActions.loadSymbolsFromPath) {
                const lookupResults = importLookup(loaderActions.uri);
                if (lookupResults) {
                    moduleType.priv.fields = lookupResults.symbolTable;
                    moduleType.priv.docString = lookupResults.docString;
                } else {
                    // Note that all module attributes that are not found in the
                    // symbol table should be treated as Any or Unknown rather than
                    // as an error.
                    moduleType.priv.notPresentFieldType = evaluatorOptions.evaluateUnknownImportsAsAny
                        ? AnyType.create()
                        : UnknownType.create();
                }
            }

            if (loaderActions.implicitImports) {
                loaderActions.implicitImports.forEach((implicitImport, name) => {
                    const existingLoaderField = moduleType.priv.loaderFields.get(name);

                    // Recursively apply loader actions.
                    let symbolType: Type;

                    if (implicitImport.isUnresolved) {
                        symbolType = UnknownType.create();
                    } else {
                        let importedModuleType: ModuleType;

                        const existingType = existingLoaderField?.getSynthesizedType();
                        if (existingType?.type && isModule(existingType.type)) {
                            importedModuleType = existingType.type;
                        } else {
                            const moduleName = moduleType.priv.moduleName
                                ? moduleType.priv.moduleName + '.' + name
                                : '';
                            importedModuleType = ModuleType.create(moduleName, implicitImport.uri);
                        }

                        symbolType = applyLoaderActionsToModuleType(importedModuleType, implicitImport, importLookup);
                    }

                    if (!existingLoaderField) {
                        const importedModuleSymbol = Symbol.createWithType(SymbolFlags.None, symbolType);
                        moduleType.priv.loaderFields.set(name, importedModuleSymbol);
                    }
                });
            }

            return moduleType;
        }

        // If the resolved declaration is still an alias, the alias
        // is pointing at a module, and we need to synthesize a
        // module type.
        if (resolvedDecl.type === DeclarationType.Alias) {
            let moduleType: ModuleType | undefined;

            // See if this is an import that shares a ModuleType with another
            // import statement. If so, used the cached type. This happens when
            // multiple import statements start with the same module name, such
            // as "import a.b" and "import a.c".
            if (resolvedDecl.node.nodeType === ParseNodeType.ImportAs) {
                const cachedType = readTypeCache(resolvedDecl.node.d.module, EvalFlags.None);
                if (cachedType && isModule(cachedType)) {
                    moduleType = cachedType;
                }
            }

            if (!moduleType) {
                // Build a module type that corresponds to the declaration and
                // its associated loader actions.
                moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.uri);

                if (resolvedDecl.node.nodeType === ParseNodeType.ImportAs) {
                    writeTypeCache(resolvedDecl.node.d.module, { type: moduleType }, EvalFlags.None);
                }
            }

            return applyLoaderActionsToModuleType(
                moduleType,
                resolvedDecl.symbolName && resolvedDecl.submoduleFallback
                    ? resolvedDecl.submoduleFallback
                    : resolvedDecl,
                importLookup
            );
        }

        const declaredType = getTypeForDeclaration(resolvedDecl);
        if (declaredType.type) {
            return declaredType.type;
        }

        // If this is part of a "py.typed" package, don't fall back on type inference
        // unless it's marked Final, is a constant, or is a declared type alias.
        const fileInfo = AnalyzerNodeInfo.getFileInfo(resolvedDecl.node);
        let isUnambiguousType = !fileInfo.isInPyTypedPackage || fileInfo.isStubFile;

        // If this is a py.typed package, determine if this is a case where an unannotated
        // variable is considered "unambiguous" because all type checkers are almost
        // guaranteed to infer its type the same.
        if (!isUnambiguousType) {
            if (resolvedDecl.type === DeclarationType.Variable) {
                // Special-case variables within an enum class. These are effectively
                // constants, so we'll treat them as unambiguous.
                const enclosingClass = ParseTreeUtils.getEnclosingClass(resolvedDecl.node, /* stopAtFunction */ true);
                if (enclosingClass) {
                    const classTypeInfo = getTypeOfClass(enclosingClass);
                    if (classTypeInfo && ClassType.isEnumClass(classTypeInfo.classType)) {
                        isUnambiguousType = true;
                    }
                }

                // Special-case constants, which are treated as unambiguous.
                if (isFinalVariableDeclaration(resolvedDecl) || resolvedDecl.isConstant) {
                    isUnambiguousType = true;
                }

                // Special-case calls to certain built-in type functions.
                if (resolvedDecl.inferredTypeSource?.nodeType === ParseNodeType.Call) {
                    const baseTypeResult = getTypeOfExpression(
                        resolvedDecl.inferredTypeSource.d.leftExpr,
                        EvalFlags.CallBaseDefaults
                    );
                    const callType = baseTypeResult.type;

                    const exemptBuiltins = [
                        'TypeVar',
                        'ParamSpec',
                        'TypeVarTuple',
                        'TypedDict',
                        'NamedTuple',
                        'NewType',
                        'TypeAliasType',
                    ];

                    if (isInstantiableClass(callType) && ClassType.isBuiltIn(callType, exemptBuiltins)) {
                        isUnambiguousType = true;
                    } else if (
                        isFunction(callType) &&
                        exemptBuiltins.some((name) => FunctionType.isBuiltIn(callType, name))
                    ) {
                        isUnambiguousType = true;
                    }
                }
            }
        }

        // If the resolved declaration had no defined type, use the
        // inferred type for this node.
        if (resolvedDecl.type === DeclarationType.Param) {
            assert(resolvedDecl.node.d.name !== undefined);
            return evaluateTypeForSubnode(resolvedDecl.node.d.name, () => {
                evaluateTypeOfParam(resolvedDecl.node);
            })?.type;
        }

        if (resolvedDecl.type === DeclarationType.Variable && resolvedDecl.inferredTypeSource) {
            const isTypeAlias =
                isExplicitTypeAliasDeclaration(resolvedDecl) || isPossibleTypeAliasOrTypedDict(resolvedDecl);

            // If this is a type alias, evaluate types for the entire assignment
            // statement rather than just the RHS of the assignment.
            const typeSource =
                isTypeAlias && resolvedDecl.inferredTypeSource.parent
                    ? resolvedDecl.inferredTypeSource.parent
                    : resolvedDecl.inferredTypeSource;
            let inferredType = evaluateTypeForSubnode(resolvedDecl.node, () => {
                evaluateTypesForStatement(typeSource);
            })?.type;

            if (inferredType && isTypeAlias && resolvedDecl.typeAliasName) {
                // If this was a speculative type alias, it becomes a real type alias only
                // in the event that its inferred type is instantiable or explicitly Any
                // (but not an ellipsis).
                if (isLegalImplicitTypeAliasType(inferredType)) {
                    const typeAliasTypeVar = synthesizeTypeAliasPlaceholder(resolvedDecl.typeAliasName);

                    inferredType = transformTypeForTypeAlias(
                        inferredType,
                        resolvedDecl.node,
                        typeAliasTypeVar,
                        /* isPep695TypeVarType */ false
                    );

                    isUnambiguousType = true;
                }
            }

            // Determine whether we need to mark the annotation as ambiguous.
            if (inferredType && fileInfo.isInPyTypedPackage && !fileInfo.isStubFile) {
                if (!isUnambiguousType) {
                    // See if this particular inference can be considered "unambiguous".
                    // Any symbol that is assigned more than once is considered ambiguous.
                    if (isUnambiguousInference(symbol, decl, inferredType)) {
                        isUnambiguousType = true;
                    }
                }

                if (!isUnambiguousType) {
                    inferredType = TypeBase.cloneForAmbiguousType(inferredType);
                }
            }

            return inferredType;
        }

        return undefined;
    }

    // Applies some heuristics to determine whether it's likely that all Python
    // type checkers will infer the same type.
    function isUnambiguousInference(symbol: Symbol, decl: Declaration, inferredType: Type): boolean {
        const nonSlotsDecls = symbol.getDeclarations().filter((decl) => {
            return decl.type !== DeclarationType.Variable || !decl.isInferenceAllowedInPyTyped;
        });

        // Any symbol with more than one assignment is considered ambiguous.
        if (nonSlotsDecls.length > 1) {
            return false;
        }

        if (decl.type !== DeclarationType.Variable) {
            return false;
        }

        // If there are no non-slots declarations, don't mark the inferred type as ambiguous.
        if (nonSlotsDecls.length === 0) {
            return true;
        }

        // TypeVar definitions don't require a declaration.
        if (isTypeVar(inferredType)) {
            return true;
        }

        let assignmentNode: AssignmentNode | undefined;

        const parentNode = decl.node.parent;
        if (parentNode) {
            // Is this a simple assignment (x = y) or an assignment of an instance variable (self.x = y)?
            if (parentNode.nodeType === ParseNodeType.Assignment) {
                assignmentNode = parentNode;
            } else if (
                parentNode.nodeType === ParseNodeType.MemberAccess &&
                parentNode.parent?.nodeType === ParseNodeType.Assignment
            ) {
                assignmentNode = parentNode.parent;
            }
        }

        if (!assignmentNode) {
            return false;
        }

        const assignedType = getTypeOfExpression(assignmentNode.d.rightExpr).type;

        // Assume that literal values will always result in the same inferred type.
        if (isClassInstance(assignedType) && isLiteralType(assignedType)) {
            return true;
        }

        // If the assignment is a simple name corresponding to an unambiguous
        // type, we'll assume the resulting variable will receive the same
        // unambiguous type.
        if (assignmentNode.d.rightExpr.nodeType === ParseNodeType.Name && !TypeBase.isAmbiguous(assignedType)) {
            return true;
        }

        return false;
    }

    // If the specified declaration is an alias declaration that points to a symbol,
    // it resolves the alias and looks up the symbol, then returns the first declaration
    // associated with that symbol. It does this recursively if necessary. If a symbol
    // lookup fails, undefined is returned. If resolveLocalNames is true, the method
    // resolves aliases through local renames ("as" clauses found in import statements).
    function resolveAliasDeclaration(
        declaration: Declaration,
        resolveLocalNames: boolean,
        options?: ResolveAliasOptions
    ): Declaration | undefined {
        return resolveAliasDeclarationUtil(importLookup, declaration, {
            resolveLocalNames,
            allowExternallyHiddenAccess: options?.allowExternallyHiddenAccess ?? false,
            skipFileNeededCheck: options?.skipFileNeededCheck ?? false,
        })?.declaration;
    }

    function resolveAliasDeclarationWithInfo(
        declaration: Declaration,
        resolveLocalNames: boolean,
        options?: ResolveAliasOptions
    ): ResolvedAliasInfo | undefined {
        return resolveAliasDeclarationUtil(importLookup, declaration, {
            resolveLocalNames,
            allowExternallyHiddenAccess: options?.allowExternallyHiddenAccess ?? false,
            skipFileNeededCheck: options?.skipFileNeededCheck ?? false,
        });
    }

    // Returns the type of the symbol. If the type is explicitly declared, that type
    // is returned. If not, the type is inferred from assignments to the symbol. All
    // assigned types are evaluated and combined into a union.
    function getEffectiveTypeOfSymbol(symbol: Symbol): Type {
        return getEffectiveTypeOfSymbolForUsage(symbol).type;
    }

    // If a "usageNode" node is specified, only declarations that are outside
    // of the current execution scope or that are reachable (as determined by
    // code flow analysis) are considered. This helps in cases where there
    // are cyclical dependencies between symbols.
    function getEffectiveTypeOfSymbolForUsage(
        symbol: Symbol,
        usageNode?: NameNode,
        useLastDecl = false
    ): EffectiveTypeResult {
        let declaredTypeInfo: DeclaredSymbolTypeInfo | undefined;

        // If there's a declared type, it takes precedence over inferred types.
        if (symbol.hasTypedDeclarations()) {
            declaredTypeInfo = getDeclaredTypeOfSymbol(symbol, usageNode);
            const declaredType = declaredTypeInfo?.type;

            let isIncomplete = false;
            if (declaredType) {
                if (isFunction(declaredType) && FunctionType.isPartiallyEvaluated(declaredType)) {
                    isIncomplete = true;
                } else if (isClass(declaredType) && ClassType.isPartiallyEvaluated(declaredType)) {
                    isIncomplete = true;
                }
            }

            // If the "declared" type uses a "TypeAlias" type annotation, then
            // we need to use the inferred type path to evaluate its type.
            if (declaredType || !declaredTypeInfo.isTypeAlias) {
                const typedDecls = symbol.getTypedDeclarations();

                // If we received an undefined declared type, this can be caused by
                // exceeding the max number of type declarations, speculative
                // evaluation, or a recursive definition.
                const isRecursiveDefinition =
                    !declaredType &&
                    !declaredTypeInfo.exceedsMaxDecls &&
                    !speculativeTypeTracker.isSpeculative(/* node */ undefined);

                const result: EffectiveTypeResult = {
                    type: declaredType ?? UnknownType.create(),
                    isIncomplete,
                    includesVariableDecl: includesVariableTypeDecl(typedDecls),
                    includesIllegalTypeAliasDecl: !typedDecls.every((decl) => isPossibleTypeAliasDeclaration(decl)),
                    includesSpeculativeResult: false,
                    isRecursiveDefinition,
                };

                return result;
            }
        }

        return inferTypeOfSymbolForUsage(symbol, usageNode, useLastDecl);
    }

    // Determines whether the set of declarations includes a variable declaration
    // that is not part of a typing.pyi or typingExtensions.pyi file.
    function includesVariableTypeDecl(decls: Declaration[]): boolean {
        return decls.some((decl) => {
            if (decl.type === DeclarationType.Variable) {
                // Exempt typing.pyi and typingExtensions.pyi, which use variables to
                // define some special forms.
                const fileInfo = AnalyzerNodeInfo.getFileInfo(decl.node);

                if (!fileInfo.isTypingStubFile && !fileInfo.isTypingExtensionsStubFile) {
                    return true;
                }
            }

            if (decl.type === DeclarationType.Param) {
                return true;
            }

            return false;
        });
    }

    function inferTypeOfSymbolForUsage(symbol: Symbol, usageNode?: NameNode, useLastDecl = false): EffectiveTypeResult {
        // Look in the inferred type cache to see if we've computed this already.
        let cacheEntries = effectiveTypeCache.get(symbol.id);
        const usageNodeId = usageNode ? usageNode.id : undefined;
        const effectiveTypeCacheKey = `${usageNodeId === undefined ? '.' : usageNodeId.toString()}${
            useLastDecl ? '*' : ''
        }`;
        const cacheEntry = cacheEntries?.get(effectiveTypeCacheKey);

        if (cacheEntry && !cacheEntry.isIncomplete) {
            return cacheEntry;
        }

        // Infer the type.
        const decls = symbol.getDeclarations();

        let declIndexToConsider: number | undefined;

        // Limit the number of declarations to explore.
        if (decls.length > maxDeclarationsToUseForInference) {
            const result: EffectiveTypeResult = {
                type: UnknownType.create(),
                isIncomplete: false,
                includesVariableDecl: false,
                includesIllegalTypeAliasDecl: !decls.every((decl) => isPossibleTypeAliasDeclaration(decl)),
                includesSpeculativeResult: false,
                isRecursiveDefinition: false,
            };

            addToEffectiveTypeCache(result);
            return result;
        }

        // If the caller has requested that we use only the last decl, we
        // will use only the last one, but we'll ignore decls that are in
        // except clauses.
        if (useLastDecl) {
            decls.forEach((decl, index) => {
                if (!decl.isInExceptSuite) {
                    declIndexToConsider = index;
                }
            });
        } else {
            // Handle the case where there are multiple imports — one of them in
            // a try block and one or more in except blocks. In this case, we'll
            // use the one in the try block rather than the excepts.
            if (decls.length > 1 && decls.every((decl) => decl.type === DeclarationType.Alias)) {
                const nonExceptDecls = decls.filter(
                    (decl) => decl.type === DeclarationType.Alias && !decl.isInExceptSuite
                );
                if (nonExceptDecls.length === 1) {
                    declIndexToConsider = decls.findIndex((decl) => decl === nonExceptDecls[0]);
                }
            }
        }

        // Determine which declarations to use for inference.
        const declsToConsider: Declaration[] = [];
        let includesVariableDecl = false;
        let includesIllegalTypeAliasDecl = false;

        let sawExplicitTypeAlias = false;
        decls.forEach((decl, index) => {
            const resolvedDecl =
                resolveAliasDeclaration(decl, /* resolveLocalNames */ true, {
                    allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(decl.node).isStubFile,
                }) ?? decl;

            if (!isPossibleTypeAliasDeclaration(resolvedDecl) && !isExplicitTypeAliasDeclaration(resolvedDecl)) {
                includesIllegalTypeAliasDecl = true;
            }

            if (includesVariableTypeDecl([resolvedDecl])) {
                includesVariableDecl = true;
            }

            if (declIndexToConsider !== undefined && declIndexToConsider !== index) {
                return;
            }

            // If we have already seen an explicit type alias, do not consider
            // additional decls. This can happen if multiple TypeAlias declarations
            // are provided -- normally an error, but it can happen in stdlib stubs
            // if the user sets the pythonPlatform to "All".
            if (sawExplicitTypeAlias) {
                return;
            }

            // If the symbol is explicitly marked as a ClassVar, consider only the
            // declarations that assign to it from within the class body, not through
            // a member access expression.
            if (
                isEffectivelyClassVar(symbol, /* isDataclass */ false) &&
                decl.type === DeclarationType.Variable &&
                decl.isDefinedByMemberAccess
            ) {
                return;
            }

            if (usageNode !== undefined) {
                if (decl.type !== DeclarationType.Alias) {
                    // Is the declaration in the same execution scope as the "usageNode" node?
                    // If so, we can skip it because code flow analysis will allow us
                    // to determine the type in this context.
                    const usageScope = ParseTreeUtils.getExecutionScopeNode(usageNode);
                    const declScope = ParseTreeUtils.getExecutionScopeNode(decl.node);
                    if (usageScope === declScope) {
                        // Skip declarations that appear after the usage in the source.
                        // Such declarations are typically only reached via loop back-edges,
                        // and including them causes circular evaluation (producing Unknown).
                        // Declarations that textually precede the usage are retained so
                        // that order-independent type aliases resolve correctly; for
                        // branching constructs (if/else, try/except), a retained
                        // declaration may be flow-unreachable at the usage site, but the
                        // code-flow engine overrides the effective type for local
                        // variables, so the narrowed type at the usage site is correct.
                        // Note: for the cases filtered out here (decl after usage in same
                        // scope), the code-flow engine handles loop-carried narrowing when
                        // it evaluates the type at the actual usage site.
                        if (decl.node.start >= usageNode.start) {
                            return;
                        }
                    }
                }
            }

            const isExplicitTypeAlias = isExplicitTypeAliasDeclaration(resolvedDecl);
            const isTypeAlias = isExplicitTypeAlias || isPossibleTypeAliasOrTypedDict(resolvedDecl);

            if (isExplicitTypeAlias) {
                sawExplicitTypeAlias = true;
            }

            // If this is a type alias, evaluate it outside of the recursive symbol
            // resolution check so we can evaluate the full assignment statement.
            if (
                isTypeAlias &&
                resolvedDecl.type === DeclarationType.Variable &&
                resolvedDecl.inferredTypeSource?.parent?.nodeType === ParseNodeType.Assignment
            ) {
                evaluateTypesForAssignmentStatement(resolvedDecl.inferredTypeSource.parent);
            }

            declsToConsider.push(resolvedDecl);
        });

        // If all of the decls come from augmented assignments, we won't be able to
        // determine its type. At least one declaration must be a simple assignment.
        if (
            declsToConsider.every(
                (decl) =>
                    isVariableDeclaration(decl) &&
                    ParseTreeUtils.isNodeContainedWithinNodeType(decl.node, ParseNodeType.AugmentedAssignment)
            )
        ) {
            declsToConsider.splice(0);
        }

        const result = getTypeOfSymbolForDecls(symbol, declsToConsider, effectiveTypeCacheKey);
        result.includesVariableDecl = includesVariableDecl;
        result.includesIllegalTypeAliasDecl = includesIllegalTypeAliasDecl;

        // Add the result to the effective type cache if it doesn't include speculative results.
        if (!result.includesSpeculativeResult) {
            addToEffectiveTypeCache(result);
        }

        return result;

        function addToEffectiveTypeCache(result: EffectiveTypeResult) {
            // Add the entry to the cache so we don't need to compute it next time.
            if (!cacheEntries) {
                cacheEntries = new Map<string, EffectiveTypeResult>();
                effectiveTypeCache.set(symbol.id, cacheEntries);
            }

            cacheEntries.set(effectiveTypeCacheKey, result);
        }
    }

    // Returns the type of a symbol based on a subset of its declarations.
    function getTypeOfSymbolForDecls(symbol: Symbol, decls: Declaration[], typeCacheKey: string): EffectiveTypeResult {
        const typesToCombine: Type[] = [];
        let isIncomplete = false;
        let sawPendingEvaluation = false;
        let includesSpeculativeResult = false;

        decls.forEach((decl) => {
            if (pushSymbolResolution(symbol, decl)) {
                let symbolPopped = false;
                try {
                    let type = getInferredTypeOfDeclaration(symbol, decl);

                    if (!popSymbolResolution(symbol)) {
                        isIncomplete = true;
                    }
                    symbolPopped = true;

                    if (type) {
                        if (decl.type === DeclarationType.Variable) {
                            let isConstant = false;
                            if (decl.type === DeclarationType.Variable) {
                                if (decl.isConstant || isFinalVariableDeclaration(decl)) {
                                    isConstant = true;
                                }
                            }

                            // Treat enum values declared within an enum class as though they are const even
                            // though they may not be named as such.
                            if (
                                isClassInstance(type) &&
                                ClassType.isEnumClass(type) &&
                                isDeclInEnumClass(evaluatorInterface, decl)
                            ) {
                                isConstant = true;
                            }

                            // If the symbol is constant, we can retain the literal
                            // value and TypeForm types. Otherwise, strip literal values
                            // and TypeForm types to widen.
                            if (TypeBase.isInstance(type) && !isConstant && !isExplicitTypeAliasDeclaration(decl)) {
                                type = stripTypeForm(stripLiteralValue(type));
                            }
                        }

                        typesToCombine.push(type);

                        if (isSpeculativeModeInUse(decl.node)) {
                            includesSpeculativeResult = true;
                        }
                    } else {
                        isIncomplete = true;
                    }
                } catch (e: any) {
                    // Clean up the stack before rethrowing, but only if we haven't already popped.
                    if (!symbolPopped) {
                        popSymbolResolution(symbol);
                    }
                    throw e;
                }
            } else {
                if (decl.type === DeclarationType.Class) {
                    const classTypeInfo = getTypeOfClass(decl.node);
                    if (classTypeInfo?.decoratedType) {
                        typesToCombine.push(classTypeInfo.decoratedType);
                    }
                }

                isIncomplete = true;

                // Note that at least one decl could not be evaluated because
                // it was already in the process of being evaluated.
                sawPendingEvaluation = true;
            }
        });

        // How many times have we already attempted to evaluate this declaration already?
        const cacheEntries = effectiveTypeCache.get(symbol.id);
        const evaluationAttempts = (cacheEntries?.get(typeCacheKey)?.evaluationAttempts ?? 0) + 1;

        let type: Type;

        if (typesToCombine.length > 0) {
            // Ignore the pending evaluation flag if we've already attempted the
            // type evaluation many times because this probably means there's a
            // cyclical dependency that cannot be broken.
            isIncomplete = sawPendingEvaluation && evaluationAttempts < maxEffectiveTypeEvaluationAttempts;

            type = combineTypes(typesToCombine);
        } else {
            // We can encounter this situation in the case of a bare ClassVar annotation.
            if (symbol.isClassVar()) {
                type = UnknownType.create();
                isIncomplete = false;
            } else {
                type = UnboundType.create();
            }
        }

        return { type, isIncomplete, includesSpeculativeResult, evaluationAttempts };
    }

    // If a declaration has an explicit type (e.g. a variable with an annotation),
    // this function evaluates the type and returns it. If the symbol has no
    // explicit declared type, its type will need to be inferred instead. In some
    // cases, non-type information (such as Final or ClassVar attributes) may be
    // provided, but type inference is still required. In such cases, the attributes
    // are returned as flags.
    function getDeclaredTypeOfSymbol(symbol: Symbol, usageNode?: NameNode): DeclaredSymbolTypeInfo {
        const synthesizedType = symbol.getSynthesizedType()?.type;
        if (synthesizedType) {
            return { type: synthesizedType };
        }

        let typedDecls = symbol.getTypedDeclarations();

        if (typedDecls.length === 0) {
            // If the symbol has no type declaration but is assigned many times,
            // treat it as though it has an explicit type annotation of "Unknown".
            // This will avoid a pathological performance condition for unannotated
            // code that reassigns the same variable hundreds of times. If the symbol
            // effectively has an "Any" annotation, it won't be narrowed.
            if (symbol.getDeclarations().length > maxDeclarationsToUseForInference) {
                return { type: UnknownType.create() };
            }

            // There was no declaration with a defined type.
            return { type: undefined };
        }

        // If there is more than one typed decl, filter out any that are not
        // reachable from the usage node (if specified). This can happen in
        // cases where a property symbol is redefined to add a setter, deleter,
        // etc.
        let exceedsMaxDecls = false;
        if (usageNode && typedDecls.length > 1) {
            if (typedDecls.length > maxTypedDeclsPerSymbol) {
                // If there are too many typed decls, don't bother filtering them
                // because this can be very expensive. Simply use the last one
                // in this case.
                typedDecls = [typedDecls[typedDecls.length - 1]];
                exceedsMaxDecls = true;
            } else {
                const filteredTypedDecls = typedDecls.filter((decl) => {
                    if (decl.type !== DeclarationType.Alias) {
                        // Is the declaration in the same execution scope as the "usageNode" node?
                        const usageScope = ParseTreeUtils.getExecutionScopeNode(usageNode);
                        const declScope = ParseTreeUtils.getExecutionScopeNode(decl.node);

                        if (usageScope === declScope) {
                            // For typed declarations we use the precise flow-graph reachability
                            // check rather than a simple position comparison, because typed decls
                            // (e.g. explicit annotations) can legitimately appear after the usage
                            // in the source text (e.g. a class attribute annotated below a method
                            // that references it) and must not be excluded by position alone.
                            if (!isFlowPathBetweenNodes(decl.node, usageNode, /* allowSelf */ false)) {
                                return false;
                            }
                        }
                    }
                    return true;
                });

                if (filteredTypedDecls.length === 0) {
                    return { type: UnboundType.create() };
                }

                typedDecls = filteredTypedDecls;
            }
        }

        // Start with the last decl. If that's already being resolved,
        // use the next-to-last decl, etc. This can happen when resolving
        // property methods. Often the setter method is defined in reference to
        // the initial property, which defines the getter method with the same
        // symbol name.
        let declIndex = typedDecls.length - 1;
        while (declIndex >= 0) {
            const decl = typedDecls[declIndex];

            // If there's a partially-constructed type that is allowed
            // for recursive symbol resolution, return it as the resolved type.
            const partialType = getSymbolResolutionPartialType(symbol, decl);
            if (partialType) {
                return { type: partialType };
            }

            if (getIndexOfSymbolResolution(symbol, decl) < 0) {
                if (pushSymbolResolution(symbol, decl)) {
                    try {
                        const declaredTypeInfo = getTypeForDeclaration(decl);

                        // If there was recursion detected, don't use this declaration.
                        // The exception is it's a class declaration because getTypeOfClass
                        // handles recursion by populating a partially-created class type
                        // in the type cache. This exception is required to handle the
                        // circular dependency between the "type" and "object" classes in
                        // builtins.pyi (since "object" is a "type" and "type" is an "object").
                        if (popSymbolResolution(symbol) || decl.type === DeclarationType.Class) {
                            return declaredTypeInfo;
                        }
                    } catch (e: any) {
                        // Clean up the stack before rethrowing.
                        popSymbolResolution(symbol);
                        throw e;
                    }
                }
            }

            declIndex--;
        }

        return { type: undefined, exceedsMaxDecls };
    }

    function inferReturnTypeIfNecessary(type: Type) {
        if (isFunction(type)) {
            getEffectiveReturnType(type);
        } else if (isOverloaded(type)) {
            OverloadedType.getOverloads(type).forEach((overload) => {
                getEffectiveReturnType(overload);
            });

            const impl = OverloadedType.getImplementation(type);
            if (impl && isFunction(impl)) {
                getEffectiveReturnType(impl);
            }
        }
    }

    function getEffectiveReturnType(type: FunctionType): Type {
        return getEffectiveReturnTypeResult(type).type;
    }

    function getInferredReturnType(type: FunctionType): Type {
        return getInferredReturnTypeResult(type).type;
    }

    // Returns the return type of the function. If the type is explicitly provided in
    // a type annotation, that type is returned. If not, an attempt is made to infer
    // the return type. If a list of args is provided, the inference logic may take
    // into account argument types to infer the return type.
    function getEffectiveReturnTypeResult(type: FunctionType, options?: EffectiveReturnTypeOptions): TypeResult {
        const specializedReturnType = FunctionType.getEffectiveReturnType(type, /* includeInferred */ false);
        if (specializedReturnType && !isUnknown(specializedReturnType)) {
            return { type: specializedReturnType };
        }

        return getInferredReturnTypeResult(type, options?.callSiteInfo);
    }

    function _getInferredReturnTypeResult(type: FunctionType, callSiteInfo?: CallSiteEvaluationInfo): TypeResult {
        let returnType: Type | undefined;
        let isIncomplete = false;
        const analyzeUnannotatedFunctions = true;

        // Don't attempt to infer the return type for a stub file.
        if (FunctionType.isStubDefinition(type)) {
            return { type: UnknownType.create() };
        }

        // Don't infer the return type for a ParamSpec value.
        if (FunctionType.isParamSpecValue(type)) {
            return { type: UnknownType.create() };
        }

        // Don't infer the return type for an overloaded function (unless it's synthesized,
        // which is needed for proper operation of the __get__ method in properties).
        if (FunctionType.isOverloaded(type) && !FunctionType.isSynthesizedMethod(type)) {
            return { type: UnknownType.create() };
        }

        const evalCount = type.shared.inferredReturnType?.evaluationCount ?? 0;

        // If the return type has already been lazily evaluated,
        // don't bother computing it again.
        if (type.shared.inferredReturnType && !type.shared.inferredReturnType.isIncomplete) {
            returnType = type.shared.inferredReturnType.type;
        } else if (evalCount > maxReturnTypeInferenceAttempts) {
            // Detect a case where a return type won't converge because of recursion.
            returnType = UnknownType.create();
        } else {
            // Don't bother inferring the return type of __init__ because it's
            // always None.
            if (FunctionType.isInstanceMethod(type) && type.shared.name === '__init__') {
                returnType = getNoneType();
            } else if (type.shared.declaration) {
                const functionNode = type.shared.declaration.node;
                const skipUnannotatedFunction =
                    !AnalyzerNodeInfo.getFileInfo(functionNode).diagnosticRuleSet.analyzeUnannotatedFunctions &&
                    ParseTreeUtils.isUnannotatedFunction(functionNode);

                // Skip return type inference if we are in "skip unannotated function" mode.
                if (!skipUnannotatedFunction && !checkCodeFlowTooComplex(functionNode.d.suite)) {
                    const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode);

                    // For very complex functions that have no annotated parameter types,
                    // don't attempt to infer the return type because it can be extremely
                    // expensive.
                    const parametersAreAnnotated =
                        type.shared.parameters.length <= 1 ||
                        type.shared.parameters.some((param) => FunctionParam.isTypeDeclared(param));

                    if (parametersAreAnnotated || codeFlowComplexity < maxReturnTypeInferenceCodeFlowComplexity) {
                        // Temporarily disable speculative mode while we
                        // lazily evaluate the return type.
                        let returnTypeResult: TypeResult | undefined;
                        disableSpeculativeMode(() => {
                            returnTypeResult = inferFunctionReturnType(
                                functionNode,
                                FunctionType.isAbstractMethod(type),
                                callSiteInfo?.errorNode
                            );
                        });

                        returnType = returnTypeResult?.type;
                        if (returnTypeResult?.isIncomplete) {
                            isIncomplete = true;
                        }
                    }
                }
            }

            if (!returnType) {
                returnType = UnknownType.create();
            }

            // Externalize any TypeVars that appear in the type.
            const typeVarScopes: TypeVarScopeId[] = [];
            if (type.shared.typeVarScopeId) {
                typeVarScopes.push(type.shared.typeVarScopeId);
            }
            if (type.shared.methodClass?.shared.typeVarScopeId) {
                typeVarScopes.push(type.shared.methodClass.shared.typeVarScopeId);
            }
            returnType = makeTypeVarsFree(returnType, typeVarScopes);

            // Cache the type for next time.
            type.shared.inferredReturnType = { type: returnType, isIncomplete, evaluationCount: evalCount + 1 };
        }

        // If the type is partially unknown and the function has one or more unannotated
        // params, try to analyze the function with the provided argument types and
        // attempt to do a better job at inference.
        if (
            !isIncomplete &&
            analyzeUnannotatedFunctions &&
            isPartlyUnknown(returnType) &&
            FunctionType.hasUnannotatedParams(type) &&
            !FunctionType.isStubDefinition(type) &&
            !FunctionType.isPyTypedDefinition(type) &&
            callSiteInfo
        ) {
            let hasDecorators = false;
            let isAsync = false;
            const declNode = type.shared.declaration?.node;
            if (declNode) {
                if (declNode.d.decorators.length > 0) {
                    hasDecorators = true;
                }
                if (declNode.d.isAsync) {
                    isAsync = true;
                }
            }

            // We can't use this technique if decorators or async are used because they
            // would need to be applied to the inferred return type.
            if (!hasDecorators && !isAsync) {
                const contextualReturnType = inferReturnTypeForCallSite(type, callSiteInfo);
                if (contextualReturnType) {
                    returnType = contextualReturnType;

                    if (type.shared.declaration?.node) {
                        // Externalize any TypeVars that appear in the type.
                        const liveScopeIds = ParseTreeUtils.getTypeVarScopesForNode(type.shared.declaration.node);
                        returnType = makeTypeVarsFree(returnType, liveScopeIds);
                    }
                }
            }
        }

        return { type: returnType, isIncomplete };
    }

    function inferReturnTypeForCallSite(type: FunctionType, callSiteInfo: CallSiteEvaluationInfo): Type | undefined {
        const args = callSiteInfo.args;
        let contextualReturnType: Type | undefined;

        if (!type.shared.declaration) {
            return undefined;
        }
        const functionNode = type.shared.declaration.node;
        const codeFlowComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(functionNode);

        if (codeFlowComplexity >= maxReturnCallSiteTypeInferenceCodeFlowComplexity) {
            return undefined;
        }

        // If an arg hasn't been matched to a specific named parameter,
        // it's an unpacked value that corresponds to multiple parameters.
        // That's an edge case that we don't handle here.
        if (args.some((arg) => !arg.paramName)) {
            return undefined;
        }

        // Detect recurrence. If a function invokes itself either directly
        // or indirectly, we won't attempt to infer contextual return
        // types any further.
        if (returnTypeInferenceContextStack.some((context) => context.functionNode === functionNode)) {
            return undefined;
        }

        const functionTypeResult = getTypeOfFunction(functionNode);
        if (!functionTypeResult) {
            return undefined;
        }

        // Very complex functions with many arguments can take a long time to analyze,
        // so we'll use a heuristic and avoiding this inference technique for any
        // call site that involves too many arguments.
        if (args.length > maxReturnTypeInferenceArgCount) {
            return undefined;
        }

        // Don't explore arbitrarily deep in the call graph.
        if (returnTypeInferenceContextStack.length >= maxReturnTypeInferenceStackSize) {
            return undefined;
        }

        const paramTypes: Type[] = [];
        let isResultFromCache = false;

        // If the call is located in a loop, don't use literal argument types
        // for the same reason we don't do literal math in loops.
        const stripLiteralArgTypes = ParseTreeUtils.isWithinLoop(callSiteInfo.errorNode);

        // Suppress diagnostics because we don't want to generate errors.
        suppressDiagnostics(functionNode, () => {
            // Allocate a new temporary type cache for the context of just
            // this function so we can analyze it separately without polluting
            // the main type cache.
            const prevTypeCache = returnTypeInferenceTypeCache;
            returnTypeInferenceContextStack.push({
                functionNode,
                codeFlowAnalyzer: codeFlowEngine.createCodeFlowAnalyzer(),
            });

            try {
                returnTypeInferenceTypeCache = new Map<number, TypeCacheEntry>();

                let allArgTypesAreUnknown = true;
                functionNode.d.params.forEach((param, index) => {
                    if (param.d.name) {
                        let paramType: Type | undefined;
                        const arg = args.find((arg) => param.d.name!.d.value === arg.paramName);

                        if (arg && arg.argument.valueExpression) {
                            paramType = getTypeOfExpression(arg.argument.valueExpression).type;
                            if (!isUnknown(paramType)) {
                                allArgTypesAreUnknown = false;
                            }
                        } else if (param.d.defaultValue) {
                            paramType = getTypeOfExpression(param.d.defaultValue).type;
                            if (!isUnknown(paramType)) {
                                allArgTypesAreUnknown = false;
                            }
                        } else if (index === 0) {
                            // If this is an instance or class method, use the implied
                            // parameter type for the "self" or "cls" parameter.
                            if (
                                FunctionType.isInstanceMethod(functionTypeResult.functionType) ||
                                FunctionType.isClassMethod(functionTypeResult.functionType)
                            ) {
                                if (functionTypeResult.functionType.shared.parameters.length > 0) {
                                    if (functionNode.d.params[0].d.name) {
                                        paramType = FunctionType.getParamType(functionTypeResult.functionType, 0);
                                    }
                                }
                            }
                        }

                        if (!paramType) {
                            paramType = UnknownType.create();
                        }

                        if (stripLiteralArgTypes) {
                            paramType = stripTypeForm(
                                convertSpecialFormToRuntimeValue(
                                    stripLiteralValue(paramType),
                                    EvalFlags.None,
                                    /* convertModule */ true
                                )
                            );
                        }

                        paramTypes.push(paramType);
                        writeTypeCache(param.d.name, { type: paramType }, EvalFlags.None);
                    }
                });

                // Don't bother trying to determine the contextual return
                // type if none of the argument types are known.
                if (!allArgTypesAreUnknown) {
                    // See if the return type is already cached. If so, skip the
                    // inference step, which is potentially very expensive.
                    const cacheEntry = functionTypeResult.functionType.priv.callSiteReturnTypeCache?.find((entry) => {
                        return (
                            entry.paramTypes.length === paramTypes.length &&
                            entry.paramTypes.every((t, i) => isTypeSame(t, paramTypes[i]))
                        );
                    });

                    if (cacheEntry) {
                        contextualReturnType = cacheEntry.returnType;
                        isResultFromCache = true;
                    } else {
                        contextualReturnType = inferFunctionReturnType(
                            functionNode,
                            FunctionType.isAbstractMethod(type),
                            callSiteInfo?.errorNode
                        )?.type;
                    }
                }
            } finally {
                returnTypeInferenceContextStack.pop();
                returnTypeInferenceTypeCache = prevTypeCache;
            }
        });

        if (contextualReturnType) {
            contextualReturnType = removeUnbound(contextualReturnType);

            if (!isResultFromCache) {
                // Cache the resulting type.
                if (!functionTypeResult.functionType.priv.callSiteReturnTypeCache) {
                    functionTypeResult.functionType.priv.callSiteReturnTypeCache = [];
                }
                if (
                    functionTypeResult.functionType.priv.callSiteReturnTypeCache.length >=
                    maxCallSiteReturnTypeCacheSize
                ) {
                    functionTypeResult.functionType.priv.callSiteReturnTypeCache =
                        functionTypeResult.functionType.priv.callSiteReturnTypeCache.slice(1);
                }
                functionTypeResult.functionType.priv.callSiteReturnTypeCache.push({
                    paramTypes,
                    returnType: contextualReturnType,
                });
            }

            return contextualReturnType;
        }

        return undefined;
    }

    // If the function has an explicitly-declared return type, it is returned
    // unaltered unless the function is a generator, in which case it is
    // modified to return only the return type for the generator.
    function getDeclaredReturnType(node: FunctionNode): Type | undefined {
        const functionTypeInfo = getTypeOfFunction(node);
        const returnType = functionTypeInfo?.functionType.shared.declaredReturnType;

        if (!returnType) {
            return undefined;
        }

        if (FunctionType.isGenerator(functionTypeInfo.functionType)) {
            return getDeclaredGeneratorReturnType(functionTypeInfo.functionType);
        }

        return returnType;
    }

    function getTypeOfMember(member: ClassMember): Type {
        if (isInstantiableClass(member.classType)) {
            return partiallySpecializeType(
                getEffectiveTypeOfSymbol(member.symbol),
                member.classType,
                getTypeClassType(),
                /* selfClass */ undefined
            );
        }
        return UnknownType.create();
    }

    function getTypeOfMemberInternal(
        errorNode: ExpressionNode | undefined,
        member: ClassMember,
        selfClass: ClassType | TypeVarType | undefined,
        flags: MemberAccessFlags
    ): TypeResult | undefined {
        if (isAnyOrUnknown(member.classType)) {
            return {
                type: member.classType,
                isIncomplete: false,
            };
        }

        if (!isInstantiableClass(member.classType)) {
            return undefined;
        }

        const typeResult = getEffectiveTypeOfSymbolForUsage(member.symbol);

        if (!typeResult) {
            return undefined;
        }

        // Report inappropriate use of variables in type expressions.
        if ((flags & MemberAccessFlags.TypeExpression) !== 0 && errorNode) {
            typeResult.type = validateSymbolIsTypeExpression(
                errorNode,
                typeResult.type,
                !!typeResult.includesVariableDecl
            );
        }

        // If the type is a function or overloaded function, infer
        // and cache the return type if necessary. This needs to be done
        // prior to specializing.
        inferReturnTypeIfNecessary(typeResult.type);

        // Check for ambiguous accesses to attributes with generic types?
        if (
            errorNode &&
            selfClass &&
            isClass(selfClass) &&
            member.isInstanceMember &&
            isClass(member.unspecializedClassType) &&
            (flags & MemberAccessFlags.DisallowGenericInstanceVariableAccess) !== 0 &&
            requiresSpecialization(typeResult.type, { ignoreSelf: true, ignoreImplicitTypeArgs: true })
        ) {
            const specializedType = partiallySpecializeType(
                typeResult.type,
                member.unspecializedClassType,
                getTypeClassType(),
                selfSpecializeClass(selfClass, { overrideTypeArgs: true })
            );

            if (
                findSubtype(
                    specializedType,
                    (subtype) =>
                        !isFunctionOrOverloaded(subtype) &&
                        requiresSpecialization(subtype, { ignoreSelf: true, ignoreImplicitTypeArgs: true })
                )
            ) {
                addDiagnostic(
                    DiagnosticRule.reportGeneralTypeIssues,
                    LocMessage.genericInstanceVariableAccess(),
                    errorNode
                );
            }
        }

        return {
            type: partiallySpecializeType(typeResult.type, member.classType, getTypeClassType(), selfClass),
            isIncomplete: !!typeResult.isIncomplete,
        };
    }

    function assignClass(
        destType: ClassType,
        srcType: ClassType,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker | undefined,
        flags: AssignTypeFlags,
        recursionCount: number,
        reportErrorsUsingObjType: boolean
    ): boolean {
        // If the source or dest types are partially evaluated (i.e. they are in the
        // process of being constructed), assume they are assignable rather than risk
        // emitting false positives.
        if (ClassType.isHierarchyPartiallyEvaluated(destType) || ClassType.isHierarchyPartiallyEvaluated(srcType)) {
            return true;
        }

        // Handle typed dicts. They also use a form of structural typing for type
        // checking, as defined in PEP 589.
        if (ClassType.isTypedDictClass(srcType)) {
            if (ClassType.isTypedDictClass(destType) && !ClassType.isSameGenericClass(destType, srcType)) {
                if (
                    !assignTypedDictToTypedDict(
                        evaluatorInterface,
                        destType,
                        srcType,
                        diag,
                        constraints,
                        flags,
                        recursionCount
                    )
                ) {
                    return false;
                }

                // If invariance is being enforced, the two TypedDicts must be assignable to each other.
                if ((flags & AssignTypeFlags.Invariant) !== 0) {
                    return assignTypedDictToTypedDict(
                        evaluatorInterface,
                        srcType,
                        destType,
                        /* diag */ undefined,
                        /* constraints */ undefined,
                        flags,
                        recursionCount
                    );
                }

                return true;
            }

            // Handle some special cases where a TypedDict can act like
            // a Mapping[str, T] or a dict[str, T].
            if (ClassType.isBuiltIn(destType, 'Mapping')) {
                const mappingValueType = getTypedDictMappingEquivalent(evaluatorInterface, srcType);

                if (
                    mappingValueType &&
                    prefetched?.mappingClass &&
                    isInstantiableClass(prefetched.mappingClass) &&
                    prefetched?.strClass &&
                    isInstantiableClass(prefetched.strClass)
                ) {
                    srcType = ClassType.specialize(prefetched.mappingClass, [
                        ClassType.cloneAsInstance(prefetched.strClass),
                        mappingValueType,
                    ]);
                }
            } else if (ClassType.isBuiltIn(destType, ['dict', 'MutableMapping'])) {
                const dictValueType = getTypedDictDictEquivalent(evaluatorInterface, srcType, recursionCount);

                if (
                    dictValueType &&
                    prefetched?.dictClass &&
                    isInstantiableClass(prefetched.dictClass) &&
                    prefetched.strClass &&
                    isInstantiableClass(prefetched.strClass)
                ) {
                    srcType = ClassType.specialize(prefetched.dictClass, [
                        ClassType.cloneAsInstance(prefetched.strClass),
                        dictValueType,
                    ]);
                }
            }
        }

        // Handle special-case type promotions.
        if (destType.priv.includePromotions) {
            const promotionList = typePromotions.get(destType.shared.fullName);
            if (
                promotionList &&
                promotionList.some((srcName) =>
                    srcType.shared.mro.some((mroClass) => isClass(mroClass) && srcName === mroClass.shared.fullName)
                )
            ) {
                if ((flags & AssignTypeFlags.Invariant) === 0) {
                    return true;
                }
            }
        }

        // Is it a structural type (i.e. a protocol)? If so, we need to
        // perform a member-by-member check.
        const inheritanceChain: InheritanceChain = [];
        const isDerivedFrom = ClassType.isDerivedFrom(srcType, destType, inheritanceChain);

        // Use the slow path for protocols if the dest doesn't explicitly
        // derive from the source. We also need to use this path if we're
        // testing to see if the metaclass matches the protocol.
        if (ClassType.isProtocolClass(destType) && !isDerivedFrom) {
            if (
                !assignClassToProtocol(
                    evaluatorInterface,
                    destType,
                    ClassType.cloneAsInstance(srcType),
                    diag?.createAddendum(),
                    constraints,
                    flags,
                    recursionCount
                )
            ) {
                diag?.addMessage(
                    LocAddendum.protocolIncompatible().format({
                        sourceType: printType(convertToInstance(srcType)),
                        destType: printType(convertToInstance(destType)),
                    })
                );
                return false;
            }

            return true;
        }

        if ((flags & AssignTypeFlags.Invariant) === 0 || ClassType.isSameGenericClass(srcType, destType)) {
            if (isDerivedFrom) {
                assert(inheritanceChain.length > 0);

                if (
                    assignClassWithTypeArgs(
                        destType,
                        srcType,
                        inheritanceChain,
                        diag?.createAddendum(),
                        constraints,
                        flags,
                        recursionCount
                    )
                ) {
                    return true;
                }
            }
        }

        // Everything is assignable to an object.
        if (ClassType.isBuiltIn(destType, 'object')) {
            if ((flags & AssignTypeFlags.Invariant) === 0) {
                return true;
            }
        }

        if (diag) {
            const destErrorType = reportErrorsUsingObjType ? ClassType.cloneAsInstance(destType) : destType;
            const srcErrorType = reportErrorsUsingObjType ? ClassType.cloneAsInstance(srcType) : srcType;

            let destErrorTypeText = printType(destErrorType);
            let srcErrorTypeText = printType(srcErrorType);

            // If the text is the same, use the fully-qualified name rather than the short name.
            if (destErrorTypeText === srcErrorTypeText && destType.shared.fullName && srcType.shared.fullName) {
                destErrorTypeText = destType.shared.fullName;
                srcErrorTypeText = srcType.shared.fullName;
            }

            diag?.addMessage(
                LocAddendum.typeIncompatible().format({
                    sourceType: srcErrorTypeText,
                    destType: destErrorTypeText,
                })
            );

            // Tell the user about the disableBytesTypePromotions if that is involved.
            if (ClassType.isBuiltIn(destType, 'bytes')) {
                const promotions = typePromotions.get(destType.shared.fullName);
                if (promotions && promotions.some((name) => name === srcType.shared.fullName)) {
                    diag?.addMessage(LocAddendum.bytesTypePromotions());
                }
            }
        }

        return false;
    }

    // This function is used to validate or infer the variance of type
    // parameters within a class. If ignoreBaseClassVariance is set to false,
    // the type parameters for the base class are honored. This is useful for
    // variance inference (PEP 695). For validation of protocol variance, we
    // want to ignore the variance for all base classes in the class hierarchy.
    function assignClassToSelf(
        destType: ClassType,
        srcType: ClassType,
        assumedVariance: Variance,
        ignoreBaseClassVariance = true,
        recursionCount = 0
    ): boolean {
        assert(ClassType.isSameGenericClass(destType, srcType));
        assert(destType.shared.typeParams.length > 0);

        srcType = makeTypeVarsBound(srcType, getTypeVarScopeIds(srcType));
        destType = makeTypeVarsBound(destType, getTypeVarScopeIds(destType));

        let isAssignable = true;

        // Use a try/catch block here to make sure that we reset
        // the assignClassToSelfClass to undefined if an exception occurs.
        try {
            // Stash the current class type so any references to it are treated
            // as though all type parameters are invariant.
            assignClassToSelfStack.push({ class: destType, assumedVariance });

            ClassType.getSymbolTable(destType).forEach((symbol, name) => {
                if (!isAssignable || symbol.isIgnoredForProtocolMatch()) {
                    return;
                }

                // Constructor methods are exempt from variance calculations.
                if (name === '__new__' || name === '__init__') {
                    return;
                }

                const memberInfo = lookUpClassMember(srcType, name);
                assert(memberInfo !== undefined);

                let destMemberType = getEffectiveTypeOfSymbol(symbol);
                const srcMemberType = getTypeOfMember(memberInfo);
                destMemberType = partiallySpecializeType(destMemberType, destType, getTypeClassType());

                // Properties require special processing.
                if (
                    isClassInstance(destMemberType) &&
                    ClassType.isPropertyClass(destMemberType) &&
                    isClassInstance(srcMemberType) &&
                    ClassType.isPropertyClass(srcMemberType)
                ) {
                    if (
                        !assignProperty(
                            evaluatorInterface,
                            ClassType.cloneAsInstantiable(destMemberType),
                            ClassType.cloneAsInstantiable(srcMemberType),
                            destType,
                            srcType,
                            /* diag */ undefined,
                            /* constraints */ undefined,
                            /* selfConstraints */ undefined,
                            recursionCount
                        )
                    ) {
                        isAssignable = false;
                    }
                } else {
                    const primaryDecl = symbol.getDeclarations()[0];

                    let flags = AssignTypeFlags.Default;
                    if (
                        primaryDecl?.type === DeclarationType.Variable &&
                        !isFinalVariableDeclaration(primaryDecl) &&
                        !isMemberReadOnly(destType, name)
                    ) {
                        // Class and instance variables that are mutable need to
                        // enforce invariance. We will exempt variables that are
                        // private or protected, since these are presumably
                        // not modifiable outside of the class.
                        if (!isPrivateOrProtectedName(name)) {
                            flags |= AssignTypeFlags.Invariant;
                        }
                    }

                    if (
                        !assignType(
                            destMemberType,
                            srcMemberType,
                            /* diag */ undefined,
                            /* constraints */ undefined,
                            flags | AssignTypeFlags.SkipSelfClsParamCheck,
                            recursionCount
                        )
                    ) {
                        isAssignable = false;
                    }
                }
            });

            if (!isAssignable) {
                return false;
            }

            // Now handle generic base classes.
            destType.shared.baseClasses.forEach((baseClass) => {
                if (
                    !isAssignable ||
                    !isInstantiableClass(baseClass) ||
                    ClassType.isBuiltIn(baseClass, ['object', 'Protocol', 'Generic']) ||
                    baseClass.shared.typeParams.length === 0
                ) {
                    return;
                }

                const specializedDestBaseClass = specializeForBaseClass(destType, baseClass);
                const specializedSrcBaseClass = specializeForBaseClass(srcType, baseClass);

                if (!ignoreBaseClassVariance) {
                    specializedDestBaseClass.shared.typeParams.forEach((param, index) => {
                        if (isParamSpec(param) || isTypeVarTuple(param) || param.shared.isSynthesized) {
                            return;
                        }

                        if (
                            !specializedSrcBaseClass.priv.typeArgs ||
                            index >= specializedSrcBaseClass.priv.typeArgs.length ||
                            !specializedDestBaseClass.priv.typeArgs ||
                            index >= specializedDestBaseClass.priv.typeArgs.length
                        ) {
                            return;
                        }

                        const paramVariance = param.shared.declaredVariance;
                        if (isTypeVar(specializedSrcBaseClass.priv.typeArgs[index])) {
                            if (paramVariance === Variance.Invariant || paramVariance === Variance.Contravariant) {
                                isAssignable = false;
                                return;
                            }
                        }

                        if (isTypeVar(specializedDestBaseClass.priv.typeArgs[index])) {
                            if (paramVariance === Variance.Invariant || paramVariance === Variance.Covariant) {
                                isAssignable = false;
                                return;
                            }
                        }
                    });
                }

                if (!isAssignable) {
                    return;
                }

                // Handle tuples specially since their type arguments are variadic.
                if (ClassType.isTupleClass(specializedDestBaseClass)) {
                    return;
                }

                if (
                    !assignClassToSelf(
                        specializedDestBaseClass,
                        specializedSrcBaseClass,
                        assumedVariance,
                        ignoreBaseClassVariance,
                        recursionCount
                    )
                ) {
                    isAssignable = false;
                }
            });

            return isAssignable;
        } finally {
            assignClassToSelfStack.pop();
        }
    }

    // Determines whether the specified type can be assigned to the
    // specified inheritance chain, taking into account its type arguments.
    function assignClassWithTypeArgs(
        destType: ClassType,
        srcType: ClassType,
        inheritanceChain: InheritanceChain,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker | undefined,
        flags: AssignTypeFlags,
        recursionCount: number
    ): boolean {
        let curSrcType = srcType;
        let prevSrcType: ClassType | undefined;

        inferVarianceForClass(destType);

        // If we're enforcing invariance, literal types must match.
        if ((flags & AssignTypeFlags.Invariant) !== 0) {
            const srcIsLiteral = isLiteralLikeType(srcType);
            const destIsLiteral = isLiteralLikeType(destType);

            if (srcIsLiteral !== destIsLiteral) {
                return false;
            }
        }

        for (let ancestorIndex = inheritanceChain.length - 1; ancestorIndex >= 0; ancestorIndex--) {
            const ancestorType = inheritanceChain[ancestorIndex];

            // If we've hit an "unknown", all bets are off, and we need to assume
            // that the type is assignable. If the destType is marked "@final",
            // we should be able to assume that it's not assignable, but we can't do
            // this in the general case because it breaks assumptions with the
            // NotImplemented symbol exported by typeshed's builtins.pyi. Instead,
            // we'll special-case only None.
            if (isUnknown(ancestorType)) {
                return !isNoneTypeClass(destType);
            }

            // If this isn't the first time through the loop, specialize
            // for the next ancestor in the chain.
            if (ancestorIndex < inheritanceChain.length - 1) {
                // If the curSrcType is a NamedTuple and the ancestorType is a tuple,
                // we need to handle this as a special case because the NamedTuple may
                // include typeParams from its parent class.
                let effectiveCurSrcType = curSrcType;
                if (
                    ClassType.isBuiltIn(curSrcType, 'NamedTuple') &&
                    ClassType.isBuiltIn(ancestorType, 'tuple') &&
                    prevSrcType
                ) {
                    effectiveCurSrcType = prevSrcType;
                }

                curSrcType = specializeForBaseClass(effectiveCurSrcType, ancestorType);
            }

            // If there are no type parameters on this class, we're done.
            const ancestorTypeParams = ClassType.getTypeParams(ancestorType);
            if (ancestorTypeParams.length === 0) {
                continue;
            }

            // If the dest type isn't specialized, there are no type args to validate.
            if (!ancestorType.priv.typeArgs) {
                return true;
            }

            prevSrcType = curSrcType;
        }

        // Handle tuple, which supports a variable number of type arguments.
        if (destType.priv.tupleTypeArgs && curSrcType.priv.tupleTypeArgs) {
            return assignTupleTypeArgs(
                evaluatorInterface,
                destType,
                curSrcType,
                diag,
                constraints,
                flags,
                recursionCount
            );
        }

        if (destType.priv.typeArgs) {
            // If the dest type is specialized, make sure the specialized source
            // type arguments are assignable to the dest type arguments.
            return assignTypeArgs(
                destType,
                curSrcType,
                // Don't emit a diag addendum if we're in an invariant context. It's
                // sufficient to simply indicate that the types are not the same
                // in this case. Adding more information is unnecessary and confusing.
                (flags & AssignTypeFlags.Invariant) === 0 ? diag : undefined,
                constraints,
                flags,
                recursionCount
            );
        }

        if (constraints && curSrcType.priv.typeArgs) {
            // Populate the typeVar map with type arguments of the source.
            const srcTypeArgs = curSrcType.priv.typeArgs;
            for (let i = 0; i < destType.shared.typeParams.length; i++) {
                let typeArgType: Type;
                const typeParam = destType.shared.typeParams[i];
                const variance = TypeVarType.getVariance(typeParam);

                if (curSrcType.priv.tupleTypeArgs) {
                    typeArgType = convertToInstance(
                        makeTupleObject(evaluatorInterface, curSrcType.priv.tupleTypeArgs, /* isUnpacked */ true)
                    );
                } else {
                    typeArgType = i < srcTypeArgs.length ? srcTypeArgs[i] : UnknownType.create();
                }

                constraints.setBounds(
                    typeParam,
                    variance !== Variance.Contravariant ? typeArgType : undefined,
                    variance !== Variance.Covariant ? typeArgType : undefined,
                    /* retainLiterals */ true
                );
            }
        }

        return true;
    }

    function getGetterTypeFromProperty(propertyClass: ClassType): Type | undefined {
        if (!ClassType.isPropertyClass(propertyClass)) {
            return undefined;
        }

        if (propertyClass.priv.fgetInfo && isFunction(propertyClass.priv.fgetInfo.methodType)) {
            return getEffectiveReturnType(propertyClass.priv.fgetInfo.methodType);
        }

        return undefined;
    }

    function assignTypeArgs(
        destType: ClassType,
        srcType: ClassType,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker | undefined,
        flags: AssignTypeFlags,
        recursionCount: number
    ): boolean {
        assert(ClassType.isSameGenericClass(destType, srcType));

        inferVarianceForClass(destType);

        const destTypeParams = ClassType.getTypeParams(destType);
        let destTypeArgs: Type[];
        let srcTypeArgs: Type[] | undefined;

        // Are we performing protocol variance validation for this class? If so,
        // treat all of the type parameters as invariant even if they are declared
        // otherwise.
        const assignClassToSelfInfo = assignClassToSelfStack.find((info) =>
            ClassType.isSameGenericClass(info.class, destType)
        );
        const assumedVariance = assignClassToSelfInfo?.assumedVariance;

        // If either source or dest type arguments are missing, they are
        // treated as "Any", so they are assumed to be assignable.
        if (!destType.priv.typeArgs || !srcType.priv.typeArgs) {
            return true;
        }

        if (ClassType.isTupleClass(destType)) {
            destTypeArgs = destType.priv.tupleTypeArgs?.map((t) => t.type) ?? [];
            srcTypeArgs = srcType.priv.tupleTypeArgs?.map((t) => t.type);
        } else {
            destTypeArgs = destType.priv.typeArgs;
            srcTypeArgs = srcType.priv.typeArgs;
        }

        let isCompatible = true;

        srcTypeArgs?.forEach((srcTypeArg, srcArgIndex) => {
            // In most cases, the number of type args should match the number
            // of type arguments, but there are a few special cases where this
            // isn't true (e.g. assigning a Tuple[X, Y, Z] to a tuple[W]).
            const destArgIndex = srcArgIndex >= destTypeArgs.length ? destTypeArgs.length - 1 : srcArgIndex;
            const destTypeArg = destArgIndex >= 0 ? destTypeArgs[destArgIndex] : UnknownType.create();
            const destTypeParam = destArgIndex < destTypeParams.length ? destTypeParams[destArgIndex] : undefined;
            const assignmentDiag = new DiagnosticAddendum();
            const variance =
                assumedVariance ?? (destTypeParam ? TypeVarType.getVariance(destTypeParam) : Variance.Covariant);
            let effectiveFlags: AssignTypeFlags;
            let errorSource: () => ParameterizedString<{ name: string; sourceType: string; destType: string }>;
            let includeDiagAddendum = true;

            if (variance === Variance.Covariant) {
                effectiveFlags = flags | AssignTypeFlags.RetainLiteralsForTypeVar;
                errorSource = LocAddendum.typeVarIsCovariant;
            } else if (variance === Variance.Contravariant) {
                effectiveFlags = flags | AssignTypeFlags.Contravariant | AssignTypeFlags.RetainLiteralsForTypeVar;
                errorSource = LocAddendum.typeVarIsContravariant;
            } else {
                effectiveFlags = flags | AssignTypeFlags.Invariant | AssignTypeFlags.RetainLiteralsForTypeVar;
                errorSource = LocAddendum.typeVarIsInvariant;

                // Omit the diagnostic addendum for the invariant case because it's obvious
                // why two types are not the same.
                includeDiagAddendum = false;
            }

            // Special-case TypeForm to retain literals when solving TypeVars.
            if (ClassType.isBuiltIn(destType, 'TypeForm')) {
                effectiveFlags |= AssignTypeFlags.RetainLiteralsForTypeVar;
            }

            if (
                !assignType(
                    variance === Variance.Contravariant ? srcTypeArg : destTypeArg,
                    variance === Variance.Contravariant ? destTypeArg : srcTypeArg,
                    assignmentDiag,
                    constraints,
                    effectiveFlags,
                    recursionCount
                )
            ) {
                // Don't report errors with type variables in "pseudo-random"
                // classes since these type variables are not real.
                if (!ClassType.isPseudoGenericClass(destType)) {
                    if (diag) {
                        if (destTypeParam) {
                            const childDiag = diag.createAddendum();

                            childDiag.addMessage(
                                errorSource().format({
                                    name: TypeVarType.getReadableName(destTypeParam),
                                    ...printSrcDestTypes(srcTypeArg, destTypeArg),
                                })
                            );

                            if (includeDiagAddendum) {
                                childDiag.addAddendum(assignmentDiag);
                            }

                            if (isCompatible && ClassType.isSameGenericClass(destType, srcType)) {
                                // Add additional notes to help the user if this is a common type mismatch.
                                if (ClassType.isBuiltIn(destType, 'dict') && srcArgIndex === 1) {
                                    childDiag.addMessage(LocAddendum.invariantSuggestionDict());
                                } else if (ClassType.isBuiltIn(destType, 'list')) {
                                    childDiag.addMessage(LocAddendum.invariantSuggestionList());
                                } else if (ClassType.isBuiltIn(destType, 'set')) {
                                    childDiag.addMessage(LocAddendum.invariantSuggestionSet());
                                }
                            }
                        } else {
                            diag.addAddendum(assignmentDiag);
                        }
                    }
                    isCompatible = false;
                }
            }
        });

        return isCompatible;
    }

    // Determines if the source type can be assigned to the dest type.
    // If constraint are provided, type variables within the destType are
    // matched against existing type variables in the map. If a type variable
    // in the dest type is not in the type map already, it is assigned a type
    // and added to the map.
    function assignType(
        destType: Type,
        srcType: Type,
        diag?: DiagnosticAddendum,
        constraints?: ConstraintTracker,
        flags = AssignTypeFlags.Default,
        recursionCount = 0
    ): boolean {
        // Handle the case where the dest and src types are the same object.
        // We can normally shortcut this and say that they are compatible,
        // but if the type includes TypeVars, we need to go through
        // the rest of the logic.
        if (destType === srcType && !requiresSpecialization(destType)) {
            return true;
        }

        // If the source type is a special form, use the literal special form
        // class rather than the symbolic form.
        const specialForm = srcType.props?.specialForm;
        if (specialForm) {
            let isSpecialFormExempt = false;

            // A few special forms that are normally not compatible with type[T]
            // are compatible specifically in the context of isinstance and issubclass.
            if ((flags & AssignTypeFlags.AllowIsinstanceSpecialForms) !== 0) {
                if (ClassType.isBuiltIn(specialForm, ['Callable', 'UnionType', 'Generic'])) {
                    isSpecialFormExempt = true;
                }
            }

            if (!isSpecialFormExempt) {
                if (srcType.props?.typeForm && !specialForm.props?.typeForm) {
                    srcType = TypeBase.cloneWithTypeForm(specialForm, srcType.props.typeForm);
                } else {
                    srcType = specialForm;
                }
            }
        }

        // If the source is a class-like type created by a call to NewType, treat it
        // as a FunctionClass instance rather than an instantiable class for
        // purposes of assignability. This reflects its actual runtime type.
        if (isInstantiableClass(srcType) && ClassType.isNewTypeClass(srcType) && !srcType.priv.includeSubclasses) {
            if (prefetched?.functionClass && isInstantiableClass(prefetched?.functionClass)) {
                srcType = ClassType.cloneAsInstance(prefetched.functionClass);
            }
        }

        if (recursionCount > maxTypeRecursionCount) {
            return true;
        }
        recursionCount++;

        // If the source and dest refer to the recursive type aliases, handle
        // the case specially to avoid recursing down both type aliases.
        if (
            isTypeVar(destType) &&
            destType.shared.recursiveAlias &&
            isTypeVar(srcType) &&
            srcType.shared.recursiveAlias
        ) {
            const destAliasInfo = destType.props?.typeAliasInfo;
            const srcAliasInfo = srcType.props?.typeAliasInfo;

            // Do the source and dest refer to the same recursive type alias?
            if (
                destAliasInfo?.typeArgs &&
                srcAliasInfo?.typeArgs &&
                destType.shared.recursiveAlias.typeVarScopeId === srcType.shared.recursiveAlias.typeVarScopeId
            ) {
                return assignRecursiveTypeAliasToSelf(
                    destAliasInfo,
                    srcAliasInfo,
                    diag,
                    constraints,
                    flags,
                    recursionCount
                );
            } else {
                // Have we already recursed once?
                if ((flags & AssignTypeFlags.SkipRecursiveTypeCheck) !== 0) {
                    return true;
                }

                // Note that we are comparing two recursive types and do
                // not recursive more than once.
                flags |= AssignTypeFlags.SkipRecursiveTypeCheck;
            }
        }

        // If one or both of the types has an instantiable depth greater than
        // zero, convert both to instances first.
        if (TypeBase.isInstantiable(destType) && TypeBase.isInstantiable(srcType)) {
            if (TypeBase.getInstantiableDepth(destType) > 0 || TypeBase.getInstantiableDepth(srcType) > 0) {
                return assignType(
                    convertToInstance(destType),
                    convertToInstance(srcType),
                    diag,
                    constraints,
                    flags,
                    recursionCount
                );
            }
        }

        // Transform recursive type aliases if necessary.
        const transformedDestType = transformPossibleRecursiveTypeAlias(destType);
        const transformedSrcType = transformPossibleRecursiveTypeAlias(srcType);

        // Did either the source or dest include recursive type aliases?
        // If so, we could be dealing with different recursive type aliases
        // or a recursive type alias and a recursive protocol definition.
        if (
            (transformedDestType !== destType && isUnion(transformedDestType)) ||
            (transformedSrcType !== srcType && isUnion(transformedSrcType))
        ) {
            // Use a smaller recursive limit in this case to prevent runaway recursion.
            if (recursionCount > maxRecursiveTypeAliasRecursionCount) {
                // Add a special case for when the source is a str, which is itself
                // a recursive type (since it derives from Sequence[str]).
                if (isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'str') && isUnion(transformedDestType)) {
                    return transformedDestType.priv.subtypes.some(
                        (subtype) => isClassInstance(subtype) && ClassType.isBuiltIn(subtype, ['object', 'str'])
                    );
                }
                return true;
            }
        }

        destType = transformedDestType;
        srcType = transformedSrcType;

        // If the source or dest is unbound, allow the assignment. The
        // error will be reported elsewhere.
        if (isUnbound(destType) || isUnbound(srcType)) {
            return true;
        }

        if (isTypeVar(destType)) {
            if (isTypeVarSame(destType, srcType)) {
                return true;
            }

            // If the dest is a constrained or bound type variable and all of the
            // types in the source are conditioned on that same type variable
            // and have compatible types, we'll consider it assignable.
            if (assignConditionalTypeToTypeVar(destType, srcType, recursionCount)) {
                return true;
            }

            // If the source is a conditional type associated with a bound TypeVar
            // and the bound TypeVar matches the condition, the types are compatible.
            const destTypeVar = destType;
            if (
                TypeBase.isInstantiable(destType) === TypeBase.isInstantiable(srcType) &&
                srcType.props?.condition &&
                srcType.props.condition.some((cond) => {
                    return (
                        !TypeVarType.hasConstraints(cond.typeVar) &&
                        cond.typeVar.priv.nameWithScope === destTypeVar.priv.nameWithScope
                    );
                })
            ) {
                return true;
            }

            if (isUnion(srcType)) {
                const srcWithoutAny = removeFromUnion(srcType, (type) => isAnyOrUnknown(type));
                if (isTypeSame(destType, srcWithoutAny)) {
                    return true;
                }
            }

            // Handle the special case where both types are Self types. We'll allow
            // them to be treated as equivalent to handle certain common idioms.
            if (
                isTypeVar(srcType) &&
                TypeVarType.isSelf(srcType) &&
                TypeVarType.hasBound(srcType) &&
                TypeVarType.isSelf(destType) &&
                TypeVarType.hasBound(destType) &&
                TypeVarType.isBound(destType) === TypeVarType.isBound(srcType) &&
                TypeBase.isInstance(srcType) === TypeBase.isInstance(destType)
            ) {
                if ((flags & AssignTypeFlags.Contravariant) === 0 && constraints) {
                    assignTypeVar(evaluatorInterface, destType, srcType, diag, constraints, flags, recursionCount);
                }
                return true;
            }

            // If the dest is a TypeVarTuple, and the source is a tuple
            // with a single entry that is the same TypeVarTuple, it's a match.
            if (
                isTypeVarTuple(destType) &&
                isClassInstance(srcType) &&
                isTupleClass(srcType) &&
                srcType.priv.tupleTypeArgs &&
                srcType.priv.tupleTypeArgs.length === 1
            ) {
                if (isTypeSame(destType, srcType.priv.tupleTypeArgs[0].type, {}, recursionCount)) {
                    return true;
                }
            }

            if ((flags & AssignTypeFlags.Contravariant) === 0 || !isTypeVar(srcType)) {
                if (!assignTypeVar(evaluatorInterface, destType, srcType, diag, constraints, flags, recursionCount)) {
                    return false;
                }

                if (isAnyOrUnknown(srcType) && (flags & AssignTypeFlags.OverloadOverlap) !== 0) {
                    return false;
                }

                return true;
            }
        }

        if (isTypeVar(srcType)) {
            if ((flags & AssignTypeFlags.Contravariant) !== 0) {
                if (TypeVarType.isBound(srcType)) {
                    return assignType(
                        makeTopLevelTypeVarsConcrete(destType),
                        makeTopLevelTypeVarsConcrete(srcType),
                        diag,
                        /* constraints */ undefined,
                        flags,
                        recursionCount
                    );
                }

                if (assignTypeVar(evaluatorInterface, srcType, destType, diag, constraints, flags, recursionCount)) {
                    return true;
                }

                // If the dest type is a union, only one of the subtypes needs to match.
                let isAssignable = false;
                if (isUnion(destType)) {
                    doForEachSubtype(destType, (destSubtype) => {
                        if (
                            assignTypeVar(
                                evaluatorInterface,
                                srcType as TypeVarType,
                                destSubtype,
                                diag,
                                constraints,
                                flags,
                                recursionCount
                            )
                        ) {
                            isAssignable = true;
                        }
                    });
                }
                return isAssignable;
            }

            if ((flags & AssignTypeFlags.Invariant) !== 0) {
                if (isAnyOrUnknown(destType)) {
                    return true;
                }

                // If the source is a ParamSpec and the dest is a "...", this is
                // effectively like an "Any" signature, so we'll treat it as though
                // it's Any.
                if (
                    isParamSpec(srcType) &&
                    isFunction(destType) &&
                    FunctionType.isGradualCallableForm(destType) &&
                    destType.shared.parameters.length <= 2
                ) {
                    return true;
                }

                // If the source is an unpacked TypeVarTuple and the dest is a
                // *tuple[Any, ...], we'll treat it as compatible.
                if (
                    isUnpackedTypeVarTuple(srcType) &&
                    isClassInstance(destType) &&
                    isUnpackedClass(destType) &&
                    destType.priv.tupleTypeArgs &&
                    destType.priv.tupleTypeArgs.length === 1 &&
                    destType.priv.tupleTypeArgs[0].isUnbounded &&
                    isAnyOrUnknown(destType.priv.tupleTypeArgs[0].type)
                ) {
                    return true;
                }

                if (!isUnion(destType)) {
                    diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
                    return false;
                }
            }
        }

        if (isAnyOrUnknown(destType)) {
            return true;
        }

        if (isAnyOrUnknown(srcType) && !srcType.props?.specialForm) {
            if (constraints) {
                // If it's an ellipsis type, convert it to a regular "Any"
                // type. These are functionally equivalent, but "Any" looks
                // better in the text representation.
                const typeVarSubstitution = isEllipsisType(srcType) ? AnyType.create() : srcType;
                setConstraintsForFreeTypeVars(destType, typeVarSubstitution, constraints);
            }
            if ((flags & AssignTypeFlags.OverloadOverlap) === 0) {
                return true;
            }
        }

        if (isNever(srcType)) {
            if ((flags & AssignTypeFlags.Invariant) !== 0) {
                if (isNever(destType)) {
                    return true;
                }

                diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
                return false;
            }

            if (constraints) {
                setConstraintsForFreeTypeVars(destType, UnknownType.create(), constraints);
            }
            return true;
        }

        if (isUnion(destType)) {
            // If both the source and dest are unions, use assignFromUnionType which has
            // special-case logic to handle this case.
            if (isUnion(srcType)) {
                return assignFromUnionType(destType, srcType, diag, constraints, flags, recursionCount);
            }

            const clonedConstraints = constraints?.clone();
            if (assignToUnionType(destType, srcType, /* diag */ undefined, clonedConstraints, flags, recursionCount)) {
                if (constraints && clonedConstraints) {
                    constraints.copyFromClone(clonedConstraints);
                }
                return true;
            }
        }

        const expandedSrcType = makeTopLevelTypeVarsConcrete(srcType);
        if (isUnion(expandedSrcType)) {
            return assignFromUnionType(destType, expandedSrcType, diag, constraints, flags, recursionCount);
        }

        if (isUnion(destType)) {
            return assignToUnionType(destType, srcType, diag, constraints, flags, recursionCount);
        }

        // Is the src a specialized "type" object?
        if (isClassInstance(expandedSrcType) && ClassType.isBuiltIn(expandedSrcType, 'type')) {
            const srcTypeArgs = expandedSrcType.priv.typeArgs;
            let typeTypeArg: Type;

            if (srcTypeArgs && srcTypeArgs.length >= 1) {
                typeTypeArg = srcTypeArgs[0];
            } else {
                typeTypeArg = UnknownType.create();
            }

            if (isAnyOrUnknown(typeTypeArg)) {
                if (isEffectivelyInstantiable(destType)) {
                    return true;
                }
            } else if (isClassInstance(typeTypeArg) || isTypeVar(typeTypeArg)) {
                if (
                    assignType(
                        destType,
                        convertToInstantiable(typeTypeArg),
                        diag?.createAddendum(),
                        constraints,
                        flags,
                        recursionCount
                    )
                ) {
                    return true;
                }

                diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
                return false;
            }
        }

        if (isInstantiableClass(destType)) {
            if (isInstantiableClass(expandedSrcType)) {
                // PEP 544 says that if the dest type is a type[Proto] class,
                // the source must be a "concrete" (non-protocol) class.
                if (ClassType.isProtocolClass(destType)) {
                    if (
                        (flags & AssignTypeFlags.AllowProtocolClassSource) === 0 &&
                        ClassType.isProtocolClass(expandedSrcType) &&
                        isInstantiableClass(srcType) &&
                        !srcType.priv.includeSubclasses
                    ) {
                        diag?.addMessage(
                            LocAddendum.protocolSourceIsNotConcrete().format({
                                sourceType: printType(convertToInstance(srcType)),
                                destType: printType(destType),
                            })
                        );
                        return false;
                    }
                }

                if (ClassType.isBuiltIn(destType, 'type') && (srcType.props?.instantiableDepth ?? 0) > 0) {
                    return true;
                }

                if (isSpecialFormClass(expandedSrcType, flags)) {
                    // Special form classes are compatible only with other special form
                    // classes, not with 'object' or 'type'.
                    const destSpecialForm = destType.props?.specialForm ?? destType;
                    if (isSpecialFormClass(destSpecialForm, flags)) {
                        return assignType(destSpecialForm, expandedSrcType, diag, constraints, flags, recursionCount);
                    }
                } else if (
                    assignClass(
                        destType,
                        expandedSrcType,
                        diag,
                        constraints,
                        flags,
                        recursionCount,
                        /* reportErrorsUsingObjType */ false
                    )
                ) {
                    return true;
                }

                diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
                return false;
            }
        }

        if (isClassInstance(destType)) {
            if (ClassType.isBuiltIn(destType, 'type')) {
                if (
                    isInstantiableClass(srcType) &&
                    isSpecialFormClass(srcType, flags) &&
                    TypeBase.getInstantiableDepth(srcType) === 0
                ) {
                    return false;
                }

                if (isAnyOrUnknown(srcType) && (flags & AssignTypeFlags.OverloadOverlap) !== 0) {
                    return false;
                }

                const destTypeArgs = destType.priv.typeArgs;
                if (destTypeArgs && destTypeArgs.length >= 1) {
                    if (TypeBase.isInstance(destTypeArgs[0]) && TypeBase.isInstantiable(srcType)) {
                        return assignType(
                            destTypeArgs[0],
                            convertToInstance(srcType),
                            diag,
                            constraints,
                            flags,
                            recursionCount
                        );
                    }
                }

                // Is the dest a "type" object? Assume that all instantiable
                // types are assignable to "type".
                if (TypeBase.isInstantiable(srcType)) {
                    const isLiteral = isClass(srcType) && srcType.priv.literalValue !== undefined;
                    return !isLiteral;
                }
            }

            let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType);

            // Handle the TypeForm special form. Add a special case for
            // type[T] to be assignable to TypeForm[T].
            if (ClassType.isBuiltIn(destType, 'TypeForm')) {
                const destTypeArg =
                    destType.priv.typeArgs && destType.priv.typeArgs.length > 0
                        ? destType.priv.typeArgs[0]
                        : UnknownType.create();

                let srcTypeArg: Type | undefined;
                if (isClassInstance(concreteSrcType) && ClassType.isBuiltIn(concreteSrcType, 'type')) {
                    srcTypeArg = concreteSrcType;
                } else if (isInstantiableClass(concreteSrcType)) {
                    srcTypeArg = convertToInstance(concreteSrcType);
                }

                if (srcTypeArg) {
                    return assignType(destTypeArg, srcTypeArg, diag, constraints, flags, recursionCount);
                }
            }

            if (isClass(concreteSrcType) && TypeBase.isInstance(concreteSrcType)) {
                // Handle the case where the source is an unpacked tuple.
                if (
                    !destType.priv.isUnpacked &&
                    concreteSrcType.priv.isUnpacked &&
                    concreteSrcType.priv.tupleTypeArgs
                ) {
                    return assignType(
                        destType,
                        combineTupleTypeArgs(concreteSrcType.priv.tupleTypeArgs),
                        diag,
                        constraints,
                        flags,
                        recursionCount
                    );
                }

                if (
                    destType.priv.literalValue !== undefined &&
                    ClassType.isSameGenericClass(destType, concreteSrcType)
                ) {
                    const srcLiteral = concreteSrcType.priv.literalValue;
                    if (srcLiteral === undefined || !ClassType.isLiteralValueSame(concreteSrcType, destType)) {
                        diag?.addMessage(
                            LocAddendum.literalAssignmentMismatch().format({
                                sourceType: printType(srcType),
                                destType: printType(destType),
                            })
                        );

                        return false;
                    }
                }

                // Handle LiteralString special form.
                if (ClassType.isBuiltIn(destType, 'LiteralString')) {
                    if (
                        ClassType.isBuiltIn(concreteSrcType, 'str') &&
                        concreteSrcType.priv.literalValue !== undefined
                    ) {
                        return (flags & AssignTypeFlags.Invariant) === 0;
                    } else if (ClassType.isBuiltIn(concreteSrcType, 'LiteralString')) {
                        return true;
                    }
                } else if (
                    ClassType.isBuiltIn(concreteSrcType, 'LiteralString') &&
                    prefetched?.strClass &&
                    isInstantiableClass(prefetched.strClass) &&
                    (flags & AssignTypeFlags.Invariant) === 0
                ) {
                    concreteSrcType = ClassType.cloneAsInstance(prefetched.strClass);
                }

                if (
                    !assignClass(
                        ClassType.cloneAsInstantiable(destType),
                        ClassType.cloneAsInstantiable(concreteSrcType),
                        diag,
                        constraints,
                        flags,
                        recursionCount,
                        /* reportErrorsUsingObjType */ true
                    )
                ) {
                    return false;
                }

                return true;
            } else if (isFunctionOrOverloaded(concreteSrcType)) {
                // Is the destination a callback protocol (defined in PEP 544)?
                const destCallbackType = getCallbackProtocolType(destType, recursionCount);
                if (destCallbackType) {
                    return assignType(destCallbackType, concreteSrcType, diag, constraints, flags, recursionCount);
                }

                // All functions are considered instances of "types.FunctionType" or "types.MethodType".
                const altClass = isMethodType(concreteSrcType) ? prefetched?.methodClass : prefetched?.functionClass;
                if (altClass) {
                    return assignType(destType, convertToInstance(altClass), diag, constraints, flags, recursionCount);
                }
            } else if (isModule(concreteSrcType)) {
                // Is the destination the built-in "ModuleType"?
                if (ClassType.isBuiltIn(destType, 'ModuleType')) {
                    return true;
                }

                if (ClassType.isProtocolClass(destType)) {
                    return assignModuleToProtocol(
                        evaluatorInterface,
                        ClassType.cloneAsInstantiable(destType),
                        concreteSrcType,
                        diag,
                        constraints,
                        flags,
                        recursionCount
                    );
                }
            } else if (isInstantiableClass(concreteSrcType)) {
                // See if the destType is an instantiation of a Protocol
                // class that is effectively a function.
                const callbackType = getCallbackProtocolType(destType, recursionCount);
                if (callbackType) {
                    return assignType(callbackType, concreteSrcType, diag, constraints, flags, recursionCount);
                }

                // If the destType is an instantiation of a Protocol,
                // see if the class type itself satisfies the protocol.
                if (ClassType.isProtocolClass(destType)) {
                    return assignClassToProtocol(
                        evaluatorInterface,
                        ClassType.cloneAsInstantiable(destType),
                        concreteSrcType,
                        diag,
                        constraints,
                        flags,
                        recursionCount
                    );
                }

                // Determine if the metaclass can be assigned to the object.
                const metaclass = concreteSrcType.shared.effectiveMetaclass;
                if (metaclass) {
                    if (!isAnyOrUnknown(metaclass)) {
                        if (
                            assignClass(
                                ClassType.cloneAsInstantiable(destType),
                                metaclass,
                                /* diag */ undefined,
                                constraints,
                                flags,
                                recursionCount,
                                /* reportErrorsUsingObjType */ true
                            )
                        ) {
                            return true;
                        }
                    }
                }
            } else if (isAnyOrUnknown(concreteSrcType) && !concreteSrcType.props?.specialForm) {
                return (flags & AssignTypeFlags.OverloadOverlap) === 0;
            } else if (isUnion(concreteSrcType)) {
                return assignType(destType, concreteSrcType, diag, constraints, flags, recursionCount);
            }
        }

        if (isFunction(destType)) {
            let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType);

            if (isClassInstance(concreteSrcType)) {
                const boundMethod = getBoundMagicMethod(
                    concreteSrcType,
                    '__call__',
                    /* selfType */ undefined,
                    /* errorNode */ undefined,
                    /* diag */ undefined,
                    recursionCount
                );
                if (boundMethod) {
                    concreteSrcType = boundMethod;
                }
            }

            // If it's a class, use the constructor for type compatibility checking.
            if (isInstantiableClass(concreteSrcType) && concreteSrcType.priv.literalValue === undefined) {
                const constructor = createFunctionFromConstructor(
                    evaluatorInterface,
                    concreteSrcType,
                    isTypeVar(srcType) ? convertToInstance(srcType) : undefined,
                    recursionCount
                );
                if (constructor) {
                    concreteSrcType = constructor;

                    // The constructor conversion may result in a union of the
                    // __init__ and __new__ callables.
                    if (isUnion(concreteSrcType)) {
                        return assignType(destType, concreteSrcType, diag, constraints, flags, recursionCount);
                    }
                }
            }

            if (isAnyOrUnknown(concreteSrcType)) {
                return (flags & AssignTypeFlags.OverloadOverlap) === 0;
            }

            if (isOverloaded(concreteSrcType)) {
                // If this is the first pass of an argument assignment, skip
                // all attempts to assign an overloaded function to a function
                // because we probably don't have enough information to properly
                // filter the overloads at this time. We will do this work on
                // subsequent passes.
                if ((flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0) {
                    return true;
                }

                // Find all of the overloaded functions that match the parameters.
                const overloads = OverloadedType.getOverloads(concreteSrcType);
                const filteredOverloads: FunctionType[] = [];
                const typeVarSignatures: ConstraintSet[] = [];

                overloads.forEach((overload) => {
                    const overloadScopeId = getTypeVarScopeId(overload) ?? '';
                    const constraintsClone = constraints?.cloneWithSignature(overloadScopeId);

                    if (assignType(destType, overload, /* diag */ undefined, constraintsClone, flags, recursionCount)) {
                        filteredOverloads.push(overload);

                        if (constraintsClone) {
                            appendArray(typeVarSignatures, constraintsClone.getConstraintSets());
                        }
                    }
                });

                if (filteredOverloads.length === 0) {
                    diag?.addMessage(LocAddendum.noOverloadAssignable().format({ type: printType(destType) }));
                    return false;
                }

                if (filteredOverloads.length === 1 || (flags & AssignTypeFlags.ArgAssignmentFirstPass) === 0) {
                    if (constraints) {
                        constraints.addConstraintSets(typeVarSignatures);
                    }
                }

                return true;
            }

            if (isFunction(concreteSrcType)) {
                if (
                    assignFunction(
                        destType,
                        concreteSrcType,
                        diag?.createAddendum(),
                        constraints ?? new ConstraintTracker(),
                        flags,
                        recursionCount
                    )
                ) {
                    return true;
                }
            }
        }

        if (isOverloaded(destType)) {
            const overloadDiag = diag?.createAddendum();

            // All overloads in the dest must be assignable.
            const destOverloads = OverloadedType.getOverloads(destType);

            // If the source is also an overload with the same number of overloads,
            // there's a good chance that there's a one-to-one mapping. Try this
            // first before using an n^2 algorithm.
            if (isOverloaded(srcType)) {
                const srcOverloads = OverloadedType.getOverloads(srcType);
                if (destOverloads.length === srcOverloads.length) {
                    if (
                        destOverloads.every((destOverload, index) => {
                            const srcOverload = srcOverloads[index];
                            return assignType(
                                destOverload,
                                srcOverload,
                                /* diag */ undefined,
                                constraints,
                                flags,
                                recursionCount
                            );
                        })
                    ) {
                        return true;
                    }
                }
            }

            const isAssignable = destOverloads.every((destOverload) => {
                const result = assignType(
                    destOverload,
                    srcType,
                    overloadDiag?.createAddendum(),
                    constraints,
                    flags,
                    recursionCount
                );
                return result;
            });

            if (!isAssignable) {
                const overloads = OverloadedType.getOverloads(destType);

                if (overloadDiag && overloads.length > 0) {
                    overloadDiag.addMessage(
                        LocAddendum.overloadNotAssignable().format({
                            name: overloads[0].shared.name,
                        })
                    );
                }
                return false;
            }

            return true;
        }

        if (isClass(destType) && ClassType.isBuiltIn(destType, 'object')) {
            if ((isInstantiableClass(destType) && TypeBase.isInstantiable(srcType)) || isClassInstance(destType)) {
                if ((flags & AssignTypeFlags.Invariant) === 0) {
                    // All types (including None, Module, Overloaded) derive from object.
                    return true;
                }
            }
        }

        // Are we trying to assign None to a protocol?
        if (isNoneInstance(srcType) && isClassInstance(destType) && ClassType.isProtocolClass(destType)) {
            if (prefetched?.noneTypeClass && isInstantiableClass(prefetched.noneTypeClass)) {
                return assignClassToProtocol(
                    evaluatorInterface,
                    ClassType.cloneAsInstantiable(destType),
                    ClassType.cloneAsInstance(prefetched.noneTypeClass),
                    diag,
                    constraints,
                    flags,
                    recursionCount
                );
            }
        }

        if (isNoneInstance(destType)) {
            diag?.addMessage(LocAddendum.assignToNone());
            return false;
        }

        diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));

        return false;
    }

    // Determines whether a recursive type alias can be assigned to itself
    // given the source and dest type args and computed variance for its
    // type params.
    function assignRecursiveTypeAliasToSelf(
        destAliasInfo: TypeAliasInfo,
        srcAliasInfo: TypeAliasInfo,
        diag?: DiagnosticAddendum,
        constraints?: ConstraintTracker,
        flags = AssignTypeFlags.Default,
        recursionCount = 0
    ) {
        assert(destAliasInfo.typeArgs !== undefined);
        assert(srcAliasInfo.typeArgs !== undefined);

        let isAssignable = true;
        const srcTypeArgs = srcAliasInfo.typeArgs;
        const variances = destAliasInfo.shared.computedVariance;

        destAliasInfo.typeArgs.forEach((destTypeArg, index) => {
            const srcTypeArg = index < srcTypeArgs.length ? srcTypeArgs[index] : UnknownType.create();

            let adjFlags = flags;
            const variance = variances && index < variances.length ? variances[index] : Variance.Covariant;

            if (variance === Variance.Invariant) {
                adjFlags |= AssignTypeFlags.Invariant;
            } else if (variance === Variance.Contravariant) {
                adjFlags ^= AssignTypeFlags.Contravariant;
            }

            if (!assignType(destTypeArg, srcTypeArg, diag, constraints, adjFlags, recursionCount)) {
                isAssignable = false;
            }
        });

        return isAssignable;
    }

    function expectedTypeWantsTypeForm(expectedType: Type): boolean {
        return someSubtypes(
            expectedType,
            (subtype) => isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'TypeForm')
        );
    }

    // If the expected type is an explicit TypeForm type, see if the source
    // type has an implicit TypeForm type that can be assigned to it. If so,
    // convert to an explicit TypeForm type.
    function convertToTypeFormType(expectedType: Type, srcType: Type): Type {
        // Is the source is a TypeForm type?
        if (!srcType.props?.typeForm) {
            return srcType;
        }

        let srcTypeFormType: Type | undefined;

        // Is the source is a TypeForm type?
        if (srcType.props?.typeForm) {
            srcTypeFormType = srcType.props.typeForm;
        } else if (isClass(srcType)) {
            if (TypeBase.isInstantiable(srcType)) {
                if (!ClassType.isSpecialBuiltIn(srcType)) {
                    srcTypeFormType = ClassType.cloneAsInstance(srcType);
                }
            } else if (ClassType.isBuiltIn(srcType, 'type')) {
                srcTypeFormType =
                    srcType.priv.typeArgs?.length && srcType.priv.typeArgs.length > 0
                        ? srcType.priv.typeArgs[0]
                        : UnknownType.create();
            }
        } else if (isTypeVar(srcType) && TypeBase.isInstantiable(srcType)) {
            if (!isTypeVarTuple(srcType) || !srcType.priv.isInUnion) {
                srcTypeFormType = convertToInstance(srcType);
            }
        }

        if (!srcTypeFormType) {
            return srcType;
        }

        let resultType: Type | undefined;

        doForEachSubtype(expectedType, (subtype) => {
            if (resultType || !isClassInstance(subtype) || !ClassType.isBuiltIn(subtype, 'TypeForm')) {
                return;
            }

            const destTypeFormType =
                subtype.priv.typeArgs && subtype.priv.typeArgs.length > 0
                    ? subtype.priv.typeArgs[0]
                    : UnknownType.create();

            if (assignType(destTypeFormType, srcTypeFormType)) {
                resultType = ClassType.specialize(subtype, [srcTypeFormType]);
            }
        });

        return resultType ?? srcType;
    }

    function assignFromUnionType(
        destType: Type,
        srcType: UnionType,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker | undefined,
        flags: AssignTypeFlags,
        recursionCount: number
    ): boolean {
        // Start by checking for an exact match. This is needed to handle unions
        // that contain recursive type aliases.
        if (isTypeSame(srcType, destType, {}, recursionCount)) {
            return true;
        }

        if (
            (flags & AssignTypeFlags.OverloadOverlap) !== 0 &&
            srcType.priv.subtypes.some((subtype) => isAnyOrUnknown(subtype))
        ) {
            return false;
        }

        // Sort the subtypes so we have a deterministic order for unions.
        let sortedSrcTypes: Type[] = sortTypes(srcType.priv.subtypes);
        let matchedSomeSubtypes = false;

        // Handle the case where the source and dest are both unions. Try
        // to eliminate as many exact type matches between the src and dest.
        if (isUnion(destType)) {
            // Handle the special case where the dest is a union of Any and
            // a type variable. This occurs, for example, with the return type of
            // the getattr function.
            const nonAnySubtypes = destType.priv.subtypes.filter((t) => !isAnyOrUnknown(t));
            if (nonAnySubtypes.length === 1 && isTypeVar(nonAnySubtypes[0])) {
                assignType(nonAnySubtypes[0], srcType, /* diag */ undefined, constraints, flags, recursionCount);

                // This always succeeds because the destination contains Any.
                return true;
            }

            const remainingDestSubtypes: Type[] = [];
            let remainingSrcSubtypes: Type[] = sortedSrcTypes;
            let canUseFastPath = true;

            // First attempt to match all of the non-generic types in the dest
            // to non-generic types in the source.
            sortTypes(destType.priv.subtypes).forEach((destSubtype) => {
                if (requiresSpecialization(destSubtype)) {
                    remainingDestSubtypes.push(destSubtype);
                } else {
                    const srcTypeIndex = remainingSrcSubtypes.findIndex((srcSubtype) =>
                        isTypeSame(srcSubtype, destSubtype, {}, recursionCount)
                    );

                    if (srcTypeIndex >= 0) {
                        remainingSrcSubtypes.splice(srcTypeIndex, 1);
                        matchedSomeSubtypes = true;
                    } else {
                        remainingDestSubtypes.push(destSubtype);
                    }
                }
            });

            // For all remaining source subtypes, attempt to find a dest subtype
            // whose primary type matches.
            remainingSrcSubtypes.forEach((srcSubtype) => {
                const destTypeIndex = remainingDestSubtypes.findIndex((destSubtype) => {
                    if (isTypeSame(destSubtype, srcSubtype)) {
                        return true;
                    }

                    if (
                        isClass(srcSubtype) &&
                        isClass(destSubtype) &&
                        TypeBase.isInstance(srcSubtype) === TypeBase.isInstance(destSubtype)
                    ) {
                        if (ClassType.isSameGenericClass(srcSubtype, destSubtype)) {
                            return true;
                        }

                        // Are they equivalent TypedDicts?
                        if (ClassType.isTypedDictClass(srcSubtype) && ClassType.isTypedDictClass(destSubtype)) {
                            if (
                                assignType(
                                    srcSubtype,
                                    destSubtype,
                                    /* diag */ undefined,
                                    /* constraints */ undefined,
                                    flags,
                                    recursionCount
                                )
                            ) {
                                return true;
                            }
                        }
                    }

                    if (isFunctionOrOverloaded(srcSubtype) && isFunctionOrOverloaded(destSubtype)) {
                        return true;
                    }

                    return false;
                });

                if (destTypeIndex >= 0) {
                    if (
                        assignType(
                            remainingDestSubtypes[destTypeIndex],
                            srcSubtype,
                            /* diag */ undefined,
                            constraints,
                            flags,
                            recursionCount
                        )
                    ) {
                        // Note that we have matched at least one subtype indicating
                        // there is at least some overlap.
                        matchedSomeSubtypes = true;
                    } else {
                        canUseFastPath = false;
                    }

                    remainingDestSubtypes.splice(destTypeIndex, 1);
                    remainingSrcSubtypes = remainingSrcSubtypes.filter((t) => t !== srcSubtype);
                }
            });

            // If there is are remaining dest subtypes and they're all type variables,
            // attempt to assign the remaining source subtypes to them.
            if (canUseFastPath && (remainingDestSubtypes.length !== 0 || remainingSrcSubtypes.length !== 0)) {
                if ((flags & AssignTypeFlags.Invariant) !== 0) {
                    // If we have no src subtypes remaining but not all dest types have been subsumed
                    // by other dest types, then the types are not compatible if we're enforcing invariance.
                    if (remainingSrcSubtypes.length === 0) {
                        return remainingDestSubtypes.every((destSubtype) =>
                            isTypeSubsumedByOtherType(
                                destSubtype,
                                destType,
                                /* allowAnyToSubsume */ true,
                                recursionCount
                            )
                        );
                    }
                }

                const isContra = (flags & AssignTypeFlags.Contravariant) !== 0;
                const effectiveDestSubtypes = isContra ? remainingSrcSubtypes : remainingDestSubtypes;

                if (effectiveDestSubtypes.length === 0 || effectiveDestSubtypes.some((t) => !isTypeVar(t))) {
                    canUseFastPath = false;

                    // We can avoid checking the source subtypes that have already been checked.
                    sortedSrcTypes = remainingSrcSubtypes;
                } else if (remainingDestSubtypes.length === remainingSrcSubtypes.length) {
                    // If the number of remaining source subtypes is the same as the number
                    // of dest TypeVars, try to assign each source subtype to its own dest TypeVar.
                    const reorderedDestSubtypes = [...remainingDestSubtypes];

                    for (let srcIndex = 0; srcIndex < remainingSrcSubtypes.length; srcIndex++) {
                        let foundMatchForSrc = false;

                        for (let destIndex = 0; destIndex < reorderedDestSubtypes.length; destIndex++) {
                            if (
                                assignType(
                                    reorderedDestSubtypes[destIndex],
                                    remainingSrcSubtypes[srcIndex],
                                    diag?.createAddendum(),
                                    constraints,
                                    flags,
                                    recursionCount
                                )
                            ) {
                                foundMatchForSrc = true;
                                // Move the matched dest TypeVar to the end of the list so the other
                                // dest TypeVars have a better chance of being assigned to.
                                reorderedDestSubtypes.push(...reorderedDestSubtypes.splice(destIndex, 1));
                                break;
                            }
                        }

                        if (!foundMatchForSrc) {
                            canUseFastPath = false;
                            break;
                        }
                    }

                    // We can avoid checking the source subtypes that have already been checked.
                    sortedSrcTypes = remainingSrcSubtypes;
                } else if (remainingSrcSubtypes.length === 0) {
                    if ((flags & AssignTypeFlags.PopulateExpectedType) !== 0) {
                        // If we're populating an expected type, try not to leave
                        // any TypeVars unsolved. Assign the full type to the remaining
                        // dest TypeVars.
                        remainingDestSubtypes.forEach((destSubtype) => {
                            assignType(destSubtype, srcType, /* diag */ undefined, constraints, flags, recursionCount);
                        });
                    }

                    // If we've assigned all of the source subtypes but one or more dest
                    // TypeVars have gone unmatched, treat this as success.
                } else {
                    // Try to assign a union of the remaining source types to
                    // the first destination TypeVar. If this is a contravariant
                    // context, use the full dest type rather than the remaining
                    // dest subtypes to keep the lower bound as wide as possible.
                    if (
                        !assignType(
                            isContra ? destType : remainingDestSubtypes[0],
                            isContra ? remainingSrcSubtypes[0] : combineTypes(remainingSrcSubtypes),
                            diag?.createAddendum(),
                            constraints,
                            flags,
                            recursionCount
                        )
                    ) {
                        canUseFastPath = false;
                    }
                }
            }

            if (canUseFastPath) {
                return true;
            }

            // If we're looking for type overlaps and at least one type was matched,
            // consider it as assignable.
            if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0 && matchedSomeSubtypes) {
                return true;
            }
        }

        let isIncompatible = false;

        sortedSrcTypes.forEach((subtype) => {
            if (isIncompatible) {
                return;
            }

            if (!assignType(destType, subtype, /* diag */ undefined, constraints, flags, recursionCount)) {
                // Determine if the current subtype is subsumed by another subtype
                // in the same union. If so, we can ignore this.
                const isSubtypeSubsumed = isTypeSubsumedByOtherType(
                    subtype,
                    srcType,
                    /* allowAnyToSubsume */ false,
                    recursionCount
                );

                // Try again with a concrete version of the subtype.
                if (
                    !isSubtypeSubsumed &&
                    !assignType(destType, subtype, diag?.createAddendum(), constraints, flags, recursionCount)
                ) {
                    isIncompatible = true;
                }
            } else {
                matchedSomeSubtypes = true;
            }
        }, /* sortSubtypes */ true);

        if (isIncompatible) {
            // If we're looking for type overlaps and at least one type was matched,
            // consider it as assignable.
            if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0 && matchedSomeSubtypes) {
                return true;
            }

            diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
            return false;
        }

        return true;
    }

    function isSpecialFormClass(classType: ClassType, flags: AssignTypeFlags): boolean {
        if ((flags & AssignTypeFlags.AllowIsinstanceSpecialForms) !== 0) {
            return false;
        }

        return ClassType.isSpecialFormClass(classType);
    }

    // Finds unsolved type variables in the destType and establishes constraints
    // in the constraint tracker for them based on the srcType.
    function setConstraintsForFreeTypeVars(
        destType: Type,
        srcType: UnknownType | AnyType,
        constraints: ConstraintTracker
    ) {
        const typeVars = getTypeVarArgsRecursive(destType);
        typeVars.forEach((typeVar) => {
            if (!TypeVarType.isBound(typeVar) && !constraints.getMainConstraintSet().getTypeVar(typeVar)) {
                // Don't set ParamSpecs or TypeVarTuples.
                if (!isParamSpec(srcType) && !isTypeVarTuple(srcType)) {
                    constraints.setBounds(typeVar, srcType);
                }
            }
        });
    }

    // Determines whether a type is "subsumed by" (i.e. is a proper subtype of) another type.
    function isTypeSubsumedByOtherType(type: Type, otherType: Type, allowAnyToSubsume: boolean, recursionCount = 0) {
        const concreteType = makeTopLevelTypeVarsConcrete(type);
        const otherSubtypes = isUnion(otherType) ? otherType.priv.subtypes : [otherType];

        for (const otherSubtype of otherSubtypes) {
            if (isTypeSame(otherSubtype, type)) {
                continue;
            }

            if (isAnyOrUnknown(otherSubtype)) {
                if (allowAnyToSubsume) {
                    return true;
                }
            } else if (isProperSubtype(otherSubtype, concreteType, recursionCount)) {
                return true;
            }
        }

        return false;
    }

    // Determines whether the srcType is a subtype of destType but the converse
    // is not true. It's important that we check both directions to avoid
    // matches for types like `tuple[Any]` and `tuple[int]` from being considered
    // proper subtypes of each other.
    function isProperSubtype(destType: Type, srcType: Type, recursionCount: number) {
        // If the destType has a condition, don't consider the srcType a proper subtype.
        if (destType.props?.condition) {
            return false;
        }

        // Shortcut the check if either type is Any or Unknown.
        if (isAnyOrUnknown(destType) || isAnyOrUnknown(srcType)) {
            return true;
        }

        // Shortcut the check if either type is a class whose hierarchy contains an unknown type.
        if (isClass(destType) && destType.shared.mro.some((mro) => isAnyOrUnknown(mro))) {
            return true;
        }

        if (isClass(srcType) && srcType.shared.mro.some((mro) => isAnyOrUnknown(mro))) {
            return true;
        }

        return (
            assignType(
                destType,
                srcType,
                /* diag */ undefined,
                /* constraints */ undefined,
                AssignTypeFlags.Default,
                recursionCount
            ) &&
            !assignType(
                srcType,
                destType,
                /* diag */ undefined,
                /* constraints */ undefined,
                AssignTypeFlags.Default,
                recursionCount
            )
        );
    }

    // Determines whether the two types are potentially comparable -- i.e.
    // their types overlap in such a way that it makes sense for them to
    // be compared with an == or != operator. The functional also supports
    // a special variant that can be used for the "is" and "is not" operator.
    // This variant can be less conservative in some cases.
    function isTypeComparable(leftType: Type, rightType: Type, assumeIsOperator = false) {
        if (isAnyOrUnknown(leftType) || isAnyOrUnknown(rightType)) {
            return true;
        }

        if (isNever(leftType) || isNever(rightType)) {
            return false;
        }

        if (isModule(leftType) || isModule(rightType)) {
            return isTypeSame(leftType, rightType, { ignoreConditions: true });
        }

        const isLeftCallable = isFunctionOrOverloaded(leftType);
        const isRightCallable = isFunctionOrOverloaded(rightType);

        // If either type is a function, assume that it may be comparable. The other
        // operand might be a callable object, an 'object' instance, etc. We could
        // make this more precise for specific cases (e.g. if the other operand is
        // None or a literal or an instance of a nominal class that doesn't override
        // __call__ and is marked final, etc.), but coming up with a comprehensive
        // list is probably not feasible.
        if (isLeftCallable || isRightCallable) {
            return true;
        }

        if (isInstantiableClass(leftType) || (isClassInstance(leftType) && ClassType.isBuiltIn(leftType, 'type'))) {
            if (
                isInstantiableClass(rightType) ||
                (isClassInstance(rightType) && ClassType.isBuiltIn(rightType, 'type'))
            ) {
                const genericLeftType = ClassType.specialize(leftType, /* typeArgs */ undefined);
                const genericRightType = ClassType.specialize(rightType, /* typeArgs */ undefined);

                if (assignType(genericLeftType, genericRightType) || assignType(genericRightType, genericLeftType)) {
                    return true;
                }
            }

            // Does the class have an operator overload for eq?
            const metaclass = leftType.shared.effectiveMetaclass;
            if (metaclass && isClass(metaclass)) {
                if (lookUpClassMember(metaclass, '__eq__', MemberAccessFlags.SkipObjectBaseClass)) {
                    return true;
                }
            }

            return false;
        }

        if (isClassInstance(leftType)) {
            if (isClass(rightType)) {
                const genericLeftType = ClassType.specialize(leftType, /* typeArgs */ undefined);
                const genericRightType = ClassType.specialize(rightType, /* typeArgs */ undefined);

                if (assignType(genericLeftType, genericRightType) || assignType(genericRightType, genericLeftType)) {
                    return true;
                }

                // Check for the "is None" or "is not None" case.
                if (assumeIsOperator && isNoneInstance(rightType)) {
                    if (isNoneInstance(leftType)) {
                        return true;
                    }

                    // The LHS could be a protocol or 'object', in which case None is
                    // potentially comparable to it. In other cases, None is not comparable
                    // because the types are disjoint.
                    return assignType(leftType, rightType);
                }

                // Assume that if the types are disjoint and built-in classes that they
                // will never be comparable.
                if (ClassType.isBuiltIn(leftType) && ClassType.isBuiltIn(rightType) && TypeBase.isInstance(rightType)) {
                    // We need to be careful with bool and int literals because
                    // they are comparable under certain circumstances.
                    let boolType: ClassType | undefined;
                    let intType: ClassType | undefined;
                    if (ClassType.isBuiltIn(leftType, 'bool') && ClassType.isBuiltIn(rightType, 'int')) {
                        boolType = leftType;
                        intType = rightType;
                    } else if (ClassType.isBuiltIn(rightType, 'bool') && ClassType.isBuiltIn(leftType, 'int')) {
                        boolType = rightType;
                        intType = leftType;
                    }

                    if (boolType && intType) {
                        const intVal = intType.priv?.literalValue as number | BigInt | undefined;
                        if (intVal === undefined) {
                            return true;
                        }
                        if (intVal !== 0 && intVal !== 1) {
                            return false;
                        }

                        const boolVal = boolType.priv?.literalValue as boolean | undefined;
                        if (boolVal === undefined) {
                            return true;
                        }

                        return boolVal === (intVal === 1);
                    }

                    return false;
                }
            }

            // Does the class have an operator overload for eq?
            const eqMethod = lookUpClassMember(
                ClassType.cloneAsInstantiable(leftType),
                '__eq__',
                MemberAccessFlags.SkipObjectBaseClass
            );

            if (eqMethod) {
                // If this is a synthesized method for a dataclass, we can assume
                // that other dataclass types will not be comparable.
                if (ClassType.isDataClass(leftType) && eqMethod.symbol.getSynthesizedType()) {
                    return false;
                }

                return true;
            }

            return false;
        }

        return true;
    }

    function assignToUnionType(
        destType: UnionType,
        srcType: Type,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker | undefined,
        flags: AssignTypeFlags,
        recursionCount: number
    ): boolean {
        // If we need to enforce invariance, the source needs to be compatible
        // with all subtypes in the dest, unless those subtypes are subclasses
        // of other subtypes.
        if (flags & AssignTypeFlags.Invariant) {
            let isIncompatible = false;

            doForEachSubtype(destType, (subtype, index) => {
                if (
                    !isIncompatible &&
                    !assignType(subtype, srcType, diag?.createAddendum(), constraints, flags, recursionCount)
                ) {
                    // Determine whether this subtype is subsumed by some other
                    // subtype in the union. If so, we can ignore the incompatibility.
                    let skipSubtype = false;
                    if (!isAnyOrUnknown(subtype)) {
                        const adjSubtype = makeTypeVarsBound(subtype, /* scopeIds */ undefined);

                        doForEachSubtype(destType, (otherSubtype, otherIndex) => {
                            if (index !== otherIndex && !skipSubtype) {
                                const adjOtherSubtype = makeTypeVarsBound(otherSubtype, /* scopeIds */ undefined);

                                if (
                                    assignType(
                                        adjOtherSubtype,
                                        adjSubtype,
                                        /* diag */ undefined,
                                        /* constraints */ undefined,
                                        AssignTypeFlags.Default,
                                        recursionCount
                                    )
                                ) {
                                    skipSubtype = true;
                                }
                            }
                        });
                    }
                    if (!skipSubtype) {
                        isIncompatible = true;
                    }
                }
            });

            if (isIncompatible) {
                diag?.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
                return false;
            }

            return true;
        }

        // For union destinations, we just need to match one of the types.
        const diagAddendum = diag ? new DiagnosticAddendum() : undefined;

        let foundMatch = false;

        // Does the union contain any type variables that need to be solved?
        // If so, we need to use a slower path.
        if (!requiresSpecialization(destType)) {
            for (const subtype of destType.priv.subtypes) {
                if (assignType(subtype, srcType, diagAddendum?.createAddendum(), constraints, flags, recursionCount)) {
                    foundMatch = true;
                    break;
                }
            }
        } else {
            // Run through all subtypes in the union. Don't stop at the first
            // match we find because we may need to match TypeVars in other
            // subtypes. We special-case "None" so we can handle Optional[T]
            // without matching the None to the type var.
            if (isNoneInstance(srcType) && isOptionalType(destType)) {
                foundMatch = true;
            } else {
                let bestConstraints: ConstraintTracker | undefined;
                let bestConstraintsScore: number | undefined;
                let nakedTypeVarMatches = 0;

                // If the srcType is a literal, try to use the fast-path lookup
                // in case the destType is a union with hundreds of literals.
                if (
                    isClassInstance(srcType) &&
                    isLiteralType(srcType) &&
                    UnionType.containsType(
                        destType,
                        srcType,
                        /* options */ undefined,
                        /* exclusionSet */ undefined,
                        recursionCount
                    )
                ) {
                    return true;
                }

                doForEachSubtype(
                    destType,
                    (subtype) => {
                        // Make a temporary clone of the constraints. We don't want to modify
                        // the original constraints until we find the "optimal" typeVar mapping.
                        const constraintsClone = constraints?.clone();
                        if (
                            assignType(
                                subtype,
                                srcType,
                                diagAddendum?.createAddendum(),
                                constraintsClone,
                                flags,
                                recursionCount
                            )
                        ) {
                            foundMatch = true;
                            if (constraintsClone) {
                                // Ask the constraints to compute a "score" for the current
                                // contents of the table.
                                let constraintsScore = constraintsClone.getScore();

                                if (isTypeVar(subtype)) {
                                    if (!constraints?.getMainConstraintSet().getTypeVar(subtype)) {
                                        nakedTypeVarMatches++;

                                        // Handicap the solution slightly so another type var with
                                        // existing constraints will be preferred.
                                        constraintsScore += 0.001;
                                    }
                                }

                                // If the type matches exactly, prefer it over other types.
                                if (isTypeSame(subtype, stripLiteralValue(srcType))) {
                                    constraintsScore = Number.POSITIVE_INFINITY;
                                }

                                if (bestConstraintsScore === undefined || bestConstraintsScore <= constraintsScore) {
                                    // We found a typeVar mapping with a higher score than before.
                                    bestConstraintsScore = constraintsScore;
                                    bestConstraints = constraintsClone;
                                }
                            }
                        }
                    },
                    /* sortSubtypes */ true
                );

                // If we saw more than one "naked" type vars that have no
                // previous constraints recorded, it's dangerous for us to
                // assign a value to any of these type vars at this time.
                // Typically, they will receive some constraints via some
                // later argument assignment.
                if (nakedTypeVarMatches > 1 && (flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0) {
                    bestConstraints = undefined;
                }

                // If we found a winning type var mapping, copy it back to constraints.
                if (constraints && bestConstraints) {
                    constraints.copyFromClone(bestConstraints);
                }
            }
        }

        // If the source is a constrained TypeVar, see if we can assign all of the
        // constraints to the union.
        if (!foundMatch) {
            if (isTypeVar(srcType) && TypeVarType.hasConstraints(srcType)) {
                foundMatch = assignType(
                    destType,
                    makeTopLevelTypeVarsConcrete(srcType),
                    diagAddendum?.createAddendum(),
                    constraints,
                    flags,
                    recursionCount
                );
            }
        }

        if (!foundMatch) {
            if (diag && diagAddendum) {
                diag.addMessage(LocAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType)));
                diag.addAddendum(diagAddendum);
            }
            return false;
        }

        return true;
    }

    function assignConditionalTypeToTypeVar(destType: TypeVarType, srcType: Type, recursionCount: number): boolean {
        // The srcType is assignable only if all of its subtypes are assignable.
        return !findSubtype(srcType, (srcSubtype) => {
            if (isTypeSame(destType, srcSubtype, { ignorePseudoGeneric: true }, recursionCount)) {
                return false;
            }

            if (isIncompleteUnknown(srcSubtype)) {
                return false;
            }

            const destTypeVarName = TypeVarType.getNameWithScope(destType);

            // Determine which conditions on this type apply to this type variable.
            // There might be more than one of them.
            const applicableConditions = (getTypeCondition(srcSubtype) ?? []).filter(
                (constraint) => constraint.typeVar.priv.nameWithScope === destTypeVarName
            );

            // If there are no applicable conditions, it's not assignable.
            if (applicableConditions.length === 0) {
                return true;
            }

            return !applicableConditions.some((condition) => {
                if (condition.typeVar.priv.nameWithScope === TypeVarType.getNameWithScope(destType)) {
                    if (destType.shared.boundType) {
                        assert(
                            condition.constraintIndex === 0,
                            'Expected constraint for bound TypeVar to have index of 0'
                        );

                        return assignType(
                            destType.shared.boundType,
                            srcSubtype,
                            /* diag */ undefined,
                            /* constraints */ undefined,
                            AssignTypeFlags.Default,
                            recursionCount
                        );
                    }

                    if (TypeVarType.hasConstraints(destType)) {
                        assert(
                            condition.constraintIndex < destType.shared.constraints.length,
                            'Constraint for constrained TypeVar is out of bounds'
                        );

                        return assignType(
                            destType.shared.constraints[condition.constraintIndex],
                            srcSubtype,
                            /* diag */ undefined,
                            /* constraints */ undefined,
                            AssignTypeFlags.Default,
                            recursionCount
                        );
                    }

                    // This is a non-bound and non-constrained type variable with a matching condition.
                    return true;
                }

                return false;
            });
        });
    }

    // If the class is a protocol and it has a `__call__` method but no other methods
    // or attributes that would be incompatible with a function, this method returns
    // the signature of the call implied by the `__call__` method. Otherwise it returns
    // undefined.
    function getCallbackProtocolType(
        objType: ClassType,
        recursionCount = 0
    ): FunctionType | OverloadedType | undefined {
        if (!isClassInstance(objType) || !ClassType.isProtocolClass(objType)) {
            return undefined;
        }

        // Make sure that the protocol class doesn't define any fields that
        // a normal function wouldn't be compatible with.
        for (const mroClass of objType.shared.mro) {
            if (isClass(mroClass) && ClassType.isProtocolClass(mroClass)) {
                for (const field of ClassType.getSymbolTable(mroClass)) {
                    const fieldName = field[0];
                    const fieldSymbol = field[1];

                    // We're expecting a __call__ method. We will also ignore a
                    // __slots__ definition, which is (by convention) ignored for
                    // protocol matching.
                    if (fieldName === '__call__' || fieldName === '__slots__') {
                        continue;
                    }

                    if (fieldSymbol.isIgnoredForProtocolMatch()) {
                        continue;
                    }

                    let fieldIsPartOfFunction = false;

                    if (prefetched?.functionClass && isClass(prefetched.functionClass)) {
                        if (ClassType.getSymbolTable(prefetched.functionClass).has(field[0])) {
                            fieldIsPartOfFunction = true;
                        }
                    }

                    if (!fieldIsPartOfFunction) {
                        return undefined;
                    }
                }
            }
        }

        const callType = getBoundMagicMethod(
            objType,
            '__call__',
            /* selfType */ undefined,
            /* errorNode */ undefined,
            /* diag */ undefined,
            recursionCount
        );

        if (!callType) {
            return undefined;
        }

        return makeFunctionTypeVarsBound(callType);
    }

    function assignParam(
        destType: Type,
        srcType: Type,
        paramIndex: number | undefined,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker,
        flags: AssignTypeFlags,
        recursionCount: number
    ) {
        if (isTypeVarTuple(destType) && !isUnpacked(srcType)) {
            return false;
        }

        let specializedSrcType = srcType;
        let specializedDestType = destType;
        let doSpecializationStep = false;

        if ((flags & AssignTypeFlags.OverloadOverlap) === 0) {
            const isFirstPass = (flags & AssignTypeFlags.ArgAssignmentFirstPass) !== 0;

            if ((flags & AssignTypeFlags.Contravariant) === 0) {
                if (!isFirstPass) {
                    specializedDestType = solveAndApplyConstraints(
                        destType,
                        constraints,
                        /* applyOptions */ undefined,
                        { useLowerBoundOnly: true }
                    );
                }
                doSpecializationStep = requiresSpecialization(specializedDestType);
            } else {
                if (!isFirstPass) {
                    specializedSrcType = solveAndApplyConstraints(srcType, constraints, /* applyOptions */ undefined, {
                        useLowerBoundOnly: true,
                    });
                }
                doSpecializationStep = requiresSpecialization(specializedSrcType);
            }
        }

        // Is an additional specialization step required?
        if (doSpecializationStep) {
            if (
                assignType(
                    specializedSrcType,
                    specializedDestType,
                    /* diag */ undefined,
                    constraints,
                    (flags ^ AssignTypeFlags.Contravariant) | AssignTypeFlags.RetainLiteralsForTypeVar,
                    recursionCount
                )
            ) {
                specializedDestType = solveAndApplyConstraints(destType, constraints);
            }
        }

        if (
            !assignType(
                specializedSrcType,
                specializedDestType,
                diag?.createAddendum(),
                constraints,
                flags,
                recursionCount
            )
        ) {
            if (diag && paramIndex !== undefined) {
                diag.addMessage(
                    LocAddendum.paramAssignment().format({
                        index: paramIndex + 1,
                        sourceType: printType(destType),
                        destType: printType(srcType),
                    })
                );
            }

            return false;
        }

        return true;
    }

    // Determines whether we need to pack some of the source positionals
    // into a tuple that matches a variadic *args parameter in the destination.
    function adjustSourceParamDetailsForDestVariadic(srcDetails: ParamListDetails, destDetails: ParamListDetails) {
        // If there is no *args parameter in the dest, we have nothing to do.
        if (destDetails.argsIndex === undefined) {
            return;
        }

        // If the *args parameter isn't an unpacked TypeVarTuple or tuple,
        // we have nothing to do.
        if (!isUnpacked(destDetails.params[destDetails.argsIndex].type)) {
            return;
        }

        // If the source doesn't have enough positional parameters, we have nothing to do.
        if (srcDetails.params.length < destDetails.argsIndex) {
            return;
        }

        let srcLastToPackIndex = srcDetails.params.findIndex((p, i) => {
            assert(destDetails.argsIndex !== undefined);
            return i >= destDetails.argsIndex && p.kind === ParamKind.Keyword;
        });
        if (srcLastToPackIndex < 0) {
            srcLastToPackIndex = srcDetails.params.length;
        }

        // If both the source and dest have an *args parameter but the dest's is
        // in a later position, then we can't assign the source's *args to the dest.
        // Don't make any adjustment in this case.
        if (srcDetails.argsIndex !== undefined && destDetails.argsIndex > srcDetails.argsIndex) {
            return;
        }

        const destFirstNonPositional = destDetails.firstKeywordOnlyIndex ?? destDetails.params.length;
        const suffixLength = destFirstNonPositional - destDetails.argsIndex - 1;
        const srcPositionalsToPack = srcDetails.params.slice(destDetails.argsIndex, srcLastToPackIndex - suffixLength);
        const srcTupleTypes: TupleTypeArg[] = [];
        srcPositionalsToPack.forEach((entry) => {
            if (entry.param.category === ParamCategory.ArgsList) {
                if (isUnpackedTypeVarTuple(entry.type)) {
                    srcTupleTypes.push({ type: entry.type, isUnbounded: false });
                } else if (isUnpackedClass(entry.type) && entry.type.priv.tupleTypeArgs) {
                    appendArray(srcTupleTypes, entry.type.priv.tupleTypeArgs);
                } else {
                    srcTupleTypes.push({ type: entry.type, isUnbounded: true });
                }
            } else {
                srcTupleTypes.push({ type: entry.type, isUnbounded: false, isOptional: !!entry.defaultType });
            }
        });

        if (srcTupleTypes.length !== 1 || !isTypeVarTuple(srcTupleTypes[0].type)) {
            const srcPositionalsType = makeTupleObject(evaluatorInterface, srcTupleTypes, /* isUnpacked */ true);

            // Snip out the portion of the source positionals that map to the variadic
            // dest parameter and replace it with a single parameter that is typed as a
            // tuple containing the individual types of the replaced parameters.
            srcDetails.params = [
                ...srcDetails.params.slice(0, destDetails.argsIndex),
                {
                    param: FunctionParam.create(
                        ParamCategory.ArgsList,
                        srcPositionalsType,
                        FunctionParamFlags.NameSynthesized | FunctionParamFlags.TypeDeclared,
                        '_arg_combined'
                    ),
                    type: srcPositionalsType,
                    declaredType: srcPositionalsType,
                    index: -1,
                    kind: ParamKind.Positional,
                },
                ...srcDetails.params.slice(
                    destDetails.argsIndex + srcPositionalsToPack.length,
                    srcDetails.params.length
                ),
            ];

            const argsIndex = srcDetails.params.findIndex((param) => param.param.category === ParamCategory.ArgsList);
            srcDetails.argsIndex = argsIndex >= 0 ? argsIndex : undefined;

            const kwargsIndex = srcDetails.params.findIndex(
                (param) => param.param.category === ParamCategory.KwargsDict
            );
            srcDetails.kwargsIndex = kwargsIndex >= 0 ? kwargsIndex : undefined;

            const firstKeywordOnlyIndex = srcDetails.params.findIndex((param) => param.kind === ParamKind.Keyword);
            srcDetails.firstKeywordOnlyIndex = firstKeywordOnlyIndex >= 0 ? firstKeywordOnlyIndex : undefined;

            srcDetails.positionOnlyParamCount = Math.max(
                0,
                srcDetails.params.findIndex(
                    (p) =>
                        p.kind !== ParamKind.Positional || p.param.category !== ParamCategory.Simple || !!p.defaultType
                )
            );
        }
    }

    function assignFunction(
        destType: FunctionType,
        srcType: FunctionType,
        diag: DiagnosticAddendum | undefined,
        constraints: ConstraintTracker,
        flags: AssignTypeFlags,
        recursionCount: number
    ): boolean {
        let canAssign = true;
        const checkReturnType = (flags & AssignTypeFlags.SkipReturnTypeCheck) === 0;
        const isContra = (flags & AssignTypeFlags.Contravariant) !== 0;
        flags &= ~AssignTypeFlags.SkipReturnTypeCheck;

        const destParamSpec = FunctionType.getParamSpecFromArgsKwargs(destType);
        if (destParamSpec) {
            destType = FunctionType.cloneRemoveParamSpecArgsKwargs(destType);
        }

        const srcParamSpec = FunctionType.getParamSpecFromArgsKwargs(srcType);
        if (srcParamSpec) {
            srcType = FunctionType.cloneRemoveParamSpecArgsKwargs(srcType);
        }

        const destParamDetails = getParamListDetails(destType, {
            disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0,
        });
        const srcParamDetails = getParamListDetails(srcType, {
            disallowExtraKwargsForTd: (flags & AssignTypeFlags.DisallowExtraKwargsForTd) !== 0,
        });

        adjustSourceParamDetailsForDestVariadic(
            isContra ? destParamDetails : srcParamDetails,
            isContra ? srcParamDetails : destParamDetails
        );

        const targetIncludesParamSpec = isContra ? !!srcParamSpec : !!destParamSpec;

        const destPositionalCount = destParamDetails.firstKeywordOnlyIndex ?? destParamDetails.params.length;
        const srcPositionalCount = srcParamDetails.firstKeywordOnlyIndex ?? srcParamDetails.params.length;
        const positionalsToMatch = Math.min(destPositionalCount, srcPositionalCount);
        const skippedPosParamIndices: number[] = [];

        // Match positional parameters.
        for (let paramIndex = 0; paramIndex < positionalsToMatch; paramIndex++) {
            if (
                paramIndex === 0 &&
                destType.shared.methodClass &&
                (flags & AssignTypeFlags.SkipSelfClsParamCheck) !== 0
            ) {
                if (FunctionType.isInstanceMethod(destType) || FunctionType.isClassMethod(destType)) {
                    continue;
                }
            }

            // Skip over the *args parameter since it's handled separately below.
            if (paramIndex === destParamDetails.argsIndex) {
                if (!isUnpackedTypeVarTuple(destParamDetails.params[destParamDetails.argsIndex].type)) {
                    skippedPosParamIndices.push(paramIndex);
                }
                continue;
            }

            const destParam = destParamDetails.params[paramIndex];
            const srcParam = srcParamDetails.params[paramIndex];

            // Find the original index of this source param. If we synthesized it above (for
            // a variadic parameter), it may not be found.
            const srcParamType = srcParam.type;
            const destParamType = destParam.type;

            const destParamName = destParam.param.name ?? '';
            const srcParamName = srcParam.param.name ?? '';
            if (destParamName) {
                const isDestPositionalOnly =
                    destParam.kind === ParamKind.Positional || destParam.kind === ParamKind.ExpandedArgs;
                if (
                    !isDestPositionalOnly &&
                    destParam.param.category !== ParamCategory.ArgsList &&
                    srcParam.param.category !== ParamCategory.ArgsList
                ) {
                    if (srcParam.kind === ParamKind.Positional || srcParam.kind === ParamKind.ExpandedArgs) {
                        diag?.createAddendum().addMessage(
                            LocAddendum.functionParamPositionOnly().format({
                                name: destParamName,
                            })
                        );
                        canAssign = false;
                    } else if (destParamName !== srcParamName) {
                        diag?.createAddendum().addMessage(
                            LocAddendum.functionParamName().format({
                                srcName: srcParamName,
                                destName: destParamName,
                            })
                        );
                        canAssign = false;
                    }
                }
            }

            if (destParam.defaultType) {
                if (!srcParam.defaultType && paramIndex !== srcParamDetails.argsIndex) {
                    diag?.createAddendum().addMessage(
                        LocAddendum.functionParamDefaultMissing().format({
                            name: srcParamName,
                        })
                    );
                    canAssign = false;
                }

                // If we're performing a partial overload match and both the source
                // and dest parameters provide defaults, assume that there could
                // be a match.
                if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) {
                    if (srcParam.defaultType) {
                        continue;
                    }
                }
            }

            // Handle the special case of an overloaded __init__ method whose self
            // parameter is annotated.
            if (
                paramIndex === 0 &&
                srcType.shared.name === '__init__' &&
                FunctionType.isInstanceMethod(srcType) &&
                destType.shared.name === '__init__' &&
                FunctionType.isInstanceMethod(destType) &&
                FunctionType.isOverloaded(destType) &&
                FunctionParam.isTypeDeclared(destParam.param)
            ) {
                continue;
            }

            if (isUnpacked(srcParamType)) {
                canAssign = false;
            } else if (
                !assignParam(
                    destParamType,
                    srcParamType,
                    paramIndex,
                    diag?.createAddendum(),
                    constraints,
                    flags,
                    recursionCount
                )
            ) {
                // Handle the special case where the source parameter is a synthesized
                // TypeVar for "self" or "cls".
                if (
                    (flags & AssignTypeFlags.SkipSelfClsTypeCheck) === 0 ||
                    !isTypeVar(srcParamType) ||
                    !srcParamType.shared.isSynthesized
                ) {
                    canAssign = false;
                }
            } else if (
                destParam.kind !== ParamKind.Positional &&
                destParam.kind !== ParamKind.ExpandedArgs &&
                srcParam.kind === ParamKind.Positional &&
                srcParamDetails.kwargsIndex === undefined &&
                !srcParamDetails.params.some(
                    (p) =>
                        p.kind === ParamKind.Keyword &&
                        p.param.category === ParamCategory.Simple &&
                        p.param.name === destParam.param.name
                )
            ) {
                diag?.addMessage(
                    LocAddendum.namedParamMissingInSource().format({
                        name: destParam.param.name ?? '',
                    })
                );
                canAssign = false;
            }
        }

        if (
            !FunctionType.isGradualCallableForm(destType) &&
            destParamDetails.firstPositionOrKeywordIndex < srcParamDetails.positionOnlyParamCount &&
            !targetIncludesParamSpec
        ) {
            diag?.createAddendum().addMessage(
                LocAddendum.argsPositionOnly().format({
                    expected: srcParamDetails.positionOnlyParamCount,
                    received: destParamDetails.firstPositionOrKeywordIndex,
                })
            );
            canAssign = false;
        }

        if (destPositionalCount < srcPositionalCount && !targetIncludesParamSpec) {
            // Add any remaining positional parameter indices to the list that
            // need to be validated.
            for (let i = destPositionalCount; i < srcPositionalCount; i++) {
                skippedPosParamIndices.push(i);
            }

            for (const i of skippedPosParamIndices) {
                // If the dest has an *args parameter, make sure it can accept the remaining
                // positional arguments in the source.
                if (destParamDetails.argsIndex !== undefined) {
                    const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type;
                    const srcParamType = srcParamDetails.params[i].type;
                    if (
                        !assignParam(
                            destArgsType,
                            srcParamType,
                            i,
                            diag?.createAddendum(),
                            constraints,
                            flags,
                            recursionCount
                        )
                    ) {
                        canAssign = false;
                    }

                    continue;
                }

                // If The source parameter has a default value, it is OK for the
                // corresponding dest parameter to be missing.
                const srcParam = srcParamDetails.params[i];

                if (srcParam.defaultType) {
                    // Assign default arg value in case it is needed for
                    // populating TypeVar constraints.
                    const paramInfo = srcParamDetails.params[i];
                    const defaultArgType = paramInfo.defaultType ?? paramInfo.defaultType;

                    // Enforce invariance below because the default arg value
                    // is constructed prior to the call, so its type is already
                    // fixed.
                    if (
                        defaultArgType &&
                        !assignType(
                            paramInfo.type,
                            defaultArgType,
                            diag?.createAddendum(),
                            constraints,
                            flags,
                            recursionCount
                        )
                    ) {
                        if ((flags & AssignTypeFlags.PartialOverloadOverlap) === 0) {
                            canAssign = false;
                        }
                    }

                    continue;
                }

                // If the source parameter is also addressable by keyword, it is OK
                // that there is no matching positional parameter in the dest.
                if (srcParam.kind === ParamKind.Standard) {
                    continue;
                }

                // If the source parameter is a variadic, it is OK that there is no
                // matching positional parameter in the dest.
                if (srcParam.param.category === ParamCategory.ArgsList) {
                    continue;
                }

                const nonDefaultSrcParamCount = srcParamDetails.params.filter(
                    (p) => !!p.param.name && !p.defaultType && p.param.category === ParamCategory.Simple
                ).length;

                diag?.createAddendum().addMessage(
                    LocAddendum.functionTooManyParams().format({
                        expected: destPositionalCount,
                        received: nonDefaultSrcParamCount,
                    })
                );
                canAssign = false;
                break;
            }
        } else if (srcPositionalCount < destPositionalCount) {
            if (srcParamDetails.argsIndex !== undefined) {
                // Make sure the remaining dest parameters can be assigned to the source
                // *args parameter type.
                const srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type;
                for (let paramIndex = srcPositionalCount; paramIndex < destPositionalCount; paramIndex++) {
                    if (paramIndex === srcParamDetails.argsIndex) {
                        continue;
                    }

                    const destParamType = destParamDetails.params[paramIndex].type;
                    if (isTypeVarTuple(destParamType) && !isTypeVarTuple(srcArgsType)) {
                        diag?.addMessage(LocAddendum.typeVarTupleRequiresKnownLength());
                        canAssign = false;
                    } else {
                        if (
                            !assignParam(
                                destParamType,
                                srcArgsType,
                                paramIndex,
                                diag?.createAddendum(),
                                constraints,
                                flags,
                                recursionCount
                            )
                        ) {
                            canAssign = false;
                        }

                        const destParamKind = destParamDetails.params[paramIndex].kind;
                        if (
                            destParamKind !== ParamKind.Positional &&
                            destParamKind !== ParamKind.ExpandedArgs &&
                            srcParamDetails.kwargsIndex === undefined
                        ) {
                            diag?.addMessage(
                                LocAddendum.namedParamMissingInSource().format({
                                    name: destParamDetails.params[paramIndex].param.name ?? '',
                                })
                            );
                            canAssign = false;
                        }
                    }
                }
            } else if (!srcParamDetails.paramSpec) {
                // If the dest contains a *args, remove it from the positional count
                // because it's OK for zero source args to match it.
                let adjDestPositionalCount = destPositionalCount;
                if (destParamDetails.argsIndex !== undefined && destParamDetails.argsIndex < destPositionalCount) {
                    adjDestPositionalCount--;
                }

                // If we're doing a partial overload overlap check, ignore dest positional
                // params with default values.
                if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) {
                    while (
                        adjDestPositionalCount > 0 &&
                        destParamDetails.params[adjDestPositionalCount - 1].defaultType
                    ) {
                        adjDestPositionalCount--;
                    }
                }

                if (srcPositionalCount < adjDestPositionalCount) {
                    diag?.addMessage(
                        LocAddendum.functionTooFewParams().format({
                            expected: adjDestPositionalCount,
                            received: srcPositionalCount,
                        })
                    );
                    canAssign = false;
                }
            }
        }

        // If both src and dest have an "*args" parameter, make sure
        // their types are compatible.
        if (
            srcParamDetails.argsIndex !== undefined &&
            destParamDetails.argsIndex !== undefined &&
            !FunctionType.isGradualCallableForm(destType)
        ) {
            let destArgsType = destParamDetails.params[destParamDetails.argsIndex].type;
            let srcArgsType = srcParamDetails.params[srcParamDetails.argsIndex].type;

            if (!isUnpacked(destArgsType)) {
                destArgsType = makeTupleObject(
                    evaluatorInterface,
                    [{ type: destArgsType, isUnbounded: true }],
                    /* isUnpacked */ true
                );
            }

            if (!isUnpacked(srcArgsType)) {
                srcArgsType = makeTupleObject(
                    evaluatorInterface,
                    [{ type: srcArgsType, isUnbounded: true }],
                    /* isUnpacked */ true
                );
            }

            if (
                !assignParam(
                    destArgsType,
                    srcArgsType,
                    destParamDetails.params[destParamDetails.argsIndex].index,
                    diag?.createAddendum(),
                    constraints,
                    flags,
                    recursionCount
                )
            ) {
                canAssign = false;
            }
        }

        // If the dest has an "*args" but the source doesn't, report the incompatibility.
        // The converse situation is OK.
        if (
            !FunctionType.isGradualCallableForm(destType) &&
            srcParamDetails.argsIndex === undefined &&
            srcParamSpec === undefined &&
            destParamDetails.argsIndex !== undefined &&
            !destParamDetails.hasUnpackedTypeVarTuple
        ) {
            diag?.createAddendum().addMessage(
                LocAddendum.argsParamMissing().format({
                    paramName: destParamDetails.params[destParamDetails.argsIndex].param.name ?? '',
                })
            );
            canAssign = false;
        }

        // Handle matching of named (keyword) parameters.
        if (!targetIncludesParamSpec) {
            // Build a dictionary of named parameters in the dest.
            const destParamMap = new Map<string, VirtualParamDetails>();

            if (destParamDetails.firstKeywordOnlyIndex !== undefined) {
                destParamDetails.params.forEach((param, index) => {
                    if (index >= destParamDetails.firstKeywordOnlyIndex!) {
                        if (
                            param.param.name &&
                            param.param.category === ParamCategory.Simple &&
                            param.kind !== ParamKind.Positional &&
                            param.kind !== ParamKind.ExpandedArgs
                        ) {
                            destParamMap.set(param.param.name, param);
                        }
                    }
                });
            }

            // If the dest has fewer positional arguments than the source, the remaining
            // positional arguments in the source can be treated as named arguments.
            let srcStartOfNamed =
                srcParamDetails.firstKeywordOnlyIndex !== undefined
                    ? srcParamDetails.firstKeywordOnlyIndex
                    : srcParamDetails.params.length;
            if (destPositionalCount < srcPositionalCount && destParamDetails.argsIndex === undefined) {
                srcStartOfNamed = destPositionalCount;
            }

            if (srcStartOfNamed >= 0) {
                srcParamDetails.params.forEach((srcParamInfo, index) => {
                    if (index < srcStartOfNamed) {
                        return;
                    }

                    if (
                        !srcParamInfo.param.name ||
                        srcParamInfo.param.category !== ParamCategory.Simple ||
                        srcParamInfo.kind === ParamKind.Positional
                    ) {
                        return;
                    }

                    const destParamInfo = destParamMap.get(srcParamInfo.param.name);
                    const paramDiag = diag?.createAddendum();
                    const srcParamType = srcParamInfo.type;

                    if (!destParamInfo) {
                        if (destParamDetails.kwargsIndex === undefined && !srcParamInfo.defaultType) {
                            if (paramDiag) {
                                paramDiag.addMessage(
                                    LocAddendum.namedParamMissingInDest().format({
                                        name: srcParamInfo.param.name,
                                    })
                                );
                            }
                            canAssign = false;
                        } else if (destParamDetails.kwargsIndex !== undefined) {
                            // Make sure we can assign the type to the Kwargs.
                            if (
                                !assignParam(
                                    destParamDetails.params[destParamDetails.kwargsIndex].type,
                                    srcParamType,
                                    destParamDetails.params[destParamDetails.kwargsIndex].index,
                                    diag?.createAddendum(),
                                    constraints,
                                    flags,
                                    recursionCount
                                )
                            ) {
                                canAssign = false;
                            }
                        } else if (srcParamInfo.defaultType) {
                            // Assign default arg values in case they are needed for
                            // populating TypeVar constraints.
                            const defaultArgType = srcParamInfo.defaultType ?? srcParamInfo.defaultType;

                            if (
                                defaultArgType &&
                                !assignType(
                                    srcParamInfo.type,
                                    defaultArgType,
                                    diag?.createAddendum(),
                                    constraints,
                                    flags,
                                    recursionCount
                                )
                            ) {
                                if ((flags & AssignTypeFlags.PartialOverloadOverlap) === 0) {
                                    canAssign = false;
                                }
                            }
                        }
                        return;
                    }

                    // If we're performing a partial overload match and both the source
                    // and dest parameters provide defaults, assume that there could
                    // be a match.
                    if (srcParamInfo.defaultType && destParamInfo.defaultType) {
                        if ((flags & AssignTypeFlags.PartialOverloadOverlap) !== 0) {
                            destParamMap.delete(srcParamInfo.param.name);
                            return;
                        }
                    }

                    const destParamType = destParamInfo.type;
                    const specializedDestParamType = constraints
                        ? solveAndApplyConstraints(destParamType, constraints)
                        : destParamType;

                    if (
                        !assignParam(
                            destParamInfo.type,
                            srcParamType,
                            /* paramIndex */ undefined,
                            paramDiag?.createAddendum(),
                            constraints,
                            flags,
                            recursionCount
                        )
                    ) {
                        if (paramDiag) {
                            paramDiag.addMessage(
                                LocAddendum.namedParamTypeMismatch().format({
                                    name: srcParamInfo.param.name,
                                    sourceType: printType(specializedDestParamType),
                                    destType: printType(srcParamType),
                                })
                            );
                        }
                        canAssign = false;
                    }

                    if (destParamInfo.defaultType && !srcParamInfo.defaultType) {
                        diag?.createAddendum().addMessage(
                            LocAddendum.functionParamDefaultMissing().format({
                                name: srcParamInfo.param.name,
                            })
                        );
                        canAssign = false;
                    }

                    destParamMap.delete(srcParamInfo.param.name);
                });
            }

            // See if there are any unmatched named parameters.
            destParamMap.forEach((destParamInfo, paramName) => {
                if (srcParamDetails.kwargsIndex !== undefined && destParamInfo.param.name) {
                    // Make sure the src kwargs type is compatible.
                    if (
                        !assignParam(
                            destParamInfo.type,
                            srcParamDetails.params[srcParamDetails.kwargsIndex].type,
                            destParamInfo.index,
                            diag?.createAddendum(),
                            constraints,
                            flags,
                            recursionCount
                        )
                    ) {
                        canAssign = false;
                    }
                    destParamMap.delete(paramName);
                } else {
                    diag?.createAddendum().addMessage(
                        LocAddendum.namedParamMissingInSource().format({ name: paramName })
                    );
                    canAssign = false;
                }
            });

            // If both src and dest have a "**kwargs" parameter, make sure their types are compatible.
            if (srcParamDetails.kwargsIndex !== undefined && destParamDetails.kwargsIndex !== undefined) {
                if (
                    !assignParam(
                        destParamDetails.params[destParamDetails.kwargsIndex].type,
                        srcParamDetails.params[srcParamDetails.kwargsIndex].type,
                        destParamDetails.params[destParamDetails.kwargsIndex].index,
                        diag?.createAddendum(),
                        constraints,
                        flags,
                        recursionCount
                    )
                ) {
                    canAssign = false;
                }
            }

            // If the dest has a "**kwargs" but the source doesn't, report the incompatibility.
            // The converse situation is OK.
            if (
                !FunctionType.isGradualCallableForm(destType) &&
                srcParamDetails.kwargsIndex === undefined &&
                srcParamSpec === undefined &&
                destParamDetails.kwargsIndex !== undefined
            ) {
                diag?.createAddendum().addMessage(
                    LocAddendum.kwargsParamMissing().format({
                        paramName: destParamDetails.params[destParamDetails.kwargsIndex].param.name!,
                    })
                );
                canAssign = false;
            }
        }

        if ((flags & AssignTypeFlags.OverloadOverlap) !== 0) {
            // If we're checking for full overlapping overloads and the source is
            // a gradual form, the dest must also be a gradual form.
            if (FunctionType.isGradualCallableForm(srcType) && !FunctionType.isGradualCallableForm(destType)) {
                canAssign = false;
            }

            // If the src contains a ParamSpec the dest must also.
            if (srcParamSpec && !destParamSpec) {
                canAssign = false;
            }
        }

        // If the source and the dest are using the same ParamSpec, any additional
        // concatenated parameters must match.
        if (targetIncludesParamSpec && srcParamSpec?.priv.nameWithScope === destParamSpec?.priv.nameWithScope) {
            if (srcParamDetails.params.length !== destParamDetails.params.length) {
                canAssign = false;
            }
        }

        // Are we assigning to a function with a ParamSpec?
        if (targetIncludesParamSpec) {
            const effectiveSrcType = isContra ? destType : srcType;
            const effectiveDestType = isContra ? srcType : destType;

            const effectiveSrcParamSpec = isContra ? destParamSpec : srcParamSpec;
            const effectiveDestParamSpec = isContra ? srcParamSpec : destParamSpec;

            if (effectiveDestParamSpec) {
                const requiredMatchParamCount = effectiveDestType.shared.parameters.filter((p, i) => {
                    if (!p.name) {
                        return false;
                    }

                    const paramType = FunctionType.getParamType(effectiveDestType, i);
                    if (p.category === ParamCategory.Simple && isParamSpec(paramType)) {
                        return false;
                    }
                    return true;
                }).length;
                let matchedParamCount = 0;
                const remainingParams: FunctionParam[] = [];

                // If there are parameters in the source that are not matched
                // to parameters in the dest, assume these are concatenated on
                // to the ParamSpec.
                effectiveSrcType.shared.parameters.forEach((p, index) => {
                    if (matchedParamCount < requiredMatchParamCount) {
                        if (p.name) {
                            matchedParamCount++;
                        }

                        // If this is a *args parameter, assume that it provides
                        // the remaining positional parameters, but also assume
                        // that it is not exhausted and can provide additional
                        // parameters.
                        if (p.category !== ParamCategory.ArgsList) {
                            return;
                        }
                    }

                    if (isPositionOnlySeparator(p) && remainingParams.length === 0) {
                        // Don't bother pushing a position-only separator if it
                        // is the first remaining param.
                        return;
                    }

                    remainingParams.push(
                        FunctionParam.create(
                            p.category,
                            FunctionType.getParamType(effectiveSrcType, index),
                            p.flags,
                            p.name,
                            FunctionType.getParamDefaultType(effectiveSrcType, index),
                            p.defaultExpr
                        )
                    );
                });

                // If there are remaining parameters and the source and dest do not contain
                // the same ParamSpec, synthesize a function for the remaining parameters.
                if (
                    remainingParams.length > 0 ||
                    !effectiveSrcParamSpec ||
                    !isTypeSame(effectiveSrcParamSpec, effectiveDestParamSpec, { ignoreTypeFlags: true })
                ) {
                    const effectiveSrcPosCount = isContra ? destPositionalCount : srcPositionalCount;
                    const effectiveDestPosCount = isContra ? srcPositionalCount : destPositionalCount;

                    // If the src and dest both have ParamSpecs but the src has additional positional
                    // parameters that have not been matched to dest positional parameters (probably due
                    // to a Concatenate), don't attempt to assign the remaining parameters to the ParamSpec.
                    if (!effectiveSrcParamSpec || effectiveSrcPosCount >= effectiveDestPosCount) {
                        const remainingFunction = FunctionType.createInstance(
                            '',
                            '',
                            '',
                            effectiveSrcType.shared.flags | FunctionTypeFlags.SynthesizedMethod,
                            effectiveSrcType.shared.docString
                        );
                        remainingFunction.shared.deprecatedMessage = effectiveSrcType.shared.deprecatedMessage;
                        remainingFunction.shared.typeVarScopeId = effectiveSrcType.shared.typeVarScopeId;
                        remainingFunction.priv.constructorTypeVarScopeId =
                            effectiveSrcType.priv.constructorTypeVarScopeId;
                        remainingFunction.shared.methodClass = effectiveSrcType.shared.methodClass;
                        remainingParams.forEach((param) => {
                            FunctionType.addParam(remainingFunction, param);
                        });
                        if (effectiveSrcParamSpec) {
                            FunctionType.addParamSpecVariadics(
                                remainingFunction,
                                convertToInstance(effectiveSrcParamSpec)
                            );
                        }

                        if (
                            !assignType(
                                effectiveDestParamSpec,
                                remainingFunction,
                                /* diag */ undefined,
                                constraints,
                                flags
                            )
                        ) {
                            // If we couldn't assign the function to the ParamSpec, see if we can
                            // assign only the ParamSpec. This is possible if there were no
                            // remaining parameters.
                            if (
                                remainingParams.length > 0 ||
                                !effectiveSrcParamSpec ||
                                !assignType(
                                    convertToInstance(effectiveDestParamSpec),
                                    convertToInstance(effectiveSrcParamSpec),
                                    /* diag */ undefined,
                                    constraints,
                                    flags
                                )
                            ) {
                                canAssign = false;
                            }
                        }
                    }
                }
            }
        }

        // Match the return parameter.
        if (checkReturnType) {
            const destReturnType = getEffectiveReturnType(destType);
            if (!isAnyOrUnknown(destReturnType)) {
                const srcReturnType = solveAndApplyConstraints(getEffectiveReturnType(srcType), constraints);
                const returnDiag = diag?.createAddendum();

                let isReturnTypeCompatible = false;

                let effectiveFlags = flags;

                // If the source has a declared return type that includes a literal
                // in its annotation, assume that we will want the constraint
                // solver to retain literals.
                if (
                    srcType.shared.declaredReturnType &&
                    containsLiteralType(srcType.shared.declaredReturnType, /* includeTypeArgs */ true)
                ) {
                    effectiveFlags |= AssignTypeFlags.RetainLiteralsForTypeVar;
                }

                if (
                    assignType(
                        destReturnType,
                        srcReturnType,
                        returnDiag?.createAddendum(),
                        constraints,
                        effectiveFlags,
                        recursionCount
                    )
                ) {
                    isReturnTypeCompatible = true;
                } else {
                    // Handle the special case where the return type is a TypeGuard[T]
                    // or TypeIs[T]. This should also act as a bool, since that's its
                    // type at runtime.
                    if (
                        isClassInstance(srcReturnType) &&
                        ClassType.isBuiltIn(srcReturnType, ['TypeGuard', 'TypeIs']) &&
                        prefetched?.boolClass &&
                        isInstantiableClass(prefetched.boolClass)
                    ) {
                        if (
                            assignType(
                                destReturnType,
                                ClassType.cloneAsInstance(prefetched.boolClass),
                                returnDiag?.createAddendum(),
                                constraints,
                                flags,
                                recursionCount
                            )
                        ) {
                            isReturnTypeCompatible = true;
                        }
                    }
                }

                if (!isReturnTypeCompatible) {
                    if (returnDiag) {
                        returnDiag.addMessage(
                            LocAddendum.functionReturnTypeMismatch().format({
                                sourceType: printType(srcReturnType),
                                destType: printType(destReturnType),
                            })
                        );
                    }
                    canAssign = false;
                }
            }
        }

        return canAssign;
    }

    // When a value is assigned to a variable with a declared type,
    // we may be able to narrow the type based on the assignment.
    function narrowTypeBasedOnAssignment(declaredType: Type, assignedTypeResult: TypeResult): TypeResult {
        // TODO: The rules for narrowing types on assignment are not defined in
        // the typing spec. Pyright's current logic is currently not even internally
        // consistent and probably not sound from a type theory perspective. It
        // should be completely reworked once there has been a public discussion
        // about the correct behavior.

        const narrowedType = mapSubtypes(assignedTypeResult.type, (assignedSubtype) => {
            // Handle the special case where the assigned type is a literal type.
            // Some types include very large unions of literal types, and we don't
            // want to use an n^2 loop to compare them.
            if (isClass(assignedSubtype) && isLiteralType(assignedSubtype)) {
                if (isUnion(declaredType) && UnionType.containsType(declaredType, assignedSubtype)) {
                    return assignedSubtype;
                }
            }

            const narrowedSubtype = mapSubtypes(declaredType, (declaredSubtype) => {
                if (!assignType(declaredSubtype, assignedSubtype)) {
                    return undefined;
                }

                // Retain unknowns for code flow analysis convergence and for
                // unknown type reporting in strict mode.
                if (isUnknown(assignedSubtype)) {
                    return assignedSubtype;
                }

                // If the two types are bidirectionally assignable, they are
                // either equivalent (in which case it doesn't matter which
                // one we choose) or one or both include gradual types (Any, etc.),
                // in which case we'll want to stick with the declared subtype.
                if (assignType(assignedSubtype, declaredSubtype)) {
                    // We need to be careful with TypedDict types that have
                    // narrowed fields. In this case, we want to return the
                    // assigned type.
                    if (
                        isClass(assignedSubtype) &&
                        assignedSubtype.priv.typedDictNarrowedEntries &&
                        isTypeSame(assignedSubtype, declaredSubtype, { ignoreTypedDictNarrowEntries: true })
                    ) {
                        return assignedSubtype;
                    }

                    // We also need to be careful with callback protocols.
                    if (isClassInstance(declaredSubtype) && ClassType.isProtocolClass(declaredSubtype)) {
                        if (isFunctionOrOverloaded(assignedSubtype)) {
                            return assignedSubtype;
                        }
                    }

                    return declaredSubtype;
                }

                return assignedSubtype;
            });

            // If we couldn't assign the assigned subtype any of the declared
            // subtypes, the types are incompatible. Return the unnarrowed form.
            if (isNever(narrowedSubtype)) {
                return assignedSubtype;
            }

            return narrowedSubtype;
        });

        // If the result of narrowing is an Unknown that is incomplete, propagate the
        // incomplete type for the benefit of code flow analysis.
        // If the result of narrowing is a complete Unknown, combine the Unknown type
        // with the declared type. In strict mode, this will retain the "unknown type"
        // diagnostics while still providing reasonable completion suggestions.
        if (isIncompleteUnknown(narrowedType)) {
            return { type: narrowedType, isIncomplete: assignedTypeResult.isIncomplete };
        } else if (isUnknown(narrowedType)) {
            return { type: combineTypes([narrowedType, declaredType]), isIncomplete: assignedTypeResult.isIncomplete };
        }

        return { type: narrowedType, isIncomplete: assignedTypeResult.isIncomplete };
    }

    function validateOverrideMethod(
        baseMethod: Type,
        overrideMethod: FunctionType | OverloadedType,
        baseClass: ClassType | undefined,
        diag: DiagnosticAddendum,
        enforceParamNames = true
    ): boolean {
        // If we're overriding a non-method with a method, report it as an error.
        // This occurs when a non-property overrides a property.
        if (!isFunctionOrOverloaded(baseMethod)) {
            diag.addMessage(LocAddendum.overrideType().format({ type: printType(baseMethod) }));
            return false;
        }

        if (isFunction(baseMethod)) {
            // Handle the easy case - a simple function overriding another simple function.
            if (isFunction(overrideMethod)) {
                return validateOverrideMethodInternal(baseMethod, overrideMethod, diag, enforceParamNames);
            }

            const overloadsAndImpl = [...OverloadedType.getOverloads(overrideMethod)];
            const impl = OverloadedType.getImplementation(overrideMethod);
            if (impl && isFunction(impl)) {
                overloadsAndImpl.push(impl);
            }

            // For an overload overriding a base method, at least one overload
            // or the implementation must be compatible with the base method.
            if (
                overloadsAndImpl.some((overrideOverload) => {
                    return validateOverrideMethodInternal(
                        baseMethod,
                        overrideOverload,
                        /* diag */ undefined,
                        enforceParamNames
                    );
                })
            ) {
                return true;
            }

            diag.addMessage(LocAddendum.overrideNoOverloadMatches());
            return false;
        }

        // For a non-overloaded method overriding an overloaded method, the
        // override must match all of the overloads.
        if (isFunction(overrideMethod)) {
            return OverloadedType.getOverloads(baseMethod).every((overload) => {
                // If the override isn't applicable for this base class, skip the check.
                if (baseClass && !isOverrideMethodApplicable(overload, baseClass)) {
                    return true;
                }

                return validateOverrideMethodInternal(
                    overload,
                    overrideMethod,
                    diag?.createAddendum(),
                    enforceParamNames
                );
            });
        }

        // For an overloaded method overriding an overloaded method, the overrides
        // must all match and be in the correct order. It is OK if the base method
        // has additional overloads that are not present in the override.

        let previousMatchIndex = -1;
        const baseOverloads = OverloadedType.getOverloads(baseMethod);

        for (const overrideOverload of OverloadedType.getOverloads(overrideMethod)) {
            let possibleMatchIndex: number | undefined;

            let matchIndex = baseOverloads.findIndex((baseOverload, index) => {
                // If the override isn't applicable for this base class, skip the check.
                if (baseClass && !isOverrideMethodApplicable(baseOverload, baseClass)) {
                    return false;
                }

                const isCompatible = validateOverrideMethodInternal(
                    baseOverload,
                    overrideOverload,
                    /* diag */ undefined,
                    enforceParamNames
                );

                // If the override is compatible but the match is one that is below the previous
                // matched index, keep looking for additional matches. Record the fact that
                // we found at least one match.
                if (isCompatible && index <= previousMatchIndex && possibleMatchIndex === undefined) {
                    possibleMatchIndex = index;
                    return false;
                }

                return isCompatible;
            });

            if (matchIndex < 0 && possibleMatchIndex !== undefined) {
                matchIndex = possibleMatchIndex;
            }

            if (matchIndex < 0) {
                break;
            }

            if (matchIndex < previousMatchIndex) {
                diag.addMessage(LocAddendum.overrideOverloadOrder());
                return false;
            }

            previousMatchIndex = matchIndex;
        }

        if (previousMatchIndex < baseOverloads.length - 1) {
            const unmatchedOverloads = baseOverloads.slice(previousMatchIndex + 1);

            // See if all of the remaining overrides are nonapplicable.
            if (
                !baseClass ||
                unmatchedOverloads.some((overload) => {
                    return isOverrideMethodApplicable(overload, baseClass);
                })
            ) {
                // We didn't find matches for all of the base overloads.
                diag.addMessage(LocAddendum.overrideOverloadNoMatch());
                return false;
            }
        }

        return true;
    }

    // Determines whether a child class override is applicable to a parent
    // class method signature. This is important in cases where the parent
    // class defines an overload where some of the overload signatures supply
    // explicit type annotations for the "self" or "cls" parameter and some
    // of these do not apply to the child class.
    function isOverrideMethodApplicable(baseMethod: FunctionType, childClass: ClassType): boolean {
        if (
            !FunctionType.isInstanceMethod(baseMethod) &&
            !FunctionType.isClassMethod(baseMethod) &&
            !FunctionType.isConstructorMethod(baseMethod)
        ) {
            return true;
        }

        const baseParamDetails = getParamListDetails(baseMethod);
        if (baseParamDetails.params.length === 0) {
            return true;
        }

        const baseParamType = baseParamDetails.params[0].param;

        if (baseParamType.category !== ParamCategory.Simple || !FunctionParam.isTypeDeclared(baseParamType)) {
            return true;
        }

        // If this is a self or cls parameter, determine whether the override
        // class can be assigned to the base parameter type. If not, then this
        // override doesn't apply. This is important for overloads where the
        // base class contains some overload signatures that are not applicable
        // to the child class.
        const childSelfOrClsType = FunctionType.isInstanceMethod(baseMethod)
            ? ClassType.cloneAsInstance(childClass)
            : childClass;

        return assignType(
            baseParamDetails.params[0].type,
            childSelfOrClsType,
            /* diag */ undefined,
            /* constraints */ undefined,
            AssignTypeFlags.Default
        );
    }

    // Determines whether the override method is compatible with the overridden method.
    // This is used both for parent/child overrides and implicit overrides for peer
    // classes in a multi-inheritance case. If enforceParamNames is true, the parameter
    // names of non-positional-only parameters are enforced.
    function validateOverrideMethodInternal(
        baseMethod: FunctionType,
        overrideMethod: FunctionType,
        diag: DiagnosticAddendum | undefined,
        enforceParamNames: boolean
    ): boolean {
        const baseParamDetails = getParamListDetails(baseMethod);
        const overrideParamDetails = getParamListDetails(overrideMethod);
        const constraints = new ConstraintTracker();

        let canOverride = true;

        if (!FunctionType.isGradualCallableForm(baseMethod) && !FunctionType.isGradualCallableForm(overrideMethod)) {
            // Verify that we're not overriding a static, class or instance method with
            // an incompatible type.
            if (FunctionType.isStaticMethod(baseMethod)) {
                if (!FunctionType.isStaticMethod(overrideMethod)) {
                    diag?.addMessage(LocAddendum.overrideNotStaticMethod());
                    canOverride = false;
                }
            } else if (FunctionType.isClassMethod(baseMethod)) {
                if (!FunctionType.isClassMethod(overrideMethod)) {
                    diag?.addMessage(LocAddendum.overrideNotClassMethod());
                    canOverride = false;
                }
            } else if (FunctionType.isInstanceMethod(baseMethod)) {
                if (!FunctionType.isInstanceMethod(overrideMethod)) {
                    diag?.addMessage(LocAddendum.overrideNotInstanceMethod());
                    canOverride = false;
                }
            }

            // Verify that the positional param count matches exactly or that the override
            // adds only params that preserve the original signature.
            let foundParamCountMismatch = false;
            if (overrideParamDetails.positionParamCount < baseParamDetails.positionParamCount) {
                if (overrideParamDetails.argsIndex === undefined) {
                    foundParamCountMismatch = true;
                } else {
                    const overrideArgsType = overrideParamDetails.params[overrideParamDetails.argsIndex].type;
                    for (
                        let i = overrideParamDetails.positionParamCount;
                        i < baseParamDetails.positionParamCount;
                        i++
                    ) {
                        if (
                            !assignType(
                                overrideArgsType,
                                baseParamDetails.params[i].type,
                                diag?.createAddendum(),
                                constraints,
                                AssignTypeFlags.Default
                            )
                        ) {
                            LocAddendum.overrideParamType().format({
                                index: i + 1,
                                baseType: printType(baseParamDetails.params[i].type),
                                overrideType: printType(overrideArgsType),
                            });
                            canOverride = false;
                        }
                    }
                }
            } else if (overrideParamDetails.positionParamCount > baseParamDetails.positionParamCount) {
                const isCallableAttrOverriddenByInstanceMethod =
                    baseParamDetails.positionParamCount === 0 &&
                    overrideParamDetails.positionParamCount === 1 &&
                    FunctionType.isInstanceMethod(overrideMethod) &&
                    !FunctionType.isStaticMethod(baseMethod) &&
                    !FunctionType.isClassMethod(baseMethod);

                if (!isCallableAttrOverriddenByInstanceMethod) {
                    // Verify that all of the override parameters that extend the
                    // signature are either *args, **kwargs or parameters with
                    // default values.

                    for (
                        let i = baseParamDetails.positionParamCount;
                        i < overrideParamDetails.positionParamCount;
                        i++
                    ) {
                        const overrideParam = overrideParamDetails.params[i].param;

                        if (
                            overrideParam.category === ParamCategory.Simple &&
                            overrideParam.name &&
                            !overrideParamDetails.params[i].defaultType
                        ) {
                            foundParamCountMismatch = true;
                        }
                    }
                }
            }

            if (foundParamCountMismatch) {
                diag?.addMessage(
                    LocAddendum.overridePositionalParamCount().format({
                        baseCount: baseParamDetails.params.length,
                        overrideCount: overrideParamDetails.params.length,
                    })
                );
                canOverride = false;
            }

            const positionalParamCount = Math.min(
                baseParamDetails.positionParamCount,
                overrideParamDetails.positionParamCount
            );

            for (let i = 0; i < positionalParamCount; i++) {
                // If the first parameter is a "self" or "cls" parameter, skip the
                // test because these are allowed to violate the Liskov substitution
                // principle.
                //
                // Note: the checker deliberately defeats this skip for the
                // "callable variable overridden by a method" case by marking both
                // sides static before calling this routine (see
                // `_getCallableVariableOverrideComparison` in checker.ts). Keep the
                // two in sync if this skip logic changes.
                if (i === 0) {
                    if (
                        FunctionType.isInstanceMethod(overrideMethod) ||
                        FunctionType.isClassMethod(overrideMethod) ||
                        FunctionType.isConstructorMethod(overrideMethod)
                    ) {
                        continue;
                    }
                }

                const baseParam = baseParamDetails.params[i].param;
                const overrideParam = overrideParamDetails.params[i].param;

                if (
                    i >= baseParamDetails.positionOnlyParamCount &&
                    !isPrivateOrProtectedName(baseParam.name || '') &&
                    baseParamDetails.params[i].kind !== ParamKind.Positional &&
                    baseParam.category === ParamCategory.Simple &&
                    enforceParamNames &&
                    baseParam.name !== overrideParam.name
                ) {
                    if (overrideParam.category === ParamCategory.Simple) {
                        if (!FunctionParam.isNameSynthesized(baseParam)) {
                            if (overrideParamDetails.params[i].kind === ParamKind.Positional) {
                                diag?.addMessage(
                                    LocAddendum.overrideParamNamePositionOnly().format({
                                        index: i + 1,
                                        baseName: baseParam.name || '*',
                                    })
                                );
                            } else {
                                diag?.addMessage(
                                    LocAddendum.overrideParamName().format({
                                        index: i + 1,
                                        baseName: baseParam.name || '*',
                                        overrideName: overrideParam.name || '*',
                                    })
                                );
                            }
                            canOverride = false;
                        }
                    }
                } else if (
                    i < overrideParamDetails.positionOnlyParamCount &&
                    i >= baseParamDetails.positionOnlyParamCount
                ) {
                    if (
                        !FunctionParam.isNameSynthesized(baseParam) &&
                        baseParamDetails.params[i].kind !== ParamKind.Positional &&
                        baseParamDetails.params[i].kind !== ParamKind.ExpandedArgs
                    ) {
                        diag?.addMessage(
                            LocAddendum.overrideParamNamePositionOnly().format({
                                index: i + 1,
                                baseName: baseParam.name || '*',
                            })
                        );
                        canOverride = false;
                    }
                } else {
                    const baseParamType = baseParamDetails.params[i].type;
                    const overrideParamType = overrideParamDetails.params[i].type;

                    const baseIsSynthesizedTypeVar = isTypeVar(baseParamType) && baseParamType.shared.isSynthesized;
                    const overrideIsSynthesizedTypeVar =
                        isTypeVar(overrideParamType) && overrideParamType.shared.isSynthesized;

                    if (!baseIsSynthesizedTypeVar && !overrideIsSynthesizedTypeVar) {
                        if (
                            baseParam.category !== overrideParam.category ||
                            !assignType(
                                overrideParamType,
                                baseParamType,
                                diag?.createAddendum(),
                                constraints,
                                AssignTypeFlags.Default
                            )
                        ) {
                            diag?.addMessage(
                                LocAddendum.overrideParamType().format({
                                    index: i + 1,
                                    baseType: printType(baseParamType),
                                    overrideType: printType(overrideParamType),
                                })
                            );
                            canOverride = false;
                        }
                    }

                    if (baseParamDetails.params[i].defaultType && !overrideParamDetails.params[i].defaultType) {
                        diag?.addMessage(
                            LocAddendum.overrideParamNoDefault().format({
                                index: i + 1,
                            })
                        );
                        canOverride = false;
                    }
                }
            }

            // Check for positional (named) parameters in the base method that
            // do not exist in the override.
            if (enforceParamNames && overrideParamDetails.kwargsIndex === undefined) {
                for (let i = positionalParamCount; i < baseParamDetails.positionParamCount; i++) {
                    const baseParam = baseParamDetails.params[i];

                    if (baseParam.kind === ParamKind.Standard && baseParam.param.category === ParamCategory.Simple) {
                        diag?.addMessage(
                            LocAddendum.overrideParamNamePositionOnly().format({
                                index: i + 1,
                                baseName: baseParam.param.name || '*',
                            })
                        );
                        canOverride = false;
                    }
                }
            }

            // Check for a *args match.
            if (baseParamDetails.argsIndex !== undefined) {
                if (overrideParamDetails.argsIndex === undefined) {
                    diag?.addMessage(
                        LocAddendum.overrideParamNameMissing().format({
                            name: baseParamDetails.params[baseParamDetails.argsIndex].param.name ?? '?',
                        })
                    );
                    canOverride = false;
                } else {
                    const overrideParamType = overrideParamDetails.params[overrideParamDetails.argsIndex].type;
                    const baseParamType = baseParamDetails.params[baseParamDetails.argsIndex].type;

                    if (
                        !assignType(
                            overrideParamType,
                            baseParamType,
                            diag?.createAddendum(),
                            constraints,
                            AssignTypeFlags.Default
                        )
                    ) {
                        diag?.addMessage(
                            LocAddendum.overrideParamKeywordType().format({
                                name: overrideParamDetails.params[overrideParamDetails.argsIndex].param.name ?? '?',
                                baseType: printType(baseParamType),
                                overrideType: printType(overrideParamType),
                            })
                        );
                        canOverride = false;
                    }
                }
            }

            // Now check any keyword-only parameters.
            const baseKwOnlyParams = baseParamDetails.params.filter(
                (paramInfo) => paramInfo.kind === ParamKind.Keyword && paramInfo.param.category === ParamCategory.Simple
            );
            const overrideKwOnlyParams = overrideParamDetails.params.filter(
                (paramInfo) => paramInfo.kind === ParamKind.Keyword && paramInfo.param.category === ParamCategory.Simple
            );

            baseKwOnlyParams.forEach((paramInfo) => {
                const overrideParamInfo = overrideKwOnlyParams.find((pi) => paramInfo.param.name === pi.param.name);

                if (!overrideParamInfo && overrideParamDetails.kwargsIndex === undefined) {
                    diag?.addMessage(
                        LocAddendum.overrideParamNameMissing().format({
                            name: paramInfo.param.name ?? '?',
                        })
                    );
                    canOverride = false;
                } else {
                    let targetParamType = overrideParamInfo?.type;
                    if (!targetParamType) {
                        targetParamType = overrideParamDetails.params[overrideParamDetails.kwargsIndex!].type;
                    }

                    if (
                        !assignType(
                            targetParamType,
                            paramInfo.type,
                            diag?.createAddendum(),
                            constraints,
                            AssignTypeFlags.Default
                        )
                    ) {
                        diag?.addMessage(
                            LocAddendum.overrideParamKeywordType().format({
                                name: paramInfo.param.name ?? '?',
                                baseType: printType(paramInfo.type),
                                overrideType: printType(targetParamType),
                            })
                        );
                        canOverride = false;
                    }

                    if (overrideParamInfo) {
                        if (paramInfo.defaultType && !overrideParamInfo.defaultType) {
                            diag?.addMessage(
                                LocAddendum.overrideParamKeywordNoDefault().format({
                                    name: overrideParamInfo.param.name ?? '?',
                                })
                            );
                            canOverride = false;
                        }
                    }
                }
            });

            // Verify that any keyword-only parameters added by the overload are compatible
            // with the **kwargs in the base.
            overrideKwOnlyParams.forEach((paramInfo) => {
                const baseParamInfo = baseKwOnlyParams.find((pi) => paramInfo.param.name === pi.param.name);

                if (!baseParamInfo) {
                    if (baseParamDetails.kwargsIndex === undefined) {
                        if (!paramInfo.defaultType) {
                            diag?.addMessage(
                                LocAddendum.overrideParamNameExtra().format({
                                    name: paramInfo.param.name ?? '?',
                                })
                            );
                            canOverride = false;
                        }
                    } else {
                        // Base has a **kwargs; ensure the added keyword-only parameter's
                        // type is compatible with the base's **kwargs value type.
                        const baseKwargsType = baseParamDetails.params[baseParamDetails.kwargsIndex].type;
                        if (
                            !assignType(
                                paramInfo.type,
                                baseKwargsType,
                                diag?.createAddendum(),
                                constraints,
                                AssignTypeFlags.Default
                            )
                        ) {
                            diag?.addMessage(
                                LocAddendum.overrideParamKeywordType().format({
                                    name: paramInfo.param.name ?? '?',
                                    baseType: printType(baseKwargsType),
                                    overrideType: printType(paramInfo.type),
                                })
                            );
                            canOverride = false;
                        }
                    }
                }
            });

            // Verify that if the base method has a **kwargs parameter, the override does too.
            if (baseParamDetails.kwargsIndex !== undefined && overrideParamDetails.kwargsIndex === undefined) {
                diag?.addMessage(
                    LocAddendum.kwargsParamMissing().format({
                        paramName: baseParamDetails.params[baseParamDetails.kwargsIndex].param.name!,
                    })
                );
                canOverride = false;
            }
        }

        // Verify that one or the other method doesn't contain a ParamSpec.
        if (baseParamDetails.paramSpec && !overrideParamDetails.paramSpec) {
            // If the override uses an `*args: Any, **kwargs: Any` signature, we
            // will allow this as an acceptable overload for a `*args: P.args, **kwargs: P.kwargs`.
            const overrideHasArgsKwargs =
                overrideParamDetails.argsIndex !== undefined &&
                isAnyOrUnknown(overrideParamDetails.params[overrideParamDetails.argsIndex].type) &&
                overrideParamDetails.kwargsIndex !== undefined &&
                isAnyOrUnknown(overrideParamDetails.params[overrideParamDetails.kwargsIndex].type);

            if (!overrideHasArgsKwargs) {
                diag?.addMessage(LocAddendum.paramSpecMissingInOverride());
                canOverride = false;
            }
        }

        // Now check the return type.
        const baseReturnType = getEffectiveReturnType(baseMethod);
        const overrideReturnType = solveAndApplyConstraints(getEffectiveReturnType(overrideMethod), constraints);

        if (
            !assignType(
                baseReturnType,
                overrideReturnType,
                diag?.createAddendum(),
                constraints,
                AssignTypeFlags.Default
            )
        ) {
            diag?.addMessage(
                LocAddendum.overrideReturnType().format({
                    baseType: printType(baseReturnType),
                    overrideType: printType(overrideReturnType),
                })
            );

            canOverride = false;
        }

        return canOverride;
    }

    // Validates that the specified source type matches the constraints
    // of the type variable. If successful, it returns the constraint
    // type that applies. If unsuccessful, it returns undefined.
    function applyTypeArgToTypeVar(destType: TypeVarType, srcType: Type, diag: DiagnosticAddendum): Type | undefined {
        if (isAnyOrUnknown(srcType)) {
            return srcType;
        }

        let effectiveSrcType: Type = transformPossibleRecursiveTypeAlias(srcType);

        if (isTypeVar(srcType)) {
            if (isTypeSame(srcType, destType)) {
                return srcType;
            }

            effectiveSrcType = makeTopLevelTypeVarsConcrete(srcType);
        }

        // If this is a partially-evaluated class, don't perform any further
        // checks. Assume in this case that the type is compatible with the
        // bound or constraint.
        if (isClass(effectiveSrcType) && ClassType.isPartiallyEvaluated(effectiveSrcType)) {
            return srcType;
        }

        // If there's a bound type, make sure the source is derived from it.
        if (destType.shared.boundType && !isTypeAliasPlaceholder(effectiveSrcType)) {
            if (
                !assignType(
                    destType.shared.boundType,
                    effectiveSrcType,
                    diag.createAddendum(),
                    /* constraints */ undefined
                )
            ) {
                // Avoid adding a message that will confuse users if the TypeVar was
                // synthesized for internal purposes.
                if (!destType.shared.isSynthesized) {
                    diag.addMessage(
                        LocAddendum.typeBound().format({
                            sourceType: printType(effectiveSrcType),
                            destType: printType(destType.shared.boundType),
                            name: TypeVarType.getReadableName(destType),
                        })
                    );
                }
                return undefined;
            }
        }

        if (isParamSpec(destType)) {
            if (isParamSpec(srcType)) {
                return srcType;
            }

            if (isFunction(srcType) && FunctionType.isParamSpecValue(srcType)) {
                return srcType;
            }

            if (isClassInstance(srcType) && ClassType.isBuiltIn(srcType, 'Concatenate')) {
                return srcType;
            }

            diag.addMessage(
                LocAddendum.typeParamSpec().format({
                    type: printType(srcType),
                    name: TypeVarType.getReadableName(destType),
                })
            );

            return undefined;
        }

        if (isParamSpec(srcType)) {
            diag.addMessage(LocMessage.paramSpecContext());
            return undefined;
        }

        // If there are no constraints, we're done.
        const constraints = destType.shared.constraints;
        if (constraints.length === 0) {
            return srcType;
        }

        if (isTypeAliasPlaceholder(srcType)) {
            return srcType;
        }

        if (isTypeVar(srcType) && TypeVarType.hasConstraints(srcType)) {
            // Make sure all the source constraint types map to constraint types in the dest.
            if (
                srcType.shared.constraints.every((sourceConstraint) => {
                    return constraints.some((destConstraint) => assignType(destConstraint, sourceConstraint));
                })
            ) {
                return srcType;
            }
        } else {
            let bestConstraintSoFar: Type | undefined;

            // Try to find the best (narrowest) match among the constraints.
            for (const constraint of constraints) {
                if (assignType(constraint, effectiveSrcType)) {
                    // Don't allow Never to match unless the constraint is also explicitly Never.
                    if (!isNever(effectiveSrcType) || isNever(constraint)) {
                        if (!bestConstraintSoFar || assignType(bestConstraintSoFar, constraint)) {
                            bestConstraintSoFar = constraint;
                        }
                    }
                }
            }

            if (bestConstraintSoFar) {
                return bestConstraintSoFar;
            }
        }

        diag.addMessage(
            LocAddendum.typeConstrainedTypeVar().format({
                type: printType(srcType),
                name: TypeVarType.getReadableName(destType),
            })
        );

        return undefined;
    }

    // Returns a list of unimplemented abstract symbols (methods or variables) for
    // the specified class.
    function getAbstractSymbols(classType: ClassType): AbstractSymbol[] {
        const symbolTable = new Map<string, AbstractSymbol>();

        ClassType.getReverseMro(classType).forEach((mroClass) => {
            if (isInstantiableClass(mroClass)) {
                // See if this class is introducing a new abstract symbol that has not been
                // introduced previously or if it is overriding an abstract symbol with
                // a non-abstract one.
                ClassType.getSymbolTable(mroClass).forEach((symbol, symbolName) => {
                    const abstractSymbolInfo = getAbstractSymbolInfo(mroClass, symbolName);

                    if (abstractSymbolInfo) {
                        symbolTable.set(symbolName, abstractSymbolInfo);
                    } else {
                        symbolTable.delete(symbolName);
                    }
                });
            }
        });

        // Create a final list of symbols that are abstract.
        const symbolList: AbstractSymbol[] = [];
        symbolTable.forEach((method) => {
            symbolList.push(method);
        });

        return symbolList;
    }

    // If the memberType is an instance or class method, creates a new
    // version of the function that has the "self" or "cls" parameter bound
    // to it. If treatConstructorAsClassMethod is true, the function is
    // treated like a class method even if it's not marked as such. That's
    // needed to special-case the __new__ magic method when it's invoked as
    // a constructor (as opposed to by name).
    function bindFunctionToClassOrObject(
        baseType: ClassType | undefined,
        memberType: FunctionType | OverloadedType,
        memberClass?: ClassType,
        treatConstructorAsClassMethod = false,
        selfType?: ClassType | TypeVarType,
        diag?: DiagnosticAddendum,
        recursionCount = 0
    ): FunctionType | OverloadedType | undefined {
        return mapSignatures(memberType, (functionType) => {
            // If the caller specified no base type, always strip the
            // first parameter. This is used in cases like constructors.
            if (!baseType) {
                return FunctionType.clone(functionType, /* stripFirstParam */ true);
            }

            // If the first parameter was already stripped, it has already been
            // bound. Don't attempt to rebind.
            if (functionType.priv.strippedFirstParamType) {
                return functionType;
            }

            if (FunctionType.isInstanceMethod(functionType)) {
                // If the baseType is a metaclass, don't specialize the function.
                if (isInstantiableMetaclass(baseType)) {
                    return functionType;
                }

                const baseObj: ClassType = isClassInstance(baseType)
                    ? baseType
                    : ClassType.cloneAsInstance(specializeWithDefaultTypeArgs(baseType));

                let stripFirstParam = false;
                if (isClassInstance(baseType)) {
                    stripFirstParam = true;
                } else if (memberClass && isInstantiableMetaclass(memberClass)) {
                    stripFirstParam = true;
                }

                return partiallySpecializeBoundMethod(
                    baseType,
                    functionType,
                    diag,
                    recursionCount,
                    selfType ?? baseObj,
                    stripFirstParam
                );
            }

            if (
                FunctionType.isClassMethod(functionType) ||
                (treatConstructorAsClassMethod && FunctionType.isConstructorMethod(functionType))
            ) {
                const baseClass = isInstantiableClass(baseType) ? baseType : ClassType.cloneAsInstantiable(baseType);
                const clsType = selfType ? (convertToInstantiable(selfType) as ClassType | TypeVarType) : undefined;

                return partiallySpecializeBoundMethod(
                    baseClass,
                    functionType,
                    diag,
                    recursionCount,
                    clsType ?? baseClass,
                    /* stripFirstParam */ true
                );
            }

            if (FunctionType.isStaticMethod(functionType)) {
                const baseClass = isInstantiableClass(baseType) ? baseType : ClassType.cloneAsInstantiable(baseType);

                return partiallySpecializeBoundMethod(
                    baseClass,
                    functionType,
                    diag,
                    recursionCount,
                    /* firstParamType */ undefined,
                    /* stripFirstParam */ false
                );
            }

            return functionType;
        });
    }

    // Specializes the specified function for the specified class,
    // optionally stripping the first first parameter (the "self" or "cls")
    // off of the specialized function in the process. The baseType
    // is the type used to reference the member.
    function partiallySpecializeBoundMethod(
        baseType: ClassType,
        memberType: FunctionType,
        diag: DiagnosticAddendum | undefined,
        recursionCount: number,
        firstParamType: ClassType | TypeVarType | undefined,
        stripFirstParam = true
    ): FunctionType | undefined {
        const constraints = new ConstraintTracker();

        if (firstParamType) {
            if (memberType.shared.parameters.length > 0) {
                const memberTypeFirstParam = memberType.shared.parameters[0];
                const memberTypeFirstParamType = FunctionType.getParamType(memberType, 0);

                if (
                    isTypeVar(memberTypeFirstParamType) &&
                    memberTypeFirstParamType.shared.boundType &&
                    isClassInstance(memberTypeFirstParamType.shared.boundType) &&
                    ClassType.isProtocolClass(memberTypeFirstParamType.shared.boundType)
                ) {
                    // Handle the protocol class specially. Some protocol classes
                    // contain references to themselves or their subclasses, so if
                    // we attempt to call assignType, we'll risk infinite recursion.
                    // Instead, we'll assume it's assignable.
                    constraints.setBounds(
                        memberTypeFirstParamType,
                        TypeBase.isInstantiable(memberTypeFirstParamType)
                            ? convertToInstance(firstParamType)
                            : firstParamType
                    );
                } else {
                    const subDiag = diag?.createAddendum();

                    // Protect against the case where a callback protocol is being
                    // bound to its own __call__ method but the first parameter
                    // is annotated with its own callable type. This can lead to
                    // infinite recursion.
                    if (isFunctionOrOverloaded(memberTypeFirstParamType)) {
                        if (isClassInstance(firstParamType) && ClassType.isProtocolClass(firstParamType)) {
                            if (subDiag) {
                                subDiag.addMessage(
                                    LocMessage.bindTypeMismatch().format({
                                        type: printType(firstParamType),
                                        methodName: memberType.shared.name || '<anonymous>',
                                        paramName: memberTypeFirstParam.name || '__p0',
                                    })
                                );
                            }
                            return undefined;
                        }
                    }

                    if (
                        !assignType(
                            memberTypeFirstParamType,
                            firstParamType,
                            subDiag?.createAddendum(),
                            constraints,
                            AssignTypeFlags.AllowUnspecifiedTypeArgs,
                            recursionCount
                        )
                    ) {
                        if (
                            memberTypeFirstParam.name &&
                            !FunctionParam.isNameSynthesized(memberTypeFirstParam) &&
                            FunctionParam.isTypeDeclared(memberTypeFirstParam)
                        ) {
                            if (subDiag) {
                                subDiag.addMessage(
                                    LocMessage.bindTypeMismatch().format({
                                        type: printType(firstParamType),
                                        methodName: memberType.shared.name || '<anonymous>',
                                        paramName: memberTypeFirstParam.name,
                                    })
                                );
                            }
                            return undefined;
                        }
                    }
                }
            } else {
                const subDiag = diag?.createAddendum();
                if (subDiag) {
                    subDiag.addMessage(
                        LocMessage.bindParamMissing().format({
                            methodName: memberType.shared.name || '<anonymous>',
                        })
                    );
                }
                return undefined;
            }
        }

        // Get the effective return type, which will have the side effect of lazily
        // evaluating (and caching) the inferred return type if there is no defined return type.
        getEffectiveReturnType(memberType);

        const specializedFunction = solveAndApplyConstraints(memberType, constraints);
        if (isFunction(specializedFunction)) {
            return FunctionType.clone(specializedFunction, stripFirstParam, baseType);
        }

        if (isOverloaded(specializedFunction)) {
            // For overloaded functions, use the first overload. This isn't
            // strictly correct, but this is an extreme edge case.
            return FunctionType.clone(OverloadedType.getOverloads(specializedFunction)[0], stripFirstParam, baseType);
        }

        return undefined;
    }

    function isFinalVariable(symbol: Symbol): boolean {
        return symbol.getDeclarations().some((decl) => isFinalVariableDeclaration(decl));
    }

    function isFinalVariableDeclaration(decl: Declaration): boolean {
        return decl.type === DeclarationType.Variable && !!decl.isFinal;
    }

    function isExplicitTypeAliasDeclaration(decl: Declaration): boolean {
        if (decl.type !== DeclarationType.Variable || !decl.typeAnnotationNode) {
            return false;
        }

        if (
            decl.typeAnnotationNode.nodeType !== ParseNodeType.Name &&
            decl.typeAnnotationNode.nodeType !== ParseNodeType.MemberAccess &&
            decl.typeAnnotationNode.nodeType !== ParseNodeType.StringList
        ) {
            return false;
        }

        const type = getTypeOfAnnotation(decl.typeAnnotationNode, { varTypeAnnotation: true, allowClassVar: true });
        return isClassInstance(type) && ClassType.isBuiltIn(type, 'TypeAlias');
    }

    function isPossibleTypeAliasDeclaration(decl: Declaration): boolean {
        if (decl.type !== DeclarationType.Variable || !decl.typeAliasName || decl.typeAnnotationNode) {
            return false;
        }

        if (decl.node.parent?.nodeType !== ParseNodeType.Assignment) {
            return false;
        }

        // Perform a sanity check on the RHS expression. Some expression
        // forms should never be considered legitimate for type aliases.
        return isLegalTypeAliasExpressionForm(decl.node.parent.d.rightExpr, /* allowStrLiteral */ false);
    }

    function isLegalTypeAliasExpressionForm(node: ExpressionNode, allowStrLiteral: boolean): boolean {
        switch (node.nodeType) {
            case ParseNodeType.Error:
            case ParseNodeType.UnaryOperation:
            case ParseNodeType.AssignmentExpression:
            case ParseNodeType.TypeAnnotation:
            case ParseNodeType.Await:
            case ParseNodeType.Ternary:
            case ParseNodeType.Unpack:
            case ParseNodeType.Tuple:
            case ParseNodeType.Call:
            case ParseNodeType.Comprehension:
            case ParseNodeType.Slice:
            case ParseNodeType.Yield:
            case ParseNodeType.YieldFrom:
            case ParseNodeType.Lambda:
            case ParseNodeType.Number:
            case ParseNodeType.Dictionary:
            case ParseNodeType.List:
            case ParseNodeType.Set:
                return false;

            case ParseNodeType.StringList:
            case ParseNodeType.String:
                return allowStrLiteral;

            case ParseNodeType.Constant:
                return node.d.constType === KeywordType.None;

            case ParseNodeType.BinaryOperation:
                return (
                    node.d.operator === OperatorType.BitwiseOr &&
                    isLegalTypeAliasExpressionForm(node.d.leftExpr, /* allowStrLiteral */ true) &&
                    isLegalTypeAliasExpressionForm(node.d.rightExpr, /* allowStrLiteral */ true)
                );

            case ParseNodeType.Index:
                return isLegalTypeAliasExpressionForm(node.d.leftExpr, allowStrLiteral);

            case ParseNodeType.MemberAccess:
                return isLegalTypeAliasExpressionForm(node.d.leftExpr, allowStrLiteral);
        }

        return true;
    }

    function isLegalImplicitTypeAliasType(type: Type) {
        // We explicitly exclude "..." and "Unknown".
        if (isEllipsisType(type)) {
            return false;
        }

        if (isUnknown(type)) {
            // If this is a union type, we'll assume that it was meant as a type
            // alias even though all of the union subtypes are Unknown.
            if (type.props?.specialForm && ClassType.isBuiltIn(type.props.specialForm, 'UnionType')) {
                return true;
            }
            return false;
        }

        // Look at the subtypes within the union. If any of them are not
        // instantiable (other than "None" which is special-cased), it is
        // not a legal type alias type.
        let isLegal = true;
        doForEachSubtype(type, (subtype) => {
            if (!TypeBase.isInstantiable(subtype) && !isNoneInstance(subtype)) {
                isLegal = false;
            }
        });

        return isLegal;
    }

    function isPossibleTypeAliasOrTypedDict(decl: Declaration) {
        return isPossibleTypeAliasDeclaration(decl) || isPossibleTypeDictFactoryCall(decl);
    }

    function isPossibleTypeDictFactoryCall(decl: Declaration) {
        if (
            decl.type !== DeclarationType.Variable ||
            !decl.node.parent ||
            decl.node.parent.nodeType !== ParseNodeType.Assignment ||
            decl.node.parent.d.rightExpr?.nodeType !== ParseNodeType.Call
        ) {
            return false;
        }

        const callLeftNode = decl.node.parent.d.rightExpr.d.leftExpr;

        // Use a simple heuristic to determine whether this is potentially
        // a call to the TypedDict call. This avoids the expensive (and potentially
        // recursive) call to getTypeOfExpression in cases where it's not needed.
        if (
            (callLeftNode.nodeType === ParseNodeType.Name && callLeftNode.d.value) === 'TypedDict' ||
            (callLeftNode.nodeType === ParseNodeType.MemberAccess &&
                callLeftNode.d.member.d.value === 'TypedDict' &&
                callLeftNode.d.leftExpr.nodeType === ParseNodeType.Name)
        ) {
            // See if this is a call to TypedDict. We want to support
            // recursive type references in a TypedDict call.
            const callType = getTypeOfExpression(callLeftNode, EvalFlags.CallBaseDefaults).type;

            if (isInstantiableClass(callType) && ClassType.isBuiltIn(callType, 'TypedDict')) {
                return true;
            }
        }

        return false;
    }

    function printObjectTypeForClass(type: ClassType): string {
        return TypePrinter.printObjectTypeForClass(type, evaluatorOptions.printTypeFlags, getEffectiveReturnType);
    }

    function printFunctionParts(type: FunctionType, extraFlags?: TypePrinter.PrintTypeFlags): [string[], string] {
        const flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags;
        return TypePrinter.printFunctionParts(type, flags, getEffectiveReturnType);
    }

    // Prints two types and determines whether they need to be output in
    // fully-qualified form for disambiguation.
    function printSrcDestTypes(
        srcType: Type,
        destType: Type,
        options?: PrintTypeOptions
    ): { sourceType: string; destType: string } {
        const simpleSrcType = printType(srcType, options);
        const simpleDestType = printType(destType, options);

        if (simpleSrcType !== simpleDestType) {
            return { sourceType: simpleSrcType, destType: simpleDestType };
        }

        const fullSrcType = printType(srcType, { ...(options ?? {}), useFullyQualifiedNames: true });
        const fullDestType = printType(destType, { ...(options ?? {}), useFullyQualifiedNames: true });

        if (fullSrcType !== fullDestType) {
            return { sourceType: fullSrcType, destType: fullDestType };
        }

        return { sourceType: simpleSrcType, destType: simpleDestType };
    }

    function printType(type: Type, options?: PrintTypeOptions): string {
        let flags = evaluatorOptions.printTypeFlags;

        if (options?.expandTypeAlias) {
            flags |= TypePrinter.PrintTypeFlags.ExpandTypeAlias;
        }
        if (options?.enforcePythonSyntax) {
            flags |= TypePrinter.PrintTypeFlags.PythonSyntax;
        }
        if (options?.useTypingUnpack) {
            flags |= TypePrinter.PrintTypeFlags.UseTypingUnpack;
        }
        if (options?.printUnknownWithAny) {
            flags |= TypePrinter.PrintTypeFlags.PrintUnknownWithAny;
        }
        if (options?.printTypeVarVariance) {
            flags |= TypePrinter.PrintTypeFlags.PrintTypeVarVariance;
        }
        if (options?.omitTypeArgsIfUnknown) {
            flags |= TypePrinter.PrintTypeFlags.OmitTypeArgsIfUnknown;
        }
        if (options?.useFullyQualifiedNames) {
            flags |= TypePrinter.PrintTypeFlags.UseFullyQualifiedNames;
        }
        if (options?.disablePep604) {
            flags &= ~TypePrinter.PrintTypeFlags.PEP604;
        }

        return TypePrinter.printType(type, flags, getEffectiveReturnType);
    }

    // Calls back into the parser to parse the contents of a string literal.
    // This is unfortunately needed in some cases — specifically where the
    // parser couldn't determine that the string literal would be used in
    // a context where it should be treated as a forward-declared type. This
    // call produces an expression tree that is not attached to the main parse
    // expression tree because we don't want to mutate the latter; the
    // expression tree created by this function is therefore used only temporarily.
    function parseStringAsTypeAnnotation(node: StringListNode, reportErrors: boolean): ExpressionNode | undefined {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const parser = new Parser();
        const textValue = node.d.strings[0].d.value;

        // Determine the offset within the file where the string
        // literal's contents begin.
        let valueOffset = node.d.strings[0].start;
        if (node.d.strings[0].nodeType === ParseNodeType.String) {
            valueOffset += node.d.strings[0].d.token.prefixLength + node.d.strings[0].d.token.quoteMarkLength;
        }

        // Construct a temporary dummy string with the text value at the appropriate
        // offset so as to mimic the original file. This will keep all of the token
        // and diagnostic offsets correct.
        const dummyFileContents = ' '.repeat(valueOffset) + textValue;

        const parseOptions = new ParseOptions();
        parseOptions.isStubFile = fileInfo.isStubFile;
        parseOptions.pythonVersion = fileInfo.executionEnvironment.pythonVersion;
        parseOptions.reportErrorsForParsedStringContents = true;

        const parseResults = parser.parseTextExpression(
            dummyFileContents,
            valueOffset,
            textValue.length,
            parseOptions,
            ParseTextMode.Expression,
            /* initialParenDepth */ undefined,
            fileInfo.typingSymbolAliases
        );

        if (parseResults.parseTree) {
            // If there are errors but we are not reporting them, return
            // undefined to indicate that the parse failed.
            if (!reportErrors && parseResults.diagnostics.length > 0) {
                return undefined;
            }

            const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
            parseResults.diagnostics.forEach((diag) => {
                fileInfo.diagnosticSink.addDiagnosticWithTextRange('error', diag.message, node);
            });

            parseResults.parseTree.parent = node;

            // Optionally add the new subtree to the parse tree so it can
            // participate in language server operations like find and replace.
            if (reportErrors) {
                node.d.annotation = parseResults.parseTree;
            }

            return parseResults.parseTree;
        }

        return undefined;
    }

    // Given a code flow node and a constrained TypeVar, determines whether that type
    // var can be "narrowed" to a single one of its constraints based on isinstance
    // checks within the code flow.
    function narrowConstrainedTypeVar(node: ParseNode, typeVar: TypeVarType): Type | undefined {
        const flowNode = AnalyzerNodeInfo.getFlowNode(node);

        if (!flowNode) {
            return undefined;
        }

        return codeFlowEngine.narrowConstrainedTypeVar(flowNode, typeVar);
    }

    function getPrintExpressionTypesSpaces() {
        return ' '.repeat(printExpressionSpaceCount);
    }

    function getLineNum(node: ParseNode) {
        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
        const range = convertOffsetsToRange(node.start, node.start + node.length, fileInfo.lines);
        return (range.start.line + 1).toString();
    }

    function printControlFlowGraph(
        flowNode: FlowNode,
        reference: CodeFlowReferenceExpressionNode | undefined,
        callName: string,
        logger: ConsoleInterface
    ) {
        return codeFlowEngine.printControlFlowGraph(flowNode, reference, callName, logger);
    }

    // Track these apis internal usages when logging is on. otherwise, it should be noop.
    const getInferredReturnTypeResult = wrapWithLogger(_getInferredReturnTypeResult);

    const evaluatorInterface: TypeEvaluator = {
        runWithCancellationToken,
        getType,
        getTypeResult,
        getTypeResultForDecorator,
        getCachedType,
        getTypeOfExpression,
        getTypeOfAnnotation,
        getTypeOfClass,
        createSubclass,
        getTypeOfFunction,
        getTypeOfExpressionExpectingType,
        getExpectedType,
        evaluateTypeForSubnode,
        evaluateTypesForStatement,
        evaluateTypesForMatchStatement,
        evaluateTypesForCaseStatement,
        evaluateTypeOfParam,
        canBeTruthy,
        canBeFalsy,
        stripLiteralValue,
        removeTruthinessFromType,
        removeFalsinessFromType,
        stripTypeGuard,
        solveAndApplyConstraints,
        verifyRaiseExceptionType,
        verifyDeleteExpression,
        validateOverloadedArgTypes,
        validateInitSubclassArgs,
        isNodeReachable,
        isAfterNodeReachable,
        getNodeReachability,
        getAfterNodeReachability,
        isAsymmetricAccessorAssignment,
        suppressDiagnostics,
        isSpecialFormClass,
        getDeclInfoForStringNode,
        getDeclInfoForNameNode,
        getTypeForDeclaration,
        resolveAliasDeclaration,
        resolveAliasDeclarationWithInfo,
        getTypeOfIterable,
        getTypeOfIterator,
        getGetterTypeFromProperty,
        getTypeOfArg,
        convertNodeToArg,
        buildTupleTypesList,
        markNamesAccessed,
        expandPromotionTypes,
        makeTopLevelTypeVarsConcrete,
        mapSubtypesExpandTypeVars,
        isTypeSubsumedByOtherType,
        lookUpSymbolRecursive,
        getDeclaredTypeOfSymbol,
        getEffectiveTypeOfSymbol,
        getEffectiveTypeOfSymbolForUsage,
        getInferredTypeOfDeclaration,
        getDeclaredTypeForExpression,
        getDeclaredReturnType,
        getInferredReturnType,
        getBestOverloadForArgs,
        getBuiltInType,
        getTypeOfMember,
        getTypeOfBoundMember,
        getBoundMagicMethod,
        getTypeOfMagicMethodCall,
        bindFunctionToClassOrObject,
        getCallbackProtocolType,
        getCallSignatureInfo,
        getAbstractSymbols,
        narrowConstrainedTypeVar,
        isTypeComparable,
        assignType,
        validateOverrideMethod,
        validateCallArgs,
        validateTypeArg,
        assignTypeToExpression,
        assignClassToSelf,
        getTypedDictClassType,
        getTupleClassType,
        getDictClassType,
        getStrClassType,
        getObjectType,
        getNoneType,
        getUnionClassType,
        getTypeClassType,
        getBuiltInObject,
        getTypingType,
        getTypeCheckerInternalsType,
        assignTypeArgs,
        reportMissingTypeArgs,
        inferReturnTypeIfNecessary,
        inferVarianceForClass,
        isFinalVariable,
        isFinalVariableDeclaration,
        isExplicitTypeAliasDeclaration,
        addInformation,
        addUnreachableCode,
        addDeprecated,
        addDiagnostic,
        addDiagnosticForTextRange,
        printType,
        printSrcDestTypes,
        printFunctionParts,
        getTypeCacheEntryCount,
        disposeEvaluator,
        useSpeculativeMode,
        isSpeculativeModeInUse,
        setTypeResultForNode,
        checkForCancellation,
        printControlFlowGraph,
    };

    const codeFlowEngine = getCodeFlowEngine(evaluatorInterface, speculativeTypeTracker);

    return evaluatorInterface;
}
