feat(resolution): local-variable method calls in Lua, Luau, R, Pascal (#1112) (#1113)

Extends the local-variable receiver-type inference (#1108/#1110) to the
remaining supported languages with object-method calls. An empirical
sweep found Objective-C, Svelte, Vue, and Astro already resolved
`localVar.method()` (ObjC via message-send handling; the template langs
ride the TypeScript path), leaving Lua, Luau, R, and Pascal.

Lua/Luau/R were a resolution gap, not extraction: the call ref IS
extracted (`lg:log`, `lg$log`), but (1) the resolver's fast pre-filter
`hasAnyPossibleMatch` only understood `.`/`::` separators, so a `:`/`$`
ref was dropped before any strategy ran, and (2) matchMethodCall only
parsed `.`/`::` receivers with no local-var inference for these langs.
Fixes: pre-filter now checks the member/receiver around `:` and `$`;
matchMethodCall recognizes `lg:log` / `lg$log` and routes them through
the same inference + validated resolveMethodOnType path; and inference
patterns are added for Lua/Luau (`local x = T.new()` / `T()` / `x: T`),
R (`x <- T$new()`), and Pascal (`var x: T` / `x := T.Create`).

Pascal statement-form calls (`obj.Method;`) now resolve via the new
inference pattern. The assignment-RHS parameterless form
(`x := obj.Method`) is deliberately left as a field read by the existing
Pascal extractor — an intentional field-vs-call ambiguity tradeoff — so
it stays out of scope.

Validated with single-file and two-file same-name repros per language
(resolves to the right method; two-file is same-file-correct, #1079).
Adds all four to the local-variable inference test matrix. Full suite
green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-01 15:59:06 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 3424ff36c5
commit 358f400c40
4 changed files with 56 additions and 2 deletions
+16
View File
@@ -627,6 +627,22 @@ export class ReferenceResolver {
}
}
// Lua/Luau method calls use a single `:` (`lg:log`); R uses `$` (`lg$log`).
// Check the member (and receiver) around these separators too, so the ref
// isn't dropped here before the method-call resolver ever sees it. The `:`
// case is skipped when the name actually contains `::` (handled above).
for (const sep of [':', '$']) {
if (sep === ':' && name.includes('::')) continue;
const sepIdx = name.indexOf(sep);
if (sepIdx > 0) {
const receiver = name.substring(0, sepIdx);
const member = name.substring(sepIdx + 1);
if (this.knownNames.has(member) || this.knownNames.has(receiver)) return true;
const capitalized = receiver.charAt(0).toUpperCase() + receiver.slice(1);
if (this.knownNames.has(capitalized)) return true;
}
}
// For path-like references (e.g., "snippets/drawer-menu.liquid"), check the filename
const slashIdx = name.lastIndexOf('/');
if (slashIdx > 0) {