_window?.Refresh() parses as a conditional_access_expression, not a
member_access_expression, so the invocation branch fell through to the
raw-text fallback. That split "_window?.Refresh" on the dot and recorded
the receiver as "_window?", which no receiver table types, so the call
was dropped with no unresolved entry.
Read both shapes through one helper that returns the name and receiver
nodes, and use it for the call-site generic-argument walk as well, so
recv?.Do<T>() keeps its references edges. The cast and partial-class
receivers from #3797 are separate and not touched here.
(cherry picked from commit e6e39a9aa5)
Adds the two cases the deep-dive flagged: a built-in-typed primary-ctor param
(int count) must not fabricate a referenced node, and the branch's struct_declaration
claim is locked by a struct primary-ctor test. Also adds the CHANGELOG entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A C# 12 primary constructor puts its parameters on the class/record declaration
itself, which the extractor never walked: class Holder(IDep dep) emitted no
references edge Holder->IDep, and because dep was never registered in the class
receiver table, a dep.Method() call inside the class was dropped too. Scan the
declaration's parameter_list, register each param name->type, and emit the
param type reference — mirroring the field/property handlers. Built-in and
type-parameter types are not fabricated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Track C# receiver types per lexical declaration scope (byte ranges) and
resolve by the call's position, instead of a method-wide flat table that
poisoned a name on any None-typed binding. A typed static local-function
parameter now keeps resolving even when an out var reuses the name in the
enclosing body. Fixes a regression from #2346; #2299 cross-method
independence and field-conflict poisoning are unchanged.
Thanks @JensD-git for the bisect and repro.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- C# receivers declared inline via out-var / is / case / switch-arm patterns
are now typed into the per-method table, so their member calls resolve (#2346).
- partial class halves across files now merge to one class node (new
_merge_csharp_partial_class_nodes pass, mirroring the Swift-extension merge),
so cross-half member calls resolve instead of splitting the class (#2332).
- Kotlin anonymous-object (object : Foo {}) members now get nodes, contains/
implements edges, and their calls resolve (#2347).
All in-corpus only, never a wrong edge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The C# receiver-type table was per-FILE and poisoned a name on any
conflicting/untypable rebind anywhere in the file, so `var item = items[i]`
in one method silently deleted the true calls edge in another method where
the same name was a typed parameter (~2.3% of calls lost, per the reporter).
Ported C# to a per-method table mirroring the Java resolver (per-class field
scope + method params/locals, method-local poisoning only) and retired the
file-wide table. The namespace resolver, ambiguity bail, and inherits-chain
guards are unchanged, so the no-wrong-edge bar holds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adapted from #1620 by @TheFedaikin, reworked onto v8 as a focused change
(without the module split or the references-fallback behavior change).
Builds on the shipped #1609 resolver: instead of bailing when a receiver's
class name is ambiguous corpus-wide, the declared type is resolved with a
shared CsharpNameResolver (same-namespace, using-directive, and alias aware)
against the caller's namespace/scope, falling back to the unique bare match
only when scoping is non-decisive. Adds base./this.field receivers and
inherited-member lookup through the inherits chain (an out-of-corpus base
poisons the lookup, so no wrong edge). The per-file type table now poisons a
name on any conflicting rebinding, killing the wrong-edge class where a local
shadows a field of a different type. C#-gated; never emits a wrong edge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reported bug — a method chained directly onto a `new X(...)` expression
(no intermediate variable) producing no calls edge — is already fixed on v8:
`new Merger(ctx).Combine(...)` emits calls -> Merger.Combine. Add a regression
test so the fluent new-expression receiver stays covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
C# had no member-call resolver (unlike Swift/Python/Ruby/TS/C++/ObjC), so
`recv.Method()` fell back to a bare method-name match against label_to_nid —
which, under ambiguity, silently mis-bound `_server.Save()` to an unrelated
`Cache.Save()`. That's a WRONG edge, not just a missing one, and it left
delegation-heavy C# call graphs (wrappers, service layers) blind across typed
member/param boundaries.
Mirrors the C++ #1547 pattern:
- capture the member_access_expression receiver (simple identifier or `this`)
into member_receiver and set is_member_call in the C# invocation branch;
- defer ALL C# member calls with a receiver to the resolver (tgt_nid = None) so
the bare in-file match can't fire, and emit a raw_call tagged lang="csharp";
- _csharp_member_type_table: file-wide name -> Type from fields, properties,
parameters, and locals (incl. `var v = new T()`), first-binding-wins;
- _resolve_csharp_member_calls: `this` -> enclosing class (EXTRACTED),
capitalized -> the named type (EXTRACTED), else the receiver's table type
(INFERRED), each gated by the single-definition guard; no method on the type
-> no edge. Registered for .cs.
Verified: the ambiguous `_server.Save()` now resolves to Server.Save and NOT
Cache.Save; field/param/local/this/Type.static/cross-file all resolve; dynamic
receiver and absent-method emit nothing; unqualified calls unregressed. 8 new
tests, full suite 2841.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>