Skip to content

Commit b00f68a

Browse files
authored
[ruff] Allow more field calls from attrs (RUF009) (#19021)
Summary -- Closes #19014 by identifying more `field` functions from `attrs`. We already detected these when imported from `attrs` but not the `attr` module from the same package. These functions are identical to the `attrs` versions: ```pycon >>> import attrs, attr >>> attrs.field is attr.field True >>> attrs.Factory is attr.Factory True >>> ``` Test Plan -- Regression tests based on the issue
1 parent 710c60f commit b00f68a

File tree

3 files changed

+31
-3
lines changed

3 files changed

+31
-3
lines changed

crates/ruff_linter/resources/test/fixtures/ruff/RUF009_attrs.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,20 @@ class J:
125125
class K:
126126
f: F = F()
127127
g: G = G()
128+
129+
130+
# Regression test for https://github.com/astral-sh/ruff/issues/19014
131+
# These are all valid field calls and should not cause diagnostics.
132+
@attr.define
133+
class TestAttrField:
134+
attr_field_factory: list[int] = attr.field(factory=list)
135+
attr_field_default: list[int] = attr.field(default=attr.Factory(list))
136+
attr_factory: list[int] = attr.Factory(list)
137+
attr_ib: list[int] = attr.ib(factory=list)
138+
attr_attr: list[int] = attr.attr(factory=list)
139+
attr_attrib: list[int] = attr.attrib(factory=list)
140+
141+
142+
@attr.attributes
143+
class TestAttrAttributes:
144+
x: list[int] = list() # RUF009

crates/ruff_linter/src/rules/ruff/helpers.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ fn is_stdlib_dataclass_field(func: &Expr, semantic: &SemanticModel) -> bool {
2525
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["dataclasses", "field"]))
2626
}
2727

28-
/// Returns `true` if the given [`Expr`] is a call to `attr.ib()` or `attrs.field()`.
28+
/// Returns `true` if the given [`Expr`] is a call to an `attrs` field function.
2929
fn is_attrs_field(func: &Expr, semantic: &SemanticModel) -> bool {
3030
semantic
3131
.resolve_qualified_name(func)
3232
.is_some_and(|qualified_name| {
3333
matches!(
3434
qualified_name.segments(),
35-
["attrs", "field" | "Factory"] | ["attr", "ib"]
35+
["attrs", "field" | "Factory"]
36+
// See https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py#L33
37+
| ["attr", "ib" | "attr" | "attrib" | "field" | "Factory"]
3638
)
3739
})
3840
}
@@ -120,7 +122,8 @@ pub(super) fn dataclass_kind<'a>(
120122

121123
match qualified_name.segments() {
122124
["attrs" | "attr", func @ ("define" | "frozen" | "mutable")]
123-
| ["attr", func @ ("s" | "attrs")] => {
125+
// See https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py#L32
126+
| ["attr", func @ ("s" | "attributes" | "attrs")] => {
124127
// `.define`, `.frozen` and `.mutable` all default `auto_attribs` to `None`,
125128
// whereas `@attr.s` implicitly sets `auto_attribs=False`.
126129
// https://www.attrs.org/en/stable/api.html#attrs.define

crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF009_RUF009_attrs.py.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,11 @@ RUF009_attrs.py:127:12: RUF009 Do not perform function call `G` in dataclass def
9898
127 | g: G = G()
9999
| ^^^ RUF009
100100
|
101+
102+
RUF009_attrs.py:144:20: RUF009 Do not perform function call `list` in dataclass defaults
103+
|
104+
142 | @attr.attributes
105+
143 | class TestAttrAttributes:
106+
144 | x: list[int] = list() # RUF009
107+
| ^^^^^^ RUF009
108+
|

0 commit comments

Comments
 (0)