From e86b5009dd5a9649727afba9a45c17c31c3afb5f Mon Sep 17 00:00:00 2001 From: Sparsh Date: Tue, 2 Jun 2026 17:41:36 +0530 Subject: [PATCH 1/4] =?UTF-8?q?fix(rust):=20scope-resolution=20coverage=20?= =?UTF-8?q?gaps=20=E2=80=94=20F66,F68,F71,F72,F73=20(#1934)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gitnexus/bench/scope-capture/baselines.json | 5 +- .../core/ingestion/languages/rust/captures.ts | 2 +- .../core/ingestion/languages/rust/query.ts | 35 +++++- .../lang-resolution/rust-coverage/macros.rs | 5 + .../lang-resolution/rust-coverage/patterns.rs | 10 ++ .../lang-resolution/rust-coverage/union.rs | 5 + .../lang-resolution/rust-coverage/variadic.rs | 4 + .../expected-captures.json | 76 +++++++----- .../resolvers/rust-coverage.test.ts | 110 ++++++++++++++++++ 9 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/rust-coverage/macros.rs create mode 100644 gitnexus/test/fixtures/lang-resolution/rust-coverage/patterns.rs create mode 100644 gitnexus/test/fixtures/lang-resolution/rust-coverage/union.rs create mode 100644 gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs create mode 100644 gitnexus/test/integration/resolvers/rust-coverage.test.ts diff --git a/gitnexus/bench/scope-capture/baselines.json b/gitnexus/bench/scope-capture/baselines.json index a1d4fbea41..94b189eaff 100644 --- a/gitnexus/bench/scope-capture/baselines.json +++ b/gitnexus/bench/scope-capture/baselines.json @@ -27,9 +27,10 @@ "scaling_budget": 1.5 }, "rust": { - "fingerprint": "2ffad4ba7b1d2eb1ac407cb6d75d0eb98cbc1878260dbdfe982c0fc925b2d00c", + "fingerprint": "357cc5b2cefb4dd40e2790966a52134d6bc9f63a967ba221190248b6bc0eaeef", "scaling_budget": 1.5, - "_rebaselined": "#1956 tri-review U1: rust-qualified-trait fixture (scoped + generic-of-scoped impl trait paths); bareTypeIdentifier now resolves scoped_type_identifier bases by their name: tail (additive, no existing-fixture drift); linear (~1.04)." + "_rebaselined": "#1956 tri-review U1: rust-qualified-trait fixture (scoped + generic-of-scoped impl trait paths); bareTypeIdentifier now resolves scoped_type_identifier bases by their name: tail (additive, no existing-fixture drift); linear (~1.04).", + "_note": "PR #1934: F66/F68 pattern wildcards, F71 union, F72 macro, F73 variadic — fixture count 117→124, fingerprint drift expected." }, "php": { "fingerprint": "f9c8eaf6d1084f9b95a9fb97ccce5e618a24d936c85fb8af4b96c73a560f7a7f", diff --git a/gitnexus/src/core/ingestion/languages/rust/captures.ts b/gitnexus/src/core/ingestion/languages/rust/captures.ts index 00f7759d45..b73f80ec11 100644 --- a/gitnexus/src/core/ingestion/languages/rust/captures.ts +++ b/gitnexus/src/core/ingestion/languages/rust/captures.ts @@ -274,7 +274,7 @@ function computeRustDeclarationArity(fnNode: SyntaxNode): { const child = params.namedChild(i); if (child === null) continue; if (child.type === 'self_parameter') continue; - if (child.type === 'parameter') count++; + if (child.type === 'parameter' || child.type === 'variadic_parameter') count++; } // Rust has no default parameters or overloading return { parameterCount: count, requiredParameterCount: count }; diff --git a/gitnexus/src/core/ingestion/languages/rust/query.ts b/gitnexus/src/core/ingestion/languages/rust/query.ts index 8e4671ba3e..757cd1606c 100644 --- a/gitnexus/src/core/ingestion/languages/rust/query.ts +++ b/gitnexus/src/core/ingestion/languages/rust/query.ts @@ -8,6 +8,7 @@ const RUST_SCOPE_QUERY = ` (trait_item) @scope.class (impl_item) @scope.class (enum_item) @scope.class +(union_item) @scope.class (function_item) @scope.function (closure_expression) @scope.function (block) @scope.block @@ -30,6 +31,10 @@ const RUST_SCOPE_QUERY = ` (enum_item name: (type_identifier) @declaration.name) @declaration.enum +;; Declarations — union +(union_item + name: (type_identifier) @declaration.name) @declaration.struct + ;; Declarations — function (top-level or inside mod) (function_item name: (identifier) @declaration.name) @declaration.function @@ -40,8 +45,12 @@ const RUST_SCOPE_QUERY = ` type: (_) @declaration.field-type) @declaration.field ;; Declarations — variables (let bindings) +;; Uses pattern: (_) to match any pattern shape — bare identifier, tuple, +;; struct, ref, captured patterns. tree-sitter-rust puts mutable_specifier +;; and ref as siblings of the pattern child, not wrappers, so the pattern +;; node is directly accessible even with let mut x / let ref x. (let_declaration - pattern: (identifier) @declaration.name) @declaration.variable + pattern: (_) @declaration.name) @declaration.variable ;; Declarations — const (const_item @@ -59,33 +68,37 @@ const RUST_SCOPE_QUERY = ` pattern: (identifier) @type-binding.name type: (_) @type-binding.type) @type-binding.parameter +;; Type bindings — variadic parameter annotations +(variadic_parameter + pattern: (_) @type-binding.name) @type-binding.parameter + ;; Type bindings — let with type annotation (let_declaration - pattern: (identifier) @type-binding.name + pattern: (_) @type-binding.name type: (_) @type-binding.type) @type-binding.assignment ;; Type bindings — struct literal constructor inference (let_declaration - pattern: (identifier) @type-binding.name + pattern: (_) @type-binding.name value: (struct_expression name: (_) @type-binding.type)) @type-binding.constructor ;; Type bindings — call-return inference (let x = Foo::new()) (let_declaration - pattern: (identifier) @type-binding.name + pattern: (_) @type-binding.name value: (call_expression function: (_) @type-binding.type)) @type-binding.call-return ;; Type bindings — call-return inference through .await (let x = foo().await) (let_declaration - pattern: (identifier) @type-binding.name + pattern: (_) @type-binding.name value: (await_expression (call_expression function: (_) @type-binding.type))) @type-binding.call-return ;; Type bindings — variable alias (let x = y) (let_declaration - pattern: (identifier) @type-binding.name + pattern: (_) @type-binding.name value: (identifier) @type-binding.type) @type-binding.alias ;; Type bindings — return type annotation @@ -109,9 +122,19 @@ const RUST_SCOPE_QUERY = ` name: (identifier) @reference.name)) @reference.call.free ;; References — constructor calls (struct literal) +;; Covers bare names (Foo {}), scoped (foo::bar::Baz {}), and turbofish +;; (Foo:: {}) — the name: field resolves to the trailing identifier +;; in all cases through tree-sitter-rust's grammar. (struct_expression name: (_) @reference.name) @reference.call.constructor +;; References — macro invocations +(macro_invocation + macro: (identifier) @reference.name) @reference.call.free + +(macro_invocation + macro: (scoped_identifier) @reference.name) @reference.call.free + ;; References — field reads (field_expression value: (_) @reference.receiver diff --git a/gitnexus/test/fixtures/lang-resolution/rust-coverage/macros.rs b/gitnexus/test/fixtures/lang-resolution/rust-coverage/macros.rs new file mode 100644 index 0000000000..08af370e52 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-coverage/macros.rs @@ -0,0 +1,5 @@ +// F72 — macro invocations +fn use_macros() { + println!("hello"); + let v = vec![1, 2, 3]; +} diff --git a/gitnexus/test/fixtures/lang-resolution/rust-coverage/patterns.rs b/gitnexus/test/fixtures/lang-resolution/rust-coverage/patterns.rs new file mode 100644 index 0000000000..4aceece6e7 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-coverage/patterns.rs @@ -0,0 +1,10 @@ +// F66/F68 — let binding with various pattern shapes +fn pattern_shapes() { + let x = 1; // bare identifier + let mut y = 2; // identifier with mut + let (a, b) = (1, 2); // tuple pattern + let Some(val) = Some(3); // tuple struct pattern + let Foo { field } = Foo { field: 1 }; // struct pattern + let ref z = 4; // ref pattern + let n @ 1..=10 = 5; // captured pattern +} diff --git a/gitnexus/test/fixtures/lang-resolution/rust-coverage/union.rs b/gitnexus/test/fixtures/lang-resolution/rust-coverage/union.rs new file mode 100644 index 0000000000..3bb32cc2cb --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-coverage/union.rs @@ -0,0 +1,5 @@ +// F71 — union declaration +union MyUnion { + x: i32, + y: f64, +} diff --git a/gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs b/gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs new file mode 100644 index 0000000000..b5491bc745 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs @@ -0,0 +1,4 @@ +// F73 — variadic parameter in extern function +extern "C" { + fn printf(fmt: *const u8, ...); +} diff --git a/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json b/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json index 187ade0a72..bbc3619ffa 100644 --- a/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json +++ b/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json @@ -1,7 +1,7 @@ { "rust-abstract-dispatch/src/lib.rs": { - "captureGroups": 27, - "digest": "e7a8f5bca32037a547093095eae32d68560b9ef2bbb29c51bef9f2e7b3e57614" + "captureGroups": 28, + "digest": "fdc8b7b9f53f170886e7941cad945fcd201f223147bc08953f97b44582fca1c5" }, "rust-abstract-dispatch/src/main.rs": { "captureGroups": 19, @@ -123,6 +123,22 @@ "captureGroups": 15, "digest": "043cd8b9341ab299750f8d07f1d1ca34b714c4ecf69acba80ec827e93e3852c0" }, + "rust-coverage/macros.rs": { + "captureGroups": 7, + "digest": "73cbc0c935939a58f59ca7e70e6ee49f4b5226171a7ad6915e8bdf5a5b7ede62" + }, + "rust-coverage/patterns.rs": { + "captureGroups": 15, + "digest": "71c8643c4792ba5343640a727bb369ea5fc2f8d0ec4a65660870ab2a8a274a82" + }, + "rust-coverage/union.rs": { + "captureGroups": 5, + "digest": "886081c47a5a9d84b00024e09843753e3dc1731f5e1c520e73b60ede8b9701ae" + }, + "rust-coverage/variadic.rs": { + "captureGroups": 2, + "digest": "f53f226bc8bc1a33359790a87330811171491b217e60bebb06c682f3164884a7" + }, "rust-cross-module-collision/src/a.rs": { "captureGroups": 11, "digest": "27e5b3770416d99fa69fcd492adf565c296780181e11fd5ac6bd26f3c57a9ab0" @@ -188,12 +204,12 @@ "digest": "292eba3d86490a2ee600ebe4d9e19434204b43706af459c25ae82c2b646da5f9" }, "rust-for-call-expr/src/repo.rs": { - "captureGroups": 13, - "digest": "2b7e531ed136a976228b8073066f1fd9ba30e9409c10d5708c544a617ad2f31c" + "captureGroups": 14, + "digest": "aacaa96cd994d4444c89167d64fb67e2d2ac73c43f482a8ac8de803525bb0400" }, "rust-for-call-expr/src/user.rs": { - "captureGroups": 13, - "digest": "9e5ee9a073c988caace8946b7008e56e7a1cfa852972a28387f8bf0c52b73555" + "captureGroups": 14, + "digest": "bc91206bf8fcb5e4414306a9d0c8655f4e85fd522bdb1aa113f8bfe497a4d4a2" }, "rust-for-loop/src/main.rs": { "captureGroups": 24, @@ -208,12 +224,12 @@ "digest": "a8562a331eb945b4c099a7a9ec6c9b5eed8ff603c9359897b0445ce316a1424e" }, "rust-grouped-imports/src/helpers/mod.rs": { - "captureGroups": 13, - "digest": "04ce0efd7458e7ff624e5611d34bbb0c01e77259108e0477895bba61e9568249" + "captureGroups": 14, + "digest": "8045523097b065cb4ecbb98c0bd871e143372e780104faed1ac625d74b63a653" }, "rust-grouped-imports/src/main.rs": { - "captureGroups": 13, - "digest": "17fff90e2627d094f24ffb7cd06b258d3f69b026b52f892c66132d061d031b1e" + "captureGroups": 14, + "digest": "84e1843745b4a749a6a94ac5820da04511c9657f978c551bf469a53c6cac7783" }, "rust-if-let-unwrap/models/mod.rs": { "captureGroups": 1, @@ -260,12 +276,12 @@ "digest": "a8562a331eb945b4c099a7a9ec6c9b5eed8ff603c9359897b0445ce316a1424e" }, "rust-local-shadow/src/main.rs": { - "captureGroups": 15, - "digest": "a9903b31883988b89bf634ea2bda7de522f256d06b0d80a65050a3fb4fae4a80" + "captureGroups": 16, + "digest": "bd1e11591a05230bae7d12f64acaceb62d29304f24d20dc9e3ae7b43cdd4990f" }, "rust-local-shadow/src/utils.rs": { - "captureGroups": 5, - "digest": "8007a597a21f60c078ceaaa96c660d157d4f1c27401345145be0b5b8b1fb4c0d" + "captureGroups": 6, + "digest": "7de700579d56abc915862d05d83a4a66f1924da06673a3455f7f379e9a0da915" }, "rust-match-unwrap/src/main.rs": { "captureGroups": 24, @@ -296,8 +312,8 @@ "digest": "cd836a2a9c15ab240961d2e15f192f7e33d65eb5ebf2e1a8af2f620a47fe66ae" }, "rust-method-enrichment/src/lib.rs": { - "captureGroups": 38, - "digest": "014c09ab82a5a348c2a6225e07da0773981dd6f282492b4517e33071873dcda6" + "captureGroups": 39, + "digest": "7893c6983821edc00c3de76e191841ef230ab3f3b907a70fac2b9b9fbca2cb95" }, "rust-method-enrichment/src/main.rs": { "captureGroups": 18, @@ -348,8 +364,8 @@ "digest": "15be069f28f1400e4beb0b0860acb59979f78549960486f36a92f56578f05a06" }, "rust-qualified-trait/src/widget.rs": { - "captureGroups": 22, - "digest": "3e1d4c6167338e410d9d93bf5f80f289ffb2f05611813e59c7a53402bf6a101d" + "captureGroups": 23, + "digest": "3f621b40b53c406992906b1ed8cfe1522761a0fe9a512eedf30c1a6079343da6" }, "rust-receiver-resolution/src/main.rs": { "captureGroups": 21, @@ -400,20 +416,20 @@ "digest": "7346b2cf62e4946b261ed0eb46f2623fb1882f46d832abdc2068012917c7b16a" }, "rust-scoped-multi-file/src/models/repo.rs": { - "captureGroups": 18, - "digest": "5fdd3d9d0fa35089cfda53a3e84ac4b788c5dca97b7268c3f634d3bca5ba40d8" + "captureGroups": 19, + "digest": "f4da1eef6875f92aa2dc38d3c7de4bb0f1019236431dd7f093ca663b674b329a" }, "rust-scoped-multi-file/src/models/user.rs": { - "captureGroups": 18, - "digest": "cc1404f69ca2264fd6ae12aebbd5cc6844eccd68b11595ebe9c1e861d168a86b" + "captureGroups": 19, + "digest": "19ad24f3486c3f32ad4430b5335f9052f83c5c991cbe8e28f7bfb1595432cdec" }, "rust-self-struct-literal/main.rs": { "captureGroups": 11, "digest": "3c0beb60f1487a63c60329e0843c1913b6a3854bb1ed3df84e4a07bc16dbd82d" }, "rust-self-struct-literal/models.rs": { - "captureGroups": 31, - "digest": "91e3ba35c7dcf31ab8914f052bbec884d0b9f4f0ab991878b084fbf9e4fac192" + "captureGroups": 32, + "digest": "b6d991a3d62e1f7cdf6d25317eb9c679de62d828dfb84f2e5938682fd9554d40" }, "rust-self-this-resolution/src/repo.rs": { "captureGroups": 10, @@ -424,8 +440,8 @@ "digest": "9354dad2a222a66b46f00ceede6777ba8c0634294c41313f393bc9ebcbbb6aa7" }, "rust-struct-destructuring/main.rs": { - "captureGroups": 15, - "digest": "6d96109296b7f5bd709e2704c14c332bfa0bca12453ae289b38a74da2c38124d" + "captureGroups": 17, + "digest": "0fdaf4c6b73a804d69c5edf8eb85427ee0bc58657f2877ecf87be99934d7795d" }, "rust-struct-destructuring/point.rs": { "captureGroups": 6, @@ -440,8 +456,8 @@ "digest": "e71269ff626cd0b0655591ee08e48d90e9a1421f3be8d9e8ac23262388a3f7ad" }, "rust-struct-literal-inference/models.rs": { - "captureGroups": 26, - "digest": "363f8d0d3948d88b2b656a80db15d6c0c0c43e0f8ecc3e6a504cf8d8d7e6a020" + "captureGroups": 27, + "digest": "39dfefe62ac7a246eb106582733d64283351c28f85a91da3e3cafcd9f92786d4" }, "rust-struct-literals/app.rs": { "captureGroups": 12, @@ -452,8 +468,8 @@ "digest": "60fc4ac44f58ae67d462e243655b0571392a18de85b9e200e9391b50c941a6c9" }, "rust-traits/src/impls/button.rs": { - "captureGroups": 32, - "digest": "ba93629d0e5a008a5ea84b6a93ea93b1ae4901cc24d8c4c7ee685f51e2712f12" + "captureGroups": 34, + "digest": "333311f8cbf1fc6371cf3fc4ac08fa9f651aee8643e50cff85497ab6df70f5ba" }, "rust-traits/src/main.rs": { "captureGroups": 11, diff --git a/gitnexus/test/integration/resolvers/rust-coverage.test.ts b/gitnexus/test/integration/resolvers/rust-coverage.test.ts new file mode 100644 index 0000000000..73d71aa3b7 --- /dev/null +++ b/gitnexus/test/integration/resolvers/rust-coverage.test.ts @@ -0,0 +1,110 @@ +/** + * Regression tests for Rust scope-resolution coverage gaps (issue #1934). + */ +import { describe, it, expect } from 'vitest'; +import { emitRustScopeCaptures } from '../../../src/core/ingestion/languages/rust/index.js'; +import type { CaptureMatch } from 'gitnexus-shared'; + +// --------------------------------------------------------------------------- +// F66/F68 — let binding patterns +// --------------------------------------------------------------------------- + +describe('F66/F68 — let binding pattern shapes', () => { + it('bare identifier let binding emits @declaration.variable', () => { + const src = `fn f() { let x = 1; }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const vars = matches.filter((m) => m['@declaration.variable']); + expect(vars.length).toBe(1); + expect(vars[0]['@declaration.name'].text).toBe('x'); + }); + + it('let mut x emits @declaration.variable', () => { + const src = `fn f() { let mut x = 1; }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const vars = matches.filter((m) => m['@declaration.variable']); + expect(vars.length).toBe(1); + expect(vars[0]['@declaration.name'].text).toBe('x'); + }); + + it('let (a, b) tuple pattern emits @declaration.variable', () => { + const src = `fn f() { let (a, b) = (1, 2); }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const vars = matches.filter((m) => m['@declaration.variable']); + expect(vars.length).toBe(1); + }); + + it('let Some(val) tuple struct pattern emits @declaration.variable', () => { + const src = `fn f() { let Some(val) = Some(3); }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const vars = matches.filter((m) => m['@declaration.variable']); + expect(vars.length).toBe(1); + }); + + it('let ref x pattern emits @declaration.variable', () => { + const src = `fn f() { let ref x = 4; }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const vars = matches.filter((m) => m['@declaration.variable']); + expect(vars.length).toBe(1); + // pattern: (_) captures the ref_pattern node whose text is "ref x" + expect(vars[0]['@declaration.name'].text).toBe('ref x'); + }); + + it('let x @ 1..=10 captured pattern emits @declaration.variable', () => { + const src = `fn f() { let x @ 1..=10 = 5; }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const vars = matches.filter((m) => m['@declaration.variable']); + expect(vars.length).toBe(1); + }); +}); + +// --------------------------------------------------------------------------- +// F71 — union declarations +// --------------------------------------------------------------------------- + +describe('F71 — union declaration', () => { + it('union item emits @scope.class and @declaration.struct', () => { + const src = `union MyUnion { x: i32, y: f64 }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const scopes = matches.filter((m) => m['@scope.class']); + expect(scopes.length).toBe(1); + const decls = matches.filter((m) => m['@declaration.struct']); + expect(decls.length).toBe(1); + expect(decls[0]['@declaration.name'].text).toBe('MyUnion'); + }); +}); + +// --------------------------------------------------------------------------- +// F72 — macro invocations +// --------------------------------------------------------------------------- + +describe('F72 — macro invocations', () => { + it('macro_invocation with bare identifier emits @reference.call.free', () => { + const src = `fn f() { println!("hi"); }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const macroRefs = matches.filter((m) => m['@reference.call.free']); + const macroNames = macroRefs.map((m) => m['@reference.name']?.text); + expect(macroNames).toContain('println'); + }); + + it('vec! macro emits @reference.call.free', () => { + const src = `fn f() { let v = vec![1, 2, 3]; }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const macroRefs = matches.filter((m) => m['@reference.call.free']); + const macroNames = macroRefs.map((m) => m['@reference.name']?.text); + expect(macroNames).toContain('vec'); + }); +}); + +// --------------------------------------------------------------------------- +// F73 — variadic parameters +// --------------------------------------------------------------------------- + +describe('F73 — variadic parameters', () => { + it('variadic_parameter in extern fn emits @type-binding.parameter', () => { + const src = `extern \"C\" { fn printf(fmt: *const u8, ...); }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const params = matches.filter((m) => m['@type-binding.parameter']); + // Should have at least one parameter binding (fmt is a parameter) + expect(params.length).toBeGreaterThanOrEqual(1); + }); +}); From 93e4096fd2379b6b01b995f0ae9ea48e0c9ea300 Mon Sep 17 00:00:00 2001 From: Sparsh Date: Tue, 2 Jun 2026 18:41:02 +0530 Subject: [PATCH 2/4] =?UTF-8?q?fix(rust):=20reviewer=20fixes=20=E2=80=94?= =?UTF-8?q?=20macro=20namespace,=20revert=20pattern:(=5F),=20drop=20variad?= =?UTF-8?q?ic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gitnexus/bench/scope-capture/baselines.json | 2 +- .../core/ingestion/languages/rust/captures.ts | 2 +- .../core/ingestion/languages/rust/query.ts | 30 +++++------ .../lang-resolution/rust-coverage/variadic.rs | 4 -- .../expected-captures.json | 42 +++++++-------- .../resolvers/rust-coverage.test.ts | 54 ++----------------- 6 files changed, 39 insertions(+), 95 deletions(-) delete mode 100644 gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs diff --git a/gitnexus/bench/scope-capture/baselines.json b/gitnexus/bench/scope-capture/baselines.json index 48254797c2..2bac79dc0e 100644 --- a/gitnexus/bench/scope-capture/baselines.json +++ b/gitnexus/bench/scope-capture/baselines.json @@ -27,7 +27,7 @@ "scaling_budget": 1.5 }, "rust": { - "fingerprint": "357cc5b2cefb4dd40e2790966a52134d6bc9f63a967ba221190248b6bc0eaeef", + "fingerprint": "27c1fac675f3ccdf76ff9b9094b7b93df797f6a62428c1078ed047cc3b060c1e", "scaling_budget": 1.5, "_rebaselined": "#1956 tri-review U1: rust-qualified-trait fixture (scoped + generic-of-scoped impl trait paths); bareTypeIdentifier now resolves scoped_type_identifier bases by their name: tail (additive, no existing-fixture drift); linear (~1.04).", "_note": "PR #1934: F66/F68 pattern wildcards, F71 union, F72 macro, F73 variadic — fixture count 117→124, fingerprint drift expected." diff --git a/gitnexus/src/core/ingestion/languages/rust/captures.ts b/gitnexus/src/core/ingestion/languages/rust/captures.ts index b73f80ec11..00f7759d45 100644 --- a/gitnexus/src/core/ingestion/languages/rust/captures.ts +++ b/gitnexus/src/core/ingestion/languages/rust/captures.ts @@ -274,7 +274,7 @@ function computeRustDeclarationArity(fnNode: SyntaxNode): { const child = params.namedChild(i); if (child === null) continue; if (child.type === 'self_parameter') continue; - if (child.type === 'parameter' || child.type === 'variadic_parameter') count++; + if (child.type === 'parameter') count++; } // Rust has no default parameters or overloading return { parameterCount: count, requiredParameterCount: count }; diff --git a/gitnexus/src/core/ingestion/languages/rust/query.ts b/gitnexus/src/core/ingestion/languages/rust/query.ts index 757cd1606c..13a7d9c435 100644 --- a/gitnexus/src/core/ingestion/languages/rust/query.ts +++ b/gitnexus/src/core/ingestion/languages/rust/query.ts @@ -45,12 +45,12 @@ const RUST_SCOPE_QUERY = ` type: (_) @declaration.field-type) @declaration.field ;; Declarations — variables (let bindings) -;; Uses pattern: (_) to match any pattern shape — bare identifier, tuple, -;; struct, ref, captured patterns. tree-sitter-rust puts mutable_specifier -;; and ref as siblings of the pattern child, not wrappers, so the pattern -;; node is directly accessible even with let mut x / let ref x. +;; Uses pattern:(identifier) — works for let x and let mut x (mutable_specifier +;; is a sibling, not a wrapper). Destructuring patterns like let (a, b) use +;; tuple_pattern etc. which pattern:(identifier) intentionally does not match; +;; capturing them with (_) would produce "(a, b)" as the name, which is useless. (let_declaration - pattern: (_) @declaration.name) @declaration.variable + pattern: (identifier) @declaration.name) @declaration.variable ;; Declarations — const (const_item @@ -68,37 +68,33 @@ const RUST_SCOPE_QUERY = ` pattern: (identifier) @type-binding.name type: (_) @type-binding.type) @type-binding.parameter -;; Type bindings — variadic parameter annotations -(variadic_parameter - pattern: (_) @type-binding.name) @type-binding.parameter - ;; Type bindings — let with type annotation (let_declaration - pattern: (_) @type-binding.name + pattern: (identifier) @type-binding.name type: (_) @type-binding.type) @type-binding.assignment ;; Type bindings — struct literal constructor inference (let_declaration - pattern: (_) @type-binding.name + pattern: (identifier) @type-binding.name value: (struct_expression name: (_) @type-binding.type)) @type-binding.constructor ;; Type bindings — call-return inference (let x = Foo::new()) (let_declaration - pattern: (_) @type-binding.name + pattern: (identifier) @type-binding.name value: (call_expression function: (_) @type-binding.type)) @type-binding.call-return ;; Type bindings — call-return inference through .await (let x = foo().await) (let_declaration - pattern: (_) @type-binding.name + pattern: (identifier) @type-binding.name value: (await_expression (call_expression function: (_) @type-binding.type))) @type-binding.call-return ;; Type bindings — variable alias (let x = y) (let_declaration - pattern: (_) @type-binding.name + pattern: (identifier) @type-binding.name value: (identifier) @type-binding.type) @type-binding.alias ;; Type bindings — return type annotation @@ -128,12 +124,12 @@ const RUST_SCOPE_QUERY = ` (struct_expression name: (_) @reference.name) @reference.call.constructor -;; References — macro invocations +;; References — macro invocations (disjoint namespace from functions) (macro_invocation - macro: (identifier) @reference.name) @reference.call.free + macro: (identifier) @reference.name) @reference.macro (macro_invocation - macro: (scoped_identifier) @reference.name) @reference.call.free + macro: (scoped_identifier) @reference.name) @reference.macro ;; References — field reads (field_expression diff --git a/gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs b/gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs deleted file mode 100644 index b5491bc745..0000000000 --- a/gitnexus/test/fixtures/lang-resolution/rust-coverage/variadic.rs +++ /dev/null @@ -1,4 +0,0 @@ -// F73 — variadic parameter in extern function -extern "C" { - fn printf(fmt: *const u8, ...); -} diff --git a/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json b/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json index bbc3619ffa..4a98eb5ae5 100644 --- a/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json +++ b/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json @@ -1,7 +1,7 @@ { "rust-abstract-dispatch/src/lib.rs": { "captureGroups": 28, - "digest": "fdc8b7b9f53f170886e7941cad945fcd201f223147bc08953f97b44582fca1c5" + "digest": "f1fad77028347c5102a39dbe6522786fa7863e5d4fcaf675294dffc23555d23b" }, "rust-abstract-dispatch/src/main.rs": { "captureGroups": 19, @@ -125,20 +125,16 @@ }, "rust-coverage/macros.rs": { "captureGroups": 7, - "digest": "73cbc0c935939a58f59ca7e70e6ee49f4b5226171a7ad6915e8bdf5a5b7ede62" + "digest": "ff4606a898325894a9ef71fa5d16446fba7f24976bb88d7fcf5e6e62f76f99d3" }, "rust-coverage/patterns.rs": { - "captureGroups": 15, - "digest": "71c8643c4792ba5343640a727bb369ea5fc2f8d0ec4a65660870ab2a8a274a82" + "captureGroups": 8, + "digest": "107045c7d648c89b825214f56bdaffee80fccbc063b69a126b95d5aca27618b6" }, "rust-coverage/union.rs": { "captureGroups": 5, "digest": "886081c47a5a9d84b00024e09843753e3dc1731f5e1c520e73b60ede8b9701ae" }, - "rust-coverage/variadic.rs": { - "captureGroups": 2, - "digest": "f53f226bc8bc1a33359790a87330811171491b217e60bebb06c682f3164884a7" - }, "rust-cross-module-collision/src/a.rs": { "captureGroups": 11, "digest": "27e5b3770416d99fa69fcd492adf565c296780181e11fd5ac6bd26f3c57a9ab0" @@ -205,11 +201,11 @@ }, "rust-for-call-expr/src/repo.rs": { "captureGroups": 14, - "digest": "aacaa96cd994d4444c89167d64fb67e2d2ac73c43f482a8ac8de803525bb0400" + "digest": "6c6c53ab458997365f7ee4a9675ec64809aaebd2dd101b6418928b0d9313bdec" }, "rust-for-call-expr/src/user.rs": { "captureGroups": 14, - "digest": "bc91206bf8fcb5e4414306a9d0c8655f4e85fd522bdb1aa113f8bfe497a4d4a2" + "digest": "7331591b986aa2a9b0ee57020c1f0285ad0da10d5044c326b4beaaeb26019aa8" }, "rust-for-loop/src/main.rs": { "captureGroups": 24, @@ -225,11 +221,11 @@ }, "rust-grouped-imports/src/helpers/mod.rs": { "captureGroups": 14, - "digest": "8045523097b065cb4ecbb98c0bd871e143372e780104faed1ac625d74b63a653" + "digest": "32bc4f57a1dc0ffe8e9cbf0f0d6ce2ac5b1f2f93e2e3de3f217c92636480de63" }, "rust-grouped-imports/src/main.rs": { "captureGroups": 14, - "digest": "84e1843745b4a749a6a94ac5820da04511c9657f978c551bf469a53c6cac7783" + "digest": "d0997acf33c23b27864423aebaa1b1f4341d57bf5374c6f40c7efa6a166b39e7" }, "rust-if-let-unwrap/models/mod.rs": { "captureGroups": 1, @@ -277,11 +273,11 @@ }, "rust-local-shadow/src/main.rs": { "captureGroups": 16, - "digest": "bd1e11591a05230bae7d12f64acaceb62d29304f24d20dc9e3ae7b43cdd4990f" + "digest": "66d3a2678fb33301bbaa419684d208a0dc5c914d6e3bcf20e1c99e9666ed6fa2" }, "rust-local-shadow/src/utils.rs": { "captureGroups": 6, - "digest": "7de700579d56abc915862d05d83a4a66f1924da06673a3455f7f379e9a0da915" + "digest": "493dbeab554e28bed4a03cb62b951cad4f43bce5b5b522a192cd04159b3b2d9c" }, "rust-match-unwrap/src/main.rs": { "captureGroups": 24, @@ -313,7 +309,7 @@ }, "rust-method-enrichment/src/lib.rs": { "captureGroups": 39, - "digest": "7893c6983821edc00c3de76e191841ef230ab3f3b907a70fac2b9b9fbca2cb95" + "digest": "8e60a44f5e18d1e26eea4bb21129494d2e3be697fec373dd4b6044f989176d9e" }, "rust-method-enrichment/src/main.rs": { "captureGroups": 18, @@ -365,7 +361,7 @@ }, "rust-qualified-trait/src/widget.rs": { "captureGroups": 23, - "digest": "3f621b40b53c406992906b1ed8cfe1522761a0fe9a512eedf30c1a6079343da6" + "digest": "ee34385539f7e9398123c056738c6a662a80dac41fc038db96fab0da5c84c8ac" }, "rust-receiver-resolution/src/main.rs": { "captureGroups": 21, @@ -417,11 +413,11 @@ }, "rust-scoped-multi-file/src/models/repo.rs": { "captureGroups": 19, - "digest": "f4da1eef6875f92aa2dc38d3c7de4bb0f1019236431dd7f093ca663b674b329a" + "digest": "25a3d44bc451ccaf8c6875b226fbda22ed1a18dcc884d4c6d9914ed83fc555b0" }, "rust-scoped-multi-file/src/models/user.rs": { "captureGroups": 19, - "digest": "19ad24f3486c3f32ad4430b5335f9052f83c5c991cbe8e28f7bfb1595432cdec" + "digest": "77dc9858229322d8e1abbf209230bfbf6fca00627a5864888e91f1841b1f0cbb" }, "rust-self-struct-literal/main.rs": { "captureGroups": 11, @@ -429,7 +425,7 @@ }, "rust-self-struct-literal/models.rs": { "captureGroups": 32, - "digest": "b6d991a3d62e1f7cdf6d25317eb9c679de62d828dfb84f2e5938682fd9554d40" + "digest": "e02d230c1215b3fd87fedbdaf98649a407fa1f2bfc61beb66f49d7ba070de7de" }, "rust-self-this-resolution/src/repo.rs": { "captureGroups": 10, @@ -440,8 +436,8 @@ "digest": "9354dad2a222a66b46f00ceede6777ba8c0634294c41313f393bc9ebcbbb6aa7" }, "rust-struct-destructuring/main.rs": { - "captureGroups": 17, - "digest": "0fdaf4c6b73a804d69c5edf8eb85427ee0bc58657f2877ecf87be99934d7795d" + "captureGroups": 15, + "digest": "6d96109296b7f5bd709e2704c14c332bfa0bca12453ae289b38a74da2c38124d" }, "rust-struct-destructuring/point.rs": { "captureGroups": 6, @@ -457,7 +453,7 @@ }, "rust-struct-literal-inference/models.rs": { "captureGroups": 27, - "digest": "39dfefe62ac7a246eb106582733d64283351c28f85a91da3e3cafcd9f92786d4" + "digest": "dedd93d6214ff00ee0ee267140918403b7f59e0ab010cb9058af40cb3b08bb81" }, "rust-struct-literals/app.rs": { "captureGroups": 12, @@ -469,7 +465,7 @@ }, "rust-traits/src/impls/button.rs": { "captureGroups": 34, - "digest": "333311f8cbf1fc6371cf3fc4ac08fa9f651aee8643e50cff85497ab6df70f5ba" + "digest": "80fc76cdf20e3e0594a1ed933ce25682519151632157d18ef011dba60a2245d1" }, "rust-traits/src/main.rs": { "captureGroups": 11, diff --git a/gitnexus/test/integration/resolvers/rust-coverage.test.ts b/gitnexus/test/integration/resolvers/rust-coverage.test.ts index 73d71aa3b7..d2e4bae620 100644 --- a/gitnexus/test/integration/resolvers/rust-coverage.test.ts +++ b/gitnexus/test/integration/resolvers/rust-coverage.test.ts @@ -6,7 +6,7 @@ import { emitRustScopeCaptures } from '../../../src/core/ingestion/languages/rus import type { CaptureMatch } from 'gitnexus-shared'; // --------------------------------------------------------------------------- -// F66/F68 — let binding patterns +// F66/F68 — let binding patterns (identifier-only, works with let mut x) // --------------------------------------------------------------------------- describe('F66/F68 — let binding pattern shapes', () => { @@ -25,36 +25,6 @@ describe('F66/F68 — let binding pattern shapes', () => { expect(vars.length).toBe(1); expect(vars[0]['@declaration.name'].text).toBe('x'); }); - - it('let (a, b) tuple pattern emits @declaration.variable', () => { - const src = `fn f() { let (a, b) = (1, 2); }\n`; - const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const vars = matches.filter((m) => m['@declaration.variable']); - expect(vars.length).toBe(1); - }); - - it('let Some(val) tuple struct pattern emits @declaration.variable', () => { - const src = `fn f() { let Some(val) = Some(3); }\n`; - const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const vars = matches.filter((m) => m['@declaration.variable']); - expect(vars.length).toBe(1); - }); - - it('let ref x pattern emits @declaration.variable', () => { - const src = `fn f() { let ref x = 4; }\n`; - const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const vars = matches.filter((m) => m['@declaration.variable']); - expect(vars.length).toBe(1); - // pattern: (_) captures the ref_pattern node whose text is "ref x" - expect(vars[0]['@declaration.name'].text).toBe('ref x'); - }); - - it('let x @ 1..=10 captured pattern emits @declaration.variable', () => { - const src = `fn f() { let x @ 1..=10 = 5; }\n`; - const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const vars = matches.filter((m) => m['@declaration.variable']); - expect(vars.length).toBe(1); - }); }); // --------------------------------------------------------------------------- @@ -78,33 +48,19 @@ describe('F71 — union declaration', () => { // --------------------------------------------------------------------------- describe('F72 — macro invocations', () => { - it('macro_invocation with bare identifier emits @reference.call.free', () => { + it('macro_invocation with bare identifier emits @reference.macro', () => { const src = `fn f() { println!("hi"); }\n`; const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const macroRefs = matches.filter((m) => m['@reference.call.free']); + const macroRefs = matches.filter((m) => m['@reference.macro']); const macroNames = macroRefs.map((m) => m['@reference.name']?.text); expect(macroNames).toContain('println'); }); - it('vec! macro emits @reference.call.free', () => { + it('vec! macro emits @reference.macro', () => { const src = `fn f() { let v = vec![1, 2, 3]; }\n`; const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const macroRefs = matches.filter((m) => m['@reference.call.free']); + const macroRefs = matches.filter((m) => m['@reference.macro']); const macroNames = macroRefs.map((m) => m['@reference.name']?.text); expect(macroNames).toContain('vec'); }); }); - -// --------------------------------------------------------------------------- -// F73 — variadic parameters -// --------------------------------------------------------------------------- - -describe('F73 — variadic parameters', () => { - it('variadic_parameter in extern fn emits @type-binding.parameter', () => { - const src = `extern \"C\" { fn printf(fmt: *const u8, ...); }\n`; - const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; - const params = matches.filter((m) => m['@type-binding.parameter']); - // Should have at least one parameter binding (fmt is a parameter) - expect(params.length).toBeGreaterThanOrEqual(1); - }); -}); From 49d29403a093fbfb8882af95d6b4a9e0eb63ec02 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Wed, 3 Jun 2026 05:25:04 +0000 Subject: [PATCH 3/4] fix(rust): wire macro resolution end-to-end + materialize unions (#1974 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the outstanding #1974 review (second batch). Per maintainer decision, F72 is FULLY WIRED rather than documented capture-only. F72 macro — was a capture-only no-op (@reference.macro dropped downstream): - gitnexus-shared: add 'macro' ReferenceKind + Reference.kind; add MACRO_KINDS (['Macro']) and a MacroRegistry that resolves a macro invocation ONLY to a macro_rules! definition — never a same-named free function (the disjoint-namespace guarantee the review required). - scope-extractor: referenceKindFromAnchor @reference.macro -> 'macro'; normalizeNodeLabel 'macro' -> Macro. - resolve-references: route 'macro' sites through MacroRegistry. - emit-references / graph-bridge edges: 'macro' -> USES (kept out of the CALLS keyspace, which denotes function/method dispatch). - node-lookup isLinkableLabel: Macro is linkable, bridging the registry def to the legacy @definition.macro graph node. - rust query: capture macro_rules! as @declaration.macro; fix the scoped macro arm to capture the tail identifier, not the full path (P3). F71 union — the @declaration.struct scope capture had no graph node to resolve to (legacy RUST_QUERIES never captured union_item): - legacy query: capture union_item as @definition.struct so the union is materialized as a Struct node and is genuinely resolvable. - query.ts: document the deliberate union->Struct downgrade rationale. Tests: - rust.test.ts (parity-gated): pipeline-level union resolution + macro resolution (USES to the Macro, exactly one CALLS to fn, none to Macro). Macro resolution is registry-primary-only -> listed in LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES['rust']. - rust-coverage.test.ts: scoped-macro tail + macro-def capture assertions; reframed as capture-layer only, pointing at the pipeline tests. - new fixtures rust-macro, rust-union. F73: dropped from baselines.json _note (variadic was never implemented). Rebaselined the rust capture golden + scope-capture fingerprint (a5fdff2c..., scaling ~0.99, fixture_count 126). Co-Authored-By: Claude Opus 4.8 (1M context) --- gitnexus-shared/src/index.ts | 9 ++- .../src/scope-resolution/reference-site.ts | 7 +- .../scope-resolution/registries/context.ts | 7 ++ .../registries/macro-registry.ts | 43 ++++++++++ gitnexus-shared/src/scope-resolution/types.ts | 9 ++- gitnexus/bench/scope-capture/baselines.json | 4 +- .../src/core/ingestion/emit-references.ts | 10 ++- .../core/ingestion/languages/rust/query.ts | 23 +++++- .../src/core/ingestion/resolve-references.ts | 23 +++++- .../src/core/ingestion/scope-extractor.ts | 4 + .../scope-resolution/graph-bridge/edges.ts | 5 ++ .../graph-bridge/node-lookup.ts | 7 +- .../src/core/ingestion/tree-sitter-queries.ts | 5 ++ .../lang-resolution/rust-macro/lib.rs | 21 +++++ .../lang-resolution/rust-union/lib.rs | 12 +++ .../expected-captures.json | 8 ++ .../test/integration/resolvers/helpers.ts | 11 +++ .../resolvers/rust-coverage.test.ts | 29 ++++++- .../test/integration/resolvers/rust.test.ts | 80 +++++++++++++++++++ 19 files changed, 305 insertions(+), 12 deletions(-) create mode 100644 gitnexus-shared/src/scope-resolution/registries/macro-registry.ts create mode 100644 gitnexus/test/fixtures/lang-resolution/rust-macro/lib.rs create mode 100644 gitnexus/test/fixtures/lang-resolution/rust-union/lib.rs diff --git a/gitnexus-shared/src/index.ts b/gitnexus-shared/src/index.ts index fc591fdb01..d732d2633f 100644 --- a/gitnexus-shared/src/index.ts +++ b/gitnexus-shared/src/index.ts @@ -116,6 +116,8 @@ export type { FieldRegistry, FieldLookupOptions, } from './scope-resolution/registries/field-registry.js'; +export { buildMacroRegistry } from './scope-resolution/registries/macro-registry.js'; +export type { MacroRegistry } from './scope-resolution/registries/macro-registry.js'; export { lookupCore } from './scope-resolution/registries/lookup-core.js'; export type { CoreLookupParams } from './scope-resolution/registries/lookup-core.js'; export { lookupQualified } from './scope-resolution/registries/lookup-qualified.js'; @@ -127,7 +129,12 @@ export { CONFIDENCE_EPSILON, } from './scope-resolution/registries/tie-breaks.js'; export type { TieBreakKey } from './scope-resolution/registries/tie-breaks.js'; -export { CLASS_KINDS, METHOD_KINDS, FIELD_KINDS } from './scope-resolution/registries/context.js'; +export { + CLASS_KINDS, + METHOD_KINDS, + FIELD_KINDS, + MACRO_KINDS, +} from './scope-resolution/registries/context.js'; export type { RegistryContext, RegistryProviders, diff --git a/gitnexus-shared/src/scope-resolution/reference-site.ts b/gitnexus-shared/src/scope-resolution/reference-site.ts index 58fabd4277..7b7b8be8e5 100644 --- a/gitnexus-shared/src/scope-resolution/reference-site.ts +++ b/gitnexus-shared/src/scope-resolution/reference-site.ts @@ -37,7 +37,12 @@ export type ReferenceKind = | 'write' | 'type-reference' | 'inherits' - | 'import-use'; + | 'import-use' + // A macro invocation (`log!(...)` / `vec![...]`). Resolved against + // `Macro`-labeled definitions ONLY (see `MacroRegistry`) so a macro + // never aliases a same-named free function — macros and functions are + // disjoint namespaces. Emitted as a `USES` edge, not `CALLS`. + | 'macro'; /** * How a call site binds its target. Informs `Registry.lookup` Step 2 diff --git a/gitnexus-shared/src/scope-resolution/registries/context.ts b/gitnexus-shared/src/scope-resolution/registries/context.ts index 657856f3e2..cd37a3e95d 100644 --- a/gitnexus-shared/src/scope-resolution/registries/context.ts +++ b/gitnexus-shared/src/scope-resolution/registries/context.ts @@ -162,3 +162,10 @@ export const FIELD_KINDS: readonly NodeLabel[] = Object.freeze([ 'Const', 'Static', ]); + +// Macros occupy a namespace disjoint from functions/methods: a `log!` +// invocation must resolve ONLY to a `macro_rules! log` definition, never +// to a same-named `fn log`. `MACRO_KINDS` is therefore a singleton +// (`['Macro']`) and is NOT merged into METHOD_KINDS — keeping the two +// keyspaces separate is what prevents the cross-namespace false-edge. +export const MACRO_KINDS: readonly NodeLabel[] = Object.freeze(['Macro']); diff --git a/gitnexus-shared/src/scope-resolution/registries/macro-registry.ts b/gitnexus-shared/src/scope-resolution/registries/macro-registry.ts new file mode 100644 index 0000000000..0eaf0e96af --- /dev/null +++ b/gitnexus-shared/src/scope-resolution/registries/macro-registry.ts @@ -0,0 +1,43 @@ +/** + * `MacroRegistry` — scope-aware lookup for macro definitions + * (`macro_rules!` in Rust; `#define` in C/C++) referenced from a macro + * invocation site. + * + * Thin wrapper over `lookupCore`, specialized for the macro namespace: + * + * - `acceptedKinds` = `MACRO_KINDS` (`['Macro']` only). Crucially this + * does NOT include `Function`/`Method`, so a `log!(…)` invocation can + * never resolve to a same-named free function `fn log` — macros and + * functions are disjoint namespaces (the false-`CALLS`-edge class the + * #1934 review flagged). + * - `useReceiverTypeBinding` is **false** — a macro invocation has no + * receiver; resolution is name-through-the-lexical-chain + the global + * qualified fallback, exactly like `ClassRegistry`. + * - Arity is not applied — macros are variadic by nature. + */ + +import type { Resolution, ScopeId } from '../types.js'; +import { lookupCore, type CoreLookupParams } from './lookup-core.js'; +import { MACRO_KINDS, type RegistryContext } from './context.js'; + +export interface MacroRegistry { + /** + * Look up a macro definition by simple or scoped name anchored at + * `scope`. Returns a confidence-ranked `Resolution[]`; consume `[0]` + * for the best answer. + */ + lookup(name: string, scope: ScopeId): readonly Resolution[]; +} + +export function buildMacroRegistry(ctx: RegistryContext): MacroRegistry { + const params: CoreLookupParams = { + acceptedKinds: MACRO_KINDS, + useReceiverTypeBinding: false, + ownerScopedContributor: null, + }; + return { + lookup(name: string, scope: ScopeId) { + return lookupCore(name, scope, params, ctx); + }, + }; +} diff --git a/gitnexus-shared/src/scope-resolution/types.ts b/gitnexus-shared/src/scope-resolution/types.ts index 828874a7f1..b873274900 100644 --- a/gitnexus-shared/src/scope-resolution/types.ts +++ b/gitnexus-shared/src/scope-resolution/types.ts @@ -439,7 +439,14 @@ export interface Reference { readonly toDef: DefId; /** Location of the reference in source. */ readonly atRange: Range; - readonly kind: 'call' | 'read' | 'write' | 'type-reference' | 'inherits' | 'import-use'; + readonly kind: + | 'call' + | 'read' + | 'write' + | 'type-reference' + | 'inherits' + | 'import-use' + | 'macro'; readonly confidence: number; readonly evidence: readonly ResolutionEvidence[]; } diff --git a/gitnexus/bench/scope-capture/baselines.json b/gitnexus/bench/scope-capture/baselines.json index 263f706417..96fa1430d1 100644 --- a/gitnexus/bench/scope-capture/baselines.json +++ b/gitnexus/bench/scope-capture/baselines.json @@ -28,10 +28,10 @@ "scaling_budget": 1.5 }, "rust": { - "fingerprint": "RECOMPUTE-PENDING-REBASELINE", + "fingerprint": "a5fdff2cf427504e33e66d0221b3ad62739c64bd0898e1dafedc15dbbe347b4d", "scaling_budget": 1.5, "_rebaselined": "#1956 tri-review U1: rust-qualified-trait fixture (scoped + generic-of-scoped impl trait paths); bareTypeIdentifier now resolves scoped_type_identifier bases by their name: tail (additive, no existing-fixture drift); linear (~1.04). #1975: + rust-scoped-impl fixture (impl a::Inner / b::Inner inherent scoped impls) — legacy @definition.impl scoped arm + findEnclosingClassInfo inherent-impl scoped target; rust scope-extractor captures byte-identical.", - "_note": "PR #1934: F66/F68 let-binding pattern narrowing, F71 union (Struct-labeled), F72 macro (capture-only @reference.macro). Merged with origin/main #1975 rust-scoped-impl fixture; fixture corpus is the union of both — fingerprint re-baselined." + "_note": "PR #1934: F66/F68 let-binding pattern narrowing; F71 union (Struct-labeled, now materialized via legacy @definition.struct + resolvable); F72 macro FULLY WIRED — @declaration.macro/@reference.macro + MacroRegistry → USES edges to Macro nodes (never a same-named fn). + rust-macro / rust-union fixtures and merged with origin/main #1975 rust-scoped-impl; fingerprint re-baselined (scaling ~0.99, fixture_count 126)." }, "php": { "fingerprint": "f9c8eaf6d1084f9b95a9fb97ccce5e618a24d936c85fb8af4b96c73a560f7a7f", diff --git a/gitnexus/src/core/ingestion/emit-references.ts b/gitnexus/src/core/ingestion/emit-references.ts index 1d2c4db5b3..8c1145874c 100644 --- a/gitnexus/src/core/ingestion/emit-references.ts +++ b/gitnexus/src/core/ingestion/emit-references.ts @@ -267,9 +267,14 @@ function buildRelationship( /** * Map a `Reference.kind` to an existing `RelationshipType`. Read/write - * both fold into `ACCESSES`; `type-reference` + `import-use` both fold - * into `USES`. This keeps the graph schema additive — no new + * both fold into `ACCESSES`; `type-reference`, `import-use`, and `macro` + * all fold into `USES`. This keeps the graph schema additive — no new * RelationshipType values are introduced by this module. + * + * `macro` folds into `USES` (not `CALLS`) deliberately: a macro + * invocation targets a `Macro` node, not a callable function, so keeping + * it out of the `CALLS` keyspace preserves the invariant that `CALLS` + * edges denote function/method dispatch. */ function mapKindToType(kind: Reference['kind']): RelationshipType { switch (kind) { @@ -282,6 +287,7 @@ function mapKindToType(kind: Reference['kind']): RelationshipType { return 'INHERITS'; case 'type-reference': case 'import-use': + case 'macro': return 'USES'; } } diff --git a/gitnexus/src/core/ingestion/languages/rust/query.ts b/gitnexus/src/core/ingestion/languages/rust/query.ts index 13a7d9c435..d85660981d 100644 --- a/gitnexus/src/core/ingestion/languages/rust/query.ts +++ b/gitnexus/src/core/ingestion/languages/rust/query.ts @@ -32,9 +32,25 @@ const RUST_SCOPE_QUERY = ` name: (type_identifier) @declaration.name) @declaration.enum ;; Declarations — union +;; Deliberately tagged @declaration.struct (→ Struct label), NOT a +;; @declaration.union: every registry-primary resolution gate — +;; isLinkableLabel (node-lookup.ts), CALLABLE_OR_TYPE_LIKE +;; (finalize-algorithm.ts), ClassLikeNodeLabel (class-types.ts) — includes +;; Struct but EXCLUDES Union, so a Union-labeled node would be an +;; unresolvable orphan. A Rust union is a type whose literal is a real +;; constructor, so Struct is both the resolvable and the semantically +;; honest label here. #1934 F71. (union_item name: (type_identifier) @declaration.name) @declaration.struct +;; Declarations — macro (macro_rules! foo { ... }) +;; Captured as @declaration.macro → Macro label. A macro invocation +;; (@reference.macro, below) resolves to this definition via MacroRegistry, +;; whose acceptedKinds is ['Macro'] ONLY — so an invoked macro never binds +;; to a same-named free function (log!() is not fn log). #1934 F72. +(macro_definition + name: (identifier) @declaration.name) @declaration.macro + ;; Declarations — function (top-level or inside mod) (function_item name: (identifier) @declaration.name) @declaration.function @@ -125,11 +141,16 @@ const RUST_SCOPE_QUERY = ` name: (_) @reference.name) @reference.call.constructor ;; References — macro invocations (disjoint namespace from functions) +;; Resolved via MacroRegistry → Macro defs only (never fn of the same name). (macro_invocation macro: (identifier) @reference.name) @reference.macro +;; Scoped macro invocation (log::info!(…)) — capture the tail identifier, +;; mirroring the scoped free-call pattern above, so the resolved name is +;; the tail (info), not the full path (log::info). (macro_invocation - macro: (scoped_identifier) @reference.name) @reference.macro + macro: (scoped_identifier + name: (identifier) @reference.name)) @reference.macro ;; References — field reads (field_expression diff --git a/gitnexus/src/core/ingestion/resolve-references.ts b/gitnexus/src/core/ingestion/resolve-references.ts index 837635422d..e4413c119a 100644 --- a/gitnexus/src/core/ingestion/resolve-references.ts +++ b/gitnexus/src/core/ingestion/resolve-references.ts @@ -41,12 +41,14 @@ import { buildClassRegistry, buildFieldRegistry, + buildMacroRegistry, buildMethodRegistry, CLASS_KINDS, FIELD_KINDS, METHOD_KINDS, type ClassRegistry, type FieldRegistry, + type MacroRegistry, type MethodRegistry, type Reference, type ReferenceIndex, @@ -102,6 +104,7 @@ export function resolveReferenceSites(input: ResolveReferencesInput): ResolveRef const classRegistry = buildClassRegistry(ctx); const methodRegistry = buildMethodRegistry(ctx); const fieldRegistry = buildFieldRegistry(ctx); + const macroRegistry = buildMacroRegistry(ctx); // bySourceScope is the canonical index; byTargetDef is derived from it. const bySourceScope = new Map(); @@ -114,7 +117,13 @@ export function resolveReferenceSites(input: ResolveReferencesInput): ResolveRef for (const site of scopes.referenceSites) { sitesProcessed++; - const resolutions = lookupForSite(site, classRegistry, methodRegistry, fieldRegistry); + const resolutions = lookupForSite( + site, + classRegistry, + methodRegistry, + fieldRegistry, + macroRegistry, + ); if (resolutions.length === 0) { unresolved++; continue; @@ -165,6 +174,12 @@ export function resolveReferenceSites(input: ResolveReferencesInput): ResolveRef * | `type-reference` | ClassRegistry | CLASS_KINDS | * | `read`/`write` | FieldRegistry | FIELD_KINDS | * | `import-use` | tiered fallback | METHOD ∪ CLASS ∪ FIELD | + * | `macro` | MacroRegistry | MACRO_KINDS (`Macro` only) | + * + * `macro` has its own single-kind registry so a macro invocation + * (`log!(…)`) resolves ONLY to a `macro_rules! log` definition and never + * to a same-named free function — macros and functions are disjoint + * namespaces (the false-`CALLS`-edge class flagged in the #1934 review). * * `import-use` doesn't have a single registry — the imported name might * be a class, a function, or a constant. Try each in priority order and @@ -177,6 +192,7 @@ function lookupForSite( classRegistry: ClassRegistry, methodRegistry: MethodRegistry, fieldRegistry: FieldRegistry, + macroRegistry: MacroRegistry, ): readonly Resolution[] { switch (site.kind) { case 'call': { @@ -213,6 +229,11 @@ function lookupForSite( if (methodHits.length > 0) return methodHits; return fieldRegistry.lookup(site.name, site.inScope); } + case 'macro': { + // Macro-only namespace: resolves against `Macro`-labeled defs, never + // functions. No receiver, no arity — see `MacroRegistry`. + return macroRegistry.lookup(site.name, site.inScope); + } } } diff --git a/gitnexus/src/core/ingestion/scope-extractor.ts b/gitnexus/src/core/ingestion/scope-extractor.ts index 31a59d2f5c..446ab75fec 100644 --- a/gitnexus/src/core/ingestion/scope-extractor.ts +++ b/gitnexus/src/core/ingestion/scope-extractor.ts @@ -745,6 +745,8 @@ function normalizeNodeLabel(kindStr: string): SymbolDefinition['type'] | undefin return 'Annotation'; case 'namespace': return 'Namespace'; + case 'macro': + return 'Macro'; default: return undefined; } @@ -1044,6 +1046,8 @@ function referenceKindFromAnchor(name: string): ReferenceKind | undefined { case 'import_use': case 'import-use': return 'import-use'; + case 'macro': + return 'macro'; default: return undefined; } diff --git a/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/edges.ts b/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/edges.ts index 19b6bf0f16..5c7120495c 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/edges.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/edges.ts @@ -39,6 +39,11 @@ export function mapReferenceKindToEdgeType( return 'EXTENDS'; case 'type-reference': return 'USES'; + // Macro invocations resolve to a `Macro` node (never a function), so + // they emit `USES` — kept out of the `CALLS` keyspace which denotes + // function/method dispatch (#1934 review). + case 'macro': + return 'USES'; case 'import-use': return undefined; default: diff --git a/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/node-lookup.ts b/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/node-lookup.ts index 229c83f450..80bc5dedd0 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/node-lookup.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/graph-bridge/node-lookup.ts @@ -186,6 +186,11 @@ export function isLinkableLabel(label: NodeLabel): boolean { // `Variable` def for `export const fooService = {...}` to the canonical // `Const:filePath:name` graph node id, against which object-literal // method symbols register their `ownerId` (PR #1718 / issue #1358). - label === 'Const' + label === 'Const' || + // Macro nodes are linkable so a macro invocation (`log!(…)`) resolved + // via `MacroRegistry` can bridge its scope-resolution `Macro` def to + // the legacy `@definition.macro` graph node and emit the `USES` edge + // (Rust #1934 F72; also covers C/C++ `#define` macro defs). + label === 'Macro' ); } diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index 91881f4af4..1be596a69e 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -1215,6 +1215,11 @@ export const RUST_QUERIES = ` (function_item name: (identifier) @name) @definition.function (function_signature_item name: (identifier) @name) @definition.function (struct_item name: (type_identifier) @name) @definition.struct +; A union is materialized as a Struct node (same rationale as the +; scope-resolution @declaration.struct in languages/rust/query.ts: every +; registry-primary resolution gate includes Struct but excludes Union, so a +; Union-labeled node would be an unresolvable orphan). #1934 F71. +(union_item name: (type_identifier) @name) @definition.struct (enum_item name: (type_identifier) @name) @definition.enum (trait_item name: (type_identifier) @name) @definition.trait (impl_item type: (type_identifier) @name !trait) @definition.impl diff --git a/gitnexus/test/fixtures/lang-resolution/rust-macro/lib.rs b/gitnexus/test/fixtures/lang-resolution/rust-macro/lib.rs new file mode 100644 index 0000000000..d3ba24ea84 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-macro/lib.rs @@ -0,0 +1,21 @@ +// F72 — a macro invocation resolves to its `macro_rules!` definition (a +// Macro node) via a USES edge, and NEVER to a same-named free function. +// Macros and functions are disjoint namespaces. + +macro_rules! greet { + ($name:expr) => { + let _ = $name; + }; +} + +// Same simple name as the macro, on purpose: proves the macro invocation +// does not bind to this function (no false CALLS edge) and the function +// call does not bind to the macro. +fn greet() -> u32 { + 0 +} + +fn run() { + greet!("world"); // macro invocation -> USES edge to Macro greet + let _ = greet(); // function call -> CALLS edge to Function greet +} diff --git a/gitnexus/test/fixtures/lang-resolution/rust-union/lib.rs b/gitnexus/test/fixtures/lang-resolution/rust-union/lib.rs new file mode 100644 index 0000000000..d6c19ce009 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/rust-union/lib.rs @@ -0,0 +1,12 @@ +// F71 — a `union` is captured as a Struct-labeled node (every +// registry-primary resolution gate includes Struct but excludes Union), +// and it is resolvable: the union literal is a real type constructor. + +union MyUnion { + int_val: i32, + float_val: f64, +} + +fn make() -> MyUnion { + MyUnion { int_val: 5 } // constructor -> CALLS edge to the Struct MyUnion +} diff --git a/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json b/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json index aec9fd4caf..9a89f154ce 100644 --- a/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json +++ b/gitnexus/test/fixtures/rust-captures-golden/expected-captures.json @@ -279,6 +279,10 @@ "captureGroups": 6, "digest": "493dbeab554e28bed4a03cb62b951cad4f43bce5b5b522a192cd04159b3b2d9c" }, + "rust-macro/lib.rs": { + "captureGroups": 11, + "digest": "19b650be99256aa211356edc5a3dde83aff21e5d763e2c079322a24045bf69c9" + }, "rust-match-unwrap/src/main.rs": { "captureGroups": 24, "digest": "1d9ead4663799e035816ed06a17d37e9a98d0fd8090f9dea256ec6f019788648" @@ -483,6 +487,10 @@ "captureGroups": 5, "digest": "1dca39bbc7c1b1b66f1a34730b9a5b4dba04c54ee9d2688255e0fd4e6bc48499" }, + "rust-union/lib.rs": { + "captureGroups": 10, + "digest": "e2a6fb9eab259b8c7104f1530b96b8c1f42ab32fe1d71d6bdca04d68263507f2" + }, "rust-write-access/models.rs": { "captureGroups": 9, "digest": "660f755fd70cd1796f9da02ad7d65f599dea8029665ee45ecd18cd27919741f3" diff --git a/gitnexus/test/integration/resolvers/helpers.ts b/gitnexus/test/integration/resolvers/helpers.ts index 391f5733eb..fe0410572b 100644 --- a/gitnexus/test/integration/resolvers/helpers.ts +++ b/gitnexus/test/integration/resolvers/helpers.ts @@ -181,6 +181,17 @@ const LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES: Readonly([ // #1756 companion-vs-instance dispatch: the registry-primary path // suppresses `instance.companionMethod()` via `ScopeResolver. diff --git a/gitnexus/test/integration/resolvers/rust-coverage.test.ts b/gitnexus/test/integration/resolvers/rust-coverage.test.ts index d2e4bae620..17a8714d2d 100644 --- a/gitnexus/test/integration/resolvers/rust-coverage.test.ts +++ b/gitnexus/test/integration/resolvers/rust-coverage.test.ts @@ -44,10 +44,16 @@ describe('F71 — union declaration', () => { }); // --------------------------------------------------------------------------- -// F72 — macro invocations +// F72 — macro invocations (capture layer) +// +// These pin the tree-sitter CAPTURE shape only. End-to-end macro RESOLUTION +// (the @reference.macro → MacroRegistry → USES-edge-to-a-Macro-node path, and +// the guarantee that a macro never binds to a same-named function) is asserted +// at the pipeline level — and under the legacy-vs-registry-primary scope-parity +// gate — in `rust.test.ts` › "Rust macro resolution (issue #1934 F72)". // --------------------------------------------------------------------------- -describe('F72 — macro invocations', () => { +describe('F72 — macro invocations (capture layer)', () => { it('macro_invocation with bare identifier emits @reference.macro', () => { const src = `fn f() { println!("hi"); }\n`; const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; @@ -63,4 +69,23 @@ describe('F72 — macro invocations', () => { const macroNames = macroRefs.map((m) => m['@reference.name']?.text); expect(macroNames).toContain('vec'); }); + + it('scoped macro invocation captures the TAIL identifier, not the full path', () => { + const src = `fn f() { log::info!("hi"); }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const macroRefs = matches.filter((m) => m['@reference.macro']); + const macroNames = macroRefs.map((m) => m['@reference.name']?.text); + // Must be the tail `info`, not the whole path `log::info` — mirrors the + // scoped free-call pattern. Guards the P3 fix. + expect(macroNames).toContain('info'); + expect(macroNames).not.toContain('log::info'); + }); + + it('macro_rules! definition emits a @declaration.macro capture', () => { + const src = `macro_rules! greet { () => {}; }\n`; + const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[]; + const macroDecls = matches.filter((m) => m['@declaration.macro']); + expect(macroDecls.length).toBe(1); + expect(macroDecls[0]['@declaration.name'].text).toBe('greet'); + }); }); diff --git a/gitnexus/test/integration/resolvers/rust.test.ts b/gitnexus/test/integration/resolvers/rust.test.ts index bcfa4200d9..91af9af7a1 100644 --- a/gitnexus/test/integration/resolvers/rust.test.ts +++ b/gitnexus/test/integration/resolvers/rust.test.ts @@ -12,9 +12,15 @@ import { findDanglingEdges, edgeSet, runPipelineFromRepo, + createResolverParityIt, type PipelineResult, } from './helpers.js'; +// Registry-primary-only assertions (e.g. macro resolution, which the legacy +// DAG does not implement) use this parity-aware `it` so they are skipped — +// not failed — under the legacy half of the scope-parity gate. +const rustParityIt = createResolverParityIt('rust'); + // --------------------------------------------------------------------------- // Heritage: trait implementations // --------------------------------------------------------------------------- @@ -2047,3 +2053,77 @@ describe('Rust scoped inherent impl — ownership + collision (issue #1975)', () expect(fromA!.source).not.toBe(fromB!.source); }); }); + +// --------------------------------------------------------------------------- +// F71 — union declarations resolve as Struct nodes (issue #1934) +// +// A `union` is deliberately captured as a Struct-labeled node (see the +// rationale in languages/rust/query.ts): every registry-primary resolution +// gate includes Struct but excludes Union, so a Union-labeled node would be +// an unresolvable orphan. These pipeline-level assertions pin BOTH that the +// node is labeled Struct AND that it is genuinely resolvable (the union +// literal is a real constructor) — works on the legacy + registry-primary +// paths, so it runs under both halves of the scope-parity gate. +// --------------------------------------------------------------------------- + +describe('Rust union resolution (issue #1934 F71)', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo(path.join(FIXTURES, 'rust-union'), () => {}); + }, 60000); + + it('captures the union as a Struct node named MyUnion (not Union)', () => { + expect(getNodesByLabel(result, 'Struct')).toContain('MyUnion'); + expect(getNodesByLabel(result, 'Union')).toEqual([]); + }); + + it('resolves the union literal MyUnion { .. } as a CALLS edge to the Struct', () => { + const calls = getRelationships(result, 'CALLS'); + const ctor = calls.find((e) => e.source === 'make' && e.target === 'MyUnion'); + expect(ctor).toBeDefined(); + expect(ctor!.targetLabel).toBe('Struct'); + }); +}); + +// --------------------------------------------------------------------------- +// F72 — macro invocations resolve to their definition (issue #1934) +// +// A `macro_rules! greet` invocation (`greet!(...)`) resolves via the +// MacroRegistry to the Macro node, emitting a USES edge — NEVER a CALLS +// edge, and NEVER binding to a same-named free function `fn greet`. This is +// a registry-primary-only capability (the legacy DAG does not resolve +// macros), so the resolution assertions use `rustParityIt` and are listed +// in helpers' LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES. +// --------------------------------------------------------------------------- + +describe('Rust macro resolution (issue #1934 F72)', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo(path.join(FIXTURES, 'rust-macro'), () => {}); + }, 60000); + + it('materializes both a Macro and a same-named Function node', () => { + expect(getNodesByLabel(result, 'Macro')).toContain('greet'); + expect(getNodesByLabel(result, 'Function')).toContain('greet'); + }); + + rustParityIt('resolves greet!(..) as a USES edge to the Macro (not the Function)', () => { + const uses = getRelationships(result, 'USES'); + const macroUse = uses.find((e) => e.source === 'run' && e.target === 'greet'); + expect(macroUse).toBeDefined(); + expect(macroUse!.targetLabel).toBe('Macro'); + }); + + rustParityIt('does NOT emit a CALLS edge from the macro invocation to fn greet', () => { + const calls = getRelationships(result, 'CALLS'); + // The only run -> greet CALLS edge is the genuine fn call; it must target + // the Function, and there must be exactly one (the macro adds no CALLS). + const greetCalls = calls.filter((e) => e.source === 'run' && e.target === 'greet'); + expect(greetCalls.length).toBe(1); + expect(greetCalls[0].targetLabel).toBe('Function'); + // And no CALLS edge anywhere targets the Macro node. + expect(calls.every((e) => e.targetLabel !== 'Macro')).toBe(true); + }); +}); From 8ceb08c3a69d93c4cf7abc8ef146a1ae269e2c03 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Wed, 3 Jun 2026 05:35:04 +0000 Subject: [PATCH 4/4] style(rust): prettier-format the Reference.kind union (#1974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI quality/format gate — collapse the multi-line 'macro' addition back to one line (fits the 100-col print width). Co-Authored-By: Claude Opus 4.8 (1M context) --- gitnexus-shared/src/scope-resolution/types.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/gitnexus-shared/src/scope-resolution/types.ts b/gitnexus-shared/src/scope-resolution/types.ts index b873274900..c73543d053 100644 --- a/gitnexus-shared/src/scope-resolution/types.ts +++ b/gitnexus-shared/src/scope-resolution/types.ts @@ -439,14 +439,7 @@ export interface Reference { readonly toDef: DefId; /** Location of the reference in source. */ readonly atRange: Range; - readonly kind: - | 'call' - | 'read' - | 'write' - | 'type-reference' - | 'inherits' - | 'import-use' - | 'macro'; + readonly kind: 'call' | 'read' | 'write' | 'type-reference' | 'inherits' | 'import-use' | 'macro'; readonly confidence: number; readonly evidence: readonly ResolutionEvidence[]; }