§02 — Header-Assisted Extern Blocks — #header(...)
Context
Per approved proposal §2. Supersedes docs/ori_lang/proposals/superseded/c-header-import-proposal.md — the namespace-import @cImport(...) approach is rejected; this section implements the block-scoped #header(...) approach.
§02.1 — Grammar + parser
- Extend
compiler/ori_parse/src/grammar/item/extern_def.rsto accept block-level attributes#header("file.h")and#header_path("search/path")at theextern "c" from "lib"position. - Grammar production (see
grammar.ebnfadditions tracked in §08):extern_block_attr = "#header" "(" string_literal ")" | "#header_path" "(" string_literal ")" | (existing: #error, #free) . - Update AST: add
header: Option<PathBuf>,header_path: Option<PathBuf>fields to the extern block AST node. - Parser-level validation:
#headerand#header_pathrequire a string literal path. E1xxx error code (reserve at parser layer) for malformed attribute syntax; NOT in the E40xx FFI range.
§02.2 — libclang integration
- Create new crate
compiler/ori_cheader/(candidate name; final name decided during implementation) that wraps libclang. Crate is a pure leaf (no Ori-compiler dependencies — produces aCHeaderFactsstruct consumed byori_types). Why a separate crate: libclang is a heavy native dependency; isolating it keeps theori_typesrebuild graph clean when libclang’s transitive C++ deps change. - Dependencies: use the
clang-sysorclangRust crate (evaluate in design phase). libclang itself is already present on the host (LLVM 21 toolchain perc-header-import-proposal.mdobservation). - Public API:
fn parse_header(path: &Path, search_paths: &[PathBuf]) -> Result<CHeaderFacts, CHeaderError>returning declared functions, structs, typedefs, enum values (not used by §02 but needed by §03). - Cache parsed headers per-build using Salsa (the
CHeaderFactsfor a given(path, search_paths)tuple is pure with respect to the header’s content hash).
§02.3 — Signature auto-derivation + validation
-
In
ori_typesduring extern-block type-checking, when#header(...)is present:- For each listed function in the block body WITHOUT an explicit signature: derive signature from header facts. Map C types → Ori types per the table below.
- For each listed function WITH an explicit signature: validate the signature MATCHES the header’s declaration. Mismatch → E4016.
- If a function is listed but NOT present in the header → E4020 or E4021 (depending on whether the header parsed at all).
-
C → Ori type mapping table (normative):
C Type Ori Type int,int32_tc_intunsigned int,uint32_tc_uintlong,int64_tc_longunsigned long,uint64_tc_ulongshort,int16_tc_shortunsigned short,uint16_tc_ushortchar,int8_tc_charunsigned char,uint8_tbytesize_tc_sizessize_tc_longfloatc_floatdoublec_doublevoidvoidvoid*CPtrconst char*str(borrowed by default)T*(named struct)CPtr(opaque, unless Ori has matching#repr("c")struct with#verify_layout)T[]byte arrays[byte]
§02.4 — Header path resolution (deterministic)
Per approved proposal §2 rule 5:
- Implement resolution order:
- Explicit
#header_path("...")on the block → use verbatim. pkg-config --cflags <lib>whenfrom "<lib>"is a pkg-config-registered library → extract-Ipaths.- System default include paths (
/usr/include,/usr/local/include, compiler-default on host) → host C toolchain defaults. - No match → E4020 “header path not resolvable.”
- Explicit
- First match wins.
#header_pathALWAYS trumps pkg-config; hermetic build systems (Bazel, Nix) use this to vendor headers. - pkg-config invocation wrapped in a cached helper (cache per
(lib, working_dir)via Salsa). - When pkg-config is ABSENT on the host (Windows without MSYS2, sandboxed builds), resolution falls through to step 3 silently — no error unless step 4 fires.
§02.5 — Matrix tests
- Failing tests FIRST:
- AOT test
tests/spec/ffi/header_sqlite.ori— simulates a small SQLite binding via#header("sqlite3.h")— requires libsqlite3-dev in CI. If CI does not provide sqlite3-dev, use a checked-in fixture headertests/fixtures/mock_sqlite.h. - FileCheck test: derived signatures match the header’s actual declarations.
- AOT test
- Matrix dimensions:
- Declaration style × Header availability:
{no #header, explicit sig}×{#header, explicit sig}×{#header, no explicit sig}×{#header, sig conflicts with header}= 4 cells. - Type mapping coverage: at least one test per row in §02.3’s mapping table (15 cells).
- Path resolution:
{explicit #header_path}×{pkg-config resolves}×{system include resolves}×{none resolve → E4020}= 4 cells.
- Declaration style × Header availability:
- Semantic pins:
- A program that compiles ONLY with §02 (uses
#header(...)without explicit signatures) — FileCheck that the signatures are present in the LLVM IR. - A negative pin: a program whose explicit Ori signature diverges from the header MUST emit E4016 at compile time.
- A program that compiles ONLY with §02 (uses
- Regression guard: 20+ approved Deep FFI test programs (no
#header) continue to compile unchanged — run as a separatecargo test -p oric -- existing_ffi_regressionsuite.
§02.6 — Documentation
- Add §02-specific section to
docs/ori_lang/v2026/spec/26-ffi.md(co-commit with §08 sync run — see §08 for the aggregate). - Add attribute entries to
.claude/rules/ori-syntax.mdDeep FFI section (#header,#header_path). - Document the C→Ori type mapping table in the spec; reference it from
ori-syntax.mdwithout duplicating.
Completion Checklist
- §02.1 grammar + parser ships with tests.
- §02.2 libclang wrapper crate ships; Salsa-cached.
- §02.3 signature auto-derivation + validation works; type mapping complete.
- §02.4 path resolution deterministic; pkg-config absence handled.
- §02.5 matrix tests green, semantic pin green, negative pin green, regression guard green.
- §02.6 spec clause +
ori-syntax.mdentries committed. -
./test-all.sh,./clippy-all.sh,./llvm-test.shall green (debug + release). -
/tpr-reviewclean./impl-hygiene-reviewclean. -
/improve-toolingsection-close sweep./sync-claudesection-close. -
sections[id=02].status: complete,reviewed: true.
§02.R — Third Party Review Findings
- None.