@@ -39,6 +39,7 @@ def __init__(
39
39
column : int ,
40
40
type : Optional [Type ],
41
41
info : TypeInfo ,
42
+ kw_only : bool ,
42
43
) -> None :
43
44
self .name = name
44
45
self .is_in_init = is_in_init
@@ -48,6 +49,7 @@ def __init__(
48
49
self .column = column
49
50
self .type = type
50
51
self .info = info
52
+ self .kw_only = kw_only
51
53
52
54
def to_argument (self ) -> Argument :
53
55
return Argument (
@@ -77,6 +79,8 @@ def deserialize(
77
79
cls , info : TypeInfo , data : JsonDict , api : SemanticAnalyzerPluginInterface
78
80
) -> 'DataclassAttribute' :
79
81
data = data .copy ()
82
+ if data .get ('kw_only' ) is None :
83
+ data ['kw_only' ] = False
80
84
typ = deserialize_and_fixup_type (data .pop ('type' ), api )
81
85
return cls (type = typ , info = info , ** data )
82
86
@@ -215,6 +219,7 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]:
215
219
cls = self ._ctx .cls
216
220
attrs : List [DataclassAttribute ] = []
217
221
known_attrs : Set [str ] = set ()
222
+ kw_only = _get_decorator_bool_argument (ctx , 'kw_only' , False )
218
223
for stmt in cls .defs .body :
219
224
# Any assignment that doesn't use the new type declaration
220
225
# syntax can be ignored out of hand.
@@ -251,6 +256,10 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]:
251
256
is_init_var = True
252
257
node .type = node_type .args [0 ]
253
258
259
+ if (isinstance (node_type , Instance ) and
260
+ node_type .type .fullname == 'dataclasses._KW_ONLY_TYPE' ):
261
+ kw_only = True
262
+
254
263
has_field_call , field_args = _collect_field_args (stmt .rvalue )
255
264
256
265
is_in_init_param = field_args .get ('init' )
@@ -274,6 +283,13 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]:
274
283
# on self in the generated __init__(), not in the class body.
275
284
sym .implicit = True
276
285
286
+ is_kw_only = kw_only
287
+ # Use the kw_only field arg if it is provided. Otherwise use the
288
+ # kw_only value from the decorator parameter.
289
+ field_kw_only_param = field_args .get ('kw_only' )
290
+ if field_kw_only_param is not None :
291
+ is_kw_only = bool (ctx .api .parse_bool (field_kw_only_param ))
292
+
277
293
known_attrs .add (lhs .name )
278
294
attrs .append (DataclassAttribute (
279
295
name = lhs .name ,
@@ -284,6 +300,7 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]:
284
300
column = stmt .column ,
285
301
type = sym .type ,
286
302
info = cls .info ,
303
+ kw_only = is_kw_only ,
287
304
))
288
305
289
306
# Next, collect attributes belonging to any class in the MRO
@@ -323,10 +340,10 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]:
323
340
# arguments that have a default.
324
341
found_default = False
325
342
for attr in all_attrs :
326
- # If we find any attribute that is_in_init but that
343
+ # If we find any attribute that is_in_init, not kw_only, and that
327
344
# doesn't have a default after one that does have one,
328
345
# then that's an error.
329
- if found_default and attr .is_in_init and not attr .has_default :
346
+ if found_default and attr .is_in_init and not attr .has_default and not attr . kw_only :
330
347
# If the issue comes from merging different classes, report it
331
348
# at the class definition point.
332
349
context = (Context (line = attr .line , column = attr .column ) if attr in attrs
0 commit comments