diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index 7bb27f38a..4bcd87d38 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -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 ); diff --git a/packages/pyright-internal/src/analyzer/dataClasses.ts b/packages/pyright-internal/src/analyzer/dataClasses.ts index 6673c17e9..792324455 100644 --- a/packages/pyright-internal/src/analyzer/dataClasses.ts +++ b/packages/pyright-internal/src/analyzer/dataClasses.ts @@ -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); } diff --git a/packages/pyright-internal/src/analyzer/protocols.ts b/packages/pyright-internal/src/analyzer/protocols.ts index e9522e6ea..719de991e 100644 --- a/packages/pyright-internal/src/analyzer/protocols.ts +++ b/packages/pyright-internal/src/analyzer/protocols.ts @@ -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; } diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 336ff7c75..cba58de61 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -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 diff --git a/packages/pyright-internal/src/analyzer/types.ts b/packages/pyright-internal/src/analyzer/types.ts index edcdfec70..ef2d7074a 100644 --- a/packages/pyright-internal/src/analyzer/types.ts +++ b/packages/pyright-internal/src/analyzer/types.ts @@ -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) { diff --git a/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts b/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts index fde0ba3ce..5ba476d17 100644 --- a/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts +++ b/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts @@ -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'], diff --git a/packages/pyright-internal/src/tests/samples/dataclassTransform5.py b/packages/pyright-internal/src/tests/samples/dataclassTransform5.py new file mode 100644 index 000000000..a1fdc66bf --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/dataclassTransform5.py @@ -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) diff --git a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts index d4cc61be1..bf458ba00 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts @@ -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']);