mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-26 02:38:31 +03:00
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:
parent
75d0f2b5a1
commit
cbd189a29f
@ -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
|
||||
);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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'],
|
||||
|
@ -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)
|
@ -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']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user