function getTypeOfClass()

in packages/pyright-internal/src/analyzer/typeEvaluator.ts [13744:14319]


    function getTypeOfClass(node: ClassNode): ClassTypeResult | undefined {
        // Is this type already cached?
        const cachedClassType = readTypeCache(node.name, EvaluatorFlags.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, EvaluatorFlags.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
        ) {
            classFlags |= ClassTypeFlags.BuiltInClass;

            if (fileInfo.isTypingExtensionsStubFile) {
                classFlags |= ClassTypeFlags.TypingExtensionClass;
            }

            if (node.name.value === 'property') {
                classFlags |= ClassTypeFlags.PropertyClass;
            }

            if (node.name.value === 'tuple') {
                classFlags |= ClassTypeFlags.TupleClass;
            }
        }

        if (fileInfo.isStubFile) {
            classFlags |= ClassTypeFlags.DefinedInStub;
        }

        const classType = ClassType.createInstantiable(
            node.name.value,
            ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, node.name.value),
            fileInfo.moduleName,
            fileInfo.filePath,
            classFlags,
            /* typeSourceId */ 0,
            /* declaredMetaclass */ undefined,
            /* effectiveMetaclass */ undefined,
            ParseTreeUtils.getDocString(node.suite.statements)
        );

        classType.details.typeVarScopeId = getScopeIdForNode(node);

        // 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.name.value);
        let classDecl: ClassDeclaration | undefined;
        const decl = AnalyzerNodeInfo.getDeclaration(node);
        if (decl) {
            classDecl = decl as ClassDeclaration;
        }
        if (classDecl && classSymbol) {
            setSymbolResolutionPartialType(classSymbol, classDecl, classType);
        }
        classType.details.flags |= ClassTypeFlags.PartiallyConstructed;
        writeTypeCache(node, classType, /* flags */ undefined, /* isIncomplete */ false);
        writeTypeCache(node.name, classType, /* flags */ undefined, /* isIncomplete */ false);

        // Keep a list of unique type parameters that are used in the
        // base class arguments.
        const typeParameters: TypeVarType[] = [];

        // If the class derives from "Generic" directly, it will provide
        // all of the type parameters in the specified order.
        let genericTypeParameters: TypeVarType[] | undefined;

        const initSubclassArgs: FunctionArgument[] = [];
        let metaclassNode: ExpressionNode | undefined;
        let exprFlags =
            EvaluatorFlags.ExpectingType |
            EvaluatorFlags.AllowGenericClassType |
            EvaluatorFlags.DisallowNakedGeneric |
            EvaluatorFlags.DisallowTypeVarsWithScopeId |
            EvaluatorFlags.AssociateTypeVarsWithCurrentScope;
        if (fileInfo.isStubFile) {
            exprFlags |= EvaluatorFlags.AllowForwardReferences;
        }

        node.arguments.forEach((arg) => {
            if (!arg.name) {
                let argType = getTypeOfExpression(arg.valueExpression, undefined, exprFlags).type;

                // 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);
                }

                if (!isAnyOrUnknown(argType) && !isUnbound(argType)) {
                    if (!isInstantiableClass(argType)) {
                        addDiagnostic(
                            fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                            DiagnosticRule.reportGeneralTypeIssues,
                            Localizer.Diagnostic.baseClassInvalid(),
                            arg
                        );
                        argType = UnknownType.create();
                    } else {
                        if (ClassType.isBuiltIn(argType, 'Protocol')) {
                            if (
                                !fileInfo.isStubFile &&
                                !ClassType.isTypingExtensionClass(argType) &&
                                fileInfo.executionEnvironment.pythonVersion < PythonVersion.V3_7
                            ) {
                                addError(Localizer.Diagnostic.protocolIllegal(), arg.valueExpression);
                            }
                            classType.details.flags |= ClassTypeFlags.ProtocolClass;
                        }

                        if (ClassType.isBuiltIn(argType, 'property')) {
                            classType.details.flags |= ClassTypeFlags.PropertyClass;
                        }

                        // If the class directly derives from NamedTuple (in Python 3.6 or
                        // newer), it's considered a (read-only) dataclass.
                        if (fileInfo.executionEnvironment.pythonVersion >= PythonVersion.V3_6) {
                            if (ClassType.isBuiltIn(argType, 'NamedTuple')) {
                                classType.details.flags |=
                                    ClassTypeFlags.DataClass | ClassTypeFlags.ReadOnlyInstanceVariables;
                            }
                        }

                        // 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.details.flags |= ClassTypeFlags.TypedDictClass;
                        } else if (ClassType.isTypedDictClass(classType) && !ClassType.isTypedDictClass(argType)) {
                            // TypedDict classes must derive only from other
                            // TypedDict classes.
                            addError(Localizer.Diagnostic.typedDictBaseClass(), arg);
                        }

                        // Validate that the class isn't deriving from itself, creating a
                        // circular dependency.
                        if (derivesFromClassRecursive(argType, classType, /* ignoreUnknown */ true)) {
                            addError(Localizer.Diagnostic.baseClassCircular(), arg);
                            argType = UnknownType.create();
                        }
                    }
                }

                if (isUnknown(argType)) {
                    addDiagnostic(
                        fileInfo.diagnosticRuleSet.reportUntypedBaseClass,
                        DiagnosticRule.reportUntypedBaseClass,
                        Localizer.Diagnostic.baseClassUnknown(),
                        arg
                    );
                }

                // Check for a duplicate class.
                if (
                    classType.details.baseClasses.some((prevBaseClass) => {
                        return (
                            isInstantiableClass(prevBaseClass) &&
                            isInstantiableClass(argType) &&
                            ClassType.isSameGenericClass(argType, prevBaseClass)
                        );
                    })
                ) {
                    addDiagnostic(
                        fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        Localizer.Diagnostic.duplicateBaseClass(),
                        arg.name || arg
                    );
                }

                classType.details.baseClasses.push(argType);
                if (isInstantiableClass(argType)) {
                    if (ClassType.isEnumClass(argType)) {
                        classType.details.flags |= ClassTypeFlags.EnumClass;
                    }

                    // Determine if the class is abstract. Protocol classes support abstract methods
                    // even though they don't derive from the ABCMeta class. We'll exclude built-in
                    // protocol classes because these are known not to contain any abstract methods
                    // and getAbstractMethods causes problems because of dependencies on some of these
                    // built-in protocol classes.
                    if (
                        ClassType.supportsAbstractMethods(argType) ||
                        (ClassType.isProtocolClass(argType) && !ClassType.isBuiltIn(argType))
                    ) {
                        classType.details.flags |= ClassTypeFlags.SupportsAbstractMethods;
                    }

                    if (ClassType.isPropertyClass(argType)) {
                        classType.details.flags |= ClassTypeFlags.PropertyClass;
                    }

                    if (ClassType.isFinal(argType)) {
                        const className = printObjectTypeForClass(argType);
                        addError(
                            Localizer.Diagnostic.baseClassFinal().format({ type: className }),
                            arg.valueExpression
                        );
                    }
                }

                addTypeVarsToListIfUnique(typeParameters, getTypeVarArgumentsRecursive(argType));
                if (isInstantiableClass(argType) && ClassType.isBuiltIn(argType, 'Generic')) {
                    if (!genericTypeParameters) {
                        genericTypeParameters = [];
                        addTypeVarsToListIfUnique(genericTypeParameters, getTypeVarArgumentsRecursive(argType));
                    }
                }
            } else if (arg.name.value === 'metaclass') {
                if (metaclassNode) {
                    addError(Localizer.Diagnostic.metaclassDuplicate(), arg);
                } else {
                    metaclassNode = arg.valueExpression;
                }
            } else if (arg.name.value === 'total' && ClassType.isTypedDictClass(classType)) {
                // The "total" parameter name applies only for TypedDict classes.
                // PEP 589 specifies that the parameter must be either True or False.
                const constArgValue = evaluateStaticBoolExpression(arg.valueExpression, fileInfo.executionEnvironment);
                if (constArgValue === undefined) {
                    addError(Localizer.Diagnostic.typedDictTotalParam(), arg.valueExpression);
                } else if (!constArgValue) {
                    classType.details.flags |= ClassTypeFlags.CanOmitDictValues;
                }
            } else {
                // Collect arguments that will be passed to the `__init_subclass__`
                // method described in PEP 487.
                initSubclassArgs.push({
                    argumentCategory: ArgumentCategory.Simple,
                    node: arg,
                    name: arg.name,
                    valueExpression: arg.valueExpression,
                });
            }
        });

        // Check for NamedTuple multiple inheritance.
        if (classType.details.baseClasses.length > 1) {
            if (
                classType.details.baseClasses.some(
                    (baseClass) => isInstantiableClass(baseClass) && ClassType.isBuiltIn(baseClass, 'NamedTuple')
                )
            ) {
                addDiagnostic(
                    fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                    DiagnosticRule.reportGeneralTypeIssues,
                    Localizer.Diagnostic.namedTupleMultipleInheritance(),
                    node.name
                );
            }
        }

        // Make sure we don't have 'object' derive from itself. Infinite
        // recursion will result.
        if (
            !ClassType.isBuiltIn(classType, 'object') &&
            classType.details.baseClasses.filter((baseClass) => isClass(baseClass)).length === 0
        ) {
            // If there are no other (known) base classes, the class implicitly derives from object.
            classType.details.baseClasses.push(getBuiltInType(node, 'object'));
        }

        // If genericTypeParameters are provided, make sure that typeParameters is a proper subset.
        if (genericTypeParameters) {
            verifyGenericTypeParameters(node.name, typeParameters, genericTypeParameters);
        }
        classType.details.typeParameters = genericTypeParameters || typeParameters;

        // Make sure there's at most one variadic type parameter.
        const variadics = classType.details.typeParameters.filter((param) => isVariadicTypeVar(param));
        if (variadics.length > 1) {
            addError(
                Localizer.Diagnostic.variadicTypeParamTooManyClass().format({
                    names: variadics.map((v) => `"${v.details.name}"`).join(', '),
                }),
                node.name,
                TextRange.combine(node.arguments) || node.name
            );
        }

        if (!computeMroLinearization(classType)) {
            addError(Localizer.Diagnostic.methodOrdering(), node.name);
        }

        // The scope for this class becomes the "fields" for the corresponding type.
        const innerScope = ScopeUtils.getScopeForNode(node.suite);
        classType.details.fields = innerScope?.symbolTable || new Map<string, Symbol>();

        // 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.details.localSlotsNames = slotsNames;
        }

        if (ClassType.isTypedDictClass(classType)) {
            synthesizeTypedDictClassMethods(evaluatorInterface, node, classType);
        }

        // 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.details.typeParameters.length === 0) {
            const initMethod = classType.details.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.parameters;

                    if (
                        initParams.length > 1 &&
                        !initParams.some((param, index) => !!getTypeAnnotationForParameter(initDeclNode, index))
                    ) {
                        const genericParams = initParams.filter(
                            (param, index) => index > 0 && param.name && param.category === ParameterCategory.Simple
                        );

                        if (genericParams.length > 0) {
                            classType.details.flags |= ClassTypeFlags.PseudoGenericClass;

                            // Create a type parameter for each simple, named parameter
                            // in the __init__ method.
                            classType.details.typeParameters = genericParams.map((param) => {
                                const typeVar = TypeVarType.createInstance(`__type_of_${param.name!.value}`);
                                typeVar.details.isSynthesized = true;
                                typeVar.scopeId = getScopeIdForNode(initDeclNode);
                                typeVar.details.boundType = UnknownType.create();
                                return TypeVarType.cloneForScopeId(
                                    typeVar,
                                    getScopeIdForNode(node),
                                    node.name.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.details.typeParameters.length === 0 && !ClassType.isBuiltIn(classType, 'type')) {
            if (
                classType.details.baseClasses.some(
                    (baseClass) => isInstantiableClass(baseClass) && ClassType.hasCustomClassGetItem(baseClass)
                ) ||
                classType.details.fields.has('__class_getitem__')
            ) {
                classType.details.flags |= ClassTypeFlags.HasCustomClassGetItem;
            }
        }

        // Determine the effective metaclass and detect metaclass conflicts.
        if (metaclassNode) {
            const metaclassType = getTypeOfExpression(metaclassNode, undefined, exprFlags).type;
            if (isInstantiableClass(metaclassType) || isUnknown(metaclassType)) {
                if (requiresSpecialization(metaclassType)) {
                    addDiagnostic(
                        fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        Localizer.Diagnostic.metaclassIsGeneric(),
                        metaclassNode
                    );
                }

                classType.details.declaredMetaclass = metaclassType;
                if (isInstantiableClass(metaclassType)) {
                    if (ClassType.isBuiltIn(metaclassType, 'EnumMeta')) {
                        classType.details.flags |= ClassTypeFlags.EnumClass;
                    } else if (ClassType.isBuiltIn(metaclassType, 'ABCMeta')) {
                        classType.details.flags |= ClassTypeFlags.SupportsAbstractMethods;
                    }
                }
            }
        }

        let effectiveMetaclass = classType.details.declaredMetaclass;
        let reportedMetaclassConflict = false;

        if (!effectiveMetaclass || isInstantiableClass(effectiveMetaclass)) {
            for (const baseClass of classType.details.baseClasses) {
                if (isInstantiableClass(baseClass)) {
                    const baseClassMeta = baseClass.details.effectiveMetaclass || typeClassType;
                    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) {
                                addDiagnostic(
                                    fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    Localizer.Diagnostic.metaclassConflict(),
                                    node.name
                                );
                                // 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(node, 'type');
            effectiveMetaclass =
                typeMetaclass && isInstantiableClass(typeMetaclass) ? typeMetaclass : UnknownType.create();
        }

        classType.details.effectiveMetaclass = effectiveMetaclass;

        // Now determine the decorated type of the class.
        let decoratedType: Type = classType;
        let foundUnknown = false;

        for (let i = node.decorators.length - 1; i >= 0; i--) {
            const decorator = node.decorators[i];

            const newDecoratedType = applyClassDecorator(decoratedType, classType, decorator);
            if (containsUnknown(newDecoratedType)) {
                // Report this error only on the first unknown type.
                if (!foundUnknown) {
                    addDiagnostic(
                        fileInfo.diagnosticRuleSet.reportUntypedClassDecorator,
                        DiagnosticRule.reportUntypedClassDecorator,
                        Localizer.Diagnostic.classDecoratorTypeUnknown(),
                        node.decorators[i].expression
                    );

                    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.details.classDataClassTransform) {
            dataClassBehaviors = effectiveMetaclass.details.classDataClassTransform;
        } else {
            const baseClassDataTransform = classType.details.mro.find((mroClass) => {
                return isClass(mroClass) && mroClass.details.classDataClassTransform !== undefined;
            });

            if (baseClassDataTransform) {
                dataClassBehaviors = (baseClassDataTransform as ClassType).details.classDataClassTransform!;
            }
        }

        if (dataClassBehaviors) {
            applyDataClassDefaultBehaviors(classType, dataClassBehaviors);
            applyDataClassClassBehaviorOverrides(evaluatorInterface, classType, initSubclassArgs);
        }

        // Clear the "partially constructed" flag.
        classType.details.flags &= ~ClassTypeFlags.PartiallyConstructed;

        // Synthesize dataclass methods.
        if (ClassType.isDataClass(classType)) {
            let skipSynthesizedInit = ClassType.isSkipSynthesizedDataClassInit(classType);
            if (!skipSynthesizedInit) {
                // See if there's already a non-synthesized __init__ method.
                // We shouldn't override it.
                const initSymbol = lookUpClassMember(classType, '__init__', ClassMemberLookupFlags.SkipBaseClasses);
                if (initSymbol) {
                    const initSymbolType = getTypeOfMember(initSymbol);
                    if (isFunction(initSymbolType)) {
                        if (!FunctionType.isSynthesizedMethod(initSymbolType)) {
                            skipSynthesizedInit = true;
                        }
                    } else {
                        skipSynthesizedInit = true;
                    }
                }
            }

            let skipSynthesizeHash = false;
            const hashSymbol = lookUpClassMember(classType, '__hash__', ClassMemberLookupFlags.SkipBaseClasses);
            if (hashSymbol) {
                const hashSymbolType = getTypeOfMember(hashSymbol);
                if (isFunction(hashSymbolType) && !FunctionType.isSynthesizedMethod(hashSymbolType)) {
                    skipSynthesizeHash = true;
                }
            }

            synthesizeDataClassMethods(evaluatorInterface, node, classType, skipSynthesizedInit, skipSynthesizeHash);
        }

        // Build a complete list of all slots names defined by the class hierarchy.
        // This needs to be done after dataclass processing.
        if (classType.details.localSlotsNames) {
            let isLimitedToSlots = true;
            const extendedSlotsNames = [...classType.details.localSlotsNames];

            classType.details.baseClasses.forEach((baseClass) => {
                if (isInstantiableClass(baseClass)) {
                    if (
                        !ClassType.isBuiltIn(baseClass, 'object') &&
                        !ClassType.isBuiltIn(baseClass, 'type') &&
                        !ClassType.isBuiltIn(baseClass, 'Generic')
                    ) {
                        if (baseClass.details.inheritedSlotsNames === undefined) {
                            isLimitedToSlots = false;
                        } else {
                            extendedSlotsNames.push(...baseClass.details.inheritedSlotsNames);
                        }
                    }
                } else {
                    isLimitedToSlots = false;
                }
            });

            if (isLimitedToSlots) {
                classType.details.inheritedSlotsNames = extendedSlotsNames;
            }
        }

        // Update the undecorated class type.
        writeTypeCache(node.name, classType, EvaluatorFlags.None, /* isIncomplete */ false);

        // Update the decorated class type.
        writeTypeCache(node, decoratedType, EvaluatorFlags.None, /* isIncomplete */ false);

        // Validate __init_subclass__ call or metaclass keyword arguments.
        validateInitSubclassArgs(node, classType, initSubclassArgs);

        // Stash away a reference to the UnionType class if we encounter it.
        // There's no easy way to otherwise reference it.
        if (ClassType.isBuiltIn(classType, 'UnionType')) {
            unionType = ClassType.cloneAsInstance(classType);
        }

        return { classType, decoratedType };
    }