fix(resolution): infer typed-parameter receivers in TS/JS (#1125) (#1129)

The local-variable receiver-type inference from #1108/#1110 covered typed
parameters for every language except TypeScript/JavaScript (+ TSX/JSX). The
TS/JS `:`-annotation pattern required a leading `const|let|var`, so it only
matched a local's own annotation (`const lg: Logger`) and never a bare
parameter (`function use(lg: Logger)` / `(lg: Logger) =>`). With a second
class sharing the method name — the case where a same-name fallback can't
paper over it — `lg.log()` resolved to no edge, dropping it from callers and
impact/blast-radius. TS/JS is the most common language pair in the userbase,
so this was a real precision gap.

Replace the keyword-anchored pattern with the keyword-free
`\b${r}\b\s*:\s*([A-Z][\w.$]*)`, mirroring Kotlin/Swift/Scala. It's a strict
superset (still matches `const lg: Logger`) plus the typed-parameter case,
and the capture stops at `<` so a generic-typed param
(`repo: Repository<User>`) still yields `Repository`. resolveMethodOnType
already validates the inferred type declares the method, so the looser match
produces no edge on a mis-inference — the same safety net the other
languages rely on; Swift already ships this identical bare-colon pattern with
the same theoretical ternary/dict-literal exposure.

Adds a regression test using two ambiguous classes + typed params, asserting
each call routes to its OWN class's method (verified to fail without the fix
and pass with it — a single-class version would pass either way via the
same-name fallback, which is why the collision is load-bearing).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-02 12:07:25 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 7c7514f43f
commit 385001398b
3 changed files with 50 additions and 1 deletions
+8 -1
View File
@@ -1065,7 +1065,14 @@ function localReceiverTypePatterns(language: Language, r: string): RegExp[] {
case 'jsx':
return [
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), // = new Logger()
new RegExp(`\\b(?:const|let|var)\\s+${r}\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger
// No keyword requirement, so this matches BOTH a local annotation
// (`const lg: Logger`) and a typed parameter (`function use(lg: Logger)`
// / `(lg: Logger) =>`) — the parameter case the old `const|let|var`
// prefix excluded (#1125). Mirrors Kotlin/Swift/Scala; the capture stops
// at `<` so a generic-typed param (`repo: Repository<User>`) still yields
// `Repository`. resolveMethodOnType validates the type actually declares
// the method, so the looser match produces no edge on a mis-inference.
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger (annotation or typed param)
];
case 'python':
return [