Skip to content

Commit 528afe2

Browse files
committed
[ty] Dataclass transform: complete set of parameters
1 parent 29acc1e commit 528afe2

File tree

4 files changed

+111
-40
lines changed

4 files changed

+111
-40
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,82 @@ m.name = "new" # No error
455455
reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
456456
```
457457

458+
## Other `dataclass` parameters
459+
460+
Other parameters from normal dataclasses can also be set on models created using
461+
`dataclass_transform`.
462+
463+
### Using function-based transformers
464+
465+
```py
466+
from typing_extensions import dataclass_transform, TypeVar, Callable
467+
468+
T = TypeVar("T", bound=type)
469+
470+
@dataclass_transform()
471+
def fancy_model(*, slots: bool = False) -> Callable[[T], T]:
472+
raise NotImplementedError
473+
474+
@fancy_model()
475+
class NoSlots:
476+
name: str
477+
478+
NoSlots.__slots__ # error: [unresolved-attribute]
479+
480+
@fancy_model(slots=True)
481+
class WithSlots:
482+
name: str
483+
484+
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
485+
```
486+
487+
### Using metaclass-based transformers
488+
489+
```py
490+
from typing_extensions import dataclass_transform
491+
492+
@dataclass_transform()
493+
class FancyMeta(type):
494+
def __new__(cls, name, bases, namespace, *, slots: bool = False):
495+
...
496+
return super().__new__(cls, name, bases, namespace)
497+
498+
class FancyBase(metaclass=FancyMeta): ...
499+
500+
class NoSlots(FancyBase):
501+
name: str
502+
503+
# error: [unresolved-attribute]
504+
NoSlots.__slots__
505+
506+
class WithSlots(FancyBase, slots=True):
507+
name: str
508+
509+
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
510+
```
511+
512+
### Using base-class-based transformers
513+
514+
```py
515+
from typing_extensions import dataclass_transform
516+
517+
@dataclass_transform()
518+
class FancyBase:
519+
def __init_subclass__(cls, *, slots: bool = False):
520+
...
521+
super().__init_subclass__()
522+
523+
class NoSlots(FancyBase):
524+
name: str
525+
526+
NoSlots.__slots__ # error: [unresolved-attribute]
527+
528+
class WithSlots(FancyBase, slots=True):
529+
name: str
530+
531+
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
532+
```
533+
458534
## `field_specifiers`
459535

460536
The `field_specifiers` argument can be used to specify a list of functions that should be treated

crates/ty_python_semantic/src/types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,19 @@ bitflags! {
622622
}
623623
}
624624

625+
pub(crate) const DATACLASS_FLAGS: &[(&str, DataclassFlags)] = &[
626+
("init", DataclassFlags::INIT),
627+
("repr", DataclassFlags::REPR),
628+
("eq", DataclassFlags::EQ),
629+
("order", DataclassFlags::ORDER),
630+
("unsafe_hash", DataclassFlags::UNSAFE_HASH),
631+
("frozen", DataclassFlags::FROZEN),
632+
("match_args", DataclassFlags::MATCH_ARGS),
633+
("kw_only", DataclassFlags::KW_ONLY),
634+
("slots", DataclassFlags::SLOTS),
635+
("weakref_slot", DataclassFlags::WEAKREF_SLOT),
636+
];
637+
625638
impl get_size2::GetSize for DataclassFlags {}
626639

627640
impl Default for DataclassFlags {

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ use crate::types::generics::{
3535
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
3636
use crate::types::tuple::{TupleLength, TupleType};
3737
use crate::types::{
38-
BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DataclassFlags, DataclassParams,
39-
FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy,
40-
NominalInstanceType, PropertyInstanceType, SpecialFormType, TrackedConstraintSet,
41-
TypeAliasType, TypeContext, TypeVarVariance, UnionBuilder, UnionType, WrapperDescriptorKind,
42-
enums, ide_support, infer_isolated_expression, todo_type,
38+
BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DATACLASS_FLAGS, DataclassFlags,
39+
DataclassParams, FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType,
40+
MemberLookupPolicy, NominalInstanceType, PropertyInstanceType, SpecialFormType,
41+
TrackedConstraintSet, TypeAliasType, TypeContext, TypeVarVariance, UnionBuilder, UnionType,
42+
WrapperDescriptorKind, enums, ide_support, infer_isolated_expression, todo_type,
4343
};
4444
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
4545
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
@@ -1134,28 +1134,12 @@ impl<'db> Bindings<'db> {
11341134
);
11351135
let mut flags = dataclass_params.flags(db);
11361136

1137-
if let Ok(Some(Type::BooleanLiteral(order))) =
1138-
overload.parameter_type_by_name("order", false)
1139-
{
1140-
flags.set(DataclassFlags::ORDER, order);
1141-
}
1142-
1143-
if let Ok(Some(Type::BooleanLiteral(eq))) =
1144-
overload.parameter_type_by_name("eq", false)
1145-
{
1146-
flags.set(DataclassFlags::EQ, eq);
1147-
}
1148-
1149-
if let Ok(Some(Type::BooleanLiteral(kw_only))) =
1150-
overload.parameter_type_by_name("kw_only", false)
1151-
{
1152-
flags.set(DataclassFlags::KW_ONLY, kw_only);
1153-
}
1154-
1155-
if let Ok(Some(Type::BooleanLiteral(frozen))) =
1156-
overload.parameter_type_by_name("frozen", false)
1157-
{
1158-
flags.set(DataclassFlags::FROZEN, frozen);
1137+
for (param, flag) in DATACLASS_FLAGS {
1138+
if let Ok(Some(Type::BooleanLiteral(value))) =
1139+
overload.parameter_type_by_name(param, false)
1140+
{
1141+
flags.set(*flag, value);
1142+
}
11591143
}
11601144

11611145
Type::DataclassDecorator(DataclassParams::new(

crates/ty_python_semantic/src/types/class.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ use crate::types::tuple::{TupleSpec, TupleType};
3232
use crate::types::typed_dict::typed_dict_params_from_class_def;
3333
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
3434
use crate::types::{
35-
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DataclassFlags,
36-
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
37-
IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
38-
MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
39-
TypeContext, TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable,
40-
declaration_type, determine_upper_bound, exceeds_max_specialization_depth,
41-
infer_definition_types,
35+
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DATACLASS_FLAGS,
36+
DataclassFlags, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor,
37+
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType,
38+
ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType,
39+
StringLiteralType, TypeAliasType, TypeContext, TypeMapping, TypeRelation, TypedDictParams,
40+
UnionBuilder, VarianceInferable, declaration_type, determine_upper_bound,
41+
exceeds_max_specialization_depth, infer_definition_types,
4242
};
4343
use crate::{
4444
Db, FxIndexMap, FxIndexSet, FxOrderSet, Program,
@@ -2229,12 +2229,10 @@ impl<'db> ClassLiteral<'db> {
22292229
if let Some(is_set) =
22302230
keyword.value.as_boolean_literal_expr().map(|b| b.value)
22312231
{
2232-
match arg_name.as_str() {
2233-
"eq" => flags.set(DataclassFlags::EQ, is_set),
2234-
"order" => flags.set(DataclassFlags::ORDER, is_set),
2235-
"kw_only" => flags.set(DataclassFlags::KW_ONLY, is_set),
2236-
"frozen" => flags.set(DataclassFlags::FROZEN, is_set),
2237-
_ => {}
2232+
for (flag_name, flag) in DATACLASS_FLAGS {
2233+
if arg_name.as_str() == *flag_name {
2234+
flags.set(*flag, is_set);
2235+
}
22382236
}
22392237
}
22402238
}

0 commit comments

Comments
 (0)