Skip to content

Namespace-aware C# type resolution with lexical using-scope and qualified refs#1562

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

Namespace-aware C# type resolution with lexical using-scope and qualified refs#1562
TheFedaikin wants to merge 2 commits into
Graphify-Labs:v8from
TheFedaikin:v8

Conversation

@TheFedaikin

Copy link
Copy Markdown
Contributor

Summary

The C# cross-file resolver was sound but conservatively incomplete: it resolved
the common case and skipped three reference classes it couldn't faithfully
represent. This turns those three skips into real resolution while keeping the
same hard guarantee — never emit a wrong edge: resolve only on exact, unique
facts, otherwise leave the reference dangling.

What changed

1. Namespace in the C# type-node id. A C# type node's id was built from the
file stem + simple name, so two same-named types in different namespaces of one
file (namespace A { class T } namespace B { class T }) collapsed to one node
and the second was dropped. The dotted namespace is now folded into the id at
the three sites that construct or probe it (class declaration,
ensure_named_node, base-list). make_id drops the empty namespace segment, so
every non-C# id and every C# global-namespace id stays byte-identical — the
change is opt-in for C#. The previous ns_collision detect-and-skip workaround
is removed.

2. Lexical using-scope. C# using scope is lexical (per declaration
block), not per namespace name — namespace A { using N; class Good : T {} } namespace A { class Bad : T {} } rejects Bad : T even though both blocks are
named A. The previous file-wide clamp, keyed on unique namespace strings, was
unsound for that case. Each declaration node now carries a per-file lexical
scope-id chain, and each using edge carries its scope_kind (file vs
namespace) and scope_id. A using — namespace or alias — applies to a
reference iff it is file-scoped or its scope id is in the reference's scope
chain, matched within the same file. This is sound for sibling same-name blocks,
nested blocks (a using flows inward), file-level usings, and aliases, and it
recovers usings in multi-namespace files that were previously dropped. The
scenarios were verified against dotnet build.

3. Qualified references. Qualified references (B.T) were skipped and
qualified generics (A.B<C>) emitted junk labels. The qualifier is now
preserved on the edge (metadata.ref_qualifier; ref_token stays the simple
lookup key), and a qualified Q.T resolves via an in-scope namespace alias
(using Q = X.Y) or an exact known namespace, otherwise dangles. Generic args
are stripped from qualified tails so N.Box<int> resolves to the real N.Box.
An in-scope alias shadows a like-named namespace.

Soundness

Every emitted edge is exact and uniquely scoped; uncertainty dangles rather than
guessing. The ns_collision removal and def-index completion (which expose the
de-collapsed types) land in the same change as the lexical resolver (which makes

13 new C# resolution tests cover id-distinctness, same-name-in-one-file
resolution, the four lexical using cases, and qualifier resolution (generics,
namespace aliases, and out-of-scope / shadowing alias edge cases). Three
superseded tests were rewritten around the new behavior. The full graphify suite
passes.

Residual gaps (sound, deferred)

  • Partial qualifiers (Core.T meaning Game.Core.T via using Game;) —
    exact-match only; prefix composition is deferred.
  • Ambiguous same-name in-scope aliases dangle (innermost-shadows-outer is not
    modeled).
  • Nested-type identity and member-call resolution remain follow-ups.

@safishamsi

Copy link
Copy Markdown
Collaborator

Merged into v8 (with your authorship) — thanks, this is a clean advance on the #1466 resolver. Folding the namespace into the node id (dropping the multi-namespace detect-and-skip), the lexical per-block using-scope, and qualified-reference resolution all landed. I verified the false-edge discipline holds: using A resolves Widget to A.Widget only (not B), an ambiguous using A; using B; resolves to nothing rather than fanning out, qualified B.Widget resolves regardless of usings, and sibling-block using-scope stays isolated — no dangling edges.

One note: your branch predated the C++/ObjC cross-file member-call resolvers that landed on v8 (#1547/#1556), so a straight merge was a deceptively-clean 3-way (it kept that newer code as 'theirs'). I reconciled it onto current v8 and re-ran the full suite there to confirm both features coexist — all green. Ships next release.

safishamsi pushed a commit that referenced this pull request Jun 30, 2026
Extends the C# type-reference resolver (#1466) to be namespace-aware,
advancing the #1318 shadow-node umbrella for C#:

- The namespace is folded into the C# node id (_make_id(stem, namespace,
  name)), so two same-named types in different namespaces in one file no
  longer collapse — replacing #1466's detect-and-skip workaround for
  multi-namespace files.
- Lexical per-block using-scope: a `using` applies only where it is in
  scope (file-level, or the enclosing namespace block via a scope chain),
  so sibling namespace blocks no longer share each other's usings.
- Qualified references (`Namespace.Type`) resolve via in-scope aliases
  (`using Q = X.Y`) then exact known namespaces; generics are stripped.

Preserves (and tightens) the refuse-rather-than-guess discipline: a bare
reference resolves only when exactly one in-scope namespace provides the
type; an ambiguous reference (e.g. `using A; using B;` both defining
`Widget`) resolves to nothing rather than fanning out. Verified: `using A`
-> A.Widget only; ambiguous -> no edge; qualified `B.Widget` -> B.Widget
regardless of usings; sibling-block using-scope isolated; no dangling
edges or fan-out.

Reconciled onto current v8 (the PR predated the C++/ObjC member-call
resolvers); full suite green, the C++/ObjC resolution coexists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safishamsi safishamsi closed this Jun 30, 2026
safishamsi added a commit that referenced this pull request Jun 30, 2026
Cross-file member-call resolution for C++/ObjC (#1547/#1556) and
namespace-aware C# type resolution (#1562), the work-memory overlay
(#1441), test-mock call-graph fix (#1553), hyperedge member-key aliases
(#1561), plus the TS/JS/ObjC resolution fixes (#1316/#1544/#1552/#1475).
See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants