File tree Expand file tree Collapse file tree 5 files changed +77
-3
lines changed
crates/ty_python_semantic
resources/mdtest/dataclasses Expand file tree Collapse file tree 5 files changed +77
-3
lines changed Original file line number Diff line number Diff line change @@ -84,3 +84,47 @@ reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]]
84
84
reveal_type(field(default = None )) # revealed: dataclasses.Field[None]
85
85
reveal_type(field(default_factory = get_default)) # revealed: dataclasses.Field[str]
86
86
```
87
+
88
+ ## dataclass_transform field_specifiers
89
+
90
+ If ` field_specifiers ` is not specified, it defaults to an empty tuple, meaning no field specifiers
91
+ are supported and ` dataclasses.field ` and ` dataclasses.Field ` should not be accepted by default.
92
+
93
+ ``` py
94
+ from typing_extensions import dataclass_transform
95
+ from dataclasses import field, dataclass
96
+ from typing import TypeVar
97
+
98
+ T = TypeVar(" T" )
99
+
100
+ @dataclass_transform ()
101
+ def create_model (* , init : bool = True ):
102
+ def deco (cls : type[T]) -> type[T]:
103
+ return cls
104
+ return deco
105
+
106
+ @create_model ()
107
+ class A :
108
+ name: str = field(init = False )
109
+
110
+ # field(init=False) should be ignored for dataclass_transform without explicit field_specifiers
111
+ reveal_type(A.__init__ ) # revealed: (self: A, name: str = Unknown) -> None
112
+
113
+ @dataclass
114
+ class B :
115
+ name: str = field(init = False )
116
+
117
+ # Regular @dataclass should respect field(init=False)
118
+ reveal_type(B.__init__ ) # revealed: (self: B) -> None
119
+ ```
120
+
121
+ Test constructor calls:
122
+
123
+ ``` py
124
+ # This should NOT error because field(init=False) is ignored for A
125
+ A(name = " foo" )
126
+
127
+ # This should error because field(init=False) is respected for B
128
+ # error: [unknown-argument]
129
+ B(name = " foo" )
130
+ ```
Original file line number Diff line number Diff line change @@ -510,6 +510,10 @@ bitflags! {
510
510
const KW_ONLY = 0b0000_1000_0000 ;
511
511
const SLOTS = 0b0001_0000_0000 ;
512
512
const WEAKREF_SLOT = 0b0010_0000_0000 ;
513
+ // This is not an actual argument from `dataclass(...)` but a flag signaling that no
514
+ // `field_specifiers` was specified for the `dataclass_transform`, see [1].
515
+ // [1]: https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-transform-parameters
516
+ const NO_FIELD_SPECIFIERS = 0b0100_0000_0000 ;
513
517
}
514
518
}
515
519
@@ -542,6 +546,11 @@ impl From<DataclassTransformerParams> for DataclassParams {
542
546
params. contains ( DataclassTransformerParams :: FROZEN_DEFAULT ) ,
543
547
) ;
544
548
549
+ result. set (
550
+ Self :: NO_FIELD_SPECIFIERS ,
551
+ !params. contains ( DataclassTransformerParams :: FIELD_SPECIFIERS ) ,
552
+ ) ;
553
+
545
554
result
546
555
}
547
556
}
Original file line number Diff line number Diff line change @@ -900,7 +900,7 @@ impl<'db> Bindings<'db> {
900
900
order_default,
901
901
kw_only_default,
902
902
frozen_default,
903
- _field_specifiers ,
903
+ field_specifiers ,
904
904
_kwargs,
905
905
] = overload. parameter_types ( )
906
906
{
@@ -919,6 +919,16 @@ impl<'db> Bindings<'db> {
919
919
params |= DataclassTransformerParams :: FROZEN_DEFAULT ;
920
920
}
921
921
922
+ if let Some ( field_specifiers_type) = field_specifiers {
923
+ // For now, we'll do a simple check: if field_specifiers is not
924
+ // None/empty, we assume it might contain dataclasses.field
925
+ // TODO: Implement proper parsing to check for
926
+ // dataclasses.field/Field specifically
927
+ if !field_specifiers_type. is_none ( db) {
928
+ params |= DataclassTransformerParams :: FIELD_SPECIFIERS ;
929
+ }
930
+ }
931
+
922
932
overload. set_return_type ( Type :: DataclassTransformer ( params) ) ;
923
933
}
924
934
}
Original file line number Diff line number Diff line change @@ -2407,8 +2407,18 @@ impl<'db> ClassLiteral<'db> {
2407
2407
let mut kw_only = None ;
2408
2408
if let Some ( Type :: KnownInstance ( KnownInstanceType :: Field ( field) ) ) = default_ty {
2409
2409
default_ty = Some ( field. default_type ( db) ) ;
2410
- init = field. init ( db) ;
2411
- kw_only = field. kw_only ( db) ;
2410
+ if self
2411
+ . dataclass_params ( db)
2412
+ . map ( |params| params. contains ( DataclassParams :: NO_FIELD_SPECIFIERS ) )
2413
+ . unwrap_or ( false )
2414
+ {
2415
+ // This happens when constructing a `dataclass` with a `dataclass_transform`
2416
+ // without defining the `field_specifiers`, meaning it should ignore
2417
+ // `dataclasses.field` and `dataclasses.Field`.
2418
+ } else {
2419
+ init = field. init ( db) ;
2420
+ kw_only = field. kw_only ( db) ;
2421
+ }
2412
2422
}
2413
2423
2414
2424
let mut field = Field {
Original file line number Diff line number Diff line change @@ -164,6 +164,7 @@ bitflags! {
164
164
const ORDER_DEFAULT = 1 << 1 ;
165
165
const KW_ONLY_DEFAULT = 1 << 2 ;
166
166
const FROZEN_DEFAULT = 1 << 3 ;
167
+ const FIELD_SPECIFIERS = 1 << 4 ;
167
168
}
168
169
}
169
170
You can’t perform that action at this time.
0 commit comments