Skip to content

Commit cb2d7c7

Browse files
Reject stdlib dataclass on Enum subclasses
Enum classes cannot be turned into dataclasses at runtime. Emit BadClassDefinition for @DataClass on Enum and for dataclass(EnumCls) calls; add test_enum_dataclass_rejected. Fixes #2922 Made-with: Cursor
1 parent 9e083ae commit cb2d7c7

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

pyrefly/lib/alt/call.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,44 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
10251025
format!("Cannot call abstract method `{method_name}`"),
10261026
);
10271027
}
1028+
if let Some(meta) = metadata
1029+
&& meta.kind == FunctionKind::Dataclass
1030+
&& let Some(first_ty) = self.first_arg_type(args, errors)
1031+
{
1032+
let first_arg_range = args
1033+
.first()
1034+
.map(|a| a.range())
1035+
.unwrap_or(arguments_range);
1036+
self.map_over_union(&first_ty, |ty| {
1037+
if let Some((_, inner)) = self.unwrap_class_object_silently(ty) {
1038+
if let Type::ClassType(ct) = inner
1039+
&& self.get_metadata_for_class(ct.class_object()).is_enum()
1040+
{
1041+
self.error(
1042+
errors,
1043+
first_arg_range,
1044+
ErrorInfo::Kind(ErrorKind::BadClassDefinition),
1045+
format!(
1046+
"Cannot apply dataclasses.dataclass to Enum class `{}`",
1047+
ct.class_object().name()
1048+
),
1049+
);
1050+
}
1051+
} else if let Type::ClassType(ct) = ty
1052+
&& self.get_metadata_for_class(ct.class_object()).is_enum()
1053+
{
1054+
self.error(
1055+
errors,
1056+
first_arg_range,
1057+
ErrorInfo::Kind(ErrorKind::BadClassDefinition),
1058+
format!(
1059+
"Cannot apply dataclasses.dataclass to Enum class `{}`",
1060+
ct.class_object().name()
1061+
),
1062+
);
1063+
}
1064+
});
1065+
}
10281066
// Does this call target correspond to a function whose keyword arguments we should save?
10291067
let kw_metadata = {
10301068
if let Some(m) = metadata

pyrefly/lib/alt/class/class_metadata.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,14 +371,32 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
371371
let is_attrs_class =
372372
self.is_attrs_class(&dataclass_from_dataclass_transform, &bases_with_metadata);
373373
let is_from_dataclass_transform = dataclass_from_dataclass_transform.is_some();
374-
let dataclass_metadata = self.dataclass_metadata(
374+
let mut dataclass_metadata = self.dataclass_metadata(
375375
cls,
376376
&decorators,
377377
&bases_with_metadata,
378378
dataclass_from_dataclass_transform,
379379
pydantic_config.as_ref(),
380380
is_attrs_class,
381381
);
382+
let has_dataclass_decorator = decorators.iter().any(|(decorator, _)| {
383+
matches!(
384+
decorator.ty.callee_kind(),
385+
Some(CalleeKind::Function(FunctionKind::Dataclass))
386+
) || matches!(&decorator.ty, Type::KwCall(call) if call.has_function_kind(FunctionKind::Dataclass))
387+
});
388+
if enum_metadata.is_some() && dataclass_metadata.is_some() && has_dataclass_decorator {
389+
self.error(
390+
errors,
391+
cls.range(),
392+
ErrorInfo::Kind(ErrorKind::BadClassDefinition),
393+
format!(
394+
"Cannot apply @dataclass to Enum class `{}`",
395+
cls.name()
396+
),
397+
);
398+
dataclass_metadata = None;
399+
}
382400
if let Some(dm) = dataclass_metadata.as_ref()
383401
&& pydantic_config.is_none()
384402
{

pyrefly/lib/test/dataclasses.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@ assert_type(Data, type[Data])
2323
"#,
2424
);
2525

26+
testcase!(
27+
test_enum_dataclass_rejected,
28+
r#"
29+
from dataclasses import dataclass
30+
import dataclasses
31+
from enum import Enum
32+
33+
class Good(Enum):
34+
RED = 1
35+
36+
@dataclass
37+
class Bad1(Enum): # E: Cannot apply @dataclass to Enum class `Bad1`
38+
RED = 1
39+
40+
@dataclasses.dataclass
41+
class Bad2(Enum): # E: Cannot apply @dataclass to Enum class `Bad2`
42+
RED = 1
43+
44+
@dataclass()
45+
class Bad3(Enum): # E: Cannot apply @dataclass to Enum class `Bad3`
46+
RED = 1
47+
48+
dataclass(Good) # E: Cannot apply dataclasses.dataclass to Enum class `Good`
49+
"#,
50+
);
51+
2652
testcase!(
2753
test_kw_only_sentinel_deep_inheritance,
2854
r#"

0 commit comments

Comments
 (0)