mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
fix(zig): Union is class-like + missing-grammar warning (review pass)
Self-review findings on the Zig branch:
- scope/walkers.ts `isClassLike` and finalize-algorithm's
CALLABLE_OR_TYPE_LIKE did not include 'Union': a `union(enum)`
container's methods got no ownerId from populateClassOwnedMembers, so
method dispatch on union receivers silently dropped. Widened both
sets; the zig-basic fixture gains a Tag method + a CALLS assertion
(main → isEnergy) that fails without the widening (verified by
reverting).
- optional-grammars.ts now lists tree-sitter-zig with an npm `probe`
(it is an optionalDependency, not vendored): users with .zig files
and no prebuild get the standard one-line stderr warning instead of
a silently degraded index.
- Deduplicated the container-method predicate: `isZigContainerMethod`
+ ZIG_CONTAINER_TYPES now live once in languages/zig/captures.ts and
feed both the provider labelOverride and the scope-capture relabel.
- README language matrices: Zig row now claims Type Annotations,
Constructor Inference, and Config (build.zig.zon) — all true since
the scope-resolution hooks landed.
This commit is contained in:
parent
81eccec36e
commit
46bd72b8ca
11 changed files with 64 additions and 36 deletions
|
|
@ -580,7 +580,7 @@ GitNexus builds a complete knowledge graph of your codebase through a multi-phas
|
|||
| C | — | — | ✓ | — | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Dart | ✓ | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Zig | ✓ | — | ✓ | — | — | — | — | — | ✓ |
|
||||
| Zig | ✓ | — | ✓ | — | ✓ | ✓ | ✓ | — | ✓ |
|
||||
|
||||
**Imports** — cross-file import resolution · **Named Bindings** — `import { X as Y }` / re-export tracking · **Exports** — public/exported symbol detection · **Heritage** — class inheritance, interfaces, mixins · **Type Annotations** — explicit type extraction for receiver resolution · **Constructor Inference** — infer receiver type from constructor calls (`self`/`this` resolution included for all languages) · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics
|
||||
|
||||
|
|
|
|||
|
|
@ -1072,6 +1072,7 @@ const CALLABLE_OR_TYPE_LIKE: ReadonlySet<string> = new Set([
|
|||
'Interface',
|
||||
'Enum',
|
||||
'Struct',
|
||||
'Union',
|
||||
'Record',
|
||||
'Trait',
|
||||
'Namespace',
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift,
|
|||
| C | — | — | ✓ | — | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Dart | ✓ | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
|
||||
| Zig | ✓ | — | ✓ | — | — | — | — | — | ✓ |
|
||||
| Zig | ✓ | — | ✓ | — | ✓ | ✓ | ✓ | — | ✓ |
|
||||
|
||||
**Imports** — cross-file import resolution · **Named Bindings** — `import { X as Y }` / re-export tracking · **Exports** — public/exported symbol detection · **Heritage** — class inheritance, interfaces, mixins · **Type Annotations** — explicit type extraction for receiver resolution · **Constructor Inference** — infer receiver type from constructor calls (`self`/`this` resolution included for all languages) · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics
|
||||
|
||||
|
|
|
|||
|
|
@ -7,19 +7,23 @@
|
|||
* prebuilds activated via node-gyp-build. All can be skipped via
|
||||
* GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
|
||||
* soft-fail when no prebuild matches the host platform (and a source build was
|
||||
* unavailable / not attempted).
|
||||
* unavailable / not attempted). tree-sitter-zig is the one npm-installed
|
||||
* optionalDependency in the list; its `probe` overrides the vendored load.
|
||||
*
|
||||
* Either path produces the same observable: the .node binding is absent
|
||||
* at runtime. This helper detects that condition and surfaces a single
|
||||
* stderr line per missing grammar so users learn why .dart/.proto/.swift/.kt
|
||||
* stderr line per missing grammar so users learn why .dart/.proto/.swift/.kt/.zig
|
||||
* support is unavailable instead of silently getting a degraded index.
|
||||
*/
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { isGrammarRuntimeSkipped } from '../core/tree-sitter/parser-loader.js';
|
||||
import { requireVendoredGrammar } from '../core/tree-sitter/vendored-grammars.js';
|
||||
import { cliWarn } from './cli-message.js';
|
||||
|
||||
const _require = createRequire(import.meta.url);
|
||||
|
||||
interface OptionalGrammar {
|
||||
/** Display name in warnings */
|
||||
name: string;
|
||||
|
|
@ -34,6 +38,12 @@ interface OptionalGrammar {
|
|||
* `.proto`, which is a gRPC-extractor concern, not a SupportedLanguages.
|
||||
*/
|
||||
language?: SupportedLanguages;
|
||||
/**
|
||||
* Availability probe. Defaults to `requireVendoredGrammar(pkg)`; grammars
|
||||
* that install from npm as an optionalDependency (zig) override it with a
|
||||
* plain `require` of the package.
|
||||
*/
|
||||
probe?: () => unknown;
|
||||
}
|
||||
|
||||
const OPTIONAL_GRAMMARS: OptionalGrammar[] = [
|
||||
|
|
@ -56,6 +66,14 @@ const OPTIONAL_GRAMMARS: OptionalGrammar[] = [
|
|||
extensions: ['.kt', '.kts'],
|
||||
language: SupportedLanguages.Kotlin,
|
||||
},
|
||||
{
|
||||
name: 'tree-sitter-zig',
|
||||
pkg: '@tree-sitter-grammars/tree-sitter-zig',
|
||||
extensions: ['.zig'],
|
||||
language: SupportedLanguages.Zig,
|
||||
// npm optionalDependency, not vendored — probe via plain require.
|
||||
probe: () => _require('@tree-sitter-grammars/tree-sitter-zig'),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -105,7 +123,11 @@ export function detectMissingOptionalGrammars(): MissingGrammar[] {
|
|||
continue;
|
||||
}
|
||||
try {
|
||||
requireVendoredGrammar(g.pkg);
|
||||
if (g.probe !== undefined) {
|
||||
g.probe();
|
||||
} else {
|
||||
requireVendoredGrammar(g.pkg);
|
||||
}
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
|
|
|
|||
|
|
@ -37,34 +37,16 @@ import { zigMethodConfig } from '../method-extractors/configs/zig.js';
|
|||
import { createVariableExtractor } from '../variable-extractors/generic.js';
|
||||
import { zigVariableConfig } from '../variable-extractors/configs/zig.js';
|
||||
import { zigTypeConfig } from '../type-extractors/zig.js';
|
||||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
import {
|
||||
emitZigScopeCaptures,
|
||||
interpretZigImport,
|
||||
interpretZigTypeBinding,
|
||||
isZigContainerMethod,
|
||||
zigArityCompatibility,
|
||||
zigBindingScopeFor,
|
||||
zigReceiverBinding,
|
||||
} from './zig/index.js';
|
||||
|
||||
const ZIG_CONTAINER_TYPES = new Set(['struct_declaration', 'enum_declaration', 'union_declaration']);
|
||||
|
||||
/**
|
||||
* A Zig `fn` declared directly inside a struct/enum/union container is a
|
||||
* method (mirrors `isKotlinClassMethod`). Containers nest function
|
||||
* declarations directly — there is no intermediate body node.
|
||||
*/
|
||||
function isZigContainerMethod(
|
||||
captureNode: { parent?: SyntaxNode | null } | null | undefined,
|
||||
): boolean {
|
||||
let ancestor = captureNode?.parent;
|
||||
while (ancestor) {
|
||||
if (ZIG_CONTAINER_TYPES.has(ancestor.type)) return true;
|
||||
ancestor = ancestor.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const zigProvider = defineLanguage({
|
||||
id: SupportedLanguages.Zig,
|
||||
extensions: ['.zig'],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getZigParser, getZigScopeQuery } from './query.js';
|
|||
import { getTreeSitterBufferSize } from '../../constants.js';
|
||||
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
||||
|
||||
const ZIG_CONTAINER_TYPES = new Set([
|
||||
export const ZIG_CONTAINER_TYPES: ReadonlySet<string> = new Set([
|
||||
'struct_declaration',
|
||||
'enum_declaration',
|
||||
'union_declaration',
|
||||
|
|
@ -27,11 +27,15 @@ function isContainerOrImportBinding(declNode: SyntaxNode): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** A `fn` nested in a struct/enum/union container is a method — mirror the
|
||||
* provider's `labelOverride` so scope-side defs carry the same label the
|
||||
* worker gives the graph node. */
|
||||
function isContainerMethod(fnNode: SyntaxNode): boolean {
|
||||
let ancestor = fnNode.parent;
|
||||
/** A `fn` nested in a struct/enum/union container is a method. Single
|
||||
* predicate shared between the provider's `labelOverride` (worker
|
||||
* structure phase) and the scope-capture relabel below, so the graph
|
||||
* node label and the scope-side def label cannot drift apart. The loose
|
||||
* parameter shape matches what `labelOverride` receives. */
|
||||
export function isZigContainerMethod(
|
||||
captureNode: { readonly parent?: SyntaxNode | null } | null | undefined,
|
||||
): boolean {
|
||||
let ancestor = captureNode?.parent;
|
||||
while (ancestor) {
|
||||
if (ZIG_CONTAINER_TYPES.has(ancestor.type)) return true;
|
||||
ancestor = ancestor.parent;
|
||||
|
|
@ -75,7 +79,7 @@ export function emitZigScopeCaptures(
|
|||
// Relabel container-nested fns Function → Method (provider labelOverride
|
||||
// parity). The anchor capture name carries the kind, so rebuild it.
|
||||
const fnAnchor = nodeMap['@declaration.function'];
|
||||
if (fnAnchor !== undefined && isContainerMethod(fnAnchor)) {
|
||||
if (fnAnchor !== undefined && isZigContainerMethod(fnAnchor)) {
|
||||
const fnCapture = grouped['@declaration.function']!;
|
||||
delete grouped['@declaration.function'];
|
||||
grouped['@declaration.method'] = { ...fnCapture, name: '@declaration.method' };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export { emitZigScopeCaptures } from './captures.js';
|
||||
export { emitZigScopeCaptures, isZigContainerMethod, ZIG_CONTAINER_TYPES } from './captures.js';
|
||||
export { interpretZigImport, interpretZigTypeBinding, normalizeZigTypeName } from './interpret.js';
|
||||
export {
|
||||
zigArityCompatibility,
|
||||
|
|
|
|||
|
|
@ -173,9 +173,10 @@ export function namesAtScope(scopeId: ScopeId, scopes: ScopeResolutionIndexes):
|
|||
* that collapses to `@scope.class` in the scope-extractor query contract.
|
||||
*
|
||||
* Semantics widened historically from `'Class' | 'Interface'` to cover
|
||||
* C#-shape languages (struct, record, enum, trait). Languages that emit
|
||||
* only `'Class'` are unaffected — the extra kinds never appear in their
|
||||
* parsed output.
|
||||
* C#-shape languages (struct, record, enum, trait) and Zig tagged unions
|
||||
* (`union(enum)` containers own methods like structs do). Languages that
|
||||
* emit only `'Class'` are unaffected — the extra kinds never appear in
|
||||
* their parsed output.
|
||||
*/
|
||||
export function isClassLike(t: string): boolean {
|
||||
return (
|
||||
|
|
@ -184,7 +185,8 @@ export function isClassLike(t: string): boolean {
|
|||
t === 'Struct' ||
|
||||
t === 'Record' ||
|
||||
t === 'Enum' ||
|
||||
t === 'Trait'
|
||||
t === 'Trait' ||
|
||||
t === 'Union'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ pub fn main() void {
|
|||
var p = pioneer.Pioneer{ .energy = 0 };
|
||||
p.tick();
|
||||
helper();
|
||||
const t = pioneer.Tag{ .energy = 5 };
|
||||
_ = t.isEnergy();
|
||||
}
|
||||
|
||||
fn helper() void {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ pub const State = enum { idle, working };
|
|||
pub const Tag = union(enum) {
|
||||
none,
|
||||
energy: u32,
|
||||
|
||||
pub fn isEnergy(self: Tag) bool {
|
||||
return self == .energy;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Pioneer = struct {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,17 @@ describe('Zig basic resolution', () => {
|
|||
expect(methods).toContain('reset');
|
||||
});
|
||||
|
||||
it('extracts union(enum) methods as Methods (Union is class-like)', () => {
|
||||
expect(getNodesByLabel(result, 'Method')).toContain('isEnergy');
|
||||
});
|
||||
|
||||
it('dispatches method calls on a union receiver (main → isEnergy)', () => {
|
||||
// Pins the `isClassLike('Union')` widening in scope/walkers.ts: without
|
||||
// it `populateClassOwnedMembers` finds no class-like def in the Tag
|
||||
// scope, the method gets no ownerId, and dispatch silently drops.
|
||||
expect(edgeSet(getRelationships(result, 'CALLS'))).toContain('main → isEnergy');
|
||||
});
|
||||
|
||||
it('resolves the relative @import("./pioneer.zig") to pioneer.zig', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const internal = imports.filter((e) => e.targetFilePath.endsWith('pioneer.zig'));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue