Skip to content

Namespace-aware C# type resolution and member-call receiver inference#1620

Open
TheFedaikin wants to merge 2 commits into
Graphify-Labs:v8from
TheFedaikin:v8
Open

Namespace-aware C# type resolution and member-call receiver inference#1620
TheFedaikin wants to merge 2 commits into
Graphify-Labs:v8from
TheFedaikin:v8

Conversation

@TheFedaikin

Copy link
Copy Markdown
Contributor

Summary

Adds two C# capabilities and splits the C# extractor out of the 16.6k-line
graphify/extract.py into dedicated modules:

  1. Cross-file type resolutionclass/interface/enum/struct/record
    references resolve to their real definitions across files, namespace- and
    using-aware, with lexical using-scope and qualified references.
  2. Member-call receiver resolutionreceiver.Method() calls become real
    calls edges by inferring the receiver's type, under a strict
    never-wrong-edge bar: any ambiguity is skipped, never guessed.

This supersedes #1609 (see below).

This builds on and improves upon #1609 (see below).

Relationship to #1609

#1609 ("C# receiver-typed member-call resolution") introduced the first C#
receiver-typed resolver: a file-wide, first-binding-wins name → type table with
this / capitalized-type / receiver-type resolution, inline in extract.py.
This PR builds on that foundation and improves upon it with a modular, tiered
implementation — it keeps every #1609 case working (including the
_server.Save() lowercase-field ambiguity) and adds more receiver tiers,
method-return var inference, lexical (rather than file-wide) scoping with
shadow/poison handling, and the module split. #1609's inline resolver is folded
into the modular version here, and its cases are carried into the 61-test suite
alongside the new tiers.

Cross-file type resolution

  • Type declarations carry their enclosing namespace (block, nested, and
    file-scoped namespace N;) and a lexical scope chain.
  • References resolve namespace- and using-aware: file-scoped using,
    namespace-scoped using, global using, and the global namespace.
  • Qualified references (N.Type) and unqualified references both resolve to
    the real definition, including cross-file inheritance (class Weapon : Damage).
  • Collision disambiguation — when the same type name is defined in multiple
    namespaces, a reference binds only to the unique candidate reachable from the
    referrer's usings; genuinely ambiguous references stay dangling rather than bind
    to an arbitrary definition.

Member-call receiver resolution

obj.M() resolves to the real method when the receiver's type is known or safely
inferable:

  • Declared localsService x = …; x.M()
  • Parameters, fields, properties, this.field — including inherited members
    through a single resolvable base chain
  • this.M() / implicit-enclosingM() inside a type
  • Static / dottedType.M(), Ns.Type.M()
  • Records — positional parameters
  • Method-return varvar x = Get(); x.M(), inferred from the callee's
    unique bare return type resolved in the callee's namespace context
    (emitted as INFERRED)

Everything else is skipped, not guessed: overloaded RHS, non-bare returns
(T[], T?, N.T, Box<T>, ref, tuples, type parameters, void),
reassigned/redeclared or shadowed locals, cross-file name collisions without a
unique resolution, extension methods, and chained receivers (a.b.c.M()).

Modular C# extractor (no behavior change from the split)

  • New extractors/csharp_extract.py — per-file C# extraction (binding/type-table/
    shadow model, type references, imports).
  • New extractors/csharp_resolve.py — the member-call resolver.
  • extractors/csharp.py — cross-file name resolver + corpus passes.
  • LanguageConfigextractors/base.py; C#-only logic lifted out of the shared
    _extract_generic core into fact-returning helpers.
  • extract.py re-exports every moved name (import surface unchanged); the split
    itself is byte-identical extractor output, verified by a per-entry-point
    node/edge snapshot.

Docs

  • extractors/MIGRATION.md updated to record the C# module layout and the
    "split the language helpers + cross-file resolver, leave the config-driven
    extract_<lang> entry point in extract.py" pattern — a middle path for
    config-driven languages the doctrine previously said had to wait for the shared
    _extract_generic batch.

Testing

  • Full suite: 2873 passed, 0 failed (on current upstream).
  • tests/test_csharp_member_calls.py: 61 receiver-resolution + never-wrong-edge
    tests. tests/test_csharp_type_resolution.py: cross-file resolution.
  • The never-wrong-edge guards are mutation-verified — removing any one guard
    (overload, RHS allow-list, scope-shadow stop, callee-context resolution) fails
    a test.

# Conflicts:
#	graphify/extract.py
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