IrModule Structure
The root container for all IR definitions, plus the per-source-module
index that mirrors mod foo { ... } hierarchy.
Architecture Overview
IrModule (root)
|
+-- structs: Vec<IrStruct>
| |
| +-- name: String
| +-- visibility: Visibility
| +-- traits: Vec<IrTraitRef> ----> trait_id + optional generic-trait args
| +-- fields: Vec<IrField>
| | |
| | +-- name: String
| | +-- ty: ResolvedType (may contain StructId/TraitId/EnumId refs)
| | +-- mutable: bool
| | +-- optional: bool
| | +-- default: Option<IrExpr>
| |
| +-- generic_params: Vec<IrGenericParam>
| |
| +-- name: String
| +-- constraints: Vec<IrTraitRef> (trait_id + Vec<ResolvedType> args)
|
+-- traits: Vec<IrTrait>
| |
| +-- name: String
| +-- visibility: Visibility
| +-- composed_traits: Vec<TraitId> -----> trait inheritance
| +-- fields: Vec<IrField>
| +-- methods: Vec<IrFunctionSig> -----> required method signatures
| +-- generic_params: Vec<IrGenericParam>
|
+-- enums: Vec<IrEnum>
| |
| +-- name: String
| +-- visibility: Visibility
| +-- variants: Vec<IrEnumVariant>
| | |
| | +-- name: String
| | +-- fields: Vec<IrField>
| |
| +-- generic_params: Vec<IrGenericParam>
|
+-- impls: Vec<IrImpl>
| |
| +-- target: ImplTarget ----------> Struct(StructId), Enum(EnumId) or Primitive(PrimitiveType)
| +-- trait_ref: Option<IrTraitRef>
| +-- functions: Vec<IrFunction>
|
+-- lets: Vec<IrLet> // Module-level let bindings
|
+-- functions: Vec<IrFunction> // Standalone function definitions
|
+-- imports: Vec<IrImport> // What the `use` statements import
|
+-- modules: Vec<IrModuleNode> // Source `mod` tree and imported modules
|
+-- file_table: Vec<PathBuf> // Source files, indexed by FileId
IrModule
#![allow(unused)] fn main() { pub struct IrModule { pub structs: Vec<IrStruct>, pub traits: Vec<IrTrait>, pub enums: Vec<IrEnum>, pub impls: Vec<IrImpl>, pub lets: Vec<IrLet>, // Module-level let bindings pub functions: Vec<IrFunction>, // Standalone function definitions pub imports: Vec<IrImport>, // What the `use` statements import pub modules: Vec<IrModuleNode>, // Source `mod foo { ... }` hierarchy pub file_table: Vec<PathBuf>, // Source files, indexed by FileId // private name-to-id indexes, skipped by serde } }
The flat per-type vectors remain authoritative: every definition
lives in the appropriate slot regardless of source nesting. The
modules tree is an index on top of those flat vectors, opt-in
for backends that need to emit code into namespaces.
The vectors start with the prelude. Array, Seq, Dictionary and
Range are the first four structs, Optional is the first enum, the
prelude's extern impl blocks are the first impls, and assert is the
first function. See Resolved Types
for the accessors that find them.
Lookup Methods
#![allow(unused)] fn main() { impl IrModule { /// Look up a definition by ID. Returns None if out of bounds. pub fn get_struct(&self, id: StructId) -> Option<&IrStruct>; pub fn get_trait(&self, id: TraitId) -> Option<&IrTrait>; pub fn get_enum(&self, id: EnumId) -> Option<&IrEnum>; pub fn get_function(&self, id: FunctionId) -> Option<&IrFunction>; /// Look up an ID by name. pub fn struct_id(&self, name: &str) -> Option<StructId>; pub fn trait_id(&self, name: &str) -> Option<TraitId>; pub fn enum_id(&self, name: &str) -> Option<EnumId>; /// For an overloaded name, the id of the first overload. pub fn function_id(&self, name: &str) -> Option<FunctionId>; /// Look up a module-level let binding by name. pub fn get_let(&self, name: &str) -> Option<&IrLet>; pub fn has_let(&self, name: &str) -> bool; /// The source path of a FileId. None for FileId(0). pub fn file_path(&self, file: FileId) -> Option<&PathBuf>; /// Add a path to `file_table`, or find it there, and return its id. pub fn register_file(&mut self, path: PathBuf) -> FileId; /// Rebuild the internal name-to-ID indices after mutating the module. /// /// Call this after adding, removing or reordering definitions in /// `structs`, `traits`, `enums`, `functions` or `lets` so that the /// `*_id()` lookup methods stay consistent. Serde skips the /// indices: call it after you deserialise a module, too. pub fn rebuild_indices(&mut self); } }
Imports
imports records what the use statements of the entry module
import. A backend can use it to emit import statements.
The public entry points link each imported module into the result (see
Programs over several files).
The imported definitions are then local definitions under a qualified
name, such as geom::Point, and each reference to them uses a local
id. So imports only records names. A backend does not need the IR of
another module.
IrImport
#![allow(unused)] fn main() { pub struct IrImport { /// Logical module path (e.g., ["utils", "helpers"]) pub module_path: Vec<String>, /// Items imported from this module pub items: Vec<IrImportItem>, /// Filesystem path to the source module file pub source_file: PathBuf, } }
IrImportItem
#![allow(unused)] fn main() { pub struct IrImportItem { /// Name of the imported item, as the `use` wrote it pub name: String, /// Kind of the item pub kind: ImportedKind, } }
ImportedKind
#![allow(unused)] fn main() { pub enum ImportedKind { Struct, Trait, Enum, Function, // use other::compute ModuleLet, // use other::CONST } }
Using Imports in Code Generators
A generator that emits one output file for each source module can
read imports for the import statements, and the
module tree for the items of
each imported module:
#![allow(unused)] fn main() { fn generate_typescript_imports(module: &IrModule) -> String { let mut output = String::new(); for import in &module.imports { let path = import.module_path.join("/"); let items: Vec<&str> = import.items.iter().map(|i| i.name.as_str()).collect(); output.push_str(&format!( "import {{ {} }} from './{}';\n", items.join(", "), path )); } output } }
IrModuleNode: source mod hierarchy
IrModule.modules mirrors the source mod foo { ... } tree. Each
node lists the IDs of struct/trait/enum/function definitions
declared directly in that module plus nested sub-modules. The
flat per-type vectors on IrModule remain authoritative: this
tree is an index on top of them for backends that need to
preserve source structure in their output (JS export * from,
Swift nested types, Kotlin packages).
#![allow(unused)] fn main() { pub struct IrModuleNode { /// Module name as written in source (the unqualified segment, /// e.g. `"shapes"` for `mod shapes { ... }`). pub name: String, /// IDs of structs declared directly in this module. pub structs: Vec<StructId>, /// IDs of traits declared directly in this module. pub traits: Vec<TraitId>, /// IDs of enums declared directly in this module. pub enums: Vec<EnumId>, /// IDs of functions declared directly in this module. pub functions: Vec<FunctionId>, /// Nested sub-modules. pub modules: Vec<IrModuleNode>, } }
Top-level (non-mod) definitions of the entry module are not
mirrored in the tree; backends iterate the flat vectors for those.
The tree also holds one node for each imported module, at the path
that the use wrote. For use lib::util::one, the node lib holds
the node util, and that node lists the id of lib::util::one.