Skip to content

Release v0.6.6 — LSP struct-field and array-subscript resolution - #231

Merged
thiagoralves merged 4 commits into
mainfrom
development
Sep 2, 2026
Merged

Release v0.6.6 — LSP struct-field and array-subscript resolution#231
thiagoralves merged 4 commits into
mainfrom
development

Conversation

@JoaoGSP

@JoaoGSP JoaoGSP commented Sep 2, 2026

Copy link
Copy Markdown
Member

Promotes development to main for the v0.6.6 release.

What's in it

#230 — two language-server resolution defects, RTOP-247 and RTOP-248, shipped together so the consuming repos pay a single binary-versions.json bump. development is exactly these four commits ahead of main.

A STRUCT field resolved against the global scope (RTOP-247). Struct fields are VarDeclaration nodes, the same kind as a POU's VAR block declarations, so both reach the same arm of resolveSymbolAtPosition. Inside a POU the analyzer has defined the local and it wins the lookup; inside TYPE ... END_TYPE there is no such symbol, so the scope is globalScope and the field name matched whatever global shared it. A declaration position now returns its node with no symbol.

Wider than the report, which described a misleading tooltip. Five call sites share that resolver:

surface before
hover showed the function block's signature
go-to-definition navigated to the function block
rename rewrote the FUNCTION BLOCK and every reference to it
find-all-references listed the block's references
IsWrappedTypeRequest misclassified the field

The rename case is data loss rather than cosmetics. It is also not function-block-only: any global symbol kind collides, so a field named after a FUNCTION mis-resolved identically.

Member access past an array subscript (RTOP-248). myArray[i]. offered the generic body-completion list — keywords plus every global symbol — instead of the element type's members. The dot-access regex in cursor-context.ts did not admit ], so the prefix never became a dot-access context; and the chain resolver walked field steps only. Chain segments now carry their subscript count and each peels one array level through the core's resolveArrayElementType.

Review on #230 found three more shapes, all fixed in the same PR:

  • A TYPE alias naming an array type, and ARRAY [*] (__VLA_<rank>D_<Element>), both resolved to nothing. Fixed in the compiler core — resolveArrayElementType now walks TypeReference aliases bounded by MAX_TYPE_ALIAS_DEPTH and matches the VLA name — because the gap is in the shared helper and the type-checker's own chain walk calls it.
  • parseChainSegments split on every dot, so an index expression containing a member access (arr[s.k].) shredded the chain. Replaced with a bracket-depth scan; the regex now also admits one nesting level, so arr[idx[i]]. resolves.
  • A comment cited grid[i][j]., which the compiler rejects on a multi-dimensional array. One level is peeled per bracket group: grid[i, j] for 2-D, mat[i][j] for a genuine array of arrays.

Deliberately unchanged: hover and go-to-definition past a subscript. Both resolve to the base variable there, and do the same for a plain non-array struct — that is the existing behaviour of those surfaces, not an array defect. Changing it would alter both paths and collides with RTOP-247's finding that struct fields are not symbols at all.

Compatibility

Language-server only, plus one compiler-core helper made more permissive. resolveArrayElementType returns an element type in cases where it previously returned undefined (aliases, VLAs) and is otherwise unchanged, so the type-checker's chain walk resolves strictly more than before and rejects nothing it used to accept. No format, manifest or debug-map change.

Verification

  • Full compiler suite 2322 passed, 7 skipped; extension suite 282 passed; tsc --noEmit clean on both packages, on development's current tip.
  • 19 tests added across resolve-symbol, hover, definition, rename and completion, each red-green verified — reverting a fix fails exactly its own tests.
  • Both defects validated by hand in the OpenPLC editor against a purpose-built project covering every array shape and a struct whose fields are named after a FUNCTION_BLOCK and a FUNCTION.

Note that .github/workflows/ci.yml triggers only on main, so #230 itself ran no checks — this promotion PR is the first time CI sees these commits.

After merge

Tag v0.6.6 on main to trigger the release workflow. openplc-web and openplc-editor both pin v0.6.4 in binary-versions.json and need this release before the LSP fixes reach either IDE; the bump to v0.6.6 also carries the v0.6.5 CONSTANT/RETAIN/NON_RETAIN work, so it is worth more validation than a routine pin.

🤖 Generated with Claude Code

https://claude.ai/code/session_011JAMx8mRf2ig4YFfs2gmsM

