Skip to content

feat(extraction): add Odin language support - #1523

Open
ivandrenjanin wants to merge 2 commits into
colbymchenry:mainfrom
ivandrenjanin:odin-language-support
Open

feat(extraction): add Odin language support#1523
ivandrenjanin wants to merge 2 commits into
colbymchenry:mainfrom
ivandrenjanin:odin-language-support

Conversation

@ivandrenjanin

Copy link
Copy Markdown

Adds Odin support: a vendored tree-sitter-odin grammar, an extractor, the registration points, and tests.

It works

I indexed three public Odin repos with this branch. A main build finds nothing in any of them, since .odin is not a known extension yet.

repo commit files nodes edges on main
DanielGavin/ols e62a371 138 4,640 10,646 0 / 0
greenya/SpaceLib 95bf115 166 3,893 8,223 0 / 0
laytan/odin-http 65f57ca 35 1,627 2,968 0 / 0

An Odin package is a directory, so a call into another package has to carry its qualifier. Every cross-directory call edge across the three resolves by qualified name, and none point at a builtin like len or append.

About #1000

@RainerXE proposed Odin back in June in #1000 and I built on his work. He picked the grammar and worked out the node types an extractor needs. I checked those against the parser and they are right.

I did not cherry-pick his code. The extractor is new, and the overlap is boilerplate the LanguageExtractor interface forces. His extractStruct fix is a real bug that is still on main, and I left it for him.

He got there first and I would rather he got the credit. Take anything useful here, it is MIT. I opened separately because #1000 has conflict markers committed and its registration edits got reverted by upstream merges, so no .odin file would actually be opened. That is not carelessness on his part: his head branch is his fork's main, so every sync merges into the PR. Up to you which one you take.

Not done

The agent A/B in CLAUDE.md step 3. It needs a logged-in claude CLI and tmux, and the harness targets macOS/Linux. Steps 1 and 2 ran fine. So I have no retrieval-quality number against a without-codegraph arm. I will run it if you can point me at an environment.

CRLF checkouts lose symbols. The grammar chokes on a trailing-backslash line continuation when the file has CRs, which is what odinfmt emits on a wrapped line. Roughly twice as many files fail to parse on a Windows checkout as on a POSIX one, and a few lose their declarations outright. Two smaller grammar gaps sit in the doc. I worked around none of them, and the README and CHANGELOG rows say so.

Windows only. I did not test macOS or Linux. The two things that break on a case-sensitive filesystem are fine: the wasm filename and the ./odin import are both lowercase.

Some tests already fail on Windows before this PR, all EPERM from fs.rmSync in cleanup. Same names on main. My branch adds passing tests and nothing else moves.

Grammar

source tree-sitter-grammars/tree-sitter-odin
tag v1.3.0
license MIT, Copyright (c) 2023 Amaan Qureshi
sha256 99fd3217b82b1ba4f438120591ec2ed24108f782ab693d69b330d753188632fb
built from the tag's checked-in src/parser.c, no regenerate, no patch

I pinned the tag rather than master because master's one unreleased commit breaks multi-value returns, turning f :: proc() -> (string, bool) into an error tree. Both binaries load cleanly, so you only catch it by diffing a corpus. Provenance and a rebuild recipe are in docs/grammars/tree-sitter-odin.md.

Wasm only. The kernel has no Odin, so codegraph-kernel is untouched.

Testing

tsc --noEmit and npm run build are clean, and the wasm reaches dist/ with its hash intact. The Odin tests pass. The fixture covers what those three repos do not reach on their own: calling conventions, when blocks, unions, bit fields, and procedure groups.


Twelve files in two commits. The wasm sits alone in the first one so the source diff stays readable. Nothing outside Odin, and no version bump.

Odin did surface a bug in codegraph affected, but the fix changes Go, Rust and Python too, so it is not this PR's to make. I can send it separately.

Refs #648, #1000.

… built from the tag's checked-in parser.c

