Skip to content

[Bug]: C# namespaces are absent from the graph identity, making namespace-sensitive call resolution undecidable #946

Description

@merlincat11

Summary

C# namespaces are not represented in the graph's C# identity. parent_name holds only the containing-type path, and the only namespace evidence is csharp_namespaces_by_file — a set of every namespace a file declares. The resolver therefore cannot establish which namespace a given type actually sits in, which makes namespace-sensitive call resolution undecidable in both directions: it fabricates edges for types the caller cannot name, and it cannot safely reject them without also rejecting valid calls.

This is long-standing behaviour, not a regression. It became visible during review of #937 (nested type identity, #934), where four consecutive rounds each closed one instance of it. It should be fixed at the cause.

Why the current evidence is insufficient

Given:

// Indexed.cs
namespace Other;

public class App
{
    public class Report
    {
        public class ExportHandler
        {
            public void Run() {}
        }
    }
}

the method is stored as:

parent_name = App.Report.ExportHandler
file        = Indexed.cs
namespaces  = {"Other"}          # per file, not per node

Other has been erased from the key. A caller meaning an entirely different App.Report.ExportHandler — typically from a referenced assembly that was never indexed — matches this node exactly.

Failing cases

All of these reproduce on main. Each was verified during #937 review.

  1. Exact path, wrong namespace. Receiver App.Report.ExportHandler, candidate above. main resolves it (on the bare name ExportHandler); fix(parser): preserve full C# nested type identity #937 narrows it to requiring the whole containing path to coincide, but still resolves it. Should be unresolved.

  2. Same file, different namespaces. File co-location does not imply visibility in C#; a type in namespace Other is not nameable from namespace ConsumerNamespace in the same file without a using. Currently resolves.

  3. Multi-namespace file. A file declaring both App and Other cannot show which namespace encloses a candidate. fix(parser): preserve full C# nested type identity #937 handles this conservatively — it requires the dropped namespace to be the file's only namespace — so genuine matches in such files are left unresolved. A false negative, chosen over a fabricated edge.

  4. using scoped to a namespace body. imports_by_file collapses every IMPORTS_FROM edge in a file into one set, but the spec scopes an ordinary using to its immediately containing compilation unit or namespace body. A using Other; inside namespace A is currently indistinguishable from one that applies to namespace B in the same file.

  5. Project-wide global using. A global using Other; in a separate file applies to every compilation unit. Any visibility check keyed on the caller's own file will wrongly reject these.

Cases 1, 2 and 4 over-permit; 3 and 5 under-permit. They share one cause.

Prior attempt

_csharp_namespace_visible() (2fb7b6d, reverted in 01371fe) tried to decide visibility from file-level evidence: same file, global namespace, a using in the caller's file, or a namespace the caller declares. It simultaneously

  • regressed case 5 — a valid global using call resolved on main and became unresolved, and
  • failed to fix cases 1, 2 and 4 — still over-permitting.

Being wrong in both directions at once is the evidence that the granularity, not the rule, is the problem. It was reverted rather than extended.

Proposed direction

Persist the declaring namespace per type/method rather than per file, and resolve against (namespace, containing-type path) instead of the containing-type path alone.

Open design questions worth settling before implementation:

  • Identity vs. sidecar. Whether the namespace joins qualified_name (a C# identity-format change, so it needs the CSHARP_IDENTITY_VERSION rebuild gate that fix(parser): preserve full C# nested type identity #937 already uses) or lands as a separate indexed column resolved against. The sidecar is cheaper and avoids re-spelling every C# node; the identity change makes wrong-namespace collisions impossible by construction.
  • Scoped usings. IMPORTS_FROM needs to record the namespace body a using applies to, otherwise case 4 stays undecidable.
  • Global usings. These need to be collected project-wide and unioned into every compilation unit's visible set, otherwise fixing cases 1–4 will regress case 5 exactly as the reverted attempt did.
  • Conservative default. When evidence is still insufficient, leave the call unresolved rather than guess — consistent with the resolver's existing policy.

Existing coverage

Two tests on the #937 branch already pin the boundary and should be honoured by any fix:

  • test_exact_path_match_should_check_the_candidate_namespace — currently xfail, documents case 1, flips to passing when this is fixed
  • test_global_using_target_still_resolves — guards case 5 against a repeat of the reverted approach

Context

Activity

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