fix(extraction): qualified Type::member refs skip the name gate — no-import references resolve (#812)
`KtHandlers::handle` registered from another file produced no edge: the
extraction gate required the scope to be a same-file type or an IMPORTED
name, but Java/Kotlin same-package references and Kotlin companion members
need no import at all, so the gate could never see them. (The "companion
members extract unqualified" limit recorded during Arc A was a probe
artifact: a SINGLE-LINE `class X { companion object { … } }` is an
upstream tree-sitter-kotlin misparse (ERROR node); real multi-line
companions extract transparently as qualified methods of the class.)
Qualified `Type::member` candidates now skip the name gate the same way
`this.<member>` ones do: the explicit-ref syntax is self-selecting, and
resolution stays scope-suffix-anchored + unique-or-drop, so a
`Decoy::handle` can never match a `KtHandlers::handle` ref (tested).
A/B vs main: rxjava +4 (same-package `Maybe::just` / `Single::just`
method refs), fmt +3 (gtest `&Test::DeleteSelf_` /
`&TestSuite::RunSetUpTestSuite` cross-file member pointers), okio 0-delta,
redis byte-identical — every new edge verified genuine, zero calls edges
touched, node counts identical.
Full suite 1392 passed. EXTRACTION_VERSION 22 → 23 (re-index to benefit).
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
1f15f93feb
commit
dce61a5f4a
@@ -21,4 +21,4 @@
|
||||
* turns the re-index hint into noise — keep it honest (see CLAUDE.md, "Honesty
|
||||
* in the product is load-bearing").
|
||||
*/
|
||||
export const EXTRACTION_VERSION = 22;
|
||||
export const EXTRACTION_VERSION = 23;
|
||||
|
||||
@@ -435,15 +435,8 @@ export class TreeSitterExtractor {
|
||||
if (isGeneratedFile(this.filePath)) return;
|
||||
|
||||
const definedHere = new Set<string>();
|
||||
const definedTypes = new Set<string>();
|
||||
for (const n of this.nodes) {
|
||||
if (n.kind === 'function' || n.kind === 'method') definedHere.add(n.name);
|
||||
if (
|
||||
n.kind === 'class' || n.kind === 'struct' || n.kind === 'interface' ||
|
||||
n.kind === 'enum' || n.kind === 'trait' || n.kind === 'protocol'
|
||||
) {
|
||||
definedTypes.add(n.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Import-binding names only (all binding emitters push kind 'imports').
|
||||
@@ -493,31 +486,20 @@ export class TreeSitterExtractor {
|
||||
// strictly class-scoped (own members or the validated supertype
|
||||
// pass), so nothing fuzzy can leak.
|
||||
// - `Scope::member` (C++ member-pointers, Java/Kotlin type-qualified
|
||||
// method refs): the SCOPE name must be a type defined here or an
|
||||
// imported name (covers `OtherClass::method` cross-file), or the
|
||||
// member matches the plain gate (back-compat for C++ same-file).
|
||||
// method refs, PHP `'Cls::m'`): ALWAYS flush — the explicit-ref
|
||||
// syntax is self-selecting, the referenced type often needs NO
|
||||
// import (Java/Kotlin same-package, Kotlin companions), and
|
||||
// resolution is scope-suffix-anchored + unique-or-drop, so a
|
||||
// same-named member on another class can't match.
|
||||
// - C-family file-scope initializers skip the gate entirely
|
||||
// (constant-expression context — see FnRefSpec.ungatedModes).
|
||||
// - everything else: name ∈ same-file functions/methods ∪ imports.
|
||||
if (!c.name.startsWith('this.')) {
|
||||
if (!c.name.startsWith('this.') && !c.name.includes('::')) {
|
||||
const skipGate =
|
||||
(ungated?.has(c.mode) === true && atFileScope) ||
|
||||
c.skipGate === true; // PHP HOF-position string callables (see FnRefCandidate.skipGate)
|
||||
if (!skipGate) {
|
||||
if (c.name.includes('::')) {
|
||||
const scopeName = c.name.slice(0, c.name.indexOf('::'));
|
||||
const memberName = c.name.slice(c.name.lastIndexOf('::') + 2);
|
||||
if (
|
||||
!definedTypes.has(scopeName) &&
|
||||
!importedNames.has(scopeName) &&
|
||||
!definedHere.has(memberName) &&
|
||||
!importedNames.has(memberName)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
} else if (!definedHere.has(c.name) && !importedNames.has(c.name)) {
|
||||
continue;
|
||||
}
|
||||
if (!skipGate && !definedHere.has(c.name) && !importedNames.has(c.name)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const key = `${c.fromNodeId}|${c.name}`;
|
||||
|
||||
Reference in New Issue
Block a user