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,
|
// If the entry is a member of a frozen dataclass, it is immutable,
|
||||||
// so it does not need to be invariant.
|
// so it does not need to be invariant.
|
||||||
if (
|
if (
|
||||||
ClassType.isFrozenDataClass(overriddenClassAndSymbol.classType) &&
|
ClassType.isDataClassFrozen(overriddenClassAndSymbol.classType) &&
|
||||||
overriddenClassAndSymbol.classType.details.dataClassEntries
|
overriddenClassAndSymbol.classType.details.dataClassEntries
|
||||||
) {
|
) {
|
||||||
const dataclassEntry = overriddenClassAndSymbol.classType.details.dataClassEntries.find(
|
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,
|
// If the entry is a member of a frozen dataclass, it is immutable,
|
||||||
// so it does not need to be invariant.
|
// 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(
|
const dataclassEntry = baseClass.details.dataClassEntries.find(
|
||||||
(entry) => entry.name === memberName
|
(entry) => entry.name === memberName
|
||||||
);
|
);
|
||||||
|
@ -85,7 +85,7 @@ export function synthesizeDataClassMethods(
|
|||||||
hasExistingInitMethod: boolean,
|
hasExistingInitMethod: boolean,
|
||||||
skipSynthesizeHash: boolean
|
skipSynthesizeHash: boolean
|
||||||
) {
|
) {
|
||||||
assert(ClassType.isDataClass(classType));
|
assert(ClassType.isDataClass(classType) || isNamedTuple);
|
||||||
|
|
||||||
const classTypeVar = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ true);
|
const classTypeVar = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ true);
|
||||||
const newType = FunctionType.createSynthesizedInstance('__new__', FunctionTypeFlags.ConstructorMethod);
|
const newType = FunctionType.createSynthesizedInstance('__new__', FunctionTypeFlags.ConstructorMethod);
|
||||||
@ -194,7 +194,7 @@ export function synthesizeDataClassMethods(
|
|||||||
let aliasName: string | undefined;
|
let aliasName: string | undefined;
|
||||||
let variableTypeEvaluator: EntryTypeEvaluator | undefined;
|
let variableTypeEvaluator: EntryTypeEvaluator | undefined;
|
||||||
let hasDefaultValue = false;
|
let hasDefaultValue = false;
|
||||||
let isKeywordOnly = ClassType.isDataClassKeywordOnlyParams(classType) || sawKeywordOnlySeparator;
|
let isKeywordOnly = ClassType.isDataClassKeywordOnly(classType) || sawKeywordOnlySeparator;
|
||||||
let defaultValueExpression: ExpressionNode | undefined;
|
let defaultValueExpression: ExpressionNode | undefined;
|
||||||
let includeInInit = true;
|
let includeInInit = true;
|
||||||
let converter: ArgumentNode | undefined;
|
let converter: ArgumentNode | undefined;
|
||||||
@ -606,20 +606,20 @@ export function synthesizeDataClassMethods(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Synthesize comparison operators.
|
// Synthesize comparison operators.
|
||||||
if (!ClassType.isSkipSynthesizedDataClassEq(classType)) {
|
if (!ClassType.isDataClassSkipGenerateEq(classType)) {
|
||||||
synthesizeComparisonMethod('__eq__', evaluator.getBuiltInObject(node, 'object'));
|
synthesizeComparisonMethod('__eq__', evaluator.getBuiltInObject(node, 'object'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ClassType.isSynthesizedDataclassOrder(classType)) {
|
if (ClassType.isDataClassGenerateOrder(classType)) {
|
||||||
const objType = ClassType.cloneAsInstance(classType);
|
const objType = ClassType.cloneAsInstance(classType);
|
||||||
['__lt__', '__le__', '__gt__', '__ge__'].forEach((operator) => {
|
['__lt__', '__le__', '__gt__', '__ge__'].forEach((operator) => {
|
||||||
synthesizeComparisonMethod(operator, objType);
|
synthesizeComparisonMethod(operator, objType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let synthesizeHashFunction = ClassType.isFrozenDataClass(classType);
|
let synthesizeHashFunction = ClassType.isDataClassFrozen(classType);
|
||||||
const synthesizeHashNone =
|
const synthesizeHashNone =
|
||||||
!ClassType.isSkipSynthesizedDataClassEq(classType) && !ClassType.isFrozenDataClass(classType);
|
!isNamedTuple && !ClassType.isDataClassSkipGenerateEq(classType) && !ClassType.isDataClassFrozen(classType);
|
||||||
|
|
||||||
if (skipSynthesizeHash) {
|
if (skipSynthesizeHash) {
|
||||||
synthesizeHashFunction = false;
|
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
|
// 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.
|
// 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;
|
synthesizeHashFunction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,12 +664,12 @@ export function synthesizeDataClassMethods(
|
|||||||
Symbol.createWithType(SymbolFlags.ClassMember | SymbolFlags.ClassVar, dictType)
|
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);
|
classType.details.localSlotsNames = localDataClassEntries.map((entry) => entry.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should we synthesize a __slots__ symbol?
|
// Should we synthesize a __slots__ symbol?
|
||||||
if (ClassType.isGeneratedDataClassSlots(classType)) {
|
if (ClassType.isDataClassGenerateSlots(classType)) {
|
||||||
let iterableType = evaluator.getTypingType(node, 'Iterable') ?? UnknownType.create();
|
let iterableType = evaluator.getTypingType(node, 'Iterable') ?? UnknownType.create();
|
||||||
|
|
||||||
if (isInstantiableClass(iterableType)) {
|
if (isInstantiableClass(iterableType)) {
|
||||||
@ -1053,10 +1053,14 @@ export function validateDataClassTransformDecorator(
|
|||||||
node: CallNode
|
node: CallNode
|
||||||
): DataClassBehaviors | undefined {
|
): DataClassBehaviors | undefined {
|
||||||
const behaviors: DataClassBehaviors = {
|
const behaviors: DataClassBehaviors = {
|
||||||
keywordOnlyParams: false,
|
skipGenerateInit: false,
|
||||||
generateEq: true,
|
skipGenerateEq: false,
|
||||||
generateOrder: false,
|
generateOrder: false,
|
||||||
|
generateSlots: false,
|
||||||
|
generateHash: false,
|
||||||
|
keywordOnly: false,
|
||||||
frozen: false,
|
frozen: false,
|
||||||
|
frozenDefault: false,
|
||||||
fieldDescriptorNames: [],
|
fieldDescriptorNames: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1089,7 +1093,7 @@ export function validateDataClassTransformDecorator(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
behaviors.keywordOnlyParams = value;
|
behaviors.keywordOnly = value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1108,7 +1112,7 @@ export function validateDataClassTransformDecorator(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
behaviors.generateEq = value;
|
behaviors.skipGenerateEq = !value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1147,6 +1151,11 @@ export function validateDataClassTransformDecorator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
behaviors.frozen = value;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1178,9 +1187,6 @@ export function validateDataClassTransformDecorator(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!behaviors.fieldDescriptorNames) {
|
|
||||||
behaviors.fieldDescriptorNames = [];
|
|
||||||
}
|
|
||||||
valueType.tupleTypeArguments.forEach((arg) => {
|
valueType.tupleTypeArguments.forEach((arg) => {
|
||||||
if (isInstantiableClass(arg.type) || isFunction(arg.type)) {
|
if (isInstantiableClass(arg.type) || isFunction(arg.type)) {
|
||||||
behaviors.fieldDescriptorNames.push(arg.type.details.fullName);
|
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.
|
// Is this the built-in dataclass? If so, return the default behaviors.
|
||||||
if (functionType.details.fullName === 'dataclasses.dataclass') {
|
if (functionType.details.fullName === 'dataclasses.dataclass') {
|
||||||
return {
|
return {
|
||||||
keywordOnlyParams: false,
|
|
||||||
generateEq: true,
|
|
||||||
generateOrder: false,
|
|
||||||
frozen: false,
|
|
||||||
fieldDescriptorNames: ['dataclasses.field', 'dataclasses.Field'],
|
fieldDescriptorNames: ['dataclasses.field', 'dataclasses.Field'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1243,12 +1245,13 @@ function applyDataClassBehaviorOverride(
|
|||||||
errorNode: ParseNode,
|
errorNode: ParseNode,
|
||||||
classType: ClassType,
|
classType: ClassType,
|
||||||
argName: string,
|
argName: string,
|
||||||
argValueExpr: ExpressionNode
|
argValueExpr: ExpressionNode,
|
||||||
|
behaviors: DataClassBehaviors
|
||||||
) {
|
) {
|
||||||
const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
|
const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
|
||||||
const value = evaluateStaticBoolExpression(argValueExpr, fileInfo.executionEnvironment, fileInfo.definedConstants);
|
const value = evaluateStaticBoolExpression(argValueExpr, fileInfo.executionEnvironment, fileInfo.definedConstants);
|
||||||
|
|
||||||
applyDataClassBehaviorOverrideValue(evaluator, errorNode, classType, argName, value);
|
applyDataClassBehaviorOverrideValue(evaluator, errorNode, classType, argName, value, behaviors);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDataClassBehaviorOverrideValue(
|
function applyDataClassBehaviorOverrideValue(
|
||||||
@ -1256,22 +1259,19 @@ function applyDataClassBehaviorOverrideValue(
|
|||||||
errorNode: ParseNode,
|
errorNode: ParseNode,
|
||||||
classType: ClassType,
|
classType: ClassType,
|
||||||
argName: string,
|
argName: string,
|
||||||
argValue: boolean | undefined
|
argValue: boolean | undefined,
|
||||||
|
behaviors: DataClassBehaviors
|
||||||
) {
|
) {
|
||||||
switch (argName) {
|
switch (argName) {
|
||||||
case 'order':
|
case 'order':
|
||||||
if (argValue === true) {
|
if (argValue !== undefined) {
|
||||||
classType.details.flags |= ClassTypeFlags.SynthesizedDataClassOrder;
|
behaviors.generateOrder = argValue;
|
||||||
} else if (argValue === false) {
|
|
||||||
classType.details.flags &= ~ClassTypeFlags.SynthesizedDataClassOrder;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'kw_only':
|
case 'kw_only':
|
||||||
if (argValue === false) {
|
if (argValue !== undefined) {
|
||||||
classType.details.flags &= ~ClassTypeFlags.DataClassKeywordOnlyParams;
|
behaviors.keywordOnly = argValue;
|
||||||
} else if (argValue === true) {
|
|
||||||
classType.details.flags |= ClassTypeFlags.DataClassKeywordOnlyParams;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1279,15 +1279,13 @@ function applyDataClassBehaviorOverrideValue(
|
|||||||
let hasUnfrozenBaseClass = false;
|
let hasUnfrozenBaseClass = false;
|
||||||
let hasFrozenBaseClass = false;
|
let hasFrozenBaseClass = false;
|
||||||
|
|
||||||
if (argValue === false) {
|
if (argValue !== undefined) {
|
||||||
classType.details.flags &= ~ClassTypeFlags.FrozenDataClass;
|
behaviors.frozen = argValue;
|
||||||
} else if (argValue === true) {
|
|
||||||
classType.details.flags |= ClassTypeFlags.FrozenDataClass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
classType.details.baseClasses.forEach((baseClass) => {
|
classType.details.baseClasses.forEach((baseClass) => {
|
||||||
if (isInstantiableClass(baseClass) && ClassType.isDataClass(baseClass)) {
|
if (isInstantiableClass(baseClass) && ClassType.isDataClass(baseClass)) {
|
||||||
if (ClassType.isFrozenDataClass(baseClass)) {
|
if (ClassType.isDataClassFrozen(baseClass)) {
|
||||||
hasFrozenBaseClass = true;
|
hasFrozenBaseClass = true;
|
||||||
} else if (
|
} else if (
|
||||||
!baseClass.details.classDataClassTransform &&
|
!baseClass.details.classDataClassTransform &&
|
||||||
@ -1328,24 +1326,20 @@ function applyDataClassBehaviorOverrideValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'init':
|
case 'init':
|
||||||
if (argValue === false) {
|
if (argValue !== undefined) {
|
||||||
classType.details.flags |= ClassTypeFlags.SkipSynthesizedDataClassInit;
|
behaviors.skipGenerateInit = !argValue;
|
||||||
} else if (argValue === true) {
|
|
||||||
classType.details.flags &= ~ClassTypeFlags.SkipSynthesizedDataClassInit;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'eq':
|
case 'eq':
|
||||||
if (argValue === false) {
|
if (argValue !== undefined) {
|
||||||
classType.details.flags |= ClassTypeFlags.SkipSynthesizedDataClassEq;
|
behaviors.skipGenerateEq = !argValue;
|
||||||
} else if (argValue === true) {
|
|
||||||
classType.details.flags &= ~ClassTypeFlags.SkipSynthesizedDataClassEq;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'slots':
|
case 'slots':
|
||||||
if (argValue === true) {
|
if (argValue === true) {
|
||||||
classType.details.flags |= ClassTypeFlags.GenerateDataClassSlots;
|
behaviors.generateSlots = true;
|
||||||
|
|
||||||
if (classType.details.localSlotsNames) {
|
if (classType.details.localSlotsNames) {
|
||||||
evaluator.addDiagnostic(
|
evaluator.addDiagnostic(
|
||||||
@ -1355,14 +1349,14 @@ function applyDataClassBehaviorOverrideValue(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (argValue === false) {
|
} else if (argValue === false) {
|
||||||
classType.details.flags &= ~ClassTypeFlags.GenerateDataClassSlots;
|
behaviors.generateSlots = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'hash':
|
case 'hash':
|
||||||
case 'unsafe_hash':
|
case 'unsafe_hash':
|
||||||
if (argValue === true) {
|
if (argValue === true) {
|
||||||
classType.details.flags |= ClassTypeFlags.SynthesizeDataClassUnsafeHash;
|
behaviors.generateHash = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1377,9 +1371,24 @@ export function applyDataClassClassBehaviorOverrides(
|
|||||||
) {
|
) {
|
||||||
let sawFrozenArg = false;
|
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) => {
|
args.forEach((arg) => {
|
||||||
if (arg.valueExpression && arg.name) {
|
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') {
|
if (arg.name.value === 'frozen') {
|
||||||
sawFrozenArg = true;
|
sawFrozenArg = true;
|
||||||
@ -1387,31 +1396,18 @@ export function applyDataClassClassBehaviorOverrides(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there was no frozen argument, it is implicitly false. This will
|
// If there was no frozen argument, it is implicitly set to the frozenDefault.
|
||||||
// validate that we're not overriding a frozen class with a non-frozen class.
|
// This check validates that we're not overriding a frozen class with a
|
||||||
|
// non-frozen class or vice versa.
|
||||||
if (!sawFrozenArg) {
|
if (!sawFrozenArg) {
|
||||||
applyDataClassBehaviorOverrideValue(evaluator, errorNode, classType, 'frozen', defaultBehaviors.frozen);
|
applyDataClassBehaviorOverrideValue(
|
||||||
}
|
evaluator,
|
||||||
}
|
errorNode,
|
||||||
|
classType,
|
||||||
export function applyDataClassDefaultBehaviors(classType: ClassType, defaultBehaviors: DataClassBehaviors) {
|
'frozen',
|
||||||
classType.details.dataClassBehaviors = defaultBehaviors;
|
defaultBehaviors.frozenDefault,
|
||||||
classType.details.flags |= ClassTypeFlags.DataClass;
|
behaviors
|
||||||
|
);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1422,7 +1418,5 @@ export function applyDataClassDecorator(
|
|||||||
defaultBehaviors: DataClassBehaviors,
|
defaultBehaviors: DataClassBehaviors,
|
||||||
callNode: CallNode | undefined
|
callNode: CallNode | undefined
|
||||||
) {
|
) {
|
||||||
applyDataClassDefaultBehaviors(classType, defaultBehaviors);
|
|
||||||
|
|
||||||
applyDataClassClassBehaviorOverrides(evaluator, errorNode, classType, callNode?.arguments ?? [], 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.
|
// 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;
|
isSrcReadOnly = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -699,7 +699,7 @@ function assignClassToProtocolInternal(
|
|||||||
if (srcMemberInfo && isClass(srcMemberInfo.classType)) {
|
if (srcMemberInfo && isClass(srcMemberInfo.classType)) {
|
||||||
if (
|
if (
|
||||||
ClassType.isReadOnlyInstanceVariables(srcMemberInfo.classType) ||
|
ClassType.isReadOnlyInstanceVariables(srcMemberInfo.classType) ||
|
||||||
ClassType.isFrozenDataClass(srcMemberInfo.classType)
|
ClassType.isDataClassFrozen(srcMemberInfo.classType)
|
||||||
) {
|
) {
|
||||||
isSrcReadOnly = true;
|
isSrcReadOnly = true;
|
||||||
}
|
}
|
||||||
|
@ -104,11 +104,7 @@ import {
|
|||||||
getBoundNewMethod,
|
getBoundNewMethod,
|
||||||
validateConstructorArguments,
|
validateConstructorArguments,
|
||||||
} from './constructors';
|
} from './constructors';
|
||||||
import {
|
import { applyDataClassClassBehaviorOverrides, synthesizeDataClassMethods } from './dataClasses';
|
||||||
applyDataClassClassBehaviorOverrides,
|
|
||||||
applyDataClassDefaultBehaviors,
|
|
||||||
synthesizeDataClassMethods,
|
|
||||||
} from './dataClasses';
|
|
||||||
import {
|
import {
|
||||||
ClassDeclaration,
|
ClassDeclaration,
|
||||||
Declaration,
|
Declaration,
|
||||||
@ -5901,7 +5897,7 @@ export function createTypeEvaluator(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
isInstantiableClass(memberInfo.classType) &&
|
isInstantiableClass(memberInfo.classType) &&
|
||||||
ClassType.isFrozenDataClass(memberInfo.classType) &&
|
ClassType.isDataClassFrozen(memberInfo.classType) &&
|
||||||
isAccessedThroughObject
|
isAccessedThroughObject
|
||||||
) {
|
) {
|
||||||
diag?.addMessage(
|
diag?.addMessage(
|
||||||
@ -16636,10 +16632,7 @@ export function createTypeEvaluator(
|
|||||||
if (fileInfo.executionEnvironment.pythonVersion.isGreaterOrEqualTo(pythonVersion3_6)) {
|
if (fileInfo.executionEnvironment.pythonVersion.isGreaterOrEqualTo(pythonVersion3_6)) {
|
||||||
if (ClassType.isBuiltIn(argType, 'NamedTuple')) {
|
if (ClassType.isBuiltIn(argType, 'NamedTuple')) {
|
||||||
isNamedTupleSubclass = true;
|
isNamedTupleSubclass = true;
|
||||||
classType.details.flags |=
|
classType.details.flags |= ClassTypeFlags.ReadOnlyInstanceVariables;
|
||||||
ClassTypeFlags.DataClass |
|
|
||||||
ClassTypeFlags.SkipSynthesizedDataClassEq |
|
|
||||||
ClassTypeFlags.ReadOnlyInstanceVariables;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17078,24 +17071,46 @@ export function createTypeEvaluator(
|
|||||||
// Determine whether this class derives from (or has a metaclass) that imbues
|
// Determine whether this class derives from (or has a metaclass) that imbues
|
||||||
// it with dataclass-like behaviors. If so, we'll apply those here.
|
// it with dataclass-like behaviors. If so, we'll apply those here.
|
||||||
let dataClassBehaviors: DataClassBehaviors | undefined;
|
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) {
|
// If this class has not already received its dataclass behaviors from a
|
||||||
dataClassBehaviors = (baseClassDataTransform as ClassType).details.classDataClassTransform;
|
// 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) {
|
if (dataClassBehaviors) {
|
||||||
applyDataClassDefaultBehaviors(classType, dataClassBehaviors);
|
|
||||||
applyDataClassClassBehaviorOverrides(
|
applyDataClassClassBehaviorOverrides(
|
||||||
evaluatorInterface,
|
evaluatorInterface,
|
||||||
node.name,
|
node.name,
|
||||||
@ -17138,8 +17153,8 @@ export function createTypeEvaluator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synthesize dataclass methods.
|
// Synthesize dataclass methods.
|
||||||
if (ClassType.isDataClass(classType)) {
|
if (ClassType.isDataClass(classType) || isNamedTupleSubclass) {
|
||||||
const skipSynthesizedInit = ClassType.isSkipSynthesizedDataClassInit(classType);
|
const skipSynthesizedInit = ClassType.isDataClassSkipGenerateInit(classType);
|
||||||
let hasExistingInitMethod = skipSynthesizedInit;
|
let hasExistingInitMethod = skipSynthesizedInit;
|
||||||
|
|
||||||
// See if there's already a non-synthesized __init__ method.
|
// See if there's already a non-synthesized __init__ method.
|
||||||
@ -22715,7 +22730,7 @@ export function createTypeEvaluator(
|
|||||||
if (
|
if (
|
||||||
primaryDecl?.type === DeclarationType.Variable &&
|
primaryDecl?.type === DeclarationType.Variable &&
|
||||||
!isFinalVariableDeclaration(primaryDecl) &&
|
!isFinalVariableDeclaration(primaryDecl) &&
|
||||||
!ClassType.isFrozenDataClass(destType)
|
!ClassType.isDataClassFrozen(destType)
|
||||||
) {
|
) {
|
||||||
// Class and instance variables that are mutable need to
|
// Class and instance variables that are mutable need to
|
||||||
// enforce invariance. We will exempt variables that are
|
// enforce invariance. We will exempt variables that are
|
||||||
|
@ -476,125 +476,106 @@ export const enum ClassTypeFlags {
|
|||||||
// and 'Union'.
|
// and 'Union'.
|
||||||
SpecialBuiltIn = 1 << 1,
|
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
|
// Introduced in PEP 589, TypedDict classes provide a way
|
||||||
// to specify type hints for dictionaries with different
|
// to specify type hints for dictionaries with different
|
||||||
// value types and a limited set of static keys.
|
// value types and a limited set of static keys.
|
||||||
TypedDictClass = 1 << 7,
|
TypedDictClass = 1 << 2,
|
||||||
|
|
||||||
// Used in conjunction with TypedDictClass, indicates that
|
// Used in conjunction with TypedDictClass, indicates that
|
||||||
// the TypedDict class is marked "closed".
|
// the TypedDict class is marked "closed".
|
||||||
TypedDictMarkedClosed = 1 << 8,
|
TypedDictMarkedClosed = 1 << 3,
|
||||||
|
|
||||||
// Used in conjunction with TypedDictClass, indicates that
|
// Used in conjunction with TypedDictClass, indicates that
|
||||||
// the TypedDict class is marked "closed" or one or more of
|
// the TypedDict class is marked "closed" or one or more of
|
||||||
// its superclasses is marked "closed".
|
// its superclasses is marked "closed".
|
||||||
TypedDictEffectivelyClosed = 1 << 9,
|
TypedDictEffectivelyClosed = 1 << 4,
|
||||||
|
|
||||||
// Used in conjunction with TypedDictClass, indicates that
|
// Used in conjunction with TypedDictClass, indicates that
|
||||||
// the dictionary values can be omitted.
|
// the dictionary values can be omitted.
|
||||||
CanOmitDictValues = 1 << 10,
|
CanOmitDictValues = 1 << 5,
|
||||||
|
|
||||||
// The class derives from a class that has the ABCMeta
|
// The class derives from a class that has the ABCMeta
|
||||||
// metaclass. Such classes are allowed to contain
|
// metaclass. Such classes are allowed to contain
|
||||||
// @abstractmethod decorators.
|
// @abstractmethod decorators.
|
||||||
SupportsAbstractMethods = 1 << 11,
|
SupportsAbstractMethods = 1 << 6,
|
||||||
|
|
||||||
// Derives from property class and has the semantics of
|
// Derives from property class and has the semantics of
|
||||||
// a property (with optional setter, deleter).
|
// a property (with optional setter, deleter).
|
||||||
PropertyClass = 1 << 12,
|
PropertyClass = 1 << 7,
|
||||||
|
|
||||||
// The class is decorated with a "@final" decorator
|
// The class is decorated with a "@final" decorator
|
||||||
// indicating that it cannot be subclassed.
|
// indicating that it cannot be subclassed.
|
||||||
Final = 1 << 13,
|
Final = 1 << 8,
|
||||||
|
|
||||||
// The class derives directly from "Protocol".
|
// The class derives directly from "Protocol".
|
||||||
ProtocolClass = 1 << 14,
|
ProtocolClass = 1 << 9,
|
||||||
|
|
||||||
// A class whose constructor (__init__ method) does not have
|
// A class whose constructor (__init__ method) does not have
|
||||||
// annotated types and is treated as though each parameter
|
// annotated types and is treated as though each parameter
|
||||||
// is a generic type for purposes of type inference.
|
// 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
|
// A protocol class that is "runtime checkable" can be used
|
||||||
// in an isinstance call.
|
// in an isinstance call.
|
||||||
RuntimeCheckable = 1 << 16,
|
RuntimeCheckable = 1 << 11,
|
||||||
|
|
||||||
// The type is defined in the typing_extensions.pyi file.
|
// 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
|
// The class type is in the process of being evaluated and
|
||||||
// is not yet complete. This allows us to detect cases where
|
// is not yet complete. This allows us to detect cases where
|
||||||
// the class refers to itself (e.g. uses itself as a type
|
// the class refers to itself (e.g. uses itself as a type
|
||||||
// argument to one of its generic base classes).
|
// 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__
|
// The class or one of its ancestors defines a __class_getitem__
|
||||||
// method that is used for subscripting. This is not set if the
|
// method that is used for subscripting. This is not set if the
|
||||||
// class is generic, and therefore supports standard subscripting
|
// class is generic, and therefore supports standard subscripting
|
||||||
// semantics.
|
// semantics.
|
||||||
HasCustomClassGetItem = 1 << 19,
|
HasCustomClassGetItem = 1 << 14,
|
||||||
|
|
||||||
// The tuple class uses a variadic type parameter and requires
|
// The tuple class uses a variadic type parameter and requires
|
||||||
// special-case handling of its type arguments.
|
// special-case handling of its type arguments.
|
||||||
TupleClass = 1 << 20,
|
TupleClass = 1 << 15,
|
||||||
|
|
||||||
// The class has a metaclass of EnumMeta or derives from
|
// The class has a metaclass of EnumMeta or derives from
|
||||||
// a class that has this metaclass.
|
// a class that has this metaclass.
|
||||||
EnumClass = 1 << 21,
|
EnumClass = 1 << 16,
|
||||||
|
|
||||||
// For dataclasses, should __init__ method always be generated
|
|
||||||
// with keyword-only parameters?
|
|
||||||
DataClassKeywordOnlyParams = 1 << 22,
|
|
||||||
|
|
||||||
// Properties that are defined using the @classmethod decorator.
|
// Properties that are defined using the @classmethod decorator.
|
||||||
ClassProperty = 1 << 23,
|
ClassProperty = 1 << 17,
|
||||||
|
|
||||||
// Class is declared within a type stub file.
|
// Class is declared within a type stub file.
|
||||||
DefinedInStub = 1 << 24,
|
DefinedInStub = 1 << 18,
|
||||||
|
|
||||||
// Class does not allow writing or deleting its instance variables
|
// Class does not allow writing or deleting its instance variables
|
||||||
// through a member access. Used with named tuples.
|
// through a member access. Used with named tuples.
|
||||||
ReadOnlyInstanceVariables = 1 << 25,
|
ReadOnlyInstanceVariables = 1 << 19,
|
||||||
|
|
||||||
// For dataclasses, should __slots__ be generated?
|
|
||||||
GenerateDataClassSlots = 1 << 26,
|
|
||||||
|
|
||||||
// For dataclasses, should __hash__ be generated?
|
|
||||||
SynthesizeDataClassUnsafeHash = 1 << 27,
|
|
||||||
|
|
||||||
// Decorated with @type_check_only.
|
// Decorated with @type_check_only.
|
||||||
TypeCheckOnly = 1 << 28,
|
TypeCheckOnly = 1 << 20,
|
||||||
|
|
||||||
// Created with the NewType call.
|
// Created with the NewType call.
|
||||||
NewTypeClass = 1 << 29,
|
NewTypeClass = 1 << 21,
|
||||||
|
|
||||||
// Class is allowed to be used as an implicit type alias even
|
// Class is allowed to be used as an implicit type alias even
|
||||||
// though it is not defined using a `class` statement.
|
// 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
|
// A special form is not compatible with type[T] and cannot
|
||||||
// be directly instantiated.
|
// be directly instantiated.
|
||||||
SpecialFormClass = 1 << 31,
|
SpecialFormClass = 1 << 23,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataClassBehaviors {
|
export interface DataClassBehaviors {
|
||||||
keywordOnlyParams: boolean;
|
skipGenerateInit?: boolean;
|
||||||
generateEq: boolean;
|
skipGenerateEq?: boolean;
|
||||||
generateOrder: boolean;
|
generateOrder?: boolean;
|
||||||
frozen: boolean;
|
generateSlots?: boolean;
|
||||||
|
generateHash?: boolean;
|
||||||
|
keywordOnly?: boolean;
|
||||||
|
frozen?: boolean;
|
||||||
|
frozenDefault?: boolean;
|
||||||
fieldDescriptorNames: string[];
|
fieldDescriptorNames: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1060,35 +1041,35 @@ export namespace ClassType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isDataClass(classType: ClassType) {
|
export function isDataClass(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.DataClass);
|
return !!classType.details.dataClassBehaviors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSkipSynthesizedDataClassInit(classType: ClassType) {
|
export function isDataClassSkipGenerateInit(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.SkipSynthesizedDataClassInit);
|
return !!classType.details.dataClassBehaviors?.skipGenerateInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSkipSynthesizedDataClassEq(classType: ClassType) {
|
export function isDataClassSkipGenerateEq(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.SkipSynthesizedDataClassEq);
|
return !!classType.details.dataClassBehaviors?.skipGenerateEq;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFrozenDataClass(classType: ClassType) {
|
export function isDataClassFrozen(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.FrozenDataClass);
|
return !!classType.details.dataClassBehaviors?.frozen;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSynthesizedDataclassOrder(classType: ClassType) {
|
export function isDataClassGenerateOrder(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.SynthesizedDataClassOrder);
|
return !!classType.details.dataClassBehaviors?.generateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDataClassKeywordOnlyParams(classType: ClassType) {
|
export function isDataClassKeywordOnly(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.DataClassKeywordOnlyParams);
|
return !!classType.details.dataClassBehaviors?.keywordOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isGeneratedDataClassSlots(classType: ClassType) {
|
export function isDataClassGenerateSlots(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.GenerateDataClassSlots);
|
return !!classType.details.dataClassBehaviors?.generateSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSynthesizeDataClassUnsafeHash(classType: ClassType) {
|
export function isDataClassGenerateHash(classType: ClassType) {
|
||||||
return !!(classType.details.flags & ClassTypeFlags.SynthesizeDataClassUnsafeHash);
|
return !!classType.details.dataClassBehaviors?.generateHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTypeCheckOnly(classType: ClassType) {
|
export function isTypeCheckOnly(classType: ClassType) {
|
||||||
|
@ -483,13 +483,9 @@ const ClassTypeFlagsToString: [ClassTypeFlags, string][] = [
|
|||||||
[ClassTypeFlags.BuiltInClass, 'BuiltInClass'],
|
[ClassTypeFlags.BuiltInClass, 'BuiltInClass'],
|
||||||
[ClassTypeFlags.CanOmitDictValues, 'CanOmitDictValues'],
|
[ClassTypeFlags.CanOmitDictValues, 'CanOmitDictValues'],
|
||||||
[ClassTypeFlags.ClassProperty, 'ClassProperty'],
|
[ClassTypeFlags.ClassProperty, 'ClassProperty'],
|
||||||
[ClassTypeFlags.DataClass, 'DataClass'],
|
|
||||||
[ClassTypeFlags.DataClassKeywordOnlyParams, 'DataClassKeywordOnlyParams'],
|
|
||||||
[ClassTypeFlags.DefinedInStub, 'DefinedInStub'],
|
[ClassTypeFlags.DefinedInStub, 'DefinedInStub'],
|
||||||
[ClassTypeFlags.EnumClass, 'EnumClass'],
|
[ClassTypeFlags.EnumClass, 'EnumClass'],
|
||||||
[ClassTypeFlags.Final, 'Final'],
|
[ClassTypeFlags.Final, 'Final'],
|
||||||
[ClassTypeFlags.FrozenDataClass, 'FrozenDataClass'],
|
|
||||||
[ClassTypeFlags.GenerateDataClassSlots, 'GenerateDataClassSlots'],
|
|
||||||
[ClassTypeFlags.HasCustomClassGetItem, 'HasCustomClassGetItem'],
|
[ClassTypeFlags.HasCustomClassGetItem, 'HasCustomClassGetItem'],
|
||||||
[ClassTypeFlags.PartiallyEvaluated, 'PartiallyEvaluated'],
|
[ClassTypeFlags.PartiallyEvaluated, 'PartiallyEvaluated'],
|
||||||
[ClassTypeFlags.PropertyClass, 'PropertyClass'],
|
[ClassTypeFlags.PropertyClass, 'PropertyClass'],
|
||||||
@ -497,12 +493,8 @@ const ClassTypeFlagsToString: [ClassTypeFlags, string][] = [
|
|||||||
[ClassTypeFlags.PseudoGenericClass, 'PseudoGenericClass'],
|
[ClassTypeFlags.PseudoGenericClass, 'PseudoGenericClass'],
|
||||||
[ClassTypeFlags.ReadOnlyInstanceVariables, 'ReadOnlyInstanceVariables'],
|
[ClassTypeFlags.ReadOnlyInstanceVariables, 'ReadOnlyInstanceVariables'],
|
||||||
[ClassTypeFlags.RuntimeCheckable, 'RuntimeCheckable'],
|
[ClassTypeFlags.RuntimeCheckable, 'RuntimeCheckable'],
|
||||||
[ClassTypeFlags.SkipSynthesizedDataClassEq, 'SkipSynthesizedDataClassEq'],
|
|
||||||
[ClassTypeFlags.SkipSynthesizedDataClassInit, 'SkipSynthesizedDataClassInit'],
|
|
||||||
[ClassTypeFlags.SpecialBuiltIn, 'SpecialBuiltIn'],
|
[ClassTypeFlags.SpecialBuiltIn, 'SpecialBuiltIn'],
|
||||||
[ClassTypeFlags.SupportsAbstractMethods, 'SupportsAbstractMethods'],
|
[ClassTypeFlags.SupportsAbstractMethods, 'SupportsAbstractMethods'],
|
||||||
[ClassTypeFlags.SynthesizeDataClassUnsafeHash, 'SynthesizeDataClassUnsafeHash'],
|
|
||||||
[ClassTypeFlags.SynthesizedDataClassOrder, 'SynthesizedDataClassOrder'],
|
|
||||||
[ClassTypeFlags.TupleClass, 'TupleClass'],
|
[ClassTypeFlags.TupleClass, 'TupleClass'],
|
||||||
[ClassTypeFlags.TypedDictClass, 'TypedDictClass'],
|
[ClassTypeFlags.TypedDictClass, 'TypedDictClass'],
|
||||||
[ClassTypeFlags.TypingExtensionClass, 'TypingExtensionClass'],
|
[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);
|
TestUtils.validateResults(analysisResults, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('DataclassTransform5', () => {
|
||||||
|
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassTransform5.py']);
|
||||||
|
|
||||||
|
TestUtils.validateResults(analysisResults, 1);
|
||||||
|
});
|
||||||
|
|
||||||
test('Async1', () => {
|
test('Async1', () => {
|
||||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['async1.py']);
|
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['async1.py']);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user