-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[ty] Add support for attribute docstrings #22036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| // When we visit the root it won't `enter_node` so do that manually. | ||
| // Can this check fail? I don't know. It's fine. | ||
| let ancestors = if root.range().contains_range(range) { | ||
| vec![root] | ||
| } else { | ||
| Vec::new() | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was required to make ModModule be included, or else we couldn't find the sibling Statement in a global attribute of a module. This had a drive-by side-effect on selection_range that I'm not sure if it's correct/problematic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How's this different from the check on line 59? Can we remove the check on line 59?
I think a better solution is to add a walk_node method to the source_order module like this:
pub fn walk_node<'a, V>(visitor: &mut V, node: AnyNodeRef<'a>)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
if visitor.enter_node(node).is_traverse() {
node.visit_source_order(visitor);
}
visitor.leave_node(node);
}and call that over node.visit_source_order and remove the check on line 59
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
| info[selection-range]: Selection Range 0 | ||
| --> main.py:1:1 | ||
| | | ||
| 1 | / | ||
| 2 | | x = 1 + 2 | ||
| | |__________^ | ||
| | | ||
| info[selection-range]: Selection Range 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drive-by side-effect: the ModModule is always a valid Even Bigger selection range. I don't use this API so I have no sense of if this is good or bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine.
|
|
MichaReiser
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but I suggest that you use the existing suite helper to also cover if, try, ...
| // When we visit the root it won't `enter_node` so do that manually. | ||
| // Can this check fail? I don't know. It's fine. | ||
| let ancestors = if root.range().contains_range(range) { | ||
| vec![root] | ||
| } else { | ||
| Vec::new() | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How's this different from the check on line 59? Can we remove the check on line 59?
I think a better solution is to add a walk_node method to the source_order module like this:
pub fn walk_node<'a, V>(visitor: &mut V, node: AnyNodeRef<'a>)
where
V: SourceOrderVisitor<'a> + ?Sized,
{
if visitor.enter_node(node).is_traverse() {
node.visit_source_order(visitor);
}
visitor.leave_node(node);
}and call that over node.visit_source_order and remove the check on line 59
| info[selection-range]: Selection Range 0 | ||
| --> main.py:1:1 | ||
| | | ||
| 1 | / | ||
| 2 | | x = 1 + 2 | ||
| | |__________^ | ||
| | | ||
| info[selection-range]: Selection Range 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine.
| let parent_body = if let Some(module) = parent.as_mod_module() { | ||
| &module.body | ||
| } else if let Some(function) = parent.as_stmt_function_def() { | ||
| // This is important for `self.a = ...` in e.g. an initializer | ||
| &function.body | ||
| } else if let Some(class) = parent.as_stmt_class_def() { | ||
| &class.body | ||
| } else { | ||
| return None; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another case where it's real pain that our AST doesn't have a Suite node.
Especially, because it's non-trivial to find the suite to which a node belongs, e.g. in the case of a try statement that has multiple suites.
But, look what I've found:
ruff/crates/ruff_python_ast/src/traversal.rs
Lines 5 to 46 in e402e27
| pub fn suite<'a>(stmt: &'a Stmt, parent: &'a Stmt) -> Option<EnclosingSuite<'a>> { | |
| // TODO: refactor this to work without a parent, ie when `stmt` is at the top level | |
| match parent { | |
| Stmt::FunctionDef(ast::StmtFunctionDef { body, .. }) => EnclosingSuite::new(body, stmt), | |
| Stmt::ClassDef(ast::StmtClassDef { body, .. }) => EnclosingSuite::new(body, stmt), | |
| Stmt::For(ast::StmtFor { body, orelse, .. }) => [body, orelse] | |
| .iter() | |
| .find_map(|suite| EnclosingSuite::new(suite, stmt)), | |
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => [body, orelse] | |
| .iter() | |
| .find_map(|suite| EnclosingSuite::new(suite, stmt)), | |
| Stmt::If(ast::StmtIf { | |
| body, | |
| elif_else_clauses, | |
| .. | |
| }) => [body] | |
| .into_iter() | |
| .chain(elif_else_clauses.iter().map(|clause| &clause.body)) | |
| .find_map(|suite| EnclosingSuite::new(suite, stmt)), | |
| Stmt::With(ast::StmtWith { body, .. }) => EnclosingSuite::new(body, stmt), | |
| Stmt::Match(ast::StmtMatch { cases, .. }) => cases | |
| .iter() | |
| .map(|case| &case.body) | |
| .find_map(|body| EnclosingSuite::new(body, stmt)), | |
| Stmt::Try(ast::StmtTry { | |
| body, | |
| handlers, | |
| orelse, | |
| finalbody, | |
| .. | |
| }) => [body, orelse, finalbody] | |
| .into_iter() | |
| .chain( | |
| handlers | |
| .iter() | |
| .filter_map(ExceptHandler::as_except_handler) | |
| .map(|handler| &handler.body), | |
| ) | |
| .find_map(|suite| EnclosingSuite::new(suite, stmt)), | |
| _ => None, | |
| } | |
| } |
You might want to change it so that it also supports ModModule.
Summary
I should have factored this better but this includes a drive-by move of find_node to ruff_python_ast so ty_python_semantic can use it too.
Test Plan
Snapshots galore