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