Skip to content

[Bug]: vbnet: cross-file and VB -> C# calls produce no calls edges (Module functions, typed locals, C# statics) #4570

Open

Description

@TerraVenil

Pre-flight checks

  • 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:

  1. 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.
  2. 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.
  3. A call through a local whose type is declared in the same statement (Dim w As New Worker() then w.Save()). C# resolves the equivalent form by the receiver's declared type (csharp_member_calls, C#: method calls on a typed receiver (field/param/local.Method()) are not emitted as calls edges #1609). VB.NET leaves it unresolved.
  4. 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

Activity

  1. github-actions commented on Oct 19, 2026

    @github-actions

    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.

  2. Deepjyoti-Sarmah commented on Oct 19, 2026

    @Deepjyoti-Sarmah
    Contributor

    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, 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;
    • nested VB types are not extracted at all.

    PR to follow.

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 working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions