Skip to content

Conversation

@Glyphack
Copy link
Contributor

@Glyphack Glyphack commented Apr 20, 2025

Summary

This was a follow up task from #17180 (comment) separated into this PR.

Test Plan

The tests run. There was already a test for type alias that I updated.
TBH I don't understand why in the representation the ExprSubscript is repeated.

But I created a sample test to check this behavior and it happens everytime visit_annotation is used:

- ModModule
  - StmtFunctionDef
    - Identifier
    - Parameters
      - ParameterWithDefault
        - Parameter
          - Identifier
          - ExprSubscript
            - ExprSubscript
              - ExprName
              - ExprName
    - StmtPass

for:

def f(x: list[T]): pass

This was a follow up task from astral-sh#17180 (comment)
separated into this PR.
@github-actions
Copy link
Contributor

github-actions bot commented Apr 20, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@Glyphack Glyphack marked this pull request as ready for review April 20, 2025 13:02
@dhruvmanila
Copy link
Member

dhruvmanila commented Apr 21, 2025

TBH I don't understand why in the representation the ExprSubscript is repeated.

Yeah, I think this is happening because we enter_node twice (1) for the visit_annotation (via walk_annotation) and (2) for the visit_expr that's inside visit_annotation so I think this is an expected behavior.

          - ExprSubscript (visit_annotation)
            - ExprSubscript (visit_expr in visit_annotation)

@dhruvmanila dhruvmanila added the internal An internal refactor or improvement label Apr 21, 2025
@dhruvmanila dhruvmanila requested a review from MichaReiser April 21, 2025 16:29
@dhruvmanila
Copy link
Member

We'll need to update other visitors as well i.e., Visitor and Transformer

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried about this change, specifically that enter_node is now called twice. This can break existing visitiors that override enter_node and assume that it is only called exactly once (Which I think is true for the formatter).

It seems to me that SourceOrderVisitor::visit_annotation isn't overriden anywhere. That makes me wonder if we should just remove the method for now and simply call visit_expr.

I think we should also update the Visitor implementation to call visit_annotation

visitor.visit_expr(value);

@Glyphack
Copy link
Contributor Author

Thanks for the link I somehow didn't find that.

Do you mean we can remove the SourceOrderVisitor::visit_annotation? Could it be useful in the future? Although with generator script it's easy to add it back.

We are not overriding Transformer::visit_annotation Shall we remove that as well?
https://github.com/Glyphack/ruff/blob/c3c799802740e497ceed7ea7cd62d467e6f22217/crates/ruff_python_ast/src/visitor/transformer.rs#L14

About the breaking change you mentioned I don't understand in what situation it can break(Should I look for an enter_node override in formatter that gives an error if it visits the node twice?) If you can give me an example I can add it to one of the test cases for future.

@MichaReiser
Copy link
Member

Here's an example of an visitor that I'm worried that would break:

fn enter_node(&mut self, node: AnyNodeRef<'ast>) -> TraversalSignal {
let node_range = node.range();
let enclosing_node = self.parents.last().copied().unwrap_or(node);
// Process all remaining comments that end before this node's start position.
// If the `preceding` node is set, then it process all comments ending after the `preceding` node
// and ending before this node's start position
while let Some(comment_range) = self.comment_ranges.peek().copied() {
// Exit if the comment is enclosed by this node or comes after it
if comment_range.end() > node_range.start() {
break;
}
let comment = DecoratedComment {
enclosing: enclosing_node,
preceding: self.preceding_node,
following: Some(node),
parent: self.parents.iter().rev().nth(1).copied(),
line_position: CommentLinePosition::for_range(
*comment_range,
self.source_code.as_str(),
),
slice: self.source_code.slice(*comment_range),
};
self.builder.push_comment(comment);
self.comment_ranges.next();
}
// From here on, we're inside of `node`, meaning, we're passed the preceding node.
self.preceding_node = None;
self.parents.push(node);
if self.can_skip(node_range.end()) {
TraversalSignal::Skip
} else {
TraversalSignal::Traverse
}
}

But there are probably others. Any visitor that internally maintains a stack of "parents" is affected by this because we may end up with a situation where the subscript becomes its own parent which, obviously, is nonsensical

Do you mean we can remove the SourceOrderVisitor::visit_annotation? Could it be useful in the future? Although with generator script it's easy to add it back.

Yes, I think we can simply remove it entirely because it's unused (I think)

We are not overriding Transformer::visit_annotation Shall we remove that as well?

If that's the case, yes, I'd remove it too

@Glyphack Glyphack marked this pull request as draft May 1, 2025 17:54
@MichaReiser
Copy link
Member

I'll close this due to inactivity. If anyone is interested in picking this up again, feel free to submit a new PR and reference this PR in the summary.

Thanks @Glyphack for exploring this.

@Glyphack Glyphack deleted the visit-annotation branch October 22, 2025 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal An internal refactor or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants