Skip to content

Conversation

@MichaReiser
Copy link
Member

@MichaReiser MichaReiser commented Oct 20, 2025

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

@MichaReiser MichaReiser added server Related to the LSP server ty Multi-file analysis & type inference labels Oct 20, 2025
@MichaReiser MichaReiser force-pushed the micha/unary-bin-op-goto branch from bc171df to c491e71 Compare October 21, 2025 10:11
");
}

impl CursorTest {
Copy link
Member Author

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

@github-actions
Copy link
Contributor

github-actions bot commented Oct 21, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

Comment on lines +968 to +973
let unary_dunder_method = match unary_op.op {
ast::UnaryOp::Invert => "__invert__",
ast::UnaryOp::UAdd => "__pos__",
ast::UnaryOp::USub => "__neg__",
ast::UnaryOp::Not => "__bool__",
};
Copy link
Member Author

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

Copy link
Member

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:

/// 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.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 21, 2025

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

Comment on lines +20 to +29
// 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__
Copy link
Member Author

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

@MichaReiser MichaReiser marked this pull request as ready for review October 21, 2025 11:26
@MichaReiser MichaReiser requested review from Gankra and removed request for carljm and dcreager October 21, 2025 11:44
@MichaReiser MichaReiser force-pushed the micha/unary-bin-op-goto branch from 9131141 to facf526 Compare October 21, 2025 11:56
@github-actions
Copy link
Contributor

github-actions bot commented Oct 21, 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.

Copy link
Member

@dhruvmanila dhruvmanila left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

Comment on lines +906 to +908
if token.kind().is_comment() {
return None;
}
Copy link
Member

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?

Copy link
Member Author

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.

@AlexWaygood
Copy link
Member

One tiny UI nit (possibly not solvable, possibly playground-specific, just posting it because I noticed it):

If I have the cmd key held down and hover over a symbol, the symbol changes colour and becomes underlined, and my mouse symbol changes to a little ✋ to tell me that I can click on it to go to its definition. But the same thing doesn't happen if I have the cmd key held down and hover over an operator, so it's hard for me to tell that cmd-clicking on it is going to take me to the method being displayed by the on-hover tooltip. Here's a video in which I have the cmd key held down for the duration:

Screen.Recording.2025-10-21.at.16.59.41.mov

Copy link
Member

@AlexWaygood AlexWaygood left a 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!

@MichaReiser
Copy link
Member Author

If I have the cmd key held down and hover over a symbol, the symbol changes colour and becomes underlined, and my mouse symbol changes to a little ✋ to tell me that I can click on it to go to its definition. But the same thing doesn't happen if I have the cmd key held down and hover over an operator, so it's hard for me to tell that cmd-clicking on it is going to take me to the method being displayed by the on-hover tooltip. Here's a video in which I have the cmd key held down for the duration:

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

@MichaReiser
Copy link
Member Author

MichaReiser commented Oct 21, 2025

Nope, that doesn't help (but I think is strictly better). I did give r-a a try and it also doesn't underline + and other operands. We also correctly set the originSelectionRange but it seems VS code doesn't care?

It does work fine with not.

Confirmed, Zed renders this correctly

@MichaReiser MichaReiser merged commit 9d1ffd6 into main Oct 21, 2025
40 checks passed
@MichaReiser MichaReiser deleted the micha/unary-bin-op-goto branch October 21, 2025 17:25
dcreager added a commit that referenced this pull request Oct 22, 2025
* 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)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

server Related to the LSP server ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Goto definition on operators

4 participants