-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Implement go-to for binary and unary operators #21001
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
bc171df to
c491e71
Compare
| "); | ||
| } | ||
|
|
||
| impl CursorTest { |
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 moved this to the end. It felt weird having it in the middle of the tests
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
| let unary_dunder_method = match unary_op.op { | ||
| ast::UnaryOp::Invert => "__invert__", | ||
| ast::UnaryOp::UAdd => "__pos__", | ||
| ast::UnaryOp::USub => "__neg__", | ||
| ast::UnaryOp::Not => "__bool__", | ||
| }; |
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.
It didn't feel worth pulling this out into its own Type method, especially because __bool__ is somewhat complicated when all we need here is to get the__bool__ dunder
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.
You could possibly consider adding methods like these to the AST nodes directly, similar to what we already have for the bin-op nodes:
ruff/crates/ruff_python_ast/src/nodes.rs
Lines 2546 to 2602 in e1cada1
| /// Returns the dunder method name for the operator. | |
| pub const fn dunder(self) -> &'static str { | |
| match self { | |
| Operator::Add => "__add__", | |
| Operator::Sub => "__sub__", | |
| Operator::Mult => "__mul__", | |
| Operator::MatMult => "__matmul__", | |
| Operator::Div => "__truediv__", | |
| Operator::Mod => "__mod__", | |
| Operator::Pow => "__pow__", | |
| Operator::LShift => "__lshift__", | |
| Operator::RShift => "__rshift__", | |
| Operator::BitOr => "__or__", | |
| Operator::BitXor => "__xor__", | |
| Operator::BitAnd => "__and__", | |
| Operator::FloorDiv => "__floordiv__", | |
| } | |
| } | |
| /// Returns the in-place dunder method name for the operator. | |
| pub const fn in_place_dunder(self) -> &'static str { | |
| match self { | |
| Operator::Add => "__iadd__", | |
| Operator::Sub => "__isub__", | |
| Operator::Mult => "__imul__", | |
| Operator::MatMult => "__imatmul__", | |
| Operator::Div => "__itruediv__", | |
| Operator::Mod => "__imod__", | |
| Operator::Pow => "__ipow__", | |
| Operator::LShift => "__ilshift__", | |
| Operator::RShift => "__irshift__", | |
| Operator::BitOr => "__ior__", | |
| Operator::BitXor => "__ixor__", | |
| Operator::BitAnd => "__iand__", | |
| Operator::FloorDiv => "__ifloordiv__", | |
| } | |
| } | |
| /// Returns the reflected dunder method name for the operator. | |
| pub const fn reflected_dunder(self) -> &'static str { | |
| match self { | |
| Operator::Add => "__radd__", | |
| Operator::Sub => "__rsub__", | |
| Operator::Mult => "__rmul__", | |
| Operator::MatMult => "__rmatmul__", | |
| Operator::Div => "__rtruediv__", | |
| Operator::Mod => "__rmod__", | |
| Operator::Pow => "__rpow__", | |
| Operator::LShift => "__rlshift__", | |
| Operator::RShift => "__rrshift__", | |
| Operator::BitOr => "__ror__", | |
| Operator::BitXor => "__rxor__", | |
| Operator::BitAnd => "__rand__", | |
| Operator::FloorDiv => "__rfloordiv__", | |
| } | |
| } | |
| } |
But yeah, not is complicated because it falls back to __len__ as well as __bool__, even if you ignore all the fancy special casing we have baked in for the truthiness of various types.
|
| // We either want to call lhs.__op__ or rhs.__rop__. The full decision tree from | ||
| // the Python spec [1] is: | ||
| // | ||
| // - If rhs is a (proper) subclass of lhs, and it provides a different | ||
| // implementation of __rop__, use that. | ||
| // - Otherwise, if lhs implements __op__, use that. | ||
| // - Otherwise, if lhs and rhs are different types, and rhs implements __rop__, | ||
| // use that. | ||
| // | ||
| // [1] https://docs.python.org/3/reference/datamodel.html#object.__radd__ |
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 extracted this from builder.rs. The lines are unchanged except that the map(|outcome| outcome.return_type()) remains in builder.rs
9131141 to
facf526
Compare
|
dhruvmanila
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, thanks!
| if token.kind().is_comment() { | ||
| 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.
Just out of curiosity, is this to avoid returning Some when the cursor is inside 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.
Short answer: yes :)
The problem with binary expressions and unary expressions is that we don't know the operator's range. That means, we would return Some for
(
a + # comment<CURSOR>
b
)Because the range is between a and b. But that's obviously not what we want.
|
One tiny UI nit (possibly not solvable, possibly playground-specific, just posting it because I noticed it): If I have the Screen.Recording.2025-10-21.at.16.59.41.mov |
AlexWaygood
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.
Really cool. The core logic looks great!
I noticed this too but it's not clear to me why it is happening, because we report the full range of the binary expression as its range. I can try to use a narrower range to see if that helps |
|
Nope, that doesn't help (but I think is strictly better). I did give r-a a try and it also doesn't underline It does work fine with Confirmed, Zed renders this correctly |
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
* main: (65 commits) [ty] Some more simplifications when rendering constraint sets (#21009) [ty] Make `attributes.md` mdtests faster (#21030) [ty] Set `INSTA_FORCE_PASS` and `INSTA_OUTPUT` environment variables from mdtest.py (#21029) [ty] Fall back to `Divergent` for deeply nested specializations (#20988) [`ruff`] Autogenerate TypeParam nodes (#21028) [ty] Add assertions to ensure that we never call `KnownClass::Tuple.to_instance()` or similar (#21027) [`ruff`] Auto generate ast Pattern nodes (#21024) [`flake8-simplify`] Skip `SIM911` when unknown arguments are present (#20697) Render a diagnostic for syntax errors introduced in formatter tests (#21021) [ty] Support goto-definition on vendored typeshed stubs (#21020) [ty] Implement go-to for binary and unary operators (#21001) [ty] Avoid ever-growing default types (#20991) [syntax-errors] Name is parameter and global (#20426) [ty] Disable panicking mdtest (#21016) [ty] Fix completions at end of file (#20993) [ty] Fix out-of-order semantic token for function with regular argument after kwargs (#21013) [ty] Fix auto import for files with `from __future__` import (#20987) [`fastapi`] Handle ellipsis defaults in FAST002 autofix (`FAST002`) (#20810) [`ruff`] Skip autofix for keyword and `__debug__` path params (#20960) [`flake8-bugbear`] Skip `B905` and `B912` if <2 iterables and no starred arguments (#20998) ...
Summary
This PR implements goto and hover for binary operations and unary expressions.
As for other hover targets, the implementation doesn't try to match overloads yet.
This PR doesn't implement go-to support for augmented assign, which seems to match Pylance. I don't see a reason why we couldn't but I suggest leaving that for a separate PR
Fixes astral-sh/ty#1151
Test plan
Added tests
Screen.Recording.2025-10-21.at.13.21.23.mov