Skip to content

[ty] Change goto-def for class constructors to always go to class definition#23071

Merged
BurntSushi merged 2 commits intomainfrom
ag/goto-class-definition-from-constructor
Feb 9, 2026
Merged

[ty] Change goto-def for class constructors to always go to class definition#23071
BurntSushi merged 2 commits intomainfrom
ag/goto-class-definition-from-constructor

Conversation

@BurntSushi
Copy link
Member

@BurntSushi BurntSushi commented Feb 4, 2026

Previously, the ty LSP would also include __init__ and __new__
targets along with the class definition. In some LSP clients (like VS
Code), this would bring up a prompt to ask the user to select one. It
seems like this is generally unexpected by users (and in particular,
deviates from pyright). This PR changes the ty LSP to behave more like
pyright here, and only include the class definition in the response.

This does also make the change to enable users to jump to the
constructor method directly by invoking goto-definition when the cursor
is on the parentheses of the call.

Reviewers might find reviewing commit-by-commit to be helpful here.
The first commit makes the change over to class definitions only.
The second commit adds special support for goto-definition when
the cursor is on a parenthesis.

Fixes astral-sh/ty#2218, Ref astral-sh/ty#2639

@BurntSushi BurntSushi added server Related to the LSP server ty Multi-file analysis & type inference labels Feb 4, 2026
@BurntSushi BurntSushi requested review from dhruvmanila and removed request for AlexWaygood, MichaReiser, carljm, dcreager and sharkdp February 4, 2026 15:33
@BurntSushi BurntSushi force-pushed the ag/goto-class-definition-from-constructor branch from 2cd98f1 to 55fc171 Compare February 4, 2026 15:34
@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 4, 2026

Typing conformance results

No changes detected ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 4, 2026

mypy_primer results

Changes were detected when running on open source projects
Expression (https://github.com/cognitedata/Expression)
- tests/test_compose.py:21:16: error[invalid-assignment] Object of type `(Never, /) -> Never` is not assignable to `(int, /) -> int`
- Found 205 diagnostics
+ Found 204 diagnostics

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
- src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 50 diagnostics
+ Found 49 diagnostics

materialize (https://github.com/MaterializeInc/materialize)
- misc/python/materialize/cli/mz_workload_anonymize.py:251:13: error[no-matching-overload] No overload of bound method `join` matches arguments
- Found 535 diagnostics
+ Found 534 diagnostics

sympy (https://github.com/sympy/sympy)
+ sympy/algebras/tests/test_quaternion.py:422:33: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/codegen/tests/test_matrix_nodes.py:27:21: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/eigen.py:1202:37: error[unresolved-attribute] Object of type `T2'return@call_highest_priority | T1'return@call_highest_priority` has no attribute `pow`
+ sympy/matrices/expressions/tests/test_blockmatrix.py:235:13: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/expressions/tests/test_blockmatrix.py:235:33: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/expressions/tests/test_blockmatrix.py:235:53: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/expressions/tests/test_blockmatrix.py:459:12: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/expressions/tests/test_derivatives.py:551:26: error[unsupported-operator] Operator `+` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/matrices/expressions/tests/test_matadd.py:36:12: error[unsupported-operator] Operator `+` is not supported between objects of type `MatrixBase` and `MatrixBase | Expr`
+ sympy/matrices/expressions/tests/test_matpow.py:124:47: error[unsupported-operator] Operator `+` is not supported between two objects of type `ImmutableDenseMatrix`
+ sympy/matrices/inverse.py:385:11: error[unsupported-operator] Operator `-` is not supported between objects of type `MatrixBase` and `MatrixBase | Expr | Unknown`
+ sympy/matrices/inverse.py:393:11: error[unsupported-operator] Operator `+` is not supported between objects of type `MatrixBase` and `MatrixBase | Expr | Unknown`
+ sympy/matrices/matrixbase.py:979:18: error[unsupported-operator] Operator `+` is not supported between two objects of type `Self@_eval_wilkinson`
+ sympy/matrices/matrixbase.py:2957:16: error[invalid-return-type] Return type does not match returned value: expected `Self@_eval_pow_by_cayley`, found `Self@_eval_pow_by_cayley | T2'return@call_highest_priority | T1'return@call_highest_priority`
+ sympy/matrices/matrixbase.py:3256:16: error[invalid-return-type] Return type does not match returned value: expected `MatrixBase`, found `T2'return@call_highest_priority | T1'return@call_highest_priority`
+ sympy/matrices/matrixbase.py:3256:29: error[invalid-argument-type] Argument is incorrect: Expected `T2'return@call_highest_priority | T1'return@call_highest_priority`, found `MatrixBase`
+ sympy/matrices/matrixbase.py:3310:16: error[unsupported-operator] Operator `+` is not supported between two objects of type `MatrixBase`
+ sympy/matrices/matrixbase.py:3314:16: error[invalid-return-type] Return type does not match returned value: expected `Tmat@__sub__`, found `MatrixBase`
+ sympy/matrices/matrixbase.py:4386:16: error[unsupported-operator] Operator `+` is not supported between two objects of type `Self@add`
+ sympy/matrices/matrixbase.py:4923:16: error[invalid-return-type] Return type does not match returned value: expected `Self@analytic_func`, found `Self@analytic_func | T2'return@call_highest_priority | T1'return@call_highest_priority`
+ sympy/matrices/repmatrix.py:321:17: error[unsupported-operator] Operator `-` is not supported between two objects of type `Self@_eval_is_symmetric`
+ sympy/matrices/tests/test_commonmatrix.py:1249:31: error[unsupported-operator] Operator `+` is not supported between objects of type `MutableDenseMatrix` and `ImmutableDenseNDimArray`
+ sympy/matrices/tests/test_eigen.py:406:20: error[invalid-argument-type] Argument to function `max` is incorrect: Expected `Iterable[Unknown]`, found `MatrixBase | Unknown`
+ sympy/matrices/tests/test_immutable.py:105:23: error[unsupported-operator] Operator `+` is not supported between two objects of type `ImmutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:121:12: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:123:32: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:141:12: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2179:22: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2207:26: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2908:21: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2921:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2936:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2937:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:2948:14: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:3471:21: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrices.py:3478:12: warning[possibly-missing-attribute] Attribute `rank` may be missing on object of type `MatrixBase | MatrixExpr | Unknown`
+ sympy/matrices/tests/test_matrices.py:3479:12: warning[possibly-missing-attribute] Attribute `rank` may be missing on object of type `MatrixBase | MatrixExpr | Unknown`
+ sympy/matrices/tests/test_matrixbase.py:491:12: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:493:32: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:544:12: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:823:31: error[unsupported-operator] Operator `+` is not supported between objects of type `MutableDenseMatrix` and `ImmutableDenseNDimArray`
+ sympy/matrices/tests/test_matrixbase.py:876:12: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:878:32: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:900:12: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:2931:22: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:2959:26: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:3610:21: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:3624:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:3639:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:3640:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_matrixbase.py:3652:14: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_reductions.py:377:21: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_reductions.py:384:12: warning[possibly-missing-attribute] Attribute `rank` may be missing on object of type `MatrixBase | MatrixExpr | Unknown`
+ sympy/matrices/tests/test_reductions.py:385:12: warning[possibly-missing-attribute] Attribute `rank` may be missing on object of type `MatrixBase | MatrixExpr | Unknown`
+ sympy/matrices/tests/test_solvers.py:68:13: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/matrices/tests/test_sparse.py:573:12: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableSparseMatrix`
+ sympy/matrices/tests/test_sparse.py:577:52: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableSparseMatrix`
+ sympy/matrices/tests/test_sparse.py:593:17: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableSparseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:13:6: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:16:38: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:16:84: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:16:131: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:21:44: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:21:90: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:21:137: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:25:37: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:25:83: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/parsing/autolev/test-examples/ruletest5.py:25:130: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/physics/control/tests/test_lti.py:3733:31: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/physics/control/tests/test_lti.py:3733:38: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/physics/mechanics/lagrange.py:222:23: error[unsupported-operator] Operator `-` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/physics/mechanics/lagrange.py:346:17: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/physics/mechanics/linearize.py:217:28: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/physics/mechanics/linearize.py:229:29: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/physics/mechanics/linearize.py:241:29: error[unsupported-operator] Operator `+` is not supported between two objects of type `Unknown | MutableDenseMatrix`
+ sympy/physics/mechanics/tests/test_jointsmethod.py:247:17: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/physics/mechanics/tests/test_jointsmethod.py:249:17: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/physics/quantum/tests/test_represent.py:89:17: error[unsupported-operator] Operator `+` is not supported between objects of type `MatrixBase | Expr` and `MatrixBase`
+ sympy/physics/quantum/tests/test_represent.py:91:24: error[unsupported-operator] Operator `-` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/physics/quantum/tests/test_represent.py:93:28: error[unsupported-operator] Operator `+` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/physics/vector/functions.py:380:21: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[Unknown]`, found `MatrixBase | Unknown`
+ sympy/solvers/tests/test_numeric.py:137:11: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/solvers/tests/test_solvers.py:722:19: error[unsupported-operator] Operator `-` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/solvers/tests/test_solvers.py:723:19: error[unsupported-operator] Operator `-` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/solvers/tests/test_solvers.py:724:19: error[unsupported-operator] Operator `-` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/solvers/tests/test_solvers.py:724:30: error[unsupported-operator] Operator `-` is not supported between two objects of type `MatrixBase | Expr`
+ sympy/stats/tests/test_symbolic_multivariate.py:84:36: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/stats/tests/test_symbolic_multivariate.py:84:84: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/stats/tests/test_symbolic_multivariate.py:85:36: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/tensor/tests/test_tensor.py:1693:19: error[unsupported-operator] Operator `+` is not supported between two objects of type `MutableDenseMatrix`
+ sympy/tensor/tests/test_tensor.py:1694:23: error[unsupported-operator] Operator `-` is not supported between two objects of type `MutableDenseMatrix`
- Found 15816 diagnostics
+ Found 15907 diagnostics

@BurntSushi BurntSushi force-pushed the ag/goto-class-definition-from-constructor branch from 55fc171 to 4caa0bf Compare February 5, 2026 13:08
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.

This is great!

Comment on lines +986 to +993
for node in covering_node
.parent()
.into_iter()
.chain(std::iter::once(node))
{
return Some(GotoTarget::Call {
call,
callable: name.into(),
});
if let AnyNodeRef::ExprCall(call) = node
&& let ast::Expr::Name(ref name) = *call.func
{
Copy link
Member

Choose a reason for hiding this comment

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

I'm not really sure why do we need to loop over the two nodes here when only one of them is going to be a ExprCall unless I'm missing something? I don't mind this, just wanted to understand it.

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 added this to account for the case when the cursor is on (. Previously, we were assuming we were on a Name node, and thus the parent was the ExprCall. But if we're on the (, then the covering node we get back is directly the ExprCall. So we try the parent first, and if that fails, try the current node to find the right ExprCall.

Comment on lines +1308 to +1313
// Note that we specifically only look for a opening parenthesis
// here. Matching on a closing parenthesis seems ambiguous. e.g.,
// what do you do in the case of `func(a<CURSOR>)`? Arguably the
// cursor is "inside" the function call. In any case, when AG tried
// to match on a closing parenthesis here, a bunch of tests failed
// in an undesirable way.
Copy link
Member

Choose a reason for hiding this comment

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

I think this is correct that we shouldn't try to match it against the closing parenthesis when inside the call and there are arguments because those should navigate as per the argument type, right?

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'm honestly not sure. I think our current tests definitely assert that. And it seems reasonable to me. I think the most important thing is that we're consistent.

Specifically, consider foo(argument). If your cursor is "on" the ), then it looks like this: foo(argument<CURSOR>). In that case, the cursor isn't really "on" the argument. It's on the ). But visually (usually), the cursor is between argument and ), and so it seems like it could be confusing.

In any case, I think this is something we can iterate on in response to user feedback.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, definitely!

…inition

Previously, we would include the class definition in addition to the
`__init__` and `__new__` methods, if present. But it seems that this
generally leads to unintuitive behavior from the perspective of users.
It's also not what pyright does.

This was also discussed a bit in the PR adding this functionality:
#20014.

Fixes astral-sh/ty#2218, Ref astral-sh/ty#2639
…es you too constructor method

This commit builds on the previous by tweaking the behavior of goto-def
on `(`. This lets users jump to the constructor method(s) directly
instead of the class definition.

Fixes astral-sh/ty#2218
@BurntSushi BurntSushi force-pushed the ag/goto-class-definition-from-constructor branch from 4caa0bf to 539c739 Compare February 9, 2026 15:57
@BurntSushi BurntSushi merged commit 9d00f3d into main Feb 9, 2026
110 of 112 checks passed
@BurntSushi BurntSushi deleted the ag/goto-class-definition-from-constructor branch February 9, 2026 17:26
@kaddkaka
Copy link

kaddkaka commented Feb 9, 2026

I would have preferred to keep the behavior of getting both.. That way I'm immediately moved to the first location (line with class) and can go to the next location (the __init__()) if I want to (no picker necessary, but I guess this depends on the editor.

Any chance that this could be made configurable?

@BurntSushi
Copy link
Member Author

My sense is that more folks were surprised by the previous behavior. It's also not how pyright works.

I'm not quite sure this is the kind of thing we want to offer a configuration knob for.

@dhruvmanila
Copy link
Member

I realized that, unless this is a standard behavior, users would probably not know that this difference exists. It might be worth documenting this somewhere, probably https://docs.astral.sh/ty/features/language-server/ ? It's not a priority but something that I've realized.

@BurntSushi
Copy link
Member Author

@dhruvmanila Aye, I opened astral-sh/ty#2772

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.

Go-to-definition for a class returns both the __init__ definition and the class itself

3 participants