tree-sitter-wasms does not ship an Odin grammar, so the wasm is built here
from tree-sitter-grammars/tree-sitter-odin @ v1.3.0 (e8adc73, MIT,
Copyright (c) 2023 Amaan Qureshi) with tree-sitter-cli 0.26.11
`build --wasm`, ABI 14. The tag's checked-in parser.c is used as-is rather
than regenerated: it predates the ABI-15 generator, and regenerating would
diverge the tables from the tag nobody could then reproduce.

Pin the TAG and not master. Master's single unreleased commit past v1.3.0
(d2ca8ef, "allow multiple identifiers before `:` in named_type") regresses
multi-value return types — `f :: proc() -> (string, bool)` parses to an
ERROR node — which is one of the most common signature shapes in real Odin.
Measured across three pinned public Odin repositories (343 files): 68 files
with parse errors at v1.3.0 against 111 at master.

docs/grammars/tree-sitter-odin.md records the sha256, the parser.c and
scanner.c hashes, the builder version, a reproducible rebuild recipe, and
the measured parse health — including the fact that a CRLF working tree
costs 1.7% of declarations to a v1.3.0 line-continuation gap.

Refs colbymchenry#648, colbymchenry#1000
…ll scoping

Registers `odin` across the five wiring points — LANGUAGES in src/types.ts,
the wasm-file map / EXTENSION_MAP / VENDORED_WASM_LANGS / display name in
grammars.ts, and the extractor map — and adds a 581-line extractor covering
procedures with their calling conventions and `@(...)` attributes, procedure
groups linked to their overloads, structs with fields, enums, unions, bit
fields, `distinct` and proc-type aliases, `::` constants, imports with and
without an alias, `foreign` blocks and `when` blocks.

Two walker branches in tree-sitter.ts, both guarded on `this.language ===
'odin'` and both there to stop a WRONG edge rather than to add one:

- extractVariable: Odin declares positionally — `[attributes] NAME :: value`,
  with no `name:` field in the grammar and a comma-separated name list on
  both the `::` and the `:` form. The generic fallback takes every identifier
  child, so `Alias :: Other` minted a second symbol named after the
  right-hand side.

- extractCall: Odin puts a call's package qualifier OUTSIDE the call node —
  `fmt.println(x)` is member_expression(`fmt`, call_expression(function:
  `println`)) — so the generic path read `println` alone and linked it to
  whatever same-named procedure the repo happened to define, which is the
  wrong-edge class colbymchenry#1079/colbymchenry#1107 fixed for same-named methods. The qualifier is
  re-attached as `pkg::callee`, matching the qualifiedName the package
  namespace already gives every top-level symbol. A `->` call is deliberately
  left bare: its receiver is a variable, not a package. Builtins (`len`,
  `append`, `max`) are suppressed at emit the way terraform's BUILTIN_HEADS
  and SCALA_BUILTIN_TYPES are.

An Odin package IS a directory, so resolution/index.ts gains a third arm
beside the two nix ones on the same `if`/`else if`: an unqualified `calls`
ref can only bind inside its own package directory, because a genuine
cross-package call carries its qualifier and resolves by qualified name.
This is the same directory-scoping shape Terraform already has and that the
Nix language PR (7f32513) established for a language's own scoping rules.
A committed test asserts the gate costs neither of the two edges Odin
genuinely has.

Imports are recorded as symbols but deliberately not resolved to file edges:
`import "core:fmt"` and `import "../shared"` name a directory, not a file.

28 extraction tests and 2 resolution tests, modeled on the Rust block.
README and CHANGELOG gain their rows, both carrying the grammar's measured
parse caveat; docs/grammars/tree-sitter-odin.md (previous commit) carries
the provenance and the numbers.

Odin routes to the wasm engine — the native Rust kernel has no Odin
counterpart, and adding one is a clean follow-up rather than a prerequisite.

Refs colbymchenry#648, colbymchenry#1000
@RainerXE

RainerXE commented Aug 6, 2026

Copy link
Copy Markdown

@ivandrenjanin - I'm happy if you could take this over and work on it, as t the moment i have very limited time for this. Thank you Rainer PS: My code was created and run on macosx.

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