Repository navigation
Conversation
|
@JesseHerrick would be great to know your thoughts about this approach and naming. |
|
Hey @flowerett, thanks for the PR! This same thing happens with all editors and LSPs when there are multiple function heads for the same definition with the same arity. IMO that is a feature. While it's something we could change in the LSP, this is something I'd actually leave up to the LSP client and editor to configure to their liking. Editors should be able to just say "give me the first option" instead of opening the list if they want it. In NeoVim this isn't too tricky to configure, but maybe in Zed it's a pain. Thoughts? |
ef4e0ff to
9e448a6
Compare
|
Hey @JesseHerrick, thanks for the feedback!
So for Zed users today there's no client-side option at all. While validating the original report locally I also found two real bugs that were producing multi-location results for calls with a single intended target: I've created a small Elixir project to debug and reproduce the issues - [dummy_dexter_ex}(https://github.com/flowerett/dummy_dexter_ex) My preference would be to keep it in this PR with default "all", this won't change default behavior and Zed users get a workable option - "first" if they want to. |
|
Thanks @flowerett. You're right - I was confusing the go-to-ref arity checking with go-to-definition. We indeed should add that filtering. I'm totally fine with adding this change as default off so that those who want it can enable it. I'll review the PR later today. |
|
Hey @JesseHerrick should I finish this fix or is it better to close the PR, wdyt? |
|
Hey @flowerett, sorry, yes I think we should finish this one up. There's two important elements:
|
When a function has multiple heads/clauses, editors like Zed show a picker UI instead of jumping directly.
The new "definitionStyle" initializationOption ("all" or "first") lets users choose whether to return all definition sites or just the first one.
Two related bugs made goto-definition return multiple Location entries for calls a human reads as resolving to a single definition. Zed renders those multi-location results as multi-cursor selections (rather than a picker), which is what knoebber reported on issue remoteoss#38. 1. LookupFunction did not filter by arity, so `Foo.square(3)` returned both `square/1` and `square/2` rows when both were defined. Added LookupFunctionByArity and compute the call-site arity in Definition() via a new TokenizedFile.ArityAtCallsite helper that handles parens, zero-arg calls, `&Foo.bar/2` captures, and pipe context. 2. lookupFollowDelegate only followed delegates when every row was a delegate, so `defdelegate foo/1` + `def foo/2` in the same module returned both rows instead of following the /1 delegate. Rewrote it to partition by arity and follow per-arity, and added LookupFollowDelegateByArity. Existing LookupFunction / LookupFollowDelegate signatures are unchanged so the 20+ other callers still work; only Definition() is arity-aware. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bdcc215 to
380912e
Compare
|
Hey @JesseHerrick ! |
There was a problem hiding this comment.
Some findings
- [important]
internal/lsp/elixir.go:85miscomputes arity for valid nested, block, capture-like, interpolation, and parenthesis-free forms, so go-to-definition can select the wrong function. - [important]
internal/lsp/server.go:1134leaves bare current-module calls on the old first-name-match path, bypassing both arity filtering anddefinitionStyle. - [important]
internal/lsp/name_navigation.go:74filters use-chain results after choosing a provider, so an earlier matching-arity import is missed; initialization coverage and the three direct token loops also need follow-up.
flowerett
left a comment
There was a problem hiding this comment.
New findings
- [important]
internal/lsp/elixir.go:191still assigns special-form commas to the outer call, so exact lookup can select the wrong arity or fall back to the module. (narrowed) - [important]
internal/lsp/elixir.go:298scans every module in the buffer, so a bare call in the first module can navigate to an unrelated nested or later declaration. (new) - [important]
internal/lsp/server.go:2538shares cycle state across parameterized uses, so a valid earlier provider can be skipped and navigation falls back to the consumer module. (repeat)
Round 2 · reviewed at fd8780a
…multiple-function-definitions # Conflicts: # internal/lsp/name_navigation.go
- Commas after a parenthesis-free call inside the argument list (`if`, `for`, `with`, `fetch user, opts`) belong to that call, so they no longer inflate the outer call's arity. - The current-buffer definition scan only collects declarations made directly in the resolved module, not nested or sibling modules. - Use-chain cycle state is keyed by consumer opts, so the same injector used twice with different providers is searched for each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Reviewed by Cursor Bugbot for commit 4c6d467. Configure here.
- A `do` after a def/defp/defmacro head opens the body, so it no longer counts as an extra argument when resolving the head's arity. - LookupName falls back to the module when no generated arity matches instead of returning nothing. - Bare-call generated definitions narrow to the call's arity when one matches and honour definitionStyle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
flowerett
left a comment
There was a problem hiding this comment.
✅ Approve
Round 3 · reviewed at 1d5ae1a
automatic review done by (claude-opus-5-5 medium)
flowerett
left a comment
There was a problem hiding this comment.
🔴 Request changes
- [critical]
internal/lsp/server_test.go:333and:614preserve personal attributions in repository history; replace them with issue/PR references and remove them from the branch’s commits. (new)
Blocking remaining: 1
Round 4 · reviewed at 1d5ae1a
automatic review done by (gpt-6.1-sol medium)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
flowerett
left a comment
There was a problem hiding this comment.
🔴 Request changes
- [critical]
internal/store/store.go:2084follows default-argument delegates at the wrapper’s arity, selecting an unrelated overload or stopping before the implementation. (new)
Round 5 · reviewed at 18b453e
automatic review done by (gpt-6.1-sol medium)
A `defdelegate run(x, opts \\ [])` indexes run/1 and run/2, but both call the target with every argument. Following the run/1 row now looks up the target's run/2 instead of an unrelated run/1 overload, and reaches the implementation when the target has no run/1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
flowerett
left a comment
There was a problem hiding this comment.
✅ Approve
Blocking remaining: none
Round 6 · reviewed at 6fac4f1
automatic review done by (gpt-6.1-sol medium)

Solves this issue
When a function has multiple heads/clauses, editors like Zed show a picker UI instead of jumping directly.
The new "definitionStyle" initializationOption ("all" or "first") lets users choose whether to return all definition sites or just the first one.
the new initializationOption will look like:
Note
Medium Risk
Changes core LSP definition resolution (arity parsing, delegate following, use chains); broad test coverage but heuristic arity detection can mis-resolve ambiguous calls.
Overview
Adds
definitionStyle("all"|"first", default"all") as an LSPinitializationOptionand applies it at the end of go-to-definition so editors can jump to a single site instead of showing a multi-head picker (e.g. Zed).Go-to-definition is now arity-aware. The server infers call arity at the cursor (
ArityAtCallsite, including keyword lists, pipes, captures, and many edge cases) and threads it through lookups: indexed store queries (LookupFunctionByArity, per-arityLookupFollowDelegateByArity), current-bufferFindDefinitionLines, generated BEAM symbols, anduse/__using__chains (including inline defs and dynamic imports). When arity is unknown, behavior stays broad; when it matches, overloads and mixeddefdelegate/defnames resolve to the right clause.use-chain visiting keys visits on consumer opts so the same injector used with different options is not skipped incorrectly.README documents
definitionStylefor Neovim and JSON LSP config.Reviewed by Cursor Bugbot for commit 6fac4f1. Bugbot is set up for automated code reviews on this repo. Configure here.