JoaoGSP and others added 4 commits September 1, 2026 00:18
Struct fields are `VarDeclaration` nodes, the same node kind as a POU's
VAR block declarations, so both reach `case "VarDeclaration"` in
`resolveSymbolAtPosition`. Inside a POU the local has been defined by the
analyzer and wins the lookup. Inside `TYPE ... END_TYPE` there is no such
symbol: `findEnclosingPOU` has no TYPE branch, so the scope is
`globalScope`, and struct field names are never defined as symbols at
all. `globalScope.lookup("Motor")` therefore answers with whatever global
happens to share the name.

A declaration position must not borrow a symbol from the global scope, so
a field returns its node with no symbol — the miss stays a miss.

Wider than the report, which described a misleading tooltip. Five call
sites share this resolver, and each guarded on the wrong symbol:

- hover showed the function block's signature;
- go-to-definition navigated to it;
- rename offered it, and applying the rename rewrote the FUNCTION BLOCK
  and every reference to it, which is data loss rather than cosmetics;
- find-all-references listed the block's references;
- the server's `isWrapped` query misclassified the field.

Nor is it function-block only: any global symbol kind collides, so a
field named after a FUNCTION mis-resolves identically. Tests cover both.

Validated in the OpenPLC editor against a project whose SensorData type
has fields named after a FUNCTION_BLOCK and a FUNCTION.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JAMx8mRf2ig4YFfs2gmsM
`myArray[i].` offered the generic body-completion list — keywords and
every global symbol — instead of the element type's members, for arrays
of user types and of elementary types alike.

Two causes, both in the completion stack:

- `getCursorContext`'s dot-access regex matched only bare identifier
  chains, so a prefix ending in `]` never became a dot-access context and
  fell through to body completions. That is what the reporter saw: not an
  empty list, a list of the wrong things.
- The chain resolver walked field steps only. Segments now carry their
  subscript count and each one peels an array level through the
  compiler's own `resolveArrayElementType`, which already understands
  both `__INLINE_ARRAY_*` synthetic names and named array TYPE
  declarations — so the synthetic-name obstacle disappears with it
  rather than needing its own unwrap.

Covered: inline arrays, named array types, multi-dimensional arrays, an
array reached through a struct field, and arrays of elementary types
(correctly no members, rather than the keyword fallback).

Hover and go-to-definition are deliberately untouched. Both resolve to
the base variable past a subscript — but they do the same for a plain
struct, so that is the existing behaviour of those surfaces rather than a
defect of arrays. Making them answer per-field is a change to both paths
and belongs with the field-symbol question in RTOP-247's wake.

Validated in the OpenPLC editor against a project carrying each array
shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JAMx8mRf2ig4YFfs2gmsM
Review on #230 found three shapes the first pass turned from a wrong
answer into no answer at all. Before this PR every subscript shape fell
back to the generic body list — useless but uniform. After it, the shapes
the chain resolver understood became correct and three it did not became
silently empty, which reads as a broken LSP rather than an unsupported
construct. All three compile clean, so they are legal ST.

`resolveArrayElementType` matched only `__INLINE_ARRAY_*` and a direct
`ArrayDefinition`:

- A TYPE alias naming an array type has a `TypeReference` definition and
  fell off the end of the loop. It now walks the alias chain, bounded by
  the module's existing `MAX_TYPE_ALIAS_DEPTH`, mirroring
  `resolveArrayShapeByName` directly below it.
- `ARRAY [*]` reaches the AST as `__VLA_<rank>D_<Element>`, which the
  `__INLINE_ARRAY_` prefix check never matched.

Fixed in the compiler core rather than worked around in the LSP: the
gap is in the shared helper, and the type-checker's own chain walk calls
the same function.

`parseChainSegments` split the chain on every dot, so an index expression
containing a member access shredded it — `arr[s.k].` parsed as
`["arr[s", "k]"]` and the base lookup missed. It now scans, splitting only
at bracket depth 0. The dot-access regex accepts one level of nested
brackets with it, so `arr[idx[i]].` resolves too.

Also corrects a misleading comment: `grid[i][j]` is not valid for a
multi-dimensional array — the compiler rejects it with "'GRID' has 2
dimensions but is indexed with 1 index". One level is peeled per bracket
group, so the supported spellings are `grid[i, j]` for a 2-D array and
`mat[i][j]` for a genuine array of arrays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JAMx8mRf2ig4YFfs2gmsM
…eld-global-fallback

fix(lsp): struct-field symbol resolution and member access past an array subscript (RTOP-247, RTOP-248)
@JoaoGSP
JoaoGSP requested a review from thiagoralves September 2, 2026 13:00
@thiagoralves
thiagoralves merged commit 1ee82ac into main Sep 2, 2026
6 checks passed
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.

2 participants