Skip to content

fix(vbnet): link fields and properties read or written through a value - #2377

Merged
colbymchenry merged 2 commits into
mainfrom
claude/inspiring-khayyam-86d563
Oct 6, 2026
Merged

colbymchenry merged 2 commits into
mainfrom
claude/inspiring-khayyam-86d563

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Problem

VB.NET linked a member read only through a type or module name (#2355). A read or write through an instance — a parameter x.Normal = 3, a local h.Normal, a field _h.Title, Me._h.Normal — produced no edge, so codegraph callers on an instance field or property listed almost none of its uses. #2355 left these out explicitly ("reads through Me / MyBase, … instance reads through a typed variable, … and With blocks").

Repro (Holder with Shared SharedCache, Normal, Property Title, Sub EndSession; User.Run(x As Holder) reading and writing them through x, _h, Me._h and a local h): on main only x.EndSession() links, and codegraph callers Normal finds nothing.

Fix

Extraction (src/extraction/tree-sitter.ts, TS only — VB.NET is not kernel-routed). Every member read or write is sent as one references ref carrying its receiver as a path, whatever the receiver's case:

  • x.Normal, Me._h.Normal, MyBase.Count, user.Settings.Value
  • .Value inside With x (nested With .Inner included), and .Switch in an object initializer New BoolParam With {.Switch = …}
  • {T}.X for a value whose type the code writes: DirectCast(o, T).X, New T().X

A With block's .Run() call, which recorded nothing before, is sent the same way. Not sent: the grammar's split of New System.Drawing.Size(…) into members read through New System, an anonymous type's New With {.X = 1}, and paths that start with System, Microsoft or My.

Resolution (src/resolution/vbnet-receivers.ts). Each link of the path is typed by the rules calls already use (#2351): declarations, casts, For Each over a typed collection, call results, type-parameter constraints, Module variables, namespaces, Imports and aliases. The read links the member the type declares or inherits.

  • A method named without parentheses is a call, unless AddressOf or NameOf only names it.
  • x.Items(0), an index that VB.NET writes as a call, reads Items.
  • An outside, Object-typed, ambiguous or untyped receiver links nothing. Nothing is guessed by name.

"Color Color" (theme As Theme): following the VB.NET spec, an instance member is read through the value, so only the member is linked. A Shared member, a Const (read from its declaration, as a VB.NET field node doesn't record it) or an Enum case still links the type too. #2355 linked the type for every such read.

membersNamed now finds a type's members through its parts' files instead of one SQLite query per member name. The graph is byte-identical on SCrawler. On staxrip, whose AutoCrop tool redeclares ServerInfo, 35 reads that main typed through the other project's field (and so left unlinked) now link, adding 70 edges.

Verification

  • New __tests__/vbnet-instance-member-refs.test.ts (11 tests): fails 9/11 on main; the 2 that pass only assert that nothing is linked. Passes with the fix.
  • vbnet-shared-member-refs.test.ts: one assertion now expects OtherSession::SessionId from Shadowed(ByVal appSession As OtherSession), which reads the parameter's Shared field through the instance. It still never links AppSession::SessionId.
  • All VB.NET suites, tsc, and the full suite pass: 462 files, 5,859 tests (22 native-kernel / platform-gated files skipped).
  • On the built CLI, the repro links every read and write, and codegraph callers lists Run for SharedCache, Normal and Title.

Validation (main → this branch)

SCrawler staxrip
nodes identical identical
edges 17,437 → 31,344 32,569 → 71,362
removed edges 1 585
self-loops 101 → 101 87 → 87
index CPU (5-run medians) 16.1 → 19.3 s 20.2 → 25.4 s
index wall 5.2 → 5.7 s 5.6 → 7.2 s
DB 28.1 → 43.2 MB 34.9 → 59.0 MB
  • Removed edges, each triaged against source. 319 are the same site and target with new metadata: Color Color reads of instance members, qualified-name → instance-method. The other 267 (staxrip) are the class links of those reads. A check of each member's declaration confirmed all 267 sat beside instance members (228 properties, 39 fields), none Shared or Const.
  • Added edges.
    • About 42k added two-link reads were re-derived from the source text by a separate check. Its 837 disagreements were all its own blind spots: type-parameter constraints, Imports aliases (TDJob, PKV), the nearest of two Dim param in sibling blocks, call-result and For Each typing, and staxrip's global Public p As New Project. None of the 705 p reads has a shadowing binding in its member.
    • No added edge crosses between staxrip and its AutoCrop tool.
    • With-block calls and methods run without parentheses were spot-checked against their declarations.
  • Cost. Profiling puts it in resolution, which now handles roughly twice the refs. The DB growth is the new edges plus reads through outside types (WinForms controls, String), kept as failed refs for re-resolution like failed calls (Incremental sync misses cross-file relations after a new export is added #1240).

Not covered

  • Unqualified reads (Normal alone inside the class).
  • Reads through an index or a call result (list(0).X).
  • Handles clauses.
  • Calls through a chained receiver written directly (g.MainForm.Update()), which still resolve by name: 24 / 178 such edges on main, and the samples checked were correct.

🤖 Generated with Claude Code

colbymchenry and others added 2 commits October 6, 2026 09:36
VB.NET linked a member read only through a type or module name (#2355),
so a read or write through an instance -- a parameter `x.Normal = 3`, a
local `h.Normal`, a field `_h.Title`, `Me._h.Normal` -- produced no edge,
and `codegraph callers` on an instance field or property listed almost
none of its uses.

- Extraction (TS only; VB.NET is not kernel-routed): every member read or
  write is sent with its receiver as a path, whatever the receiver's case:
  `x.Normal`, `Me._h.Normal`, `MyBase.Count`, `.Value` in a `With` block
  (nested `With .Inner` included), `.Switch` in an object initializer
  `New BoolParam With {...}`, and `{T}.X` for a value whose type the code
  writes (`DirectCast(o, T).X`, `New T().X`). A `With` block's `.Run()`
  call, which recorded nothing, is sent the same way. The grammar splits
  `New System.Drawing.Size(...)` into members read through `New System`;
  those, anonymous types' `New With {.X = 1}`, and paths that start with
  System, Microsoft or My are not sent.
- Resolution (vbnet-receivers.ts): each link of the path is typed by the
  rules calls use since #2351 -- declarations, casts, `For Each` over a
  typed collection, call results, type-parameter constraints, Module
  variables, namespaces and Imports -- and the read links the member the
  type declares or inherits. A method named without parentheses is called
  (unless AddressOf or NameOf only names it), and `x.Items(0)`, an index
  that VB.NET writes as a call, reads `Items`. An outside, `Object`-typed,
  ambiguous or untyped receiver links nothing; nothing is guessed by name.
- "Color Color": a value named like its type (`theme As Theme`) now reads
  an instance member through the value, so only the member is linked; a
  Shared member, a Const (read from its declaration) or an Enum case still
  links the type as well, as #2355 did for every such read.
- membersNamed looks a type's members up in its parts' files rather than
  with one query per member name. The graph is identical on SCrawler; on
  staxrip, whose AutoCrop tool redeclares `ServerInfo`, 35 reads that
  main typed through the other project's field, and so left unlinked,
  now link (70 edges).

Verification: vbnet-instance-member-refs.test.ts (11 tests) fails 9/11 on
main and passes; one vbnet-shared-member-refs assertion now expects
`OtherSession::SessionId` from `Shadowed(appSession As OtherSession)`;
the VB.NET suites, tsc and the full suite pass (462 files, 5,859 tests;
22 kernel/platform files skipped). The issue's repro on the built CLI
links every read and write, and `codegraph callers` lists `Run` for
`SharedCache`, `Normal` and `Title`.

Validation, main vs this branch: nodes unchanged, self-loops unchanged.
SCrawler 17,437 -> 31,344 edges, 1 removed (same edge, new metadata).
staxrip 32,569 -> 71,362 edges, 585 removed: 318 same edge with new
metadata (Color Color reads of instance members, qualified-name ->
instance-method) and 267 class links of those reads, every one beside an
instance member. About 42k added two-link reads were re-derived from the
source text by a separate check; its 837 disagreements were all its own
blind spots (constraints, Imports aliases, sibling-block locals, call
results, staxrip's global `p`). No added edge crosses between staxrip
and its AutoCrop tool. Cost (5-run medians): CPU 16.1 -> 19.3 s and
20.2 -> 25.4 s, DB 28.1 -> 43.2 MB and 34.9 -> 59.0 MB; the DB growth is
the new edges plus reads through outside types kept as failed refs for
re-resolution (#1240).

Not covered: unqualified reads, reads through an index or a call result
(`list(0).X`), Handles clauses, and calls through a chained receiver
written directly (`g.MainForm.Update()`), which still resolve by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yam-86d563

# Conflicts:
#	CHANGELOG.md
#	src/resolution/index.ts
@colbymchenry
colbymchenry merged commit 5a11cd7 into main Oct 6, 2026
@colbymchenry
colbymchenry deleted the claude/inspiring-khayyam-86d563 branch October 10, 2026 06:15
colbymchenry added a commit that referenced this pull request Oct 21, 2026
… run the parameterless overload (#2762)

Three kinds of VB.NET member read that #2355 and #2377 left unlinked:

- A method named without parentheses (`Me.Load`, `Calc.GetTargetDAR`,
  a constructor's `Me.New` / `MyBase.New`) went to the first overload
  declared, so `Me.Load` reached `Load(path)` instead of
  `Load(Optional force = False)`, and a `Me.New` whose first overload was
  the calling constructor itself linked nothing.
- A read whose first name is a namespace (`Billing.Invoice.MaxLines`,
  `StaxRip.UI.ButtonEx.ButtonSymbol.Delete` with the root namespace in front,
  a Module's `RedGifs.RedGifsSiteKey`) linked nothing: the receiver was
  looked up as a value or a type only.
- A field, property or Module variable named bare before the dot
  (`Button1.Text`, `Settings.Plugins`, staxrip's global `p.TargetWidth`) was
  not counted as read: a bare name is no member access, so only what is read
  through it could link, and nothing when that is .NET's (`Text`).

Fix (src/resolution/vbnet-receivers.ts; resolution only, extraction output
unchanged):

- A method a reference runs without parentheses resolves among its type's
  overloads to one that takes no arguments (none, or only Optional and
  ParamArray parameters, read from the declaration), never the member the
  reference is in. AddressOf / NameOf keep the first overload, as before.
- A receiver name that is neither a value nor a type is looked up as a
  namespace the way VB.NET does: nested in a namespace around the read,
  outward to the root namespaces, then in the file's and project's Imports.
  Each further link is a type declared in it, a nested namespace, or a
  member of one of its Modules. The read links the type (`Billing.Invoice`)
  or the member (`Assets.Logo`), as #2355's `Outer.Mode` reads do.
- A bare name before the dot that VB.NET binds to a field, property or
  Module variable (not a local or parameter, which hide it) is linked as a
  read alongside what is read through it. Not for a `With` block's `.Text`,
  a method run through the name (the call is the method's alone, as with
  parentheses), another project's Module, or a member declared with an
  outside type of its own name (`Responser As Responser`), whose Shared
  members and nested types VB.NET reads through the type; a project type of
  its own name follows #2377's Color Color rule.
- membersNamed looked a type's members up in every project's same-named
  type and memoised the list by qualified name alone, so SCrawler's two
  plugin `UserData` classes shared one list ordered by whichever was asked
  first. It now keeps to the type's own project. Alone this leaves main's
  graph unchanged on SCrawler and staxrip; without it, 17 of the new
  bare-name reads in one plugin linked the other plugin's members.

Dropped from the 2026-10-04 test set: a method named through its class
also linking the class (#2355 treats it as a call, and its test pins that);
`AppSession.SessionId` through a property typed OtherSession linking
nothing (#2377 links OtherSession's field, pinned the same way);
`SCrawler.My.Resources...` reads (#2355/#2377 send no path that starts with
`My` or `Global`); and same-named classes picked by namespace, Imports and
project, which main already does once the fixture declares its
RootNamespace.

Verification: new vbnet-member-access-scope.test.ts (9 tests) fails 6/9 on
main and passes; a mutation that restores main's membersNamed fails the
own-project test. The other VB.NET, C#, extraction, resolution, sync and
framework suites pass; tsc and build clean.

Validation, main vs this branch (indexes from each arm's own build; nodes
unchanged; self-loops unchanged; serilog and gin dumps byte-identical):

- SCrawler 31,201 -> 36,378 edges. 1 removed: `COLOR_PICKER.ColorsGet`
  re-pointed from the two-argument overload to `ColorsGet() As DataColor`.
  Added: 4,973 bare-name reads (3,731 own class, 957 Module variables,
  285 inherited), 192 namespace reads, 12 reads through a Module member
  reached by namespace, and that 1 overload.
- staxrip 70,864 -> 83,909 edges, 0 removed. Added: 12,983 bare-name reads
  (8,991 own class, 3,820 Module variables, 172 inherited), 61 reads with
  the root namespace in front, and `box.Show` reaching
  `Function Show() As DialogResult` from its Shared overload.
- Checked against source: every bare-name read's name starts the access,
  has no local of that name, and binds to its class, an ancestor (23
  through a qualified `Inherits Instagram.UserData` the graph's own extends
  edge misses) or a Module of its project; every namespace read's path ends
  its target's full name and the target's project is the reader's or one it
  references; no edge crosses between staxrip and its AutoCrop tool; 48
  sampled edges hand-checked, all correct.
- Cost (5 interleaved rounds, medians, quiet machine): SCrawler wall
  5.60 -> 5.75 s, CPU 19.5 -> 20.3 s, DB 43.4 -> 45.1 MB; staxrip wall
  7.23 -> 7.11 s (noise), CPU 25.7 -> 26.3 s, DB 59.0 -> 64.4 MB, the
  growth being the new edges (unresolved refs fall 47,074 -> 43,607 and
  38,051 -> 36,165).

Not covered: a call through a bare name (`Button1.Focus()`) or a function
named before the dot, reads in a `With Button1` block, and `My.` /
`Global.` paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant