Conversation
…ocedure
Hovering the label of a standalone procedure that has no MAP prototype
presented an unrelated same-named symbol instead:
MEMBER()
INCLUDE('shared.inc'),ONCE ! ITEMIZE ... Worker EQUATE ... END
...
Worker PROCEDURE
hovered as `Worker — EQUATE`, the ITEMIZE entry from the include.
resolveProcedureImplementation (HoverRouter step 5) builds its card only
when a MAP declaration is found, in the current file or through the MEMBER
parent. With none, it returned null even though the line had already
matched PROCEDURE_IMPLEMENTATION with the cursor on the name, so routing
carried on to the variable tiers, which match the bare name against
anything reachable through the include chain. HoverFormatter.formatProcedure
could not have covered it either: it returns null for a header-only card,
and with no prototype to preview and the cursor already on the
implementation, the header is all it has.
The no-prototype case now answers with the procedure itself, noting that no
MAP prototype was found. It is limited to a token of subtype GlobalProcedure
on the hovered line: a method declared in a CLASS or INTERFACE body matches
the same regex and keeps going to the method-declaration tier (step 7), as
before.
Tests: ProcedureHoverResolver.UnprototypedLabel.test.ts - the label answers
as the procedure rather than the include's EQUATE, and a prototyped label
keeps its MAP declaration card. With the fix stashed, the first test fails
with the reported symptom (`**Worker** — EQUATE`) and the second passes.
An earlier draft without the GlobalProcedure guard failed six existing
local-CLASS method-declaration hover tests, which is what the guard is for.
Server 3379 passing, 0 failing, 3 pending; client 307 passing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Clarion label must start at column 0 - confirmed elsewhere in this codebase
against the real compiler, which desyncs entirely on an indented one. The
PROCEDURE_IMPLEMENTATION pattern this resolver uses to recognise a procedure's
own declaration line accepted leading whitespace, so an indented, non-compiling
label still produced a full procedure card:
Helper PROCEDURE() ! valid, MAP entry above
CODE
RETURN 1
Helper PROCEDURE() ! indented - the compiler rejects this
hovering the second, invalid line answered exactly like the first: the same
`Helper 📦 Module Procedure` card, MAP prototype preview and all. The previous
commit's no-prototype fallback inherited the same gap by construction, so an
indented label with no MAP entry got the new procedure card too, just as
wrongly.
Tightened the pattern to require the label at column 0, matching the actual
rule, and updated the one call site's capture-group index (the leading-
whitespace group is gone). An indented line no longer matches at all, so the
resolver correctly has nothing to say about it - this fix's own no-prototype
card included, since it can no longer be reached from an invalid line either.
For the no-MAP case with the label indented, the hover falls through the
router to the pre-existing global-EQUATE match this PR does not touch - a bare
word can still resolve to a same-named EQUATE from an unrelated include
regardless of what's actually on its own line. That's a broader defect than a
procedure declaration's own hover and is called out as a known gap in the PR
description, not folded into this diff.
Tests: two more cases in ProcedureHoverResolver.UnprototypedLabel.test.ts -
an indented label is not answered as a procedure, with or without a MAP entry.
With the fix stashed, both fail: the prototyped case renders the same full
procedure card as its valid sibling, and the no-prototype case renders the
prior commit's new fallback card. Restored, both pass.
Server 3381 passing, 0 failing, 3 pending; client 307 passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
msarson
left a comment
There was a problem hiding this comment.
Thanks, this is the right idea, and the column-0 change is good. One regression before it can go in. The new fallback trusts findMapDeclaration plus the MEMBER-parent lookup, and neither follows an INCLUDE inside a MAP (the #313 pattern). A procedure prototyped that way used to get its correct card through the later tiers. Now it gets "
! protos.inc
Worker PROCEDURE
! mod.clw
MEMBER()
MAP
INCLUDE('protos.inc')
END
Worker PROCEDURE ! hover: base = card linking protos.inc:1, PR = "No MAP prototype found"
CODESuggest calling mapResolver.findDeclarationInMapIncludes(procName, document, tokens) before the fallback, as the call-site path does at ProcedureHoverResolver.ts:107-129 (the walk result is cached), and formatting the card from a hit. Only warn when the walk also comes back empty. Please add this case to the test file too. Everything else checks out: merged onto the current tip the suite is 3423 / 0, and your tests go red without the fix.
Review of the first commit found a regression. Its no-prototype fallback
trusted findMapDeclaration and the MEMBER-parent lookup, and neither follows
an INCLUDE inside a MAP:
! protos.inc
Worker PROCEDURE
! mod.clw
MEMBER()
MAP
INCLUDE('protos.inc')
END
Worker PROCEDURE ! hover
On the base this hovered with a card linking protos.inc:1, answered by the
variable tier after the router returned nothing. The fallback now answers
first, with "No MAP prototype found".
The fallback now runs the msarson#313 include walk before warning, and formats the
card from a hit the same way the call-site path does. The walk alone was not
enough: findDeclarationInMapIncludes only accepts a prototype inside a MODULE
block, and this one is bare. An included file's text becomes part of the MAP
that includes it, so a bare `Name PROCEDURE` there is a local prototype. The
walk gains an opt-in acceptBarePrototype flag that also accepts a
GlobalProcedure or MapProcedure token of that name; method subtypes stay
excluded. The flag is part of the walk-result cache key, and the existing
callers (call-site hover, Go to Implementation) do not pass it, so their
results and cache entries are unchanged. The index fast path keeps its
MODULE-scoped check in both modes, so a bare hit always comes from the walk,
which only starts from INCLUDEs inside a MAP.
The MODULE-block form was not affected by the regression: router step 3
already runs the strict walk and answers it before step 5 is reached.
Tests: two cases in ProcedureHoverResolver.UnprototypedLabel.test.ts. A bare
prototype in a MAP-included file must link to it without the warning; with
this commit stashed it fails with "No MAP prototype found", and with the walk
called without the new flag (the MODULE-only walk) it fails the same way. A
MODULE-block prototype in a MAP-included file is kept as a regression guard;
it passes with or without this commit.
Server 3383 passing, 0 failing, 3 pending; client 307 passing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks, you're right. I reproduced it before changing anything: on the base your fixture hovers with a card linking One thing came up when I applied your suggestion as written: What
|
msarson
left a comment
There was a problem hiding this comment.
Thanks, good catch on the MODULE-only walk, and the reasoning for the bare prototype is right: the INCLUDE's text is part of the MAP.
Verified merged onto the current tip (which now has #639 and #642 to #645): server 3471 / 0, client 307; the MAP-INCLUDE fixture gets its card again and your new test goes red without the fix. On the real solution every procedure label in a 523-label sample hovers exactly as on the base, none with the warning, and the hover/F12 agreement sweep did not move.
One small note, not blocking: the walk also starts from the MEMBER parent's MAP, so with the flag set, a bare prototype INCLUDEd into the PROGRAM file's MAP also counts. That declares a procedure implemented in the PROGRAM module, so in that (rare) case the card would link another module's prototype instead of warning. Worth a comment in the code if you touch it again.
Your call-site observation (Worker() missing a bare prototype in a MAP-included file) is real; I've opened an issue for it, it's yours if you'd like it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…#648) #609 phase 3, the PARENT half of #626. Hover and F12 took a PARENT member from ClassMemberResolver.findParentClassMemberInfo, which has no equivalent of MemberLocatorService.preferFittingInheritedOverload. So where the parent declares a method only with parameters the call does not pass, they named that one instead of the inherited overload the call runs: every generated report calls PARENT.Init() with no arguments under the vendor's ReportManager, whose only Init takes a required ProcessClass, and that runs WindowManager.Init(). MemberLocatorService.resolveParentClassAt names PARENT's class once, the way SELF's is named (#622) and PARENT. completion names it (#628, line-aware), with the parent's MODULE file as a body-search hint. Hover's resolveParentMethodCall and both of F12's PARENT branches (with and without parentheses) now name the class through it and ask findMemberInClass, as SELF and explicit receivers do. The arg-classify overlay stays in front, unchanged. Red first (ParentOverloadRule648.test.ts): hover and F12 on a no-argument PARENT.Init() named Mid.Init(LONG a). Controls green throughout: Ctrl+F12 on the same call (right since #643), PARENT.Kill() reaching the direct parent's fitting override, PARENT.Init(1) reaching Mid.Init(LONG a). Sweep against the post-#641 run: 1 changed, the no-argument PARENT.Init() under ReportManager, wrong-target -> agree; nothing else moved. The Ctrl+F12 table now has no disagreement. Server 3476 passing / 0 failing / 3 pending. (The #290 SDI disk-cache test failed once on a slow full run and passed 3/3 alone; it also timed out once this morning. Not this change.) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(hover): a procedure label with no MAP prototype answers as the procedure
Branch
fix/unprototyped-procedure-label-hover, based onorigin/version-1.0.6tip6b760ce2.What happened
Hovering the label of a standalone procedure that has no MAP prototype showed an unrelated symbol that happens to share its name, instead of the procedure. With an EQUATE of the same name reachable through the include chain, such as an ITEMIZE entry:
hovering
Workeron its ownPROCEDUREline rendered the include's EQUATE:The prototyped
Helperon the line above was unaffected.Root cause
ProcedureHoverResolver.resolveProcedureImplementation(HoverRouter step 5) builds its card only when it finds a MAP declaration, either in the current file or through the MEMBER parent:With no prototype anywhere it returned
null, even though the line had already matchedPROCEDURE_IMPLEMENTATIONwith the cursor on the name. Routing then continued through the later router steps and into HoverProvider's variable tiers, which match the bare name against anything reachable, and the include's EQUATE answered.Passing
nullfor the MAP declaration toHoverFormatter.formatProcedurewould not have been enough on its own, because it returnsnullfor a header-only card:With no prototype to preview, and the implementation link omitted because the cursor is already on the implementation, the header is the only part.
Fix
When no MAP declaration is found, the resolver now answers with the procedure itself and says that no prototype was found:
The note adds something the line itself doesn't show.
formatProcedureis still tried first, so a doc comment on the procedure produces the normal card.The fallback applies only when the hovered line carries a procedure token of subtype
GlobalProcedure. A method declared in a CLASS or INTERFACE body matches the samePROCEDURE_IMPLEMENTATIONregex, has no MAP entry either, and must keep reaching the method-declaration tier (step 7) as before. Without the guard, six existing local-CLASS method-declaration hover tests fail, which is what the guard protects.Second commit: the label must be at column 0
A second, narrower gap sits in the same function.
PROCEDURE_IMPLEMENTATIONaccepted leading whitespace on the label:A Clarion label must start at column 0 - confirmed elsewhere in this codebase against the real compiler, which desyncs entirely on an indented one. So an indented, non-compiling label still matched, and got the same full procedure card as a valid one on the line above it - MAP prototype preview and all, when one existed. The first commit's no-prototype fallback inherited the same gap by construction: an indented label with no MAP entry got the new procedure card too, just as wrongly.
Tightened the pattern to require the label at column 0 and updated the one call site's capture-group index (the leading-whitespace group is gone). An indented line no longer matches at all, so the resolver correctly has nothing to say about it.
For the no-MAP case with the label indented, the hover now falls through the router to a pre-existing, unrelated defect: a bare word can still resolve to a same-named EQUATE from an unrelated include regardless of what the hovered line actually is. That's not specific to a procedure declaration - it would equally mismatch any bare undeclared identifier - and is a broader change than this PR's scope, so it is called out here rather than folded into the diff.
Testing
New
ProcedureHoverResolver.UnprototypedLabel.test.ts, written to real files on disk (a.clwthat INCLUDEs an.incholding the ITEMIZE):Worker PROCEDUREis the procedure card with the missing-prototype note, and contains no EQUATE.Helper PROCEDURE()keeps its MAP declaration card, with theHelper(),LONGpreview and no missing-prototype note.Proved non-vacuous, both commits: with each fix stashed in turn and a clean rebuild, its own tests fail with the reported symptom, and pass again once restored. With both restored, the full server suite is 3381 passing, 0 failing, 3 pending, and the client suite is 307 passing.
Scope
Two source files (
ProcedureHoverResolver.ts,ClarionPatterns.ts), one new test file.Open questions, left rather than folded into this fix:
**Worker** (Procedure). WhenformatProcedurebuilds a card, it replaces that with a scope badge (📦 Module Procedureor🌍 Global Procedure) worked out from the file's first statement. The fallback doesn't compute that badge - adding it means moving the badge logic into a helper both paths share, touchingHoverFormatter, which every procedure hover goes through.