From 4962e8b16dc4c9acd6761f25121c7f508768a54c Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sat, 1 Aug 2026 17:26:05 +0800 Subject: [PATCH 1/2] refactor(ir,common): move ModulePass tier from common to ir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit common::pass defined both tiers of the pass family, making the supposedly layer-neutral common layer depend upward on ir (IrGenerator, ir::Error). Move the IR-specific module tier (ModulePass + ModulePassManager) into a new src/ir/pass.rs, declare pub mod pass; in ir.rs, and re-export ModulePass at the ir root. FunctionPass/FunctionPassManager stay in common::pass, which remains IR-coupled through Function — an accepted, now-documented coupling since common is crate-internal. Both tiers' module docs now spell out the fallibility split: FunctionPass is infallible by contract (run returns (); a panic is a compiler bug), ModulePass is fallible (Result<(), Error>, first error aborts the pipeline). Behavior is unchanged: the traits are moved verbatim and all users (ir::module, experimental::return_infer) are rewired to the new paths. ir::gen::module_gen needs no edit — its Pass-2.5 region never names the pass types directly. --- src/common/pass.rs | 68 ++++++++++---------------------- src/experimental/return_infer.rs | 2 +- src/ir.rs | 7 ++++ src/ir/module.rs | 2 +- src/ir/pass.rs | 60 ++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 49 deletions(-) create mode 100644 src/ir/pass.rs diff --git a/src/common/pass.rs b/src/common/pass.rs index 3bfdaeb..8c80997 100644 --- a/src/common/pass.rs +++ b/src/common/pass.rs @@ -1,56 +1,30 @@ -//! Generic pass infrastructure shared by IR generation and optimization. +//! Function-level pass infrastructure shared by IR generation and +//! optimization. //! -//! teac uses two tiers of passes: +//! teac uses two tiers of passes; this module holds the layer-neutral tier: //! -//! - **Module passes** ([`ModulePass`]) run once over the whole program -//! with `&mut IrGenerator<'_>`. Used for cross-function analysis such -//! as return-type inference. //! - **Function passes** ([`FunctionPass`]) run on a single IR //! [`Function`]. Used for optimizations; see [`crate::opt`]. +//! - **Module passes** ([`crate::ir::ModulePass`]) run once over the whole +//! program with `&mut IrGenerator<'_>`. They are IR-coupled by design and +//! live in [`crate::ir::pass`]. //! -//! Each tier has a matching [`ModulePassManager`] / [`FunctionPassManager`] -//! that runs a list of boxed trait objects in registration order. - -use crate::ir::module::IrGenerator; -use crate::ir::{Error, Function}; - -// --------------------------------------------------------------------------- -// Module-level passes -// --------------------------------------------------------------------------- - -/// A pass that runs once over the whole translation unit. -pub trait ModulePass { - /// Run this pass against `gen`. Returning `Err` aborts the pipeline - /// (subsequent passes and later compilation stages are skipped). - fn run(&self, gen: &mut IrGenerator<'_>) -> Result<(), Error>; -} - -/// Sequential pipeline of [`ModulePass`] trait objects, executed in -/// registration order. -#[derive(Default)] -pub struct ModulePassManager { - passes: Vec>, -} - -impl ModulePassManager { - /// Create an empty manager with no registered passes. - pub fn new() -> Self { - Self::default() - } - - /// Append `pass` to the end of the pipeline. - pub fn add_pass(&mut self, pass: Box) { - self.passes.push(pass); - } +//! # Fallibility policy per tier +//! +//! Function passes are **infallible by contract**: [`FunctionPass::run`] +//! returns `()` and a panic means a compiler bug, not a recoverable error. +//! Module passes are **fallible**: they return `Result<(), ir::Error>` and +//! the first error aborts the pipeline. +//! +//! Each tier has a matching pass manager — [`FunctionPassManager`] here and +//! [`crate::ir::pass::ModulePassManager`] in the IR layer — that runs a list +//! of boxed trait objects in registration order. +//! +//! Note that this module remains IR-coupled through +//! [`FunctionPass`]/[`Function`]; that is accepted because `common` is a +//! crate-internal utility layer, not a public API surface. - /// Run every pass against `gen`, stopping at the first error. - pub fn run(&self, gen: &mut IrGenerator<'_>) -> Result<(), Error> { - for pass in &self.passes { - pass.run(gen)?; - } - Ok(()) - } -} +use crate::ir::Function; // --------------------------------------------------------------------------- // Function-level passes diff --git a/src/experimental/return_infer.rs b/src/experimental/return_infer.rs index 33d9148..fa6af40 100644 --- a/src/experimental/return_infer.rs +++ b/src/experimental/return_infer.rs @@ -59,8 +59,8 @@ use std::rc::Rc; use indexmap::IndexMap; use crate::ast; -use crate::common::pass::ModulePass; use crate::ir::compose_var_def_dtype; +use crate::ir::ModulePass; use crate::ir::module::{IrGenerator, Registry}; use crate::ir::types::Dtype; use crate::ir::value::GlobalDef; diff --git a/src/ir.rs b/src/ir.rs index 2c611e9..5f33dff 100644 --- a/src/ir.rs +++ b/src/ir.rs @@ -7,6 +7,7 @@ pub mod error; pub mod function; mod gen; pub mod module; +pub mod pass; pub mod printer; pub mod stmt; pub mod types; @@ -67,6 +68,12 @@ pub(crate) fn compute_link_name(source_name: &str, is_external: bool) -> String pub use error::Error; pub use function::{BasicBlock, BlockLabel, Function, FunctionBody}; pub use module::{IrGenerator, Module, Registry}; +// +// `#[allow(unused_imports)]` because the only in-tree consumer of this +// re-export (the `experimental::ReturnInferPass` implementation) is gated +// on the `return-type-inference` feature. +#[allow(unused_imports)] +pub use pass::ModulePass; pub use types::{Dtype, StructType}; pub use value::{GlobalDef, Local, LocalId, Operand}; diff --git a/src/ir/module.rs b/src/ir/module.rs index 5051fc8..a831502 100644 --- a/src/ir/module.rs +++ b/src/ir/module.rs @@ -2,10 +2,10 @@ //! definitions, and the [`IrGenerator`] that populates them. use super::function::Function; +use super::pass::{ModulePass, ModulePassManager}; use super::types::FunctionType; use super::value::GlobalDef; use crate::ast; -use crate::common::pass::{ModulePass, ModulePassManager}; use indexmap::IndexMap; use std::path::PathBuf; use std::rc::Rc; diff --git a/src/ir/pass.rs b/src/ir/pass.rs new file mode 100644 index 0000000..a074731 --- /dev/null +++ b/src/ir/pass.rs @@ -0,0 +1,60 @@ +//! Module-level pass infrastructure: [`ModulePass`] and [`ModulePassManager`]. +//! +//! teac uses two tiers of passes, split across layers so that the +//! layer-neutral [`crate::common`] utilities never depend upward on the IR: +//! +//! - **Module passes** ([`ModulePass`], this module) run once over the whole +//! program with `&mut IrGenerator<'_>`. Used for cross-function analysis +//! such as return-type inference. +//! - **Function passes** ([`crate::common::pass::FunctionPass`]) run on a +//! single IR [`crate::ir::Function`]. Used for optimizations; see +//! [`crate::opt`]. +//! +//! # Fallibility policy per tier +//! +//! Module passes are **fallible**: [`ModulePass::run`] returns +//! `Result<(), Error>` and the first error aborts the pipeline (subsequent +//! passes and later compilation stages are skipped). Function passes are +//! **infallible by contract**: their `run` returns `()` and a panic means a +//! compiler bug. +//! +//! Each tier has a matching pass manager that runs a list of boxed trait +//! objects in registration order: [`ModulePassManager`] here and +//! [`crate::common::pass::FunctionPassManager`] for the other tier. + +use super::error::Error; +use super::module::IrGenerator; + +/// A pass that runs once over the whole translation unit. +pub trait ModulePass { + /// Run this pass against `gen`. Returning `Err` aborts the pipeline + /// (subsequent passes and later compilation stages are skipped). + fn run(&self, gen: &mut IrGenerator<'_>) -> Result<(), Error>; +} + +/// Sequential pipeline of [`ModulePass`] trait objects, executed in +/// registration order. +#[derive(Default)] +pub struct ModulePassManager { + passes: Vec>, +} + +impl ModulePassManager { + /// Create an empty manager with no registered passes. + pub fn new() -> Self { + Self::default() + } + + /// Append `pass` to the end of the pipeline. + pub fn add_pass(&mut self, pass: Box) { + self.passes.push(pass); + } + + /// Run every pass against `gen`, stopping at the first error. + pub fn run(&self, gen: &mut IrGenerator<'_>) -> Result<(), Error> { + for pass in &self.passes { + pass.run(gen)?; + } + Ok(()) + } +} From 37ddfe4a97264b2a4c9310dafc76783a982b9555 Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sat, 1 Aug 2026 18:38:32 +0800 Subject: [PATCH 2/2] docs(ir,common,experimental): discipline comments per comment spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed 15 divider banners (§5.4) across common/pass.rs and experimental/return_infer.rs. Deleted 12 waste TODO(asmt-2 ...) tag heads per the repo no-issue-ID rule (§7.2); the recipe content underneath is kept as plain comments, and the todo!("asmt-2 ...") macros are untouched code. Rewrote 7 audience-address/teaching-narration passages as facts (§5.5: "the core invariant you are maintaining", "already implemented for you", "constraints we need", "step-by-step recipe", "the key educational point"), and removed 3 historical frames (§3.2: "pre-asmt-2", "does not support those yet"). Fixed 3 comment placements (§9.1): a stray empty comment line in ir.rs, a doc-breaking // run in ir/module.rs folded into the doc block, and an attribute-misattached // in ir.rs promoted to a pub(crate) doc comment. Tightened hedged/vague phrasing (§8.1/§8.4) in the fallback/registry and boolean-walk comments. No code changes; every diff line is a comment line. --- src/common/pass.rs | 10 +- src/experimental/return_infer.rs | 156 +++++++++---------------------- src/ir.rs | 7 +- src/ir/module.rs | 6 +- 4 files changed, 52 insertions(+), 127 deletions(-) diff --git a/src/common/pass.rs b/src/common/pass.rs index 8c80997..4029070 100644 --- a/src/common/pass.rs +++ b/src/common/pass.rs @@ -20,16 +20,12 @@ //! [`crate::ir::pass::ModulePassManager`] in the IR layer — that runs a list //! of boxed trait objects in registration order. //! -//! Note that this module remains IR-coupled through -//! [`FunctionPass`]/[`Function`]; that is accepted because `common` is a -//! crate-internal utility layer, not a public API surface. +//! This module remains IR-coupled through [`FunctionPass`]/[`Function`]; +//! the coupling is accepted because `common` is a crate-internal utility +//! layer, not a public API surface. use crate::ir::Function; -// --------------------------------------------------------------------------- -// Function-level passes -// --------------------------------------------------------------------------- - /// A pass that runs on a single IR [`Function`]. /// /// Function passes are infallible: they either transform the function in diff --git a/src/experimental/return_infer.rs b/src/experimental/return_infer.rs index fa6af40..b2f3280 100644 --- a/src/experimental/return_infer.rs +++ b/src/experimental/return_infer.rs @@ -1,13 +1,11 @@ -//! Pluggable return-type inference pass (asmt-2). +//! Return-type inference pass (asmt-2): a feature-gated [`ModulePass`]. //! -//! This module is a **self-contained, feature-gated extension** of the -//! compiler. It runs between Pass 2 (signature registration) and Pass 3 +//! The pass runs between Pass 2 (signature registration) and Pass 3 //! (per-function type inference + IR generation) and fills in the return -//! type of every function that was declared without an explicit `-> T` -//! clause. The rest of the pipeline is not touched — the forward-flow -//! `type_infer` pass (inside the otherwise-private `ir::gen` module tree) -//! and the [`FunctionGenerator`] see only concrete [`Dtype`]s after we -//! are done. +//! type of every function declared without an explicit `-> T` clause. +//! Downstream stages — the forward-flow `type_infer` pass (inside the +//! otherwise-private `ir::gen` module tree) and the +//! [`FunctionGenerator`] — see only concrete [`Dtype`]s. //! //! [`FunctionGenerator`]: crate::ir::function::FunctionGenerator //! @@ -23,8 +21,8 @@ //! Registry has every return type concrete. //! ``` //! -//! When the feature is disabled, this module is not compiled at all; -//! omitted return types silently remain `void` (the pre-asmt-2 behaviour). +//! When the feature is disabled, this module is not compiled at all and +//! omitted return types remain `void`. //! //! # Algorithm //! @@ -49,9 +47,8 @@ //! to `registry.function_types[name].return_dtype`. Unresolved //! variables and non-`void`/`i32` resolutions are errors. //! -//! Steps 2 and 3 use a single shared [`UnionFind`]: constraints from one -//! function's body can resolve the return type of another — that is the -//! whole point of doing this globally. +//! Steps 2 and 3 share one [`UnionFind`], so constraints from one +//! function's body can resolve the return type of another. use std::collections::HashMap; use std::rc::Rc; @@ -66,10 +63,6 @@ use crate::ir::types::Dtype; use crate::ir::value::GlobalDef; use crate::ir::Error; -// --------------------------------------------------------------------------- -// Plug-in wrapper -// --------------------------------------------------------------------------- - /// [`ModulePass`] wrapper around [`resolve_return_types`]. pub(crate) struct ReturnInferPass; @@ -79,10 +72,6 @@ impl ModulePass for ReturnInferPass { } } -// --------------------------------------------------------------------------- -// Core data types: Ty, TypeId, UnionFind -// --------------------------------------------------------------------------- - /// Unique identifier for a type variable. A small integer; cheap to copy. type TypeId = usize; @@ -97,7 +86,7 @@ enum Ty { } impl Ty { - /// Convenience constructor — wraps a `Dtype` as a concrete `Ty`. + /// Wrap a `Dtype` as a concrete `Ty`. fn concrete(dtype: Dtype) -> Self { Self::Concrete(dtype) } @@ -159,8 +148,6 @@ impl UnionFind { /// See `docs/asmt-2.md` §3.2 (核心不变式) and §3.3 (工作示例). #[allow(unused_variables)] fn bind(&mut self, x: TypeId, dtype: Dtype, symbol: &str) -> Result<(), Error> { - // TODO(asmt-2 §3.3): Implement `bind`. - // // Steps: // 1. Find the root of x (path compression happens inside `find`). // 2. Inspect `self.concrete[root]`: @@ -169,8 +156,8 @@ impl UnionFind { // otherwise return `Error::TypeMismatch` // (expected: existing, actual: dtype). // - // The core invariant you are maintaining: **every equivalence class - // has at most one concrete type**. + // Core invariant: every equivalence class has at most one concrete + // type. todo!("asmt-2 §3.3: UnionFind::bind") } @@ -192,8 +179,6 @@ impl UnionFind { /// conflict at the end demonstrates the last row in miniature. #[allow(unused_variables)] fn union(&mut self, a: TypeId, b: TypeId, symbol: &str) -> Result<(), Error> { - // TODO(asmt-2 §3.3): Implement `union`. - // // Steps: // 1. Find the two roots `ra`, `rb`. If they coincide, return Ok. // 2. Combine `self.concrete[ra]` and `self.concrete[rb]` per the @@ -211,11 +196,9 @@ impl UnionFind { /// yet. The resolve phase of Pass 2.5 calls this exactly once per /// pending function. fn resolve(&mut self, x: TypeId) -> Option { - // TODO(asmt-2 §3.3): Implement `resolve`. - // - // Return `self.concrete[find(x)].clone()`. Make sure to go through - // `find` so that path compression is triggered and the answer - // reflects all unions performed so far. + // Return `self.concrete[find(x)].clone()`. The result goes + // through `find` so that path compression is triggered and the + // answer reflects all unions performed so far. todo!("asmt-2 §3.3: UnionFind::resolve") } } @@ -237,8 +220,6 @@ impl UnionFind { /// (usually a variable name, a function name, or `self.fn_name`). #[allow(unused_variables)] fn unify(uf: &mut UnionFind, a: &Ty, b: &Ty, symbol: &str) -> Result<(), Error> { - // TODO(asmt-2 §3.4): Implement the three-way match on `(a, b)`. - // // For the Concrete/Concrete arm, format the error the same way the // UnionFind operations do (`Error::TypeMismatch { symbol, expected, // actual }`) so the diagnostic stays consistent regardless of which @@ -246,10 +227,6 @@ fn unify(uf: &mut UnionFind, a: &Ty, b: &Ty, symbol: &str) -> Result<(), Error> todo!("asmt-2 §3.4: unify — three cases") } -// --------------------------------------------------------------------------- -// Public entry point -// --------------------------------------------------------------------------- - /// Run Pass 2.5: resolve every omitted function return type in `elements` /// and write the results back into `registry`. /// @@ -265,7 +242,7 @@ fn unify(uf: &mut UnionFind, a: &Ty, b: &Ty, symbol: &str) -> Result<(), Error> /// type but nothing in the program pins it to a concrete type (e.g. a /// function whose body only calls itself). /// - [`Error::UnsupportedReturnType`] — inference produced a type other -/// than `void` or `i32`; the backend does not support those yet. +/// than `void` or `i32`, which the backend does not lower. #[allow(unused_variables)] pub(crate) fn resolve_return_types( registry: &mut Registry, @@ -274,16 +251,12 @@ pub(crate) fn resolve_return_types( ) -> Result<(), Error> { let mut uf = UnionFind::new(); - // ----------------------------------------------------------------------- - // Phase 1 — Seed (already implemented for you). - // - // For every FnDef whose declaration has no explicit return type, hand - // it a fresh α from the UnionFind and remember the mapping keyed by - // the function name. This phase is pure book-keeping — there is no - // algorithmic insight here, so the skeleton fills it in to keep - // pre-asmt-2 regression tests (every function has `-> T`) green - // without requiring any student code. - // ----------------------------------------------------------------------- + // Phase 1 — Seed. For every FnDef whose declaration has no explicit + // return type, allocate a fresh α from the UnionFind and record the + // mapping keyed by the function name. The skeleton provides this + // phase: it is pure book-keeping, and shipping it keeps the existing + // regression tests (every function declares `-> T`) green without any + // student code. let mut pending_returns: HashMap = HashMap::new(); for elem in elements { if let ast::ProgramElementInner::FnDef(fn_def) = &elem.inner { @@ -301,40 +274,35 @@ pub(crate) fn resolve_return_types( return Ok(()); } - // TODO(asmt-2 §4.3): Phase 2 — Collect. + // Phase 2 — Collect. // // Walk every FnDef body (both pending *and* non-pending — a // non-pending body may still call pending callees, and those - // call sites contribute constraints we need). Delegate each - // body to `collect_constraints`, threading the same `uf` and - // `pending_returns` through every call so constraints from one - // body can pin the return type of another. + // call sites contribute constraints the pass needs). Delegate + // each body to `collect_constraints`, threading the same `uf` + // and `pending_returns` through every call so constraints from + // one body can pin the return type of another. // - // TODO(asmt-2 §4.3): Phase 3 — Resolve. + // Phase 3 — Resolve. // // For each `(name, α_f)` in `pending_returns`: // 1. `uf.resolve(α_f)` → `Option`. // - `None` means no constraint ever pinned this class. - // Fall back to `Dtype::Void`, matching the pre-asmt-2 - // semantics for a body with no `return`. + // Fall back to `Dtype::Void`, matching the baseline + // codegen behaviour for a body with no `return`. // 2. Reject anything other than `Void` / `I32` with // `Error::UnsupportedReturnType` (the aarch64 backend does // not lower other types). // 3. Overwrite `registry.function_types[name].return_dtype` - // with the resolved type. Use `Error::FunctionNotDefined` - // if the name somehow isn't in the registry (shouldn't - // happen after Pass 2). + // with the resolved type. The entry exists after Pass 2; + // a missing name is `Error::FunctionNotDefined`. // // Conflicts (e.g. `type_infer_5`: both `return;` and `return t;` // in one body) surface inside `unify` during Phase 2; this - // phase does not need any extra conflict checks. + // phase needs no extra conflict checks. todo!("asmt-2 §4.3: collect + resolve") } -// --------------------------------------------------------------------------- -// Constraint collection (per function body) -// --------------------------------------------------------------------------- - /// Per-function walker that emits unification constraints into the /// shared [`UnionFind`]. Operates in the [`Ty`] domain so that /// unresolved return types can flow through the walk. @@ -408,10 +376,6 @@ impl Collector<'_> { } } - // ----------------------------------------------------------------------- - // Statement dispatch - // ----------------------------------------------------------------------- - fn process_stmt(&mut self, stmt: &ast::CodeBlockStmt) -> Result<(), Error> { match &stmt.inner { ast::CodeBlockStmtInner::VarDecl(s) => match &s.inner { @@ -442,10 +406,6 @@ impl Collector<'_> { Ok(()) } - // ----------------------------------------------------------------------- - // Variable declaration (no initialiser) - // ----------------------------------------------------------------------- - fn process_var_decl(&mut self, decl: &ast::VarDecl) { // Untyped scalars are left out of the env: inferring their type // is Pass 3's job, and their first assignment will publish a @@ -467,10 +427,6 @@ impl Collector<'_> { } } - // ----------------------------------------------------------------------- - // Variable definition (with initialiser) - // ----------------------------------------------------------------------- - /// Translate `let x[: T] = e;` into an env binding, emitting a /// unification constraint when both a declared type and an /// initializer are present. @@ -489,16 +445,13 @@ impl Collector<'_> { /// - Element type defaults to `Dtype::I32` when omitted. /// - Build the concrete array `Ty` via `compose_var_def_dtype`, call /// `self.check_array_initializer` for side-effects on any pending - /// callees inside the initializer, and drop the array `Ty` into - /// the env. Arrays never hold a type variable themselves. + /// callees inside the initializer, and store the array `Ty` in the + /// env. Arrays never hold a type variable themselves. #[allow(unused_variables)] fn process_var_def(&mut self, def: &ast::VarDef) -> Result<(), Error> { - // TODO(asmt-2 §3.4 + §4.3): Implement variable definition. - // - // See the docstring above for the step-by-step recipe. The key - // educational point is: a declared type on the LHS becomes a - // `unify(lhs, rhs)` constraint rather than a direct equality - // check, which is the only change from `type_infer.rs`'s R2 rule. + // A declared type on the LHS becomes a `unify(lhs, rhs)` + // constraint rather than a direct equality check — the only + // structural difference from `type_infer.rs`'s R2 rule. todo!("asmt-2: process_var_def") } @@ -516,10 +469,6 @@ impl Collector<'_> { Ok(()) } - // ----------------------------------------------------------------------- - // Assignment - // ----------------------------------------------------------------------- - fn process_assignment(&mut self, stmt: &ast::AssignmentStmt) -> Result<(), Error> { let rhs = self.type_of_right_val(&stmt.right_val)?; @@ -548,10 +497,6 @@ impl Collector<'_> { Ok(()) } - // ----------------------------------------------------------------------- - // Branching - // ----------------------------------------------------------------------- - fn process_if(&mut self, stmt: &ast::IfStmt) -> Result<(), Error> { self.check_bool_unit(&stmt.bool_unit)?; @@ -608,7 +553,6 @@ impl Collector<'_> { env_a: &HashMap, env_b: &HashMap, ) -> Result<(), Error> { - // TODO(asmt-2 §3.4): Implement if/else merge via unify. todo!("asmt-2: merge_branches") } @@ -623,14 +567,9 @@ impl Collector<'_> { /// Mirrors `type_infer.rs::merge_env_single`. #[allow(unused_variables)] fn merge_with_body(&mut self, branch_env: &HashMap) -> Result<(), Error> { - // TODO(asmt-2 §3.4): Implement while-body merge via unify. todo!("asmt-2: merge_with_body") } - // ----------------------------------------------------------------------- - // Return - // ----------------------------------------------------------------------- - /// Emit the return-side constraint for one `return` statement. /// /// Algorithm: @@ -649,14 +588,9 @@ impl Collector<'_> { /// here. #[allow(unused_variables)] fn process_return(&mut self, stmt: &ast::ReturnStmt) -> Result<(), Error> { - // TODO(asmt-2 §4.3): Feed the return expression into α_f. todo!("asmt-2: process_return") } - // ----------------------------------------------------------------------- - // Expression typing - // ----------------------------------------------------------------------- - fn type_of_right_val(&mut self, val: &ast::RightVal) -> Result { match &val.inner { ast::RightValInner::ArithExpr(expr) => self.type_of_arith_expr(expr), @@ -735,8 +669,6 @@ impl Collector<'_> { /// callees are `Error::FunctionNotDefined`. #[allow(unused_variables)] fn type_of_fn_call(&mut self, call: &ast::FnCall) -> Result { - // TODO(asmt-2 §4.3): Return Ty::Var for pending callees, - // Ty::Concrete for registered ones. todo!("asmt-2: type_of_fn_call") } @@ -810,10 +742,8 @@ impl Collector<'_> { Ok(element_ty_of_indexing(&arr_ty)) } - // ----------------------------------------------------------------------- - // Boolean expressions (only walked for side-effects on call sites) - // ----------------------------------------------------------------------- - + // Boolean expressions are only walked for side-effects on call + // sites; the operands' own types are Pass 3's concern. fn check_bool_expr(&mut self, expr: &ast::BoolExpr) -> Result<(), Error> { match &expr.inner { ast::BoolExprInner::BoolBiOpExpr(biop) => { @@ -839,8 +769,8 @@ impl Collector<'_> { /// Array-decay on indexing: `Array` and `Pointer>` /// yield `T`. Array positions are always concrete (parameters and -/// local arrays never hold a type variable), so we only need to match -/// against `Ty::Concrete`. +/// local arrays never hold a type variable), so only `Ty::Concrete` +/// needs matching. fn element_ty_of_indexing(ty: &Ty) -> Ty { match ty { Ty::Concrete(Dtype::Array { element, .. }) => Ty::concrete(element.as_ref().clone()), diff --git a/src/ir.rs b/src/ir.rs index 5f33dff..f9900a4 100644 --- a/src/ir.rs +++ b/src/ir.rs @@ -68,7 +68,6 @@ pub(crate) fn compute_link_name(source_name: &str, is_external: bool) -> String pub use error::Error; pub use function::{BasicBlock, BlockLabel, Function, FunctionBody}; pub use module::{IrGenerator, Module, Registry}; -// // `#[allow(unused_imports)]` because the only in-tree consumer of this // re-export (the `experimental::ReturnInferPass` implementation) is gated // on the `return-type-inference` feature. @@ -80,9 +79,9 @@ pub use value::{GlobalDef, Local, LocalId, Operand}; #[cfg(feature = "return-type-inference")] pub(crate) use crate::experimental::ReturnInferPass; -// Crate-internal helper surfaced for the `experimental` layer, which -// lives outside `mod gen` and therefore cannot reach into private -// submodules directly. Not part of the public `ir` API. +/// Crate-internal helper surfaced for the `experimental` layer, which +/// lives outside `mod gen` and therefore cannot reach into private +/// submodules directly. Not part of the public `ir` API. #[cfg(feature = "return-type-inference")] pub(crate) use gen::conversions::compose_var_def_dtype; diff --git a/src/ir/module.rs b/src/ir/module.rs index a831502..c3de761 100644 --- a/src/ir/module.rs +++ b/src/ir/module.rs @@ -64,9 +64,9 @@ impl<'a> IrGenerator<'a> { } /// Append a module-level pass to the pipeline. - // - // `#[allow(dead_code)]` because the only in-tree caller is gated on - // the `return-type-inference` feature. + /// + /// `#[allow(dead_code)]` because the only in-tree caller is gated on + /// the `return-type-inference` feature. #[allow(dead_code)] pub fn add_module_pass(&mut self, pass: Box) { self.module_passes.add_pass(pass);