Skip to content

Commit a4fe617

Browse files
committed
more robust AST node finding
1 parent 6ecd7c6 commit a4fe617

File tree

3 files changed

+41
-15
lines changed

3 files changed

+41
-15
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ class E:
126126
x: int = field(default=3)
127127
# error: [dataclass-field-order]
128128
y: str
129+
130+
import sys
131+
132+
@dataclass
133+
class F:
134+
x: int = 1
135+
if sys.version_info > (3, 7):
136+
# error: [dataclass-field-order]
137+
y: str
129138
```
130139

131140
Pure class attributes (`ClassVar`) are not included in the signature of `__init__`:

crates/ty_python_semantic/src/semantic_index/definition.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,13 @@ impl DefinitionKind<'_> {
709709
}
710710
}
711711

712+
pub(crate) fn as_annotated_assignment(&self) -> Option<&AnnotatedAssignmentDefinitionKind> {
713+
match self {
714+
DefinitionKind::AnnotatedAssignment(annassign) => Some(annassign),
715+
_ => None,
716+
}
717+
}
718+
712719
/// Returns the [`TextRange`] of the definition target.
713720
///
714721
/// A definition target would mainly be the node representing the place being defined i.e.,

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,24 +1416,34 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
14161416
}
14171417
}
14181418

1419-
// Report field ordering violations
1420-
for name in required_after_default_field_names {
1421-
if let Some(field_node) = class_node.body.iter().find_map(|stmt| {
1422-
if let ast::Stmt::AnnAssign(ann_assign) = stmt {
1423-
if let ast::Expr::Name(name_expr) = ann_assign.target.as_ref() {
1424-
if name_expr.id == *name {
1425-
return Some(ann_assign.target.as_ref());
1426-
}
1427-
}
1428-
}
1429-
None
1430-
}) {
1431-
if let Some(builder) =
1432-
self.context.report_lint(&DATACLASS_FIELD_ORDER, field_node)
1419+
if !required_after_default_field_names.is_empty() {
1420+
// Report field ordering violations
1421+
let body_scope = class.body_scope(self.db()).file_scope_id(self.db());
1422+
let use_def_map = self.index.use_def_map(body_scope);
1423+
let place_table = self.index.place_table(body_scope);
1424+
1425+
for name in required_after_default_field_names {
1426+
let Some(symbol_id) = place_table.symbol_id(name.as_str()) else {
1427+
continue;
1428+
};
1429+
if let Some(ann_assign) = use_def_map
1430+
.end_of_scope_symbol_declarations(symbol_id)
1431+
.find_map(|decl_with_constraints| {
1432+
decl_with_constraints
1433+
.declaration
1434+
.definition()?
1435+
.kind(self.db())
1436+
.as_annotated_assignment()
1437+
})
14331438
{
1434-
builder.into_diagnostic(format_args!(
1439+
if let Some(builder) = self.context.report_lint(
1440+
&DATACLASS_FIELD_ORDER,
1441+
ann_assign.target(self.module()),
1442+
) {
1443+
builder.into_diagnostic(format_args!(
14351444
"Required field `{name}` cannot be defined after fields with default values",
14361445
));
1446+
}
14371447
}
14381448
}
14391449
}

0 commit comments

Comments
 (0)