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:
co-authored by
Claude Opus 4.8
parent
3424ff36c5
commit
358f400c40
@@ -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) {
|
||||
|
||||
@@ -1120,6 +1120,22 @@ function localReceiverTypePatterns(language: Language, r: string): RegExp[] {
|
||||
return [
|
||||
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
||||
];
|
||||
case 'lua':
|
||||
case 'luau':
|
||||
return [
|
||||
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\.new\\b`), // local lg = Logger.new()
|
||||
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\s*\\(`), // local lg = Logger(...) (callable table)
|
||||
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // Luau: local lg: Logger / typed param
|
||||
];
|
||||
case 'r':
|
||||
return [
|
||||
new RegExp(`\\b${r}\\b\\s*(?:<-|<<-|=)\\s*([A-Z][\\w.]*)\\$new\\b`), // lg <- Logger$new() (R6)
|
||||
];
|
||||
case 'pascal':
|
||||
return [
|
||||
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w]*)`), // var lg: TLogger / param lg: TLogger
|
||||
new RegExp(`\\b${r}\\b\\s*:=\\s*([A-Z][\\w.]*)\\.Create\\b`), // lg := TLogger.Create
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@@ -1196,20 +1212,33 @@ export function matchMethodCall(
|
||||
// `Guard.Against.X()`) matched no pattern and never resolved.
|
||||
const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/);
|
||||
const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
|
||||
// Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`).
|
||||
// Recognize these receiver/method separators so local-variable receiver-type
|
||||
// inference (#1108) applies to them too — extraction already emits the ref in
|
||||
// this shape, but the resolver otherwise only understood `.` and `::`.
|
||||
const luaColonMatch = (ref.language === 'lua' || ref.language === 'luau')
|
||||
? ref.referenceName.match(/^([\w.]+):(\w+)$/)
|
||||
: null;
|
||||
const rDollarMatch = ref.language === 'r'
|
||||
? ref.referenceName.match(/^([\w.]+)\$(\w+)$/)
|
||||
: null;
|
||||
|
||||
const match = dotMatch || colonMatch;
|
||||
const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [, objectOrClass, methodName] = match;
|
||||
// A simple `receiver.method` / `receiver:method` / `receiver$method` shape whose
|
||||
// receiver type we can try to infer from its local declaration.
|
||||
const inferableReceiver = dotMatch || luaColonMatch || rDollarMatch;
|
||||
|
||||
// Infer the receiver's type from its local declaration/initializer in the
|
||||
// enclosing scope, then resolve the method on that type (#1108). C++ keeps its
|
||||
// dedicated inferrer (header scan + `auto`); every other language uses the
|
||||
// shared source-based inferrer. resolveMethodOnType validates the method
|
||||
// exists on the inferred type, so a mis-inference produces no edge.
|
||||
if (dotMatch) {
|
||||
if (inferableReceiver) {
|
||||
const inferredType =
|
||||
ref.language === 'cpp'
|
||||
? inferCppReceiverType(objectOrClass!, ref, context)
|
||||
|
||||
Reference in New Issue
Block a user