feat(csharp-scope): parity Unit 5c — switch-expr + reasons + ACCESSES 1.0

Closes 4 parity failures (19 → 15).

- `languages/csharp/query.ts`: add captures for `switch_expression_arm`
  with `declaration_pattern` and `recursive_pattern`. C# expression-
  switch (`obj switch { User u => ..., Repo { Name: "x" } r => ... }`)
  uses a different AST node from classic `switch_statement`'s
  `switch_section` — needed separate query patterns.
- `scope-resolution/passes/receiver-bound-calls.ts`: replace the
  self-describing `'scope-resolution: *-receiver'` reason strings
  (which fail legacy-parity consumer filters) with the legacy
  convention: `'import-resolved'` when the resolved member lives in
  a different file, `'global'` otherwise. Mirrors
  `free-call-fallback.ts`'s existing reason logic.
- `scope-resolution/passes/receiver-bound-calls.ts`: pass
  `confidence: 1.0` to `tryEmitEdge` for write/read ACCESSES edges,
  matching legacy DAG behavior (default 0.85 was legacy-CALLS).

Python parity 204/204 on both flag paths; legacy C# 175/175;
15 C# parity failures remain.
This commit is contained in:
Gergo Magyar 2026-04-21 20:31:29 +01:00
parent c27cc0454d
commit ada782d350
2 changed files with 51 additions and 9 deletions

View file

@ -370,6 +370,24 @@ const CSHARP_SCOPE_QUERY = `
type: (identifier) @type-binding.type
name: (identifier) @type-binding.name)) @type-binding.annotation
;; Type bindings switch-expression arms: \`obj switch { User u => …, Repo { Name: "x" } r => … }\`.
;; Distinct from the \`switch_statement\` shape above — expression-switch
;; uses \`switch_expression_arm\` nodes.
(switch_expression_arm
(declaration_pattern
type: (identifier) @type-binding.type
name: (identifier) @type-binding.name)) @type-binding.annotation
(switch_expression_arm
(declaration_pattern
type: (generic_name) @type-binding.type
name: (identifier) @type-binding.name)) @type-binding.annotation
(switch_expression_arm
(recursive_pattern
type: (identifier) @type-binding.type
name: (identifier) @type-binding.name)) @type-binding.annotation
;; Type bindings typed foreach: \`foreach (User u in xs)\`.
;; Shape parity with \`User u = …;\` — left binds to the declared type.
(foreach_statement

View file

@ -133,7 +133,7 @@ export function emitReceiverBoundCalls(
nodeLookup,
site,
memberDef,
'scope-resolution: chain-receiver',
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
seen,
);
if (ok) {
@ -156,7 +156,7 @@ export function emitReceiverBoundCalls(
nodeLookup,
site,
memberDef,
'scope-resolution: namespace-receiver',
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
seen,
);
if (ok) {
@ -183,7 +183,7 @@ export function emitReceiverBoundCalls(
nodeLookup,
site,
memberDef,
'scope-resolution: class-receiver',
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
seen,
);
if (ok) {
@ -211,7 +211,7 @@ export function emitReceiverBoundCalls(
nodeLookup,
site,
memberDef,
'scope-resolution: dotted-typebinding',
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
seen,
);
if (ok) {
@ -252,7 +252,7 @@ export function emitReceiverBoundCalls(
nodeLookup,
site,
memberDef,
'scope-resolution: chain-typebinding',
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
seen,
);
if (ok) {
@ -281,8 +281,20 @@ export function emitReceiverBoundCalls(
const reason =
site.kind === 'write' || site.kind === 'read'
? site.kind
: 'scope-resolution: typeref-receiver';
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen);
: memberDef.filePath !== parsed.filePath
? 'import-resolved'
: 'global';
const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
const ok = tryEmitEdge(
graph,
scopes,
nodeLookup,
site,
memberDef,
reason,
seen,
confidence,
);
if (ok) {
emitted++;
handledSites.add(siteKey);
@ -311,8 +323,20 @@ export function emitReceiverBoundCalls(
const reason =
site.kind === 'write' || site.kind === 'read'
? site.kind
: 'scope-resolution: class-receiver';
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen);
: memberDef.filePath !== parsed.filePath
? 'import-resolved'
: 'global';
const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
const ok = tryEmitEdge(
graph,
scopes,
nodeLookup,
site,
memberDef,
reason,
seen,
confidence,
);
if (ok) {
emitted++;
handledSites.add(siteKey);