Skip to content

C#: calls through an interface are no longer resolved since 1.6.1 (interface members extracted as private) #2164

Closed

Description

@outofrange-consulting

Summary

Since 1.6.1, a C# call through an interface-typed receiver is no longer resolved. This covers fields, parameters and primary-constructor parameters. The call ends up in unresolved_refs instead of producing a calls edge. Nearly all service-to-service calls in dependency-injected .NET code look like this, so most callers of interface methods disappear from codegraph_callers and codegraph_impact.

The problem is still present on main (ca3bb44).

Reproduction

// Orders.Application/OrderService.cs
namespace Orders.Application;
public interface IOrderService
{
    void CancelOrder(int id);
}

// Orders.Api/OrderController.cs
namespace Orders.Api;
public class OrderController(IOrderService service)           // primary constructor
{
    public void Cancel(int id) => service.CancelOrder(id);
}

// Orders.Api/FieldController.cs
public class FieldController
{
    private readonly IOrderService _service;
    public FieldController(IOrderService service) { _service = service; }
    public void Cancel(int id) => _service.CancelOrder(id);   // field
}

// Orders.Api/ParamController.cs
public class ParamController
{
    public void Cancel(IOrderService service, int id) => service.CancelOrder(id);   // parameter
}
npx @colbymchenry/codegraph@<version> init -y .
sqlite3 .codegraph/codegraph.db "SELECT s.qualified_name, t.qualified_name FROM edges e
  JOIN nodes s ON s.id = e.source JOIN nodes t ON t.id = e.target
  WHERE e.kind = 'calls' AND s.file_path LIKE 'Orders.Api/%'"
Version calls edges from Orders.Api
1.6.0 3 (all three controllers → IOrderService::CancelOrder)
1.6.1 0 (service.CancelOrder / _service.CancelOrder in unresolved_refs)
main @ ca3bb44 0

Root cause

git bisect between v1.6.0 and v1.6.1 identifies 2c251e2, "fix(resolution): a definition its language makes file-local is not a cross-file target (#1731) (#1745)".

That commit declines cross-file targets with visibility === 'private' for csharp, which is in PRIVATE_IS_FILE_LOCAL. The C# extractor, however, labels every member without an access modifier as private (src/extraction/languages/csharp.ts):

getVisibility: (node) => {
  ...
  return 'private'; // C# defaults to private
},

That default is correct for class and struct members, but not for interface members. They are implicitly public, and they almost never carry a modifier. In a 1.6.0 index, every interface method has visibility = 'private':

method  Orders.Application::IOrderService::CancelOrder   private
method  Orders.Application::OrderService::CancelOrder    public

So since #1745, every interface method is treated as file-local, and every call through an interface from another file is dropped.

Suggested fix

In the C# getVisibility, return public for a member without a modifier when its enclosing declaration is an interface_declaration. Two related cases have the same problem:

  • Top-level types without a modifier. Their C# default is internal, not private, so they are visible to every file in the assembly.
  • Explicit interface implementations (void IOrderService.CancelOrder(int id)). They have no modifier but are reachable through the interface.

A regression test would be the reproduction above: an interface in one file, called through a field, a parameter and a primary-constructor parameter in another file. It would also be worth re-running the #1745 corpora with a C# project, since the table in that commit has no C# corpus.

Environment

  • @colbymchenry/codegraph 1.6.1, and main @ ca3bb44
  • Linux, Node 24.18, wasm extraction

Activity

  1. maxmilian commented on Oct 1, 2026

    @maxmilian
    Contributor

    Verified fixed on main: I ran the reproduction from this issue (all three controllers — primary-constructor parameter, field, and parameter) against a build of main @ 17993905, and all three calls edges to Orders.Application::IOrderService::CancelOrder are back:

    Orders.Api::OrderController::Cancel|Orders.Application::IOrderService::CancelOrder
    FieldController::Cancel|Orders.Application::IOrderService::CancelOrder
    ParamController::Cancel|Orders.Application::IOrderService::CancelOrder
    

    The same project on the published 1.6.1 still gives 0, as reported. The fix is f9530d7 (#2171), which makes C# interface members default to public (and namespace-level types to internal) in src/extraction/languages/csharp.ts, so the #1745 file-local rule no longer drops them. #2171 didn't reference this issue, so it stayed open; it should be resolved once the next release ships.

  2. colbymchenry commented on Oct 2, 2026

    @colbymchenry
    Owner

    Thanks for the precise bisect! This is fixed on main by #2171, which makes C# interface members default to public (and namespace-level types to internal), so the file-local rule no longer drops calls through an interface. I re-ran your three-controller reproduction against current main: all three calls edges to IOrderService::CancelOrder are back. It ships in the next release; re-index C# projects after upgrading.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions