[ty] Store `field_specifiers`

This commit is contained in:
David Peter 2025-10-14 16:46:45 +02:00
parent 8817ea5c84
commit 4dc88a0a5f
6 changed files with 155 additions and 117 deletions

View File

@ -32,7 +32,7 @@ pub(crate) use self::signatures::{CallableSignature, Parameter, Parameters, Sign
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
use crate::module_name::ModuleName; use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, resolve_module}; use crate::module_resolver::{KnownModule, resolve_module};
use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol}; use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol, known_module_symbol};
use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::semantic_index::place::ScopedPlaceId; use crate::semantic_index::place::ScopedPlaceId;
use crate::semantic_index::scope::ScopeId; use crate::semantic_index::scope::ScopeId;
@ -50,7 +50,8 @@ pub use crate::types::display::DisplaySettings;
use crate::types::display::TupleSpecialization; use crate::types::display::TupleSpecialization;
use crate::types::enums::{enum_metadata, is_single_member_enum}; use crate::types::enums::{enum_metadata, is_single_member_enum};
use crate::types::function::{ use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, DataclassTransformerFlags, DataclassTransformerParams, FunctionSpans, FunctionType,
KnownFunction,
}; };
use crate::types::generics::{ use crate::types::generics::{
GenericContext, InferableTypeVars, PartialSpecialization, Specialization, bind_typevar, GenericContext, InferableTypeVars, PartialSpecialization, Specialization, bind_typevar,
@ -622,8 +623,8 @@ bitflags! {
/// that were passed in. For the precise meaning of the fields, see [1]. /// that were passed in. For the precise meaning of the fields, see [1].
/// ///
/// [1]: https://docs.python.org/3/library/dataclasses.html /// [1]: https://docs.python.org/3/library/dataclasses.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DataclassParams: u16 { pub struct DataclassFlags: u16 {
const INIT = 0b0000_0000_0001; const INIT = 0b0000_0000_0001;
const REPR = 0b0000_0000_0010; const REPR = 0b0000_0000_0010;
const EQ = 0b0000_0000_0100; const EQ = 0b0000_0000_0100;
@ -634,51 +635,74 @@ bitflags! {
const KW_ONLY = 0b0000_1000_0000; const KW_ONLY = 0b0000_1000_0000;
const SLOTS = 0b0001_0000_0000; const SLOTS = 0b0001_0000_0000;
const WEAKREF_SLOT = 0b0010_0000_0000; const WEAKREF_SLOT = 0b0010_0000_0000;
// This is not an actual argument from `dataclass(...)` but a flag signaling that no
// `field_specifiers` was specified for the `dataclass_transform`, see [1].
// [1]: https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-transform-parameters
const NO_FIELD_SPECIFIERS = 0b0100_0000_0000;
} }
} }
impl get_size2::GetSize for DataclassParams {} impl get_size2::GetSize for DataclassFlags {}
impl Default for DataclassParams { impl Default for DataclassFlags {
fn default() -> Self { fn default() -> Self {
Self::INIT | Self::REPR | Self::EQ | Self::MATCH_ARGS Self::INIT | Self::REPR | Self::EQ | Self::MATCH_ARGS
} }
} }
impl From<DataclassTransformerParams> for DataclassParams { impl From<DataclassTransformerFlags> for DataclassFlags {
fn from(params: DataclassTransformerParams) -> Self { fn from(params: DataclassTransformerFlags) -> Self {
let mut result = Self::default(); let mut result = Self::default();
result.set( result.set(
Self::EQ, Self::EQ,
params.contains(DataclassTransformerParams::EQ_DEFAULT), params.contains(DataclassTransformerFlags::EQ_DEFAULT),
); );
result.set( result.set(
Self::ORDER, Self::ORDER,
params.contains(DataclassTransformerParams::ORDER_DEFAULT), params.contains(DataclassTransformerFlags::ORDER_DEFAULT),
); );
result.set( result.set(
Self::KW_ONLY, Self::KW_ONLY,
params.contains(DataclassTransformerParams::KW_ONLY_DEFAULT), params.contains(DataclassTransformerFlags::KW_ONLY_DEFAULT),
); );
result.set( result.set(
Self::FROZEN, Self::FROZEN,
params.contains(DataclassTransformerParams::FROZEN_DEFAULT), params.contains(DataclassTransformerFlags::FROZEN_DEFAULT),
);
result.set(
Self::NO_FIELD_SPECIFIERS,
!params.contains(DataclassTransformerParams::FIELD_SPECIFIERS),
); );
result result
} }
} }
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct DataclassParams<'db> {
flags: DataclassFlags,
field_specifiers: Type<'db>,
}
impl get_size2::GetSize for DataclassParams<'_> {}
impl<'db> DataclassParams<'db> {
fn default_params(db: &'db dyn Db) -> Self {
Self::from_flags(db, DataclassFlags::default())
}
fn from_flags(db: &'db dyn Db, flags: DataclassFlags) -> Self {
let dataclasses_field = known_module_symbol(db, KnownModule::Dataclasses, "field")
.place
.ignore_possibly_unbound()
.unwrap_or_else(|| Type::none(db));
Self::new(db, flags, dataclasses_field)
}
fn from_transformer_params(db: &'db dyn Db, params: DataclassTransformerParams<'db>) -> Self {
Self::new(
db,
DataclassFlags::from(params.flags(db)),
params.field_specifiers(db),
)
}
}
/// Representation of a type: a set of possible values at runtime. /// Representation of a type: a set of possible values at runtime.
/// ///
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
@ -719,9 +743,9 @@ pub enum Type<'db> {
/// A special callable that is returned by a `dataclass(…)` call. It is usually /// A special callable that is returned by a `dataclass(…)` call. It is usually
/// used as a decorator. Note that this is only used as a return type for actual /// used as a decorator. Note that this is only used as a return type for actual
/// `dataclass` calls, not for the argumentless `@dataclass` decorator. /// `dataclass` calls, not for the argumentless `@dataclass` decorator.
DataclassDecorator(DataclassParams), DataclassDecorator(DataclassParams<'db>),
/// A special callable that is returned by a `dataclass_transform(…)` call. /// A special callable that is returned by a `dataclass_transform(…)` call.
DataclassTransformer(DataclassTransformerParams), DataclassTransformer(DataclassTransformerParams<'db>),
/// The type of an arbitrary callable object with a certain specified signature. /// The type of an arbitrary callable object with a certain specified signature.
Callable(CallableType<'db>), Callable(CallableType<'db>),
/// A specific module object /// A specific module object

View File

@ -24,7 +24,8 @@ use crate::types::diagnostic::{
}; };
use crate::types::enums::is_enum_class; use crate::types::enums::is_enum_class;
use crate::types::function::{ use crate::types::function::{
DataclassTransformerParams, FunctionDecorators, FunctionType, KnownFunction, OverloadLiteral, DataclassTransformerFlags, DataclassTransformerParams, FunctionDecorators, FunctionType,
KnownFunction, OverloadLiteral,
}; };
use crate::types::generics::{ use crate::types::generics::{
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError, InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
@ -32,9 +33,9 @@ use crate::types::generics::{
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters}; use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
use crate::types::tuple::{TupleLength, TupleType}; use crate::types::tuple::{TupleLength, TupleType};
use crate::types::{ use crate::types::{
BoundMethodType, ClassLiteral, DataclassParams, FieldInstance, KnownBoundMethodType, BoundMethodType, ClassLiteral, DataclassFlags, DataclassParams, FieldInstance,
KnownClass, KnownInstanceType, MemberLookupPolicy, PropertyInstanceType, SpecialFormType, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy, PropertyInstanceType,
TrackedConstraintSet, TypeAliasType, TypeContext, UnionBuilder, UnionType, SpecialFormType, TrackedConstraintSet, TypeAliasType, TypeContext, UnionBuilder, UnionType,
WrapperDescriptorKind, enums, ide_support, infer_isolated_expression, todo_type, WrapperDescriptorKind, enums, ide_support, infer_isolated_expression, todo_type,
}; };
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity}; use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
@ -871,43 +872,45 @@ impl<'db> Bindings<'db> {
weakref_slot, weakref_slot,
] = overload.parameter_types() ] = overload.parameter_types()
{ {
let mut params = DataclassParams::empty(); let mut flags = DataclassFlags::empty();
if to_bool(init, true) { if to_bool(init, true) {
params |= DataclassParams::INIT; flags |= DataclassFlags::INIT;
} }
if to_bool(repr, true) { if to_bool(repr, true) {
params |= DataclassParams::REPR; flags |= DataclassFlags::REPR;
} }
if to_bool(eq, true) { if to_bool(eq, true) {
params |= DataclassParams::EQ; flags |= DataclassFlags::EQ;
} }
if to_bool(order, false) { if to_bool(order, false) {
params |= DataclassParams::ORDER; flags |= DataclassFlags::ORDER;
} }
if to_bool(unsafe_hash, false) { if to_bool(unsafe_hash, false) {
params |= DataclassParams::UNSAFE_HASH; flags |= DataclassFlags::UNSAFE_HASH;
} }
if to_bool(frozen, false) { if to_bool(frozen, false) {
params |= DataclassParams::FROZEN; flags |= DataclassFlags::FROZEN;
} }
if to_bool(match_args, true) { if to_bool(match_args, true) {
params |= DataclassParams::MATCH_ARGS; flags |= DataclassFlags::MATCH_ARGS;
} }
if to_bool(kw_only, false) { if to_bool(kw_only, false) {
if Program::get(db).python_version(db) >= PythonVersion::PY310 { if Program::get(db).python_version(db) >= PythonVersion::PY310 {
params |= DataclassParams::KW_ONLY; flags |= DataclassFlags::KW_ONLY;
} else { } else {
// TODO: emit diagnostic // TODO: emit diagnostic
} }
} }
if to_bool(slots, false) { if to_bool(slots, false) {
params |= DataclassParams::SLOTS; flags |= DataclassFlags::SLOTS;
} }
if to_bool(weakref_slot, false) { if to_bool(weakref_slot, false) {
params |= DataclassParams::WEAKREF_SLOT; flags |= DataclassFlags::WEAKREF_SLOT;
} }
let params = DataclassParams::from_flags(db, flags);
overload.set_return_type(Type::DataclassDecorator(params)); overload.set_return_type(Type::DataclassDecorator(params));
} }
@ -915,7 +918,7 @@ impl<'db> Bindings<'db> {
if let [Some(Type::ClassLiteral(class_literal))] = if let [Some(Type::ClassLiteral(class_literal))] =
overload.parameter_types() overload.parameter_types()
{ {
let params = DataclassParams::default(); let params = DataclassParams::default_params(db);
overload.set_return_type(Type::from(ClassLiteral::new( overload.set_return_type(Type::from(ClassLiteral::new(
db, db,
class_literal.name(db), class_literal.name(db),
@ -938,30 +941,26 @@ impl<'db> Bindings<'db> {
_kwargs, _kwargs,
] = overload.parameter_types() ] = overload.parameter_types()
{ {
let mut params = DataclassTransformerParams::empty(); let mut flags = DataclassTransformerFlags::empty();
if to_bool(eq_default, true) { if to_bool(eq_default, true) {
params |= DataclassTransformerParams::EQ_DEFAULT; flags |= DataclassTransformerFlags::EQ_DEFAULT;
} }
if to_bool(order_default, false) { if to_bool(order_default, false) {
params |= DataclassTransformerParams::ORDER_DEFAULT; flags |= DataclassTransformerFlags::ORDER_DEFAULT;
} }
if to_bool(kw_only_default, false) { if to_bool(kw_only_default, false) {
params |= DataclassTransformerParams::KW_ONLY_DEFAULT; flags |= DataclassTransformerFlags::KW_ONLY_DEFAULT;
} }
if to_bool(frozen_default, false) { if to_bool(frozen_default, false) {
params |= DataclassTransformerParams::FROZEN_DEFAULT; flags |= DataclassTransformerFlags::FROZEN_DEFAULT;
} }
if let Some(field_specifiers_type) = field_specifiers { let params = DataclassTransformerParams::new(
// For now, we'll do a simple check: if field_specifiers is not db,
// None/empty, we assume it might contain dataclasses.field flags,
// TODO: Implement proper parsing to check for field_specifiers.unwrap_or(Type::none(db)),
// dataclasses.field/Field specifically );
if !field_specifiers_type.is_none(db) {
params |= DataclassTransformerParams::FIELD_SPECIFIERS;
}
}
overload.set_return_type(Type::DataclassTransformer(params)); overload.set_return_type(Type::DataclassTransformer(params));
} }
@ -1026,36 +1025,41 @@ impl<'db> Bindings<'db> {
// the argument type and overwrite the corresponding flag in `dataclass_params` after // the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults. // constructing them from the `dataclass_transformer`-parameter defaults.
let mut dataclass_params = let dataclass_params =
DataclassParams::from(params); DataclassParams::from_transformer_params(
db, params,
);
let mut flags = dataclass_params.flags(db);
if let Ok(Some(Type::BooleanLiteral(order))) = if let Ok(Some(Type::BooleanLiteral(order))) =
overload.parameter_type_by_name("order") overload.parameter_type_by_name("order")
{ {
dataclass_params.set(DataclassParams::ORDER, order); flags.set(DataclassFlags::ORDER, order);
} }
if let Ok(Some(Type::BooleanLiteral(eq))) = if let Ok(Some(Type::BooleanLiteral(eq))) =
overload.parameter_type_by_name("eq") overload.parameter_type_by_name("eq")
{ {
dataclass_params.set(DataclassParams::EQ, eq); flags.set(DataclassFlags::EQ, eq);
} }
if let Ok(Some(Type::BooleanLiteral(kw_only))) = if let Ok(Some(Type::BooleanLiteral(kw_only))) =
overload.parameter_type_by_name("kw_only") overload.parameter_type_by_name("kw_only")
{ {
dataclass_params flags.set(DataclassFlags::KW_ONLY, kw_only);
.set(DataclassParams::KW_ONLY, kw_only);
} }
if let Ok(Some(Type::BooleanLiteral(frozen))) = if let Ok(Some(Type::BooleanLiteral(frozen))) =
overload.parameter_type_by_name("frozen") overload.parameter_type_by_name("frozen")
{ {
dataclass_params flags.set(DataclassFlags::FROZEN, frozen);
.set(DataclassParams::FROZEN, frozen);
} }
Type::DataclassDecorator(dataclass_params) Type::DataclassDecorator(DataclassParams::new(
db,
flags,
dataclass_params.field_specifiers(db),
))
}, },
) )
}) })

View File

@ -31,12 +31,12 @@ use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::typed_dict::typed_dict_params_from_class_def; use crate::types::typed_dict::typed_dict_params_from_class_def;
use crate::types::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_type}; use crate::types::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_type};
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DataclassParams, ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DataclassFlags,
DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext, MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, TypeContext, TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable,
determine_upper_bound, infer_definition_types, declaration_type, determine_upper_bound, infer_definition_types,
}; };
use crate::{ use crate::{
Db, FxIndexMap, FxIndexSet, FxOrderSet, Program, Db, FxIndexMap, FxIndexSet, FxOrderSet, Program,
@ -162,7 +162,7 @@ fn try_metaclass_cycle_recover<'db>(
_count: u32, _count: u32,
_self: ClassLiteral<'db>, _self: ClassLiteral<'db>,
) -> salsa::CycleRecoveryAction< ) -> salsa::CycleRecoveryAction<
Result<(Type<'db>, Option<DataclassTransformerParams>), MetaclassError<'db>>, Result<(Type<'db>, Option<DataclassTransformerParams<'db>>), MetaclassError<'db>>,
> { > {
salsa::CycleRecoveryAction::Iterate salsa::CycleRecoveryAction::Iterate
} }
@ -171,7 +171,7 @@ fn try_metaclass_cycle_recover<'db>(
fn try_metaclass_cycle_initial<'db>( fn try_metaclass_cycle_initial<'db>(
_db: &'db dyn Db, _db: &'db dyn Db,
_self_: ClassLiteral<'db>, _self_: ClassLiteral<'db>,
) -> Result<(Type<'db>, Option<DataclassTransformerParams>), MetaclassError<'db>> { ) -> Result<(Type<'db>, Option<DataclassTransformerParams<'db>>), MetaclassError<'db>> {
Err(MetaclassError { Err(MetaclassError {
kind: MetaclassErrorKind::Cycle, kind: MetaclassErrorKind::Cycle,
}) })
@ -179,17 +179,17 @@ fn try_metaclass_cycle_initial<'db>(
/// A category of classes with code generation capabilities (with synthesized methods). /// A category of classes with code generation capabilities (with synthesized methods).
#[derive(Clone, Copy, Debug, PartialEq, salsa::Update, get_size2::GetSize)] #[derive(Clone, Copy, Debug, PartialEq, salsa::Update, get_size2::GetSize)]
pub(crate) enum CodeGeneratorKind { pub(crate) enum CodeGeneratorKind<'db> {
/// Classes decorated with `@dataclass` or similar dataclass-like decorators /// Classes decorated with `@dataclass` or similar dataclass-like decorators
DataclassLike(Option<DataclassTransformerParams>), DataclassLike(Option<DataclassTransformerParams<'db>>),
/// Classes inheriting from `typing.NamedTuple` /// Classes inheriting from `typing.NamedTuple`
NamedTuple, NamedTuple,
/// Classes inheriting from `typing.TypedDict` /// Classes inheriting from `typing.TypedDict`
TypedDict, TypedDict,
} }
impl CodeGeneratorKind { impl<'db> CodeGeneratorKind<'db> {
pub(crate) fn from_class(db: &dyn Db, class: ClassLiteral<'_>) -> Option<Self> { pub(crate) fn from_class(db: &'db dyn Db, class: ClassLiteral<'db>) -> Option<Self> {
#[salsa::tracked( #[salsa::tracked(
cycle_fn=code_generator_of_class_recover, cycle_fn=code_generator_of_class_recover,
cycle_initial=code_generator_of_class_initial, cycle_initial=code_generator_of_class_initial,
@ -198,7 +198,7 @@ impl CodeGeneratorKind {
fn code_generator_of_class<'db>( fn code_generator_of_class<'db>(
db: &'db dyn Db, db: &'db dyn Db,
class: ClassLiteral<'db>, class: ClassLiteral<'db>,
) -> Option<CodeGeneratorKind> { ) -> Option<CodeGeneratorKind<'db>> {
if class.dataclass_params(db).is_some() { if class.dataclass_params(db).is_some() {
Some(CodeGeneratorKind::DataclassLike(None)) Some(CodeGeneratorKind::DataclassLike(None))
} else if let Ok((_, Some(transformer_params))) = class.try_metaclass(db) { } else if let Ok((_, Some(transformer_params))) = class.try_metaclass(db) {
@ -215,27 +215,27 @@ impl CodeGeneratorKind {
} }
} }
fn code_generator_of_class_initial( fn code_generator_of_class_initial<'db>(
_db: &dyn Db, _db: &'db dyn Db,
_class: ClassLiteral<'_>, _class: ClassLiteral<'db>,
) -> Option<CodeGeneratorKind> { ) -> Option<CodeGeneratorKind<'db>> {
None None
} }
#[expect(clippy::ref_option, clippy::trivially_copy_pass_by_ref)] #[expect(clippy::ref_option)]
fn code_generator_of_class_recover( fn code_generator_of_class_recover<'db>(
_db: &dyn Db, _db: &'db dyn Db,
_value: &Option<CodeGeneratorKind>, _value: &Option<CodeGeneratorKind<'db>>,
_count: u32, _count: u32,
_class: ClassLiteral<'_>, _class: ClassLiteral<'db>,
) -> salsa::CycleRecoveryAction<Option<CodeGeneratorKind>> { ) -> salsa::CycleRecoveryAction<Option<CodeGeneratorKind<'db>>> {
salsa::CycleRecoveryAction::Iterate salsa::CycleRecoveryAction::Iterate
} }
code_generator_of_class(db, class) code_generator_of_class(db, class)
} }
pub(super) fn matches(self, db: &dyn Db, class: ClassLiteral<'_>) -> bool { pub(super) fn matches(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
matches!( matches!(
(CodeGeneratorKind::from_class(db, class), self), (CodeGeneratorKind::from_class(db, class), self),
(Some(Self::DataclassLike(_)), Self::DataclassLike(_)) (Some(Self::DataclassLike(_)), Self::DataclassLike(_))
@ -1384,8 +1384,8 @@ pub struct ClassLiteral<'db> {
/// If this class is deprecated, this holds the deprecation message. /// If this class is deprecated, this holds the deprecation message.
pub(crate) deprecated: Option<DeprecatedInstance<'db>>, pub(crate) deprecated: Option<DeprecatedInstance<'db>>,
pub(crate) dataclass_params: Option<DataclassParams>, pub(crate) dataclass_params: Option<DataclassParams<'db>>,
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>, pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams<'db>>,
} }
// The Salsa heap is tracked separately. // The Salsa heap is tracked separately.
@ -1906,7 +1906,7 @@ impl<'db> ClassLiteral<'db> {
pub(super) fn try_metaclass( pub(super) fn try_metaclass(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
) -> Result<(Type<'db>, Option<DataclassTransformerParams>), MetaclassError<'db>> { ) -> Result<(Type<'db>, Option<DataclassTransformerParams<'db>>), MetaclassError<'db>> {
tracing::trace!("ClassLiteral::try_metaclass: {}", self.name(db)); tracing::trace!("ClassLiteral::try_metaclass: {}", self.name(db));
// Identify the class's own metaclass (or take the first base class's metaclass). // Identify the class's own metaclass (or take the first base class's metaclass).
@ -2268,14 +2268,17 @@ impl<'db> ClassLiteral<'db> {
let transformer_params = let transformer_params =
if let CodeGeneratorKind::DataclassLike(Some(transformer_params)) = field_policy { if let CodeGeneratorKind::DataclassLike(Some(transformer_params)) = field_policy {
Some(DataclassParams::from(transformer_params)) Some(DataclassParams::from_transformer_params(
db,
transformer_params,
))
} else { } else {
None None
}; };
let has_dataclass_param = |param| { let has_dataclass_param = |param| {
dataclass_params.is_some_and(|params| params.contains(param)) dataclass_params.is_some_and(|params| params.flags(db).contains(param))
|| transformer_params.is_some_and(|params| params.contains(param)) || transformer_params.is_some_and(|params| params.flags(db).contains(param))
}; };
let instance_ty = let instance_ty =
@ -2353,7 +2356,7 @@ impl<'db> ClassLiteral<'db> {
} }
let is_kw_only = name == "__replace__" let is_kw_only = name == "__replace__"
|| kw_only.unwrap_or(has_dataclass_param(DataclassParams::KW_ONLY)); || kw_only.unwrap_or(has_dataclass_param(DataclassFlags::KW_ONLY));
let mut parameter = if is_kw_only { let mut parameter = if is_kw_only {
Parameter::keyword_only(field_name) Parameter::keyword_only(field_name)
@ -2391,7 +2394,7 @@ impl<'db> ClassLiteral<'db> {
match (field_policy, name) { match (field_policy, name) {
(CodeGeneratorKind::DataclassLike(_), "__init__") => { (CodeGeneratorKind::DataclassLike(_), "__init__") => {
if !has_dataclass_param(DataclassParams::INIT) { if !has_dataclass_param(DataclassFlags::INIT) {
return None; return None;
} }
@ -2406,7 +2409,7 @@ impl<'db> ClassLiteral<'db> {
signature_from_fields(vec![cls_parameter], Some(Type::none(db))) signature_from_fields(vec![cls_parameter], Some(Type::none(db)))
} }
(CodeGeneratorKind::DataclassLike(_), "__lt__" | "__le__" | "__gt__" | "__ge__") => { (CodeGeneratorKind::DataclassLike(_), "__lt__" | "__le__" | "__gt__" | "__ge__") => {
if !has_dataclass_param(DataclassParams::ORDER) { if !has_dataclass_param(DataclassFlags::ORDER) {
return None; return None;
} }
@ -2457,7 +2460,7 @@ impl<'db> ClassLiteral<'db> {
signature_from_fields(vec![self_parameter], Some(instance_ty)) signature_from_fields(vec![self_parameter], Some(instance_ty))
} }
(CodeGeneratorKind::DataclassLike(_), "__setattr__") => { (CodeGeneratorKind::DataclassLike(_), "__setattr__") => {
if has_dataclass_param(DataclassParams::FROZEN) { if has_dataclass_param(DataclassFlags::FROZEN) {
let signature = Signature::new( let signature = Signature::new(
Parameters::new([ Parameters::new([
Parameter::positional_or_keyword(Name::new_static("self")) Parameter::positional_or_keyword(Name::new_static("self"))
@ -2473,7 +2476,7 @@ impl<'db> ClassLiteral<'db> {
None None
} }
(CodeGeneratorKind::DataclassLike(_), "__slots__") => { (CodeGeneratorKind::DataclassLike(_), "__slots__") => {
has_dataclass_param(DataclassParams::SLOTS).then(|| { has_dataclass_param(DataclassFlags::SLOTS).then(|| {
let fields = self.fields(db, specialization, field_policy); let fields = self.fields(db, specialization, field_policy);
let slots = fields.keys().map(|name| Type::string_literal(db, name)); let slots = fields.keys().map(|name| Type::string_literal(db, name));
Type::heterogeneous_tuple(db, slots) Type::heterogeneous_tuple(db, slots)
@ -2897,7 +2900,7 @@ impl<'db> ClassLiteral<'db> {
default_ty = Some(field.default_type(db)); default_ty = Some(field.default_type(db));
if self if self
.dataclass_params(db) .dataclass_params(db)
.map(|params| params.contains(DataclassParams::NO_FIELD_SPECIFIERS)) .map(|params| params.field_specifiers(db).is_none(db))
.unwrap_or(false) .unwrap_or(false)
{ {
// This happens when constructing a `dataclass` with a `dataclass_transform` // This happens when constructing a `dataclass` with a `dataclass_transform`
@ -3621,7 +3624,7 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
let is_frozen_dataclass = Program::get(db).python_version(db) <= PythonVersion::PY312 let is_frozen_dataclass = Program::get(db).python_version(db) <= PythonVersion::PY312
&& self && self
.dataclass_params(db) .dataclass_params(db)
.is_some_and(|params| params.contains(DataclassParams::FROZEN)); .is_some_and(|params| params.flags(db).contains(DataclassFlags::FROZEN));
if is_namedtuple || is_frozen_dataclass { if is_namedtuple || is_frozen_dataclass {
TypeVarVariance::Covariant TypeVarVariance::Covariant
} else { } else {

View File

@ -152,24 +152,32 @@ bitflags! {
/// arguments that were passed in. For the precise meaning of the fields, see [1]. /// arguments that were passed in. For the precise meaning of the fields, see [1].
/// ///
/// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform /// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, salsa::Update)]
pub struct DataclassTransformerParams: u8 { pub struct DataclassTransformerFlags: u8 {
const EQ_DEFAULT = 1 << 0; const EQ_DEFAULT = 1 << 0;
const ORDER_DEFAULT = 1 << 1; const ORDER_DEFAULT = 1 << 1;
const KW_ONLY_DEFAULT = 1 << 2; const KW_ONLY_DEFAULT = 1 << 2;
const FROZEN_DEFAULT = 1 << 3; const FROZEN_DEFAULT = 1 << 3;
const FIELD_SPECIFIERS= 1 << 4;
} }
} }
impl get_size2::GetSize for DataclassTransformerParams {} impl get_size2::GetSize for DataclassTransformerFlags {}
impl Default for DataclassTransformerParams { impl Default for DataclassTransformerFlags {
fn default() -> Self { fn default() -> Self {
Self::EQ_DEFAULT Self::EQ_DEFAULT
} }
} }
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct DataclassTransformerParams<'db> {
pub flags: DataclassTransformerFlags,
pub field_specifiers: Type<'db>,
}
impl get_size2::GetSize for DataclassTransformerParams<'_> {}
/// Representation of a function definition in the AST: either a non-generic function, or a generic /// Representation of a function definition in the AST: either a non-generic function, or a generic
/// function that has not been specialized. /// function that has not been specialized.
/// ///
@ -201,7 +209,7 @@ pub struct OverloadLiteral<'db> {
/// The arguments to `dataclass_transformer`, if this function was annotated /// The arguments to `dataclass_transformer`, if this function was annotated
/// with `@dataclass_transformer(...)`. /// with `@dataclass_transformer(...)`.
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>, pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams<'db>>,
} }
// The Salsa heap is tracked separately. // The Salsa heap is tracked separately.
@ -212,7 +220,7 @@ impl<'db> OverloadLiteral<'db> {
fn with_dataclass_transformer_params( fn with_dataclass_transformer_params(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
params: DataclassTransformerParams, params: DataclassTransformerParams<'db>,
) -> Self { ) -> Self {
Self::new( Self::new(
db, db,
@ -740,7 +748,7 @@ impl<'db> FunctionType<'db> {
pub(crate) fn with_dataclass_transformer_params( pub(crate) fn with_dataclass_transformer_params(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
params: DataclassTransformerParams, params: DataclassTransformerParams<'db>,
) -> Self { ) -> Self {
// A decorator only applies to the specific overload that it is attached to, not to all // A decorator only applies to the specific overload that it is attached to, not to all
// previous overloads. // previous overloads.

View File

@ -2567,7 +2567,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.as_function_literal() .as_function_literal()
.is_some_and(|function| function.is_known(self.db(), KnownFunction::Dataclass)) .is_some_and(|function| function.is_known(self.db(), KnownFunction::Dataclass))
{ {
dataclass_params = Some(DataclassParams::default()); dataclass_params = Some(DataclassParams::default_params(self.db()));
continue; continue;
} }
@ -2588,11 +2588,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// overload, or an overload and the implementation both. Nevertheless, this is not // overload, or an overload and the implementation both. Nevertheless, this is not
// allowed. We do not try to treat the offenders intelligently -- just use the // allowed. We do not try to treat the offenders intelligently -- just use the
// params of the last seen usage of `@dataclass_transform` // params of the last seen usage of `@dataclass_transform`
let params = f let transformer_params = f
.iter_overloads_and_implementation(self.db()) .iter_overloads_and_implementation(self.db())
.find_map(|overload| overload.dataclass_transformer_params(self.db())); .find_map(|overload| overload.dataclass_transformer_params(self.db()));
if let Some(params) = params { if let Some(transformer_params) = transformer_params {
dataclass_params = Some(params.into()); dataclass_params = Some(DataclassParams::from_transformer_params(
self.db(),
transformer_params,
));
continue; continue;
} }
} }

View File

@ -83,15 +83,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::WrapperDescriptor(_), _) => Ordering::Less, (Type::WrapperDescriptor(_), _) => Ordering::Less,
(_, Type::WrapperDescriptor(_)) => Ordering::Greater, (_, Type::WrapperDescriptor(_)) => Ordering::Greater,
(Type::DataclassDecorator(left), Type::DataclassDecorator(right)) => { (Type::DataclassDecorator(left), Type::DataclassDecorator(right)) => left.cmp(right),
left.bits().cmp(&right.bits())
}
(Type::DataclassDecorator(_), _) => Ordering::Less, (Type::DataclassDecorator(_), _) => Ordering::Less,
(_, Type::DataclassDecorator(_)) => Ordering::Greater, (_, Type::DataclassDecorator(_)) => Ordering::Greater,
(Type::DataclassTransformer(left), Type::DataclassTransformer(right)) => { (Type::DataclassTransformer(left), Type::DataclassTransformer(right)) => left.cmp(right),
left.bits().cmp(&right.bits())
}
(Type::DataclassTransformer(_), _) => Ordering::Less, (Type::DataclassTransformer(_), _) => Ordering::Less,
(_, Type::DataclassTransformer(_)) => Ordering::Greater, (_, Type::DataclassTransformer(_)) => Ordering::Greater,