Merge branch 'main' into fix/issue-1518-docker-local-path

This commit is contained in:
Gergő Magyar 2026-06-05 17:53:49 +01:00 • committed by GitHub
commit 74e81ba014
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 4 deletions

View file

@ -134,11 +134,34 @@ 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::<T> {}) — the name: field resolves to the trailing identifier
;; in all cases through tree-sitter-rust's grammar.
;; tree-sitter-rust gives struct_expression.name one of three node types
;; (type_identifier | scoped_type_identifier | generic_type_with_turbofish);
;; the turbofish form additionally nests either a type_identifier or a
;; scoped_identifier. We enumerate all four shapes below so the capture is
;; always the trailing identifier (resolved scope-aware), not the full path:
;; bare Foo {}
;; scoped foo::bar::Baz {}
;; turbofish Foo::<T> {}
;; scoped+turbofish foo::Bar::<T> {}
(struct_expression
name: (_) @reference.name) @reference.call.constructor
name: (type_identifier) @reference.name) @reference.call.constructor
;; Scoped struct (foo::bar::Baz {})
(struct_expression
name: (scoped_type_identifier
name: (type_identifier) @reference.name)) @reference.call.constructor
;; Turbofish struct (Foo::<T> {})
(struct_expression
name: (generic_type_with_turbofish
type: (type_identifier) @reference.name)) @reference.call.constructor
;; Scoped + turbofish struct (foo::Bar::<T> {}) — the turbofish wraps a
;; scoped_identifier whose tail is an identifier (not a type_identifier).
(struct_expression
name: (generic_type_with_turbofish
type: (scoped_identifier
name: (identifier) @reference.name))) @reference.call.constructor
;; References — macro invocations (disjoint namespace from functions)
;; Resolved via MacroRegistry → Macro defs only (never fn of the same name).

View file

@ -0,0 +1,56 @@
/**
* Tests for F70 — struct literal constructor calls (issue #1934).
*/
import { describe, it, expect } from 'vitest';
import { emitRustScopeCaptures } from '../../../src/core/ingestion/languages/rust/index.js';
import type { CaptureMatch } from 'gitnexus-shared';
describe('F70 — struct literal constructor calls', () => {
it('bare struct Foo {} captures Foo as @reference.name', () => {
const src = `fn f() { let _ = Foo { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
expect(ctors.length).toBe(1);
expect(ctors[0]['@reference.name'].text).toBe('Foo');
});
it('scoped struct foo::bar::Baz {} captures Baz (not the full path)', () => {
const src = `fn f() { let _ = foo::bar::Baz { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Baz');
// Guard against regressing to the old wildcard, which captured the path.
expect(names).not.toContain('foo::bar::Baz');
});
it('turbofish struct Foo::<i32> {} captures Foo (not Foo::<i32>)', () => {
const src = `fn f() { let _ = Foo::<i32> { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Foo');
expect(names).not.toContain('Foo::<i32>');
});
it('scoped + turbofish struct foo::Bar::<i32> {} captures Bar', () => {
const src = `fn f() { let _ = foo::Bar::<i32> { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Bar');
expect(names).not.toContain('foo::Bar::<i32>');
});
it('crate-scoped struct crate::Foo {} captures Foo', () => {
const src = `fn f() { let _ = crate::Foo { x: 1 }; }\n`;
const matches = emitRustScopeCaptures(src, 'test.rs') as CaptureMatch[];
const ctors = matches.filter((m) => m['@reference.call.constructor']);
const names = ctors.map((m) => m['@reference.name']?.text);
expect(ctors.length).toBe(1);
expect(names).toContain('Foo');
});
});