Repository navigation
Description
Background
PR #93 made nested-module alias scoping match Elixir on all three paths:
alias chains (c149bad), the open-buffer scope map (4bfe625), and
use-injected aliases (12f3103). Two smaller deviations from Elixir's
lexical rules are still open. Both are narrow, and neither is urgent.
Gap A — an alias in a function body escapes into the whole module
Elixir scopes an alias to the enclosing block. An alias written in one
def body is not in scope in the next one.
defmodule M do
def a do
alias My.App.Repo
Repo.all()
end
def b do
Repo.all() # Elixir: undefined alias. Both paths: My.App.Repo
end
endObserved: ExtractAliasesInScope at the line in def b returns
{"Repo": "My.App.Repo"}, and the index records the line-7 call as
My.App.Repo.
The two paths agree with each other, so nothing half-applies. The cost is
a wrong hover, a wrong jump, and a false reference on the second call.
The same applies to any block: fn, case clauses, if branches.
Gap B — the buffer accepts an alias written below the reference
defmodule M do
def a, do: Repo.all() # buffer: My.App.Repo. Index: bare Repo
alias My.App.Repo
endHere the paths disagree. The buffer filters by scope only, so it returns
an alias from anywhere in the module. The index resolves in a forward
pass, so the call stays bare. Hover resolves the call, references does
not find it, and a rename can apply to one and not the other — the same
class of half-applied edit that 12f3103 fixed.
Note the leniency may be deliberate: mid-edit, a person can write the
call before the alias. If we keep it, the index should match it, not the
other way round.
Suggested approach
The block machinery already exists. lexicalScopeAt (server.go:2696)
builds a stack of open block tokens and scopeContains (:2736) tests
prefix containment; the injected-alias path uses both.
extractAliasesFromTokens (internal/lsp/elixir.go) instead keys frames
on modules only, through parentScopes.
Widen those frames from module blocks to all blocks. Keeping the flat
alias list and recording the block stack per entry (as parentScopes
does now for modules) avoids a map copy at every do and fn. Then the
final filter becomes the containment test that Gap A needs, and the line
test that 4bfe625 added already covers Gap B.
Mirror whatever we choose in parser_tokenized.go, which saves and
restores the alias map per module frame today.
Acceptance criteria
- An alias in a
defbody is not in scope in a laterdef. - The same for
fn,case,ifand other blocks. - The buffer path and the index agree on an alias written below the
reference, whichever behavior we pick. - No measurable slowdown in
ExtractAliasesInScope. Reference: the
nested-module change cost 0 time and +11 allocs on a file with 30
nested modules and 170 aliases.
Activity