@@ -18,17 +18,42 @@ pub(super) fn is_special_attribute(value: &Expr) -> bool {
18
18
}
19
19
}
20
20
21
- /// Returns `true` if the given [`Expr`] is a `dataclasses.field` call.
22
- pub ( super ) fn is_dataclass_field ( func : & Expr , semantic : & SemanticModel ) -> bool {
23
- if !semantic. seen_module ( Modules :: DATACLASSES ) {
24
- return false ;
25
- }
26
-
21
+ /// Returns `true` if the given [`Expr`] is a stdlib `dataclasses.field` call.
22
+ fn is_stdlib_dataclass_field ( func : & Expr , semantic : & SemanticModel ) -> bool {
27
23
semantic
28
24
. resolve_qualified_name ( func)
29
25
. is_some_and ( |qualified_name| matches ! ( qualified_name. segments( ) , [ "dataclasses" , "field" ] ) )
30
26
}
31
27
28
+ /// Returns `true` if the given [`Expr`] is a call to `attr.ib()` or `attrs.field()`.
29
+ fn is_attrs_field ( func : & Expr , semantic : & SemanticModel ) -> bool {
30
+ semantic
31
+ . resolve_qualified_name ( func)
32
+ . is_some_and ( |qualified_name| {
33
+ matches ! (
34
+ qualified_name. segments( ) ,
35
+ [ "attrs" , "field" | "Factory" ] | [ "attr" , "ib" ]
36
+ )
37
+ } )
38
+ }
39
+
40
+ /// Return `true` if `func` represents a `field()` call corresponding to the `dataclass_kind` variant passed in.
41
+ ///
42
+ /// I.e., if `DataclassKind::Attrs` is passed in,
43
+ /// return `true` if `func` represents a call to `attr.ib()` or `attrs.field()`;
44
+ /// if `DataclassKind::Stdlib` is passed in,
45
+ /// return `true` if `func` represents a call to `dataclasse.field()`.
46
+ pub ( super ) fn is_dataclass_field (
47
+ func : & Expr ,
48
+ semantic : & SemanticModel ,
49
+ dataclass_kind : DataclassKind ,
50
+ ) -> bool {
51
+ match dataclass_kind {
52
+ DataclassKind :: Attrs => is_attrs_field ( func, semantic) ,
53
+ DataclassKind :: Stdlib => is_stdlib_dataclass_field ( func, semantic) ,
54
+ }
55
+ }
56
+
32
57
/// Returns `true` if the given [`Expr`] is a `typing.ClassVar` annotation.
33
58
pub ( super ) fn is_class_var_annotation ( annotation : & Expr , semantic : & SemanticModel ) -> bool {
34
59
if !semantic. seen_typing ( ) {
@@ -51,19 +76,49 @@ pub(super) fn is_final_annotation(annotation: &Expr, semantic: &SemanticModel) -
51
76
semantic. match_typing_expr ( map_subscript ( annotation) , "Final" )
52
77
}
53
78
54
- /// Returns `true` if the given class is a dataclass.
55
- pub ( super ) fn is_dataclass ( class_def : & ast:: StmtClassDef , semantic : & SemanticModel ) -> bool {
56
- if !semantic. seen_module ( Modules :: DATACLASSES ) {
57
- return false ;
79
+ /// Enumeration of various kinds of dataclasses recognised by Ruff
80
+ #[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
81
+ pub ( super ) enum DataclassKind {
82
+ /// dataclasses created by the stdlib `dataclasses` module
83
+ Stdlib ,
84
+ /// dataclasses created by the third-party `attrs` library
85
+ Attrs ,
86
+ }
87
+
88
+ impl DataclassKind {
89
+ pub ( super ) const fn is_stdlib ( self ) -> bool {
90
+ matches ! ( self , DataclassKind :: Stdlib )
58
91
}
59
92
60
- class_def. decorator_list . iter ( ) . any ( |decorator| {
61
- semantic
62
- . resolve_qualified_name ( map_callable ( & decorator. expression ) )
63
- . is_some_and ( |qualified_name| {
64
- matches ! ( qualified_name. segments( ) , [ "dataclasses" , "dataclass" ] )
65
- } )
66
- } )
93
+ pub ( super ) const fn is_attrs ( self ) -> bool {
94
+ matches ! ( self , DataclassKind :: Attrs )
95
+ }
96
+ }
97
+
98
+ /// Return the kind of dataclass this class definition is (stdlib or `attrs`), or `None` if the class is not a dataclass.
99
+ pub ( super ) fn dataclass_kind (
100
+ class_def : & ast:: StmtClassDef ,
101
+ semantic : & SemanticModel ,
102
+ ) -> Option < DataclassKind > {
103
+ if !( semantic. seen_module ( Modules :: DATACLASSES ) || semantic. seen_module ( Modules :: ATTRS ) ) {
104
+ return None ;
105
+ }
106
+
107
+ for decorator in & class_def. decorator_list {
108
+ let Some ( qualified_name) =
109
+ semantic. resolve_qualified_name ( map_callable ( & decorator. expression ) )
110
+ else {
111
+ continue ;
112
+ } ;
113
+
114
+ match qualified_name. segments ( ) {
115
+ [ "attrs" , "define" | "frozen" ] | [ "attr" , "s" ] => return Some ( DataclassKind :: Attrs ) ,
116
+ [ "dataclasses" , "dataclass" ] => return Some ( DataclassKind :: Stdlib ) ,
117
+ _ => continue ,
118
+ }
119
+ }
120
+
121
+ None
67
122
}
68
123
69
124
/// Returns `true` if the given class has "default copy" semantics.
0 commit comments