Skip to content

feat: Rust language support (.rs) - #59

Draft
qoole wants to merge 21 commits into
NanoNets:mainfrom
qoole:feat/rust-support
Draft

feat: Rust language support (.rs)#59
qoole wants to merge 21 commits into
NanoNets:mainfrom
qoole:feat/rust-support

Conversation

@qoole

@qoole qoole commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Builds on #58 (PowerShell support), which builds on #40 (tree-sitter 0.25) — this branch is stacked on #58's branch, so ancestor commits appear here until those PRs merge; the Rust work is the ten commits from chore: add Rust tree-sitter grammar onward. Opening as a draft; I'll rebase and mark ready as the stack beneath it lands.

Tier-1 graph extraction for Rust via tree-sitter-rust 0.24.0 (the tree-sitter org's own grammar; prebuilds for all six platform/arch combos — #40's 0.25 runtime is the prerequisite, and the same npm overrides pattern handles its peer range).

What

  • Definitions: functions, structs, enums, traits (→ interface), type aliases, unions, methods with impl-block owners (generics-stripped: impl Cache<T> owns as Cache), and macro_rules! definitions. Inline modules contribute scope segments (file.rs#imp.open), so cfg-gated sibling mods stay distinct and resolvable.
  • Visibility: pub/pub(crate)/pub(super) → exported; pub(self) and plain items → not; trait-impl methods inherit the trait contract (exported); #[macro_export] macros exported; everything inside #[cfg(test)] mods unexported (#[cfg(not(test))] is correctly NOT matched).
  • Calls: bare calls, method calls with typed-receiver inference (typed let, constructor-let T::new(), typed params, self → enclosing impl), Type::method / Self::method static calls, turbofish unwrapped, tuple-index calls guarded. Scoped calls (crate::worker::run()) resolve through the module tree and are restricted to the resolved file's candidates — never stripped to a bare name.
  • Macros: invocations and definitions live in their own namespace — names carry the ! (log_it!), so a macro can never wire to a same-named function (Rust's namespaces are distinct). Std/prelude macros are skipped at extraction; path-qualified forms (std::println!) are recognized too.
  • Imports: use paths (all forms: scoped, grouped per-leaf, renames, wildcards) and mod declarations resolve through the real module tree — crate::/super::/self::, both x.rs and x/mod.rs layouts, the parser-file rule (src/parser.rs's children live in src/parser/), inline-mod super consumption (a use super::* inside #[cfg(test)] mod tests correctly refers to its own file), and integration-test crate roots (tests/*.rs anchor crate:: to themselves, not the library).
  • Workspaces: each Cargo.toml's [package] name (section-aware parse; [[bin]] names ignored) maps cross-crate imports (foo_bar::…crates/foo-bar/), including dependency { package = "…" } aliases; duplicate package names are treated as ambiguous and dropped rather than guessed, and crate matching is deterministic regardless of enumeration order.
  • Domain isolation: Rust names resolve in a Rust-only domain — separate global-name and method-owner indexes — so a Rust symbol never links to a same-named symbol from another language, in either direction.
  • Ambiguity posture: a type with several trait impls of the same method name (From<A>/From<B> both giving Frame.from; Display/Debug both giving fmt) drops the member call as ambiguous instead of guessing first-in-file. Drop-don't-guess throughout, matching map clusters one entry per file instead of per directory #35's resolution philosophy.

Evidence

  • Suite: all green on this branch (77 Rust-specific assertions across extraction, bindings, resolution, and the review-derived regression pins below; plus CRLF, >32 KB, malformed-input tolerance, and double-build determinism guards mirroring the PowerShell suite).
  • Real-world battery (two production Rust codebases, 350 files total, one a multi-crate polyglot workspace with a TS frontend): builds clean, byte-identical double-build determinism on both, 99.5% and 91.8% of internal-path imports wired (the remainder are dropped, never misdirected), 81 imports wired cross-crate through the workspace map, and zero cross-language edges in either direction in the polyglot repo.
  • The branch went through two independent adversarial review passes before this PR; every wrong-edge finding became a failing-test-first fix (crate-root anchoring corner cases, inline-mod call paths, workspace crate-name shadowing of mod declarations, #[path] attributes, the cross-language method-owner leak). The reviews' dropped-edge findings are disclosed below rather than patched around.

Known limitations (deliberate; all fail toward dropped edges, never wrong ones)

  • Macro token-tree bodies are opaque — assert_eq!(compute(), 3) contributes no compute call edge (no expansion).
  • UFCS calls (<T as Tr>::method()) drop.
  • #[path = "…"] mod declarations drop (resolving conventionally would wire the wrong file).
  • Cross-file impl Trait for Type heritage is dropped (trait-default-method resolution needs the type defined in the same file).
  • impl blocks inside function bodies mint unowned functions.
  • Files nested deeper than one level under tests/ don't anchor crate:: (statically ambiguous — each test binary is its own crate).
  • pub use re-export chains and proc-macro expansion are unsupported.
  • Grammar 0.24.0 lacks Rust 1.82's &raw const/mut (~2–4% of files in modern codebases parse with errors; extraction degrades gracefully and still yields those files' other definitions). Happy to file that upstream.

qoole and others added 21 commits August 5, 2026 17:48
Newer grammar releases in the tree-sitter ecosystem require the 0.25
runtime, so bump tree-sitter ^0.21.1→^0.25.0 and the Python/Go grammars
to ^0.25.0 to unlock languages whose grammars need it. tree-sitter-typescript
stays pinned at ^0.23.2 (still the latest release) — it declares
peerOptional tree-sitter@^0.21.0, so an npm `overrides` entry pins its
nested tree-sitter to the root one so the repo-root install resolves
cleanly; verified the grammar's native binding still loads and parses
correctly under tree-sitter 0.25.1.

Also updates the stale-parse-limit comment in extract.ts: the chunked
callback parse predates 0.25 (which lifted the 32KB string-parse limit)
and is kept because it's behavior-identical and exercised by existing
tests. No behavior change; full suite green (493/493).
Use stable syntax-node IDs to avoid duplicate reference edges and align the supported Node version with native Windows prebuilds.
Adds the PowerShell language to extract.ts: describePowerShell recognizes
function/class/enum/method statements, psCalleeName/psImportKind/
psImportSpecifier classify commands (dot-source, Import-Module, using
module) versus real calls, and psExported covers PowerShell's lack of a
file-local export marker. bindings.ts's FileBindings gains a case-insensitive
mode and PowerShell-specific defName/resolveRecvType handling ($this,
scope-qualified definition names).
…, builtin cmdlets

resolve.ts bundles the name-lookup and owner-index maps (NameIndex/
OwnerIndex) and adds a PowerShell case-folded tier alongside the existing
exact-match one: same-file exact wins first, then same-file case-folded,
then whole-repo exact, then whole-repo case-folded. A PowerShell caller
that isn't itself a test file demotes test-path candidates at the two
global tiers before the uniqueness check, so Pester's habit of
redeclaring a prod name across several *.Tests.ps1 files doesn't drop
every cross-file call as ambiguous. PS_BUILTIN_CMDLETS denylists the core
cmdlet/Pester-DSL surface so a call to a builtin never wires to an
unrelated in-repo definition of the same name. $PSScriptRoot resolves as
a literal for import paths, and PS_IMPORT_EXTS keeps PowerShell's module
extensions from cross-matching TS/Python specifiers.

isTestPath moves to its own src/util/testpath.ts (a hard graph-topology
input for resolve.ts now, not just ask.ts's soft rank-penalty); ask.ts
re-exports it for backward compatibility.
CODE_EXTENSIONS picks up .ps1/.psm1/.psd1; isTestPath recognizes
.tests.ps1 (Pester's naming convention). New test/graph-powershell.test.ts,
test/testpath.test.ts, and test/utf16-routing.test.ts cover PowerShell
graph extraction/resolution end-to-end and the UTF-16LE decode path.
Every existing test file touched by the case-folding, test-aware
tie-break, builtin-cmdlet denylist, and ingest changes is extended with
PowerShell-specific coverage alongside its existing cases.
Assaf750 added a commit to Assaf750/Graft that referenced this pull request Aug 12, 2026
…and Rust behaviour

Records the verified starting point before any Rust work: PR NanoNets#40/NanoNets#58/NanoNets#59 heads
double-checked via the GitHub API and git ls-remote, strict-containment proof of
the stack, and the finding that the stack carries no upstream CI signal at all.

Suite on the pinned head is 638/645 with 3 failures. All three are an upstream
locale defect (bare toLocaleString() resolving to ar-SA digits), reproduced
verbatim on main and untouched by any Rust commit, so they are attributed
upstream rather than counted as a Rust regression. Fix is deliberately deferred
to its own commit.

Also records measured behaviour on a Cargo workspace fixture: what already
resolves, and the six gaps that Batches B-J exist to close — chiefly that
cross-crate symbol imports never resolve because module-path resolution maps
paths to files only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants