Skip to content

Add support for struct auto-completion#57

Open
JesseHerrick wants to merge 6 commits into
mainfrom
struct-support
Open

Add support for struct auto-completion#57
JesseHerrick wants to merge 6 commits into
mainfrom
struct-support

Conversation

@JesseHerrick
Copy link
Copy Markdown
Member

@JesseHerrick JesseHerrick commented May 4, 2026

#43


Note

Medium Risk
Adds new BEAM-side code-intel ops and significant completion-path logic (including caching and background prewarming), which could affect completion correctness/performance and BEAM protocol compatibility. Changes are localized to LSP/formatter sidecar interactions but touch core completion flow.

Overview
Adds struct-aware autocompletion to the Elixir LSP: field-key completions inside %Struct{...} and field-name completions for variable. when the variable can be inferred as a struct.

Implements two new BEAM CodeIntel protocol ops: struct_fields (introspects __struct__/0 to list fields) and return_type_struct (uses the compiled ExCk chunk to infer a function call’s struct return type), with Go client support in beamProcess.

Introduces token-based analyzers in elixir.go to detect struct literal contexts, struct value positions, variable field-access contexts, variable->struct inference (pattern matches + selected @spec support), and module-function-call assignments; updates completion triggers (adds {, ,, |, :, and space) and prioritizes local variables in struct value positions.

Adds a buildRoot+module struct field cache with background warming, document prewarming on open/change, and invalidation on reindex/beam eviction; expands tests across lexer helpers, completion behavior, BEAM ops, and nested/sibling _build layouts.

Reviewed by Cursor Bugbot for commit 45cf3ef. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread internal/lsp/server.go
Adds two more sources of struct-type inference for variable
dot-completion:

- @SPEC parameter typespecs: parameters annotated `t()` or `Module.t()`
  resolve to that struct (pattern matches still take precedence).
- ExCk return-type lookup: `var = Mod.func(...)` resolves `var.` against
  the struct returned by the compiled function, queried over a new
  `return_type_struct` op on the persistent BEAM process.
Comment thread internal/lsp/elixir.go
Comment thread internal/lsp/server.go Outdated
- Detect `&fn/arity` captures in `FindBareFunctionCalls` so renames and
  find-references include capture call sites alongside direct and pipe
  calls.
- Only apply the `000_`-prefixed `SortText` to variable completions in
  struct value positions (where a variable is the likely target).
  Previously the prefix leaked into all `funcPrefix != ""` completions,
  changing sort order of unrelated completions.
- Drop the unused `source []byte` parameter from `countCallArity`.
- Replace the `(line, len(text))`-based hack with a new
  `AllVariableFunctionCalls` that scans every function body in the file.
  `prewarmStructFieldsFromText` was previously only scanning the last
  function definition because `VariableFunctionCalls` anchors to the
  most recent `def` before the offset.
The BEAM formatter script hardcoded `_build/dev/lib/*/ebin` and only
looked at the build root passed by the Go side. Projects that compile
only with `MIX_ENV=test` or that keep their compiled deps in a sibling
sub-project (e.g. `apps/<app>/_build` while the file being edited lives
in `libs/<lib>/` with no `_build` of its own) couldn't load plugins like
Styler — the warning fired and the formatter silently fell back to the
standard formatter.

Try `_build/{dev,test,prod}/lib/*/ebin` in priority order, and if
nothing is found at the build root, descend one and two levels to pick
up nested mix projects' `_build` dirs. Extracted the shared logic into
`Dexter.CodePath.prepend_compiled_deps/1` so the top-level boot path and
each per-formatter init share it.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 45cf3ef. Configure here.

Comment thread internal/lsp/elixir.go
"Exception": true,
"Macro": true,
"Macro.Env": true,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Macro.Env incorrectly listed as non-struct type

Low Severity

Macro.Env is included in knownNonStructTypes, but it is actually a struct in Elixir (with fields like module, file, line, function, context, etc.). This causes classifySpecType to return "" for Macro.Env.t() parameters in typespecs, preventing struct field auto-completion for variables typed as Macro.Env.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 45cf3ef. Configure here.

Comment thread internal/lsp/elixir.go
// tokenText returns the source text for a token as a string.
func tokenText(source []byte, tok parser.Token) string {
return string(source[tok.Start:tok.End])
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant tokenText duplicates parser.TokenText

Low Severity

The new tokenText helper is identical to the existing parser.TokenText (both return string(source[tok.Start:tok.End])). Both are used inconsistently throughout the same file — e.g., tokenText at the = check and parser.TokenText on the very next variable name check. This creates confusion about which to use.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 45cf3ef. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant