Release v0.6.6 — LSP struct-field and array-subscript resolution - #231
Merged
Conversation
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)
thiagoralves
approved these changes
Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
developmenttomainfor 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.jsonbump.developmentis exactly these four commits ahead ofmain.A STRUCT field resolved against the global scope (RTOP-247). Struct fields are
VarDeclarationnodes, the same kind as a POU's VAR block declarations, so both reach the same arm ofresolveSymbolAtPosition. Inside a POU the analyzer has defined the local and it wins the lookup; insideTYPE ... END_TYPEthere is no such symbol, so the scope isglobalScopeand 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:
IsWrappedTypeRequestThe 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 incursor-context.tsdid 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'sresolveArrayElementType.Review on #230 found three more shapes, all fixed in the same PR:
ARRAY [*](__VLA_<rank>D_<Element>), both resolved to nothing. Fixed in the compiler core —resolveArrayElementTypenow walksTypeReferencealiases bounded byMAX_TYPE_ALIAS_DEPTHand matches the VLA name — because the gap is in the shared helper and the type-checker's own chain walk calls it.parseChainSegmentssplit 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, soarr[idx[i]].resolves.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.
resolveArrayElementTypereturns an element type in cases where it previously returnedundefined(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
tsc --noEmitclean on both packages, ondevelopment's current tip.resolve-symbol,hover,definition,renameandcompletion, each red-green verified — reverting a fix fails exactly its own tests.Note that
.github/workflows/ci.ymltriggers only onmain, so #230 itself ran no checks — this promotion PR is the first time CI sees these commits.After merge
Tag
v0.6.6onmainto trigger the release workflow.openplc-webandopenplc-editorboth pin v0.6.4 inbinary-versions.jsonand 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