Skip to content

fix(pipeline): field_type_hint binds by receiver variable-name substring, not declared type — 1484 edges at conf 0.85 on one Go repo #1927

Open

Description

@ilyabrykau-orca

fix(pipeline): field_type_hint binds by receiver variable-name substring, not declared type

Status: fix open — PR #1936 (owning-segment equality, fth_owner_segment_is; stacked on #1907→#1913→#1915). Measured: field_type_hint 1484 → 356, single-character receivers 376 → 0, Go→C/C++ 34 → 0, nothing relocated to other strategies. Other strategies before/after: suffix_match 113 → 114, unique_name 1573 → 1573, same_module 224 → 224, all lsp_* 10672 → 10672; CALLS total 14316 → 13189 — the 1128 removed edges were fabricated on top of already-failed resolutions, so nothing absorbed them.

What the code does

try_field_type_hint() (src/pipeline/pass_parallel.c, parallel resolver only): split callee_name at the first . → receiver identifier text; strip _/m_, uppercase the first letter → type_name; also build iface_name = "I"+type_name; then accept the first same-named candidate where strstr(candidate_qn, type_name) or strstr(candidate_qn, iface_name) hits, overwriting the resolution at confidence 0.85 as field_type_hint.

That is a raw substring test on the QN — no declared type, field type, or LSP type is ever read. For a single-letter receiver (t, f, w, c, m) type_name is one capital letter and matches almost any QN, the method name included ("F" ⊂ "…FileStore.Close").

The Go weak-match guard (#1906/#1907) deliberately keeps field_type_hint on the stated premise that it is receiver-aware for Go. The implementation does not meet that premise; the pinning test (lrp_go_s8_field_type_hint, repo/Repo) passes for the wrong reason (its GREEN path is lsp_cross).

Damage measured (~1150-file Go+C repo)

v0.10.8 with #1906/#1909/#1913/#1915 fixes
CALLS total 19653 14316
field_type_hint 493 (2.5%) 1484 (10.4%)
single-character receiver — 376

The share tripled because #1906/#1913 collapsed suffix_match/same_module: calls that used to land in a visibly weak 0.30 bucket now present at 0.85. Concrete wrong binds: err.Error → MSIError.Error (61 edges, "Err" ⊂ "MSIError"), f.Close → a bpf2go-generated …PtraceObjects.Close (51), w.Write → a test-only failingWriter.Write (40), t.Name → a C++ mock field (20). 34 edges cross into C/C++.

Minimal reproduction

// a.go
package app

type Tracker struct{ n int }
func (tr *Tracker) Name() string { return "tracker" }
// a_test.go
package app

import "testing"

func TestSomething(t *testing.T) {
    if t.Name() == "" { t.Fatal("no name") }
}

t.Name() is (*testing.T).Name — external. Expected: no CALLS edge. Actual: edge to app.Tracker.Name at 0.85 ("T" ⊂ QN).

Detection recipe

SELECT json_extract(properties,'$.strategy') AS s, count(*)
FROM edges WHERE type='CALLS' GROUP BY 1 ORDER BY 2 DESC;

-- the indefensible core: single-character receivers
SELECT count(*) FROM edges
WHERE type='CALLS'
  AND json_extract(properties,'$.strategy')='field_type_hint'
  AND json_extract(properties,'$.callee') GLOB '?.*'
  AND json_extract(properties,'$.callee') NOT GLOB '*.*.*';

Fix

Landed option (1) of the original three: the candidate's owning dot-segment — the segment immediately before the method — must equal type_name or iface_name. Measured: kills every row above, keeps the variable-named-after-its-type case, moves 63 event.Get* edges onto the interface that declares the method, and the single-character class dies without a minimum-length gate. Residual: scanner.Scan (16 edges, owner genuinely named Scanner) — only a declared-type lookup (option 3, design-sized) can reject that; deliberately out of scope.

Related: #1906/#1907 (the guard whose premise this fixes), #1942 (same class on the reference side), #725, #1114; tracked in #1932.

Activity

  1. added 4 commits that reference this issue on Aug 30, 2026
    0f2024c
    6aac10c
    27d4d34
    ac46c17
  2. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions