Changed dataclass_transform behavior to allow dataclass behaviors (like kw_only) to be overridden by intermediate base classes. This addresses #8140. (#8141)

This commit is contained in:
Eric Traut 2024-06-13 15:29:01 -07:00 committed by GitHub
parent 75d0f2b5a1
commit cbd189a29f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 193 additions and 176 deletions

View File

@ -5966,7 +5966,7 @@ export class Checker extends ParseTreeWalker {
// If the entry is a member of a frozen dataclass, it is immutable,
// so it does not need to be invariant.
if (
ClassType.isFrozenDataClass(overriddenClassAndSymbol.classType) &&
ClassType.isDataClassFrozen(overriddenClassAndSymbol.classType) &&
overriddenClassAndSymbol.classType.details.dataClassEntries
) {
const dataclassEntry = overriddenClassAndSymbol.classType.details.dataClassEntries.find(
@ -6676,7 +6676,7 @@ export class Checker extends ParseTreeWalker {
// If the entry is a member of a frozen dataclass, it is immutable,
// so it does not need to be invariant.
if (ClassType.isFrozenDataClass(baseClass) && baseClass.details.dataClassEntries) {
if (ClassType.isDataClassFrozen(baseClass) && baseClass.details.dataClassEntries) {
const dataclassEntry = baseClass.details.dataClassEntries.find(
(entry) => entry.name === memberName
);

View File

@ -85,7 +85,7 @@ export function synthesizeDataClassMethods(
hasExistingInitMethod: boolean,
skipSynthesizeHash: boolean
) {
assert(ClassType.isDataClass(classType));
assert(ClassType.isDataClass(classType) || isNamedTuple);
const classTypeVar = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ true);
const newType = FunctionType.createSynthesizedInstance('__new__', FunctionTypeFlags.ConstructorMethod);
@ -194,7 +194,7 @@ export function synthesizeDataClassMethods(
let aliasName: string | undefined;
let variableTypeEvaluator: EntryTypeEvaluator | undefined;
let hasDefaultValue = false;
let isKeywordOnly = ClassType.isDataClassKeywordOnlyParams(classType) || sawKeywordOnlySeparator;
let isKeywordOnly = ClassType.isDataClassKeywordOnly(classType) || sawKeywordOnlySeparator;
let defaultValueExpression: ExpressionNode | undefined;
let includeInInit = true;
let converter: ArgumentNode | undefined;
@ -606,20 +606,20 @@ export function synthesizeDataClassMethods(
};
// Synthesize comparison operators.
if (!ClassType.isSkipSynthesizedDataClassEq(classType)) {
if (!ClassType.isDataClassSkipGenerateEq(classType)) {
synthesizeComparisonMethod('__eq__', evaluator.getBuiltInObject(node, 'object'));
}
if (ClassType.isSynthesizedDataclassOrder(classType)) {
if (ClassType.isDataClassGenerateOrder(classType)) {
const objType = ClassType.cloneAsInstance(classType);
['__lt__', '__le__', '__gt__', '__ge__'].forEach((operator) => {
synthesizeComparisonMethod(operator, objType);
});
}
let synthesizeHashFunction = ClassType.isFrozenDataClass(classType);
let synthesizeHashFunction = ClassType.isDataClassFrozen(classType);
const synthesizeHashNone =
!ClassType.isSkipSynthesizedDataClassEq(classType) && !ClassType.isFrozenDataClass(classType);
!isNamedTuple && !ClassType.isDataClassSkipGenerateEq(classType) && !ClassType.isDataClassFrozen(classType);
if (skipSynthesizeHash) {
synthesizeHashFunction = false;
@ -627,7 +627,7 @@ export function synthesizeDataClassMethods(
// If the user has indicated that a hash function should be generated even if it's unsafe
// to do so or there is already a hash function present, override the default logic.
if (ClassType.isSynthesizeDataClassUnsafeHash(classType)) {
if (ClassType.isDataClassGenerateHash(classType)) {
synthesizeHashFunction = true;
}
@ -664,12 +664,12 @@ export function synthesizeDataClassMethods(
Symbol.createWithType(SymbolFlags.ClassMember | SymbolFlags.ClassVar, dictType)
);
if (ClassType.isGeneratedDataClassSlots(classType) && classType.details.localSlotsNames === undefined) {
if (ClassType.isDataClassGenerateSlots(classType) && classType.details.localSlotsNames === undefined) {
classType.details.localSlotsNames = localDataClassEntries.map((entry) => entry.name);
}
// Should we synthesize a __slots__ symbol?
if (ClassType.isGeneratedDataClassSlots(classType)) {
if (ClassType.isDataClassGenerateSlots(classType)) {
let iterableType = evaluator.getTypingType(node, 'Iterable') ?? UnknownType.create();
if (isInstantiableClass(iterableType)) {
@ -1053,10 +1053,14 @@ export function validateDataClassTransformDecorator(
node: CallNode
): DataClassBehaviors | undefined {
const behaviors: DataClassBehaviors = {
keywordOnlyParams: false,
generateEq: true,
skipGenerateInit: false,
skipGenerateEq: false,
generateOrder: false,
generateSlots: false,
generateHash: false,
keywordOnly: false,
frozen: false,
frozenDefault: false,
fieldDescriptorNames: [],
};
@ -1089,7 +1093,7 @@ export function validateDataClassTransformDecorator(
return;
}
behaviors.keywordOnlyParams = value;
behaviors.keywordOnly = value;
break;
}
@ -1108,7 +1112,7 @@ export function validateDataClassTransformDecorator(
return;
}
behaviors.generateEq = value;
behaviors.skipGenerateEq = !value;
break;
}
@ -1147,6 +1151,11 @@ export function validateDataClassTransformDecorator(
}
behaviors.frozen = value;
// Store the frozen default separately because any class that
// doesn't explicitly specify a frozen value will inherit this
// value rather than the value from its parent.
behaviors.frozenDefault = value;
break;
}
@ -1178,9 +1187,6 @@ export function validateDataClassTransformDecorator(
return;
}
if (!behaviors.fieldDescriptorNames) {
behaviors.fieldDescriptorNames = [];
}
valueType.tupleTypeArguments.forEach((arg) => {
if (isInstantiableClass(arg.type) || isFunction(arg.type)) {
behaviors.fieldDescriptorNames.push(arg.type.details.fullName);
@ -1227,10 +1233,6 @@ export function getDataclassDecoratorBehaviors(type: Type): DataClassBehaviors |
// Is this the built-in dataclass? If so, return the default behaviors.
if (functionType.details.fullName === 'dataclasses.dataclass') {
return {
keywordOnlyParams: false,
generateEq: true,
generateOrder: false,
frozen: false,
fieldDescriptorNames: ['dataclasses.field', 'dataclasses.Field'],
};
}
@ -1243,12 +1245,13 @@ function applyDataClassBehaviorOverride(
errorNode: ParseNode,
classType: ClassType,
argName: string,
argValueExpr: ExpressionNode
argValueExpr: ExpressionNode,
behaviors: DataClassBehaviors
) {
const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
const value = evaluateStaticBoolExpression(argValueExpr, fileInfo.executionEnvironment, fileInfo.definedConstants);
applyDataClassBehaviorOverrideValue(evaluator, errorNode, classType, argName, value);
applyDataClassBehaviorOverrideValue(evaluator, errorNode, classType, argName, value, behaviors);
}
function applyDataClassBehaviorOverrideValue(
@ -1256,22 +1259,19 @@ function applyDataClassBehaviorOverrideValue(
errorNode: ParseNode,
classType: ClassType,
argName: string,
argValue: boolean | undefined
argValue: boolean | undefined,
behaviors: DataClassBehaviors
) {
switch (argName) {
case 'order':
if (argValue === true) {
classType.details.flags |= ClassTypeFlags.SynthesizedDataClassOrder;
} else if (argValue === false) {
classType.details.flags &= ~ClassTypeFlags.SynthesizedDataClassOrder;
if (argValue !== undefined) {
behaviors.generateOrder = argValue;
}
break;
case 'kw_only':
if (argValue === false) {
classType.details.flags &= ~ClassTypeFlags.DataClassKeywordOnlyParams;
} else if (argValue === true) {
classType.details.flags |= ClassTypeFlags.DataClassKeywordOnlyParams;
if (argValue !== undefined) {
behaviors.keywordOnly = argValue;
}
break;
@ -1279,15 +1279,13 @@ function applyDataClassBehaviorOverrideValue(
let hasUnfrozenBaseClass = false;
let hasFrozenBaseClass = false;
if (argValue === false) {
classType.details.flags &= ~ClassTypeFlags.FrozenDataClass;
} else if (argValue === true) {
classType.details.flags |= ClassTypeFlags.FrozenDataClass;
if (argValue !== undefined) {
behaviors.frozen = argValue;
}
classType.details.baseClasses.forEach((baseClass) => {
if (isInstantiableClass(baseClass) && ClassType.isDataClass(baseClass)) {
if (ClassType.isFrozenDataClass(baseClass)) {
if (ClassType.isDataClassFrozen(baseClass)) {
hasFrozenBaseClass = true;
} else if (
!baseClass.details.classDataClassTransform &&
@ -1328,24 +1326,20 @@ function applyDataClassBehaviorOverrideValue(
}
case 'init':
if (argValue === false) {
classType.details.flags |= ClassTypeFlags.SkipSynthesizedDataClassInit;
} else if (argValue === true) {
classType.details.flags &= ~ClassTypeFlags.SkipSynthesizedDataClassInit;
if (argValue !== undefined) {
behaviors.skipGenerateInit = !argValue;
}
break;
case 'eq':
if (argValue === false) {
classType.details.flags |= ClassTypeFlags.SkipSynthesizedDataClassEq;
} else if (argValue === true) {
classType.details.flags &= ~ClassTypeFlags.SkipSynthesizedDataClassEq;
if (argValue !== undefined) {
behaviors.skipGenerateEq = !argValue;
}
break;
case 'slots':
if (argValue === true) {
classType.details.flags |= ClassTypeFlags.GenerateDataClassSlots;
behaviors.generateSlots = true;
if (classType.details.localSlotsNames) {
evaluator.addDiagnostic(
@ -1355,14 +1349,14 @@ function applyDataClassBehaviorOverrideValue(
);
}
} else if (argValue === false) {
classType.details.flags &= ~ClassTypeFlags.GenerateDataClassSlots;
behaviors.generateSlots = false;
}
break;
case 'hash':
case 'unsafe_hash':
if (argValue === true) {
classType.details.flags |= ClassTypeFlags.SynthesizeDataClassUnsafeHash;
behaviors.generateHash = true;
}
break;
}
@ -1377,9 +1371,24 @@ export function applyDataClassClassBehaviorOverrides(
) {
let sawFrozenArg = false;
const behaviors = { ...defaultBehaviors };
// The "frozen" behavior is not inherited from the parent class.
// Instead, it comes from the default.
behaviors.frozen = behaviors.frozenDefault;
classType.details.dataClassBehaviors = behaviors;
args.forEach((arg) => {
if (arg.valueExpression && arg.name) {
applyDataClassBehaviorOverride(evaluator, arg.name, classType, arg.name.value, arg.valueExpression);
applyDataClassBehaviorOverride(
evaluator,
arg.name,
classType,
arg.name.value,
arg.valueExpression,
behaviors
);
if (arg.name.value === 'frozen') {
sawFrozenArg = true;
@ -1387,31 +1396,18 @@ export function applyDataClassClassBehaviorOverrides(
}
});
// If there was no frozen argument, it is implicitly false. This will
// validate that we're not overriding a frozen class with a non-frozen class.
// If there was no frozen argument, it is implicitly set to the frozenDefault.
// This check validates that we're not overriding a frozen class with a
// non-frozen class or vice versa.
if (!sawFrozenArg) {
applyDataClassBehaviorOverrideValue(evaluator, errorNode, classType, 'frozen', defaultBehaviors.frozen);
}
}
export function applyDataClassDefaultBehaviors(classType: ClassType, defaultBehaviors: DataClassBehaviors) {
classType.details.dataClassBehaviors = defaultBehaviors;
classType.details.flags |= ClassTypeFlags.DataClass;
if (defaultBehaviors.keywordOnlyParams) {
classType.details.flags |= ClassTypeFlags.DataClassKeywordOnlyParams;
}
if (!defaultBehaviors.generateEq) {
classType.details.flags |= ClassTypeFlags.SkipSynthesizedDataClassEq;
}
if (defaultBehaviors.generateOrder) {
classType.details.flags |= ClassTypeFlags.SynthesizedDataClassOrder;
}
if (defaultBehaviors.frozen) {
classType.details.flags |= ClassTypeFlags.FrozenDataClass;
applyDataClassBehaviorOverrideValue(
evaluator,
errorNode,
classType,
'frozen',
defaultBehaviors.frozenDefault,
behaviors
);
}
}
@ -1422,7 +1418,5 @@ export function applyDataClassDecorator(
defaultBehaviors: DataClassBehaviors,
callNode: CallNode | undefined
) {
applyDataClassDefaultBehaviors(classType, defaultBehaviors);
applyDataClassClassBehaviorOverrides(evaluator, errorNode, classType, callNode?.arguments ?? [], defaultBehaviors);
}

View File

@ -486,7 +486,7 @@ function assignClassToProtocolInternal(
}
// Frozen dataclasses and named tuples should be treated as read-only.
if (ClassType.isFrozenDataClass(srcType) || ClassType.isReadOnlyInstanceVariables(srcType)) {
if (ClassType.isDataClassFrozen(srcType) || ClassType.isReadOnlyInstanceVariables(srcType)) {
isSrcReadOnly = true;
}
} else {
@ -699,7 +699,7 @@ function assignClassToProtocolInternal(
if (srcMemberInfo && isClass(srcMemberInfo.classType)) {
if (
ClassType.isReadOnlyInstanceVariables(srcMemberInfo.classType) ||
ClassType.isFrozenDataClass(srcMemberInfo.classType)
ClassType.isDataClassFrozen(srcMemberInfo.classType)
) {
isSrcReadOnly = true;
}

View File

@ -104,11 +104,7 @@ import {
getBoundNewMethod,
validateConstructorArguments,
} from './constructors';
import {
applyDataClassClassBehaviorOverrides,
applyDataClassDefaultBehaviors,
synthesizeDataClassMethods,
} from './dataClasses';
import { applyDataClassClassBehaviorOverrides, synthesizeDataClassMethods } from './dataClasses';
import {
ClassDeclaration,
Declaration,
@ -5901,7 +5897,7 @@ export function createTypeEvaluator(
if (
isInstantiableClass(memberInfo.classType) &&
ClassType.isFrozenDataClass(memberInfo.classType) &&
ClassType.isDataClassFrozen(memberInfo.classType) &&
isAccessedThroughObject
) {
diag?.addMessage(
@ -16636,10 +16632,7 @@ export function createTypeEvaluator(
if (fileInfo.executionEnvironment.pythonVersion.isGreaterOrEqualTo(pythonVersion3_6)) {
if (ClassType.isBuiltIn(argType, 'NamedTuple')) {
isNamedTupleSubclass = true;
classType.details.flags |=
ClassTypeFlags.DataClass |
ClassTypeFlags.SkipSynthesizedDataClassEq |
ClassTypeFlags.ReadOnlyInstanceVariables;
classType.details.flags |= ClassTypeFlags.ReadOnlyInstanceVariables;
}
}
@ -17078,24 +17071,46 @@ export function createTypeEvaluator(
// 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 &&
!ClassType.isSameGenericClass(mroClass, classType)
);
});
if (baseClassDataTransform) {
dataClassBehaviors = (baseClassDataTransform as ClassType).details.classDataClassTransform;
// If this class has not already received its dataclass behaviors from a
// decorator and is inheriting from a class that has dataclass behaviors,
// apply those inherited behaviors to this class.
if (!classType.details.dataClassBehaviors) {
for (const mroClass of classType.details.mro) {
if (!isClass(mroClass)) {
break;
}
if (ClassType.isSameGenericClass(mroClass, classType)) {
continue;
}
if (mroClass.details.dataClassBehaviors) {
dataClassBehaviors = mroClass.details.dataClassBehaviors;
break;
}
}
}
if (!dataClassBehaviors) {
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 &&
!ClassType.isSameGenericClass(mroClass, classType)
);
});
if (baseClassDataTransform) {
dataClassBehaviors = (baseClassDataTransform as ClassType).details.classDataClassTransform;
}
}
}
if (dataClassBehaviors) {
applyDataClassDefaultBehaviors(classType, dataClassBehaviors);
applyDataClassClassBehaviorOverrides(
evaluatorInterface,
node.name,
@ -17138,8 +17153,8 @@ export function createTypeEvaluator(
}
// Synthesize dataclass methods.
if (ClassType.isDataClass(classType)) {
const skipSynthesizedInit = ClassType.isSkipSynthesizedDataClassInit(classType);
if (ClassType.isDataClass(classType) || isNamedTupleSubclass) {
const skipSynthesizedInit = ClassType.isDataClassSkipGenerateInit(classType);
let hasExistingInitMethod = skipSynthesizedInit;
// See if there's already a non-synthesized __init__ method.
@ -22715,7 +22730,7 @@ export function createTypeEvaluator(
if (
primaryDecl?.type === DeclarationType.Variable &&
!isFinalVariableDeclaration(primaryDecl) &&
!ClassType.isFrozenDataClass(destType)
!ClassType.isDataClassFrozen(destType)
) {
// Class and instance variables that are mutable need to
// enforce invariance. We will exempt variables that are

View File

@ -476,125 +476,106 @@ export const enum ClassTypeFlags {
// and 'Union'.
SpecialBuiltIn = 1 << 1,
// Introduced in Python 3.7 - class either derives directly
// from NamedTuple or has a @dataclass class decorator.
DataClass = 1 << 2,
// Indicates that the dataclass is frozen.
FrozenDataClass = 1 << 3,
// Flags that control whether methods should be
// synthesized for a dataclass class.
SkipSynthesizedDataClassInit = 1 << 4,
SkipSynthesizedDataClassEq = 1 << 5,
SynthesizedDataClassOrder = 1 << 6,
// Introduced in PEP 589, TypedDict classes provide a way
// to specify type hints for dictionaries with different
// value types and a limited set of static keys.
TypedDictClass = 1 << 7,
TypedDictClass = 1 << 2,
// Used in conjunction with TypedDictClass, indicates that
// the TypedDict class is marked "closed".
TypedDictMarkedClosed = 1 << 8,
TypedDictMarkedClosed = 1 << 3,
// Used in conjunction with TypedDictClass, indicates that
// the TypedDict class is marked "closed" or one or more of
// its superclasses is marked "closed".
TypedDictEffectivelyClosed = 1 << 9,
TypedDictEffectivelyClosed = 1 << 4,
// Used in conjunction with TypedDictClass, indicates that
// the dictionary values can be omitted.
CanOmitDictValues = 1 << 10,
CanOmitDictValues = 1 << 5,
// The class derives from a class that has the ABCMeta
// metaclass. Such classes are allowed to contain
// @abstractmethod decorators.
SupportsAbstractMethods = 1 << 11,
SupportsAbstractMethods = 1 << 6,
// Derives from property class and has the semantics of
// a property (with optional setter, deleter).
PropertyClass = 1 << 12,
PropertyClass = 1 << 7,
// The class is decorated with a "@final" decorator
// indicating that it cannot be subclassed.
Final = 1 << 13,
Final = 1 << 8,
// The class derives directly from "Protocol".
ProtocolClass = 1 << 14,
ProtocolClass = 1 << 9,
// A class whose constructor (__init__ method) does not have
// annotated types and is treated as though each parameter
// is a generic type for purposes of type inference.
PseudoGenericClass = 1 << 15,
PseudoGenericClass = 1 << 10,
// A protocol class that is "runtime checkable" can be used
// in an isinstance call.
RuntimeCheckable = 1 << 16,
RuntimeCheckable = 1 << 11,
// The type is defined in the typing_extensions.pyi file.
TypingExtensionClass = 1 << 17,
TypingExtensionClass = 1 << 12,
// The class type is in the process of being evaluated and
// is not yet complete. This allows us to detect cases where
// the class refers to itself (e.g. uses itself as a type
// argument to one of its generic base classes).
PartiallyEvaluated = 1 << 18,
PartiallyEvaluated = 1 << 13,
// The class or one of its ancestors defines a __class_getitem__
// method that is used for subscripting. This is not set if the
// class is generic, and therefore supports standard subscripting
// semantics.
HasCustomClassGetItem = 1 << 19,
HasCustomClassGetItem = 1 << 14,
// The tuple class uses a variadic type parameter and requires
// special-case handling of its type arguments.
TupleClass = 1 << 20,
TupleClass = 1 << 15,
// The class has a metaclass of EnumMeta or derives from
// a class that has this metaclass.
EnumClass = 1 << 21,
// For dataclasses, should __init__ method always be generated
// with keyword-only parameters?
DataClassKeywordOnlyParams = 1 << 22,
EnumClass = 1 << 16,
// Properties that are defined using the @classmethod decorator.
ClassProperty = 1 << 23,
ClassProperty = 1 << 17,
// Class is declared within a type stub file.
DefinedInStub = 1 << 24,
DefinedInStub = 1 << 18,
// Class does not allow writing or deleting its instance variables
// through a member access. Used with named tuples.
ReadOnlyInstanceVariables = 1 << 25,
// For dataclasses, should __slots__ be generated?
GenerateDataClassSlots = 1 << 26,
// For dataclasses, should __hash__ be generated?
SynthesizeDataClassUnsafeHash = 1 << 27,
ReadOnlyInstanceVariables = 1 << 19,
// Decorated with @type_check_only.
TypeCheckOnly = 1 << 28,
TypeCheckOnly = 1 << 20,
// Created with the NewType call.
NewTypeClass = 1 << 29,
NewTypeClass = 1 << 21,
// Class is allowed to be used as an implicit type alias even
// though it is not defined using a `class` statement.
ValidTypeAliasClass = 1 << 30,
ValidTypeAliasClass = 1 << 22,
// A special form is not compatible with type[T] and cannot
// be directly instantiated.
SpecialFormClass = 1 << 31,
SpecialFormClass = 1 << 23,
}
export interface DataClassBehaviors {
keywordOnlyParams: boolean;
generateEq: boolean;
generateOrder: boolean;
frozen: boolean;
skipGenerateInit?: boolean;
skipGenerateEq?: boolean;
generateOrder?: boolean;
generateSlots?: boolean;
generateHash?: boolean;
keywordOnly?: boolean;
frozen?: boolean;
frozenDefault?: boolean;
fieldDescriptorNames: string[];
}
@ -1060,35 +1041,35 @@ export namespace ClassType {
}
export function isDataClass(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.DataClass);
return !!classType.details.dataClassBehaviors;
}
export function isSkipSynthesizedDataClassInit(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.SkipSynthesizedDataClassInit);
export function isDataClassSkipGenerateInit(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.skipGenerateInit;
}
export function isSkipSynthesizedDataClassEq(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.SkipSynthesizedDataClassEq);
export function isDataClassSkipGenerateEq(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.skipGenerateEq;
}
export function isFrozenDataClass(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.FrozenDataClass);
export function isDataClassFrozen(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.frozen;
}
export function isSynthesizedDataclassOrder(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.SynthesizedDataClassOrder);
export function isDataClassGenerateOrder(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.generateOrder;
}
export function isDataClassKeywordOnlyParams(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.DataClassKeywordOnlyParams);
export function isDataClassKeywordOnly(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.keywordOnly;
}
export function isGeneratedDataClassSlots(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.GenerateDataClassSlots);
export function isDataClassGenerateSlots(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.generateSlots;
}
export function isSynthesizeDataClassUnsafeHash(classType: ClassType) {
return !!(classType.details.flags & ClassTypeFlags.SynthesizeDataClassUnsafeHash);
export function isDataClassGenerateHash(classType: ClassType) {
return !!classType.details.dataClassBehaviors?.generateHash;
}
export function isTypeCheckOnly(classType: ClassType) {

View File

@ -483,13 +483,9 @@ const ClassTypeFlagsToString: [ClassTypeFlags, string][] = [
[ClassTypeFlags.BuiltInClass, 'BuiltInClass'],
[ClassTypeFlags.CanOmitDictValues, 'CanOmitDictValues'],
[ClassTypeFlags.ClassProperty, 'ClassProperty'],
[ClassTypeFlags.DataClass, 'DataClass'],
[ClassTypeFlags.DataClassKeywordOnlyParams, 'DataClassKeywordOnlyParams'],
[ClassTypeFlags.DefinedInStub, 'DefinedInStub'],
[ClassTypeFlags.EnumClass, 'EnumClass'],
[ClassTypeFlags.Final, 'Final'],
[ClassTypeFlags.FrozenDataClass, 'FrozenDataClass'],
[ClassTypeFlags.GenerateDataClassSlots, 'GenerateDataClassSlots'],
[ClassTypeFlags.HasCustomClassGetItem, 'HasCustomClassGetItem'],
[ClassTypeFlags.PartiallyEvaluated, 'PartiallyEvaluated'],
[ClassTypeFlags.PropertyClass, 'PropertyClass'],
@ -497,12 +493,8 @@ const ClassTypeFlagsToString: [ClassTypeFlags, string][] = [
[ClassTypeFlags.PseudoGenericClass, 'PseudoGenericClass'],
[ClassTypeFlags.ReadOnlyInstanceVariables, 'ReadOnlyInstanceVariables'],
[ClassTypeFlags.RuntimeCheckable, 'RuntimeCheckable'],
[ClassTypeFlags.SkipSynthesizedDataClassEq, 'SkipSynthesizedDataClassEq'],
[ClassTypeFlags.SkipSynthesizedDataClassInit, 'SkipSynthesizedDataClassInit'],
[ClassTypeFlags.SpecialBuiltIn, 'SpecialBuiltIn'],
[ClassTypeFlags.SupportsAbstractMethods, 'SupportsAbstractMethods'],
[ClassTypeFlags.SynthesizeDataClassUnsafeHash, 'SynthesizeDataClassUnsafeHash'],
[ClassTypeFlags.SynthesizedDataClassOrder, 'SynthesizedDataClassOrder'],
[ClassTypeFlags.TupleClass, 'TupleClass'],
[ClassTypeFlags.TypedDictClass, 'TypedDictClass'],
[ClassTypeFlags.TypingExtensionClass, 'TypingExtensionClass'],

View File

@ -0,0 +1,29 @@
# This sample tests the case where a dataclass inherits a dataclass
# behavior (like kw_only) from its parent class.
from typing import dataclass_transform
@dataclass_transform()
class ModelMeta(type):
pass
class ModelBase(metaclass=ModelMeta):
def __init_subclass__(cls, kw_only: bool = False) -> None:
pass
class Base(ModelBase, kw_only=True):
pass
class Model(Base):
a: str | None = None
b: int
Model(b=0)
# This should generate an error because of kw_only.
Model("", 1)

View File

@ -416,6 +416,12 @@ test('DataclassTransform4', () => {
TestUtils.validateResults(analysisResults, 2);
});
test('DataclassTransform5', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassTransform5.py']);
TestUtils.validateResults(analysisResults, 1);
});
test('Async1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['async1.py']);