You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: vbnet: cross-file and VB -> C# calls produce no calls edges (Module functions, typed locals, C# statics) #4570
I have checked the Troubleshooting section in the README
What happened?
In a mixed C# / VB.NET solution, the graph contains calls edges between VB.NET methods only when caller and callee are in the same file. These call forms produce no edge:
An unqualified call to a Module function declared in another file (LogIt("x")). In VB.NET, Module members are in scope without qualification, so this is the most common call form in legacy VB code.
A module-qualified call to a function declared in another file (Helpers.LogIt("x")). fix(vbnet): resolve type/module-qualified calls to their target method #3909 fixed this for a module in the caller's own file. It lists "a type declared only in another file" as a limitation. In this repro Helpers.LogIt has exactly one candidate in the whole corpus.
A VB.NET call to a C# static method (CsRules.IsLocked(1)). The reverse direction works: a C# call to a VB Module function (Helpers.LogIt("x") from a .cs file) does get a calls edge.
Expected: a calls edge for each of the four cases above, since each has exactly one candidate target in the corpus.
Impact: in a VB.NET codebase most calls cross files, so affected, path and explain miss most real callers. Callers of C# code from VB code are invisible, even though the opposite direction is present.
Steps to reproduce
Four files in one folder:
`Helpers.vb`
Public Module Helpers
Public Sub LogIt(msg As String)
End Sub
End Module
`Worker.vb`
Public Class Worker
Public Sub Save()
End Sub
End Class
`Caller.vb`
Public Class Caller
Public Sub A_SameFileSelf()
B_Target()
End Sub
Public Sub B_Target()
End Sub
%
Error output or graph output
FOUND A_ -> B_Target
MISSING C_ -> LogIt
MISSING D_ -> LogIt
MISSING E_ -> Save
MISSING F_ -> IsLocked
FOUND G_ -> LogIt
Graphify version
0.9.92
Operating System
Windows
Python Version
3.13
Installation Method
uv tool install (recommended)
Additional Environment Details
GRAPHIFY_BACKEND=none, --code-only; no provider API keys set. Reproduced in a fresh folder that contains only the four files above.
Additional context
resolve_vbnet_partial_calls (registered as vbnet_partial_calls) is the only cross-file resolver for .vb. VB raw calls that are not partial-type calls appear to stay unresolved after extraction.
The cause is in extractors/vbnet.py. For each call, the extractor looks up the type that owns the target, but it only knows the types declared in the same file. If the owner is a Module in another file, a local variable or a C# type, the extractor drops the call, so no cross-file resolver ever sees it. The only VB resolver, resolve_vbnet_partial_calls, handles Partial types. The shared name pass skips VB calls because they are marked as member calls. An unqualified LogIt("x") does get through, but with the caller's own type as owner, so the resolver looks for Caller.LogIt and finds nothing.
My first repro was missing Helpers.vb, so case G looked broken. With the file present, G works on v8, as you said.
I checked each lookup rule against the VB compiler (dotnet SDK 8.0) before writing the fix:
Case
Compiler binds to
unqualified call to a Module in another file, same namespace
the Module
Module in another namespace, no Imports
error BC30451
the same with Imports, or with the Module in an enclosing namespace
the Module
the caller's class or its base class has a LogIt
the class method, at any arity
two Modules declare LogIt, even with different arities
error BC30562
a field, property or nested type with the same name
that member, not the Module
Dim w As New Worker(), As Worker = New Worker(), Dim w = New Worker()
Worker.Save
CsRules.IsLocked(1) or csrules.islocked(1) from VB
the C# method
.Save() inside With w
w.Save, not the caller's Save
handler(1) where handler is a local Action(Of Integer)
the delegate, not a handler method
v8 gets the last two rows wrong. It binds both calls to the caller's own method of the same name.
The fix adds a VB resolver that follows these rules. It makes an edge only when exactly one target matches, and it never falls back to a name match across the corpus. A class whose base is outside the corpus, such as Inherits System.Windows.Forms.Form, still gets its Module calls, marked INFERRED because the base could declare the same name.
Not covered:
receivers typed by a field;
With block targets;
project-level Imports and RootNamespace from the .vbproj.
Two separate bugs turned up:
the C# member resolver ignores arity when calling an overloaded VB Module;
Activity
github-actions commentedon Oct 19, 2026
Thanks for opening this issue, @TerraVenil. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
Deepjyoti-Sarmah commentedon Oct 19, 2026
Reproduced on v8 (0.9.92).
The cause is in
extractors/vbnet.py. For each call, the extractor looks up the type that owns the target, but it only knows the types declared in the same file. If the owner is a Module in another file, a local variable or a C# type, the extractor drops the call, so no cross-file resolver ever sees it. The only VB resolver,resolve_vbnet_partial_calls, handlesPartialtypes. The shared name pass skips VB calls because they are marked as member calls. An unqualifiedLogIt("x")does get through, but with the caller's own type as owner, so the resolver looks forCaller.LogItand finds nothing.My first repro was missing
Helpers.vb, so case G looked broken. With the file present, G works on v8, as you said.I checked each lookup rule against the VB compiler (dotnet SDK 8.0) before writing the fix:
ImportsImports, or with the Module in an enclosing namespaceLogItLogIt, even with different aritiesDim w As New Worker(),As Worker = New Worker(),Dim w = New Worker()Worker.SaveCsRules.IsLocked(1)orcsrules.islocked(1)from VB.Save()insideWith ww.Save, not the caller'sSavehandler(1)wherehandleris a localAction(Of Integer)handlermethodv8 gets the last two rows wrong. It binds both calls to the caller's own method of the same name.
The fix adds a VB resolver that follows these rules. It makes an edge only when exactly one target matches, and it never falls back to a name match across the corpus. A class whose base is outside the corpus, such as
Inherits System.Windows.Forms.Form, still gets its Module calls, marked INFERRED because the base could declare the same name.Not covered:
Withblock targets;ImportsandRootNamespacefrom the.vbproj.Two separate bugs turned up:
PR to follow.