Skip to main content

ir_lang/
error.rs

1//! The errors of declaring, building, and validating IR.
2
3use alloc::string::String;
4use core::fmt;
5
6use crate::entity::{AggId, Block, FuncId, GlobalId, Inst, SigId, StackSlot, Value, Variable};
7use crate::ops::PolicyFields;
8use crate::types::Type;
9
10/// Why a module-level declaration was refused.
11///
12/// Every variant names what was wrong, so a front end can report it against its own
13/// source. The enum is `#[non_exhaustive]`.
14///
15/// # Examples
16///
17/// ```
18/// use ir_lang::{Linkage, Module, ModuleError, Signature};
19///
20/// let mut m = Module::new("m");
21/// let sig = Signature::new(&[], &[]);
22/// m.declare_function("f", &sig, Linkage::Export)?;
23/// let err = m.declare_function("f", &sig, Linkage::Export).unwrap_err();
24/// assert_eq!(err, ModuleError::DuplicateName { name: "f".into() });
25/// # Ok::<(), ModuleError>(())
26/// ```
27#[derive(Clone, PartialEq, Eq, Debug)]
28#[non_exhaustive]
29pub enum ModuleError {
30    /// A function or global with this name already exists. Symbol names are one
31    /// namespace per module.
32    DuplicateName {
33        /// The repeated name.
34        name: String,
35    },
36    /// A symbol name was empty.
37    EmptyName,
38    /// The function handle does not name a declaration of this module.
39    UnknownFunction {
40        /// The handle.
41        func: FuncId,
42    },
43    /// The global handle does not name a declaration of this module.
44    UnknownGlobal {
45        /// The handle.
46        global: GlobalId,
47    },
48    /// An aggregate names an aggregate that is not declared before it.
49    UnknownAggregate {
50        /// The handle.
51        agg: AggId,
52    },
53    /// An aggregate's size exceeds [`MAX_AGGREGATE_SIZE`](crate::MAX_AGGREGATE_SIZE).
54    AggregateTooLarge,
55    /// An alignment is zero, not a power of two, or larger than allowed.
56    BadAlignment {
57        /// The alignment.
58        align: u64,
59    },
60    /// A global's initializer length differs from its declared size.
61    InitSizeMismatch {
62        /// The declared size.
63        size: u64,
64        /// The initializer length.
65        found: u64,
66    },
67    /// A relocation does not fit inside its global (`offset + pointer size > size`).
68    RelocOutOfRange {
69        /// The relocation offset.
70        offset: u64,
71    },
72    /// An imported global was given an initializer or relocations.
73    ImportWithData,
74    /// An imported function cannot have a body.
75    CannotDefineImport {
76        /// The function.
77        func: FuncId,
78    },
79    /// The function already has a body (use `Module::edit` to change it).
80    AlreadyDefined {
81        /// The function.
82        func: FuncId,
83    },
84    /// The function has no body yet (use `Module::build` to create one).
85    NotDefined {
86        /// The function.
87        func: FuncId,
88    },
89    /// A table of the module reached its capacity (`u32::MAX - 1` entries).
90    CapacityExceeded,
91}
92
93impl fmt::Display for ModuleError {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            ModuleError::DuplicateName { name } => write!(f, "symbol `{name}` is already declared"),
97            ModuleError::EmptyName => f.write_str("symbol names must not be empty"),
98            ModuleError::UnknownFunction { func } => write!(f, "unknown function {func}"),
99            ModuleError::UnknownGlobal { global } => write!(f, "unknown global {global}"),
100            ModuleError::UnknownAggregate { agg } => {
101                write!(f, "aggregate {agg} is not declared before its use")
102            }
103            ModuleError::AggregateTooLarge => f.write_str("aggregate size exceeds 2^48 bytes"),
104            ModuleError::BadAlignment { align } => {
105                write!(f, "alignment {align} is not a supported power of two")
106            }
107            ModuleError::InitSizeMismatch { size, found } => {
108                write!(
109                    f,
110                    "initializer has {found} bytes but the global is {size} bytes"
111                )
112            }
113            ModuleError::RelocOutOfRange { offset } => {
114                write!(
115                    f,
116                    "relocation at offset {offset} does not fit in its global"
117                )
118            }
119            ModuleError::ImportWithData => {
120                f.write_str("an imported global cannot have an initializer or relocations")
121            }
122            ModuleError::CannotDefineImport { func } => {
123                write!(f, "function {func} is imported and cannot have a body")
124            }
125            ModuleError::AlreadyDefined { func } => write!(f, "function {func} already has a body"),
126            ModuleError::NotDefined { func } => write!(f, "function {func} has no body"),
127            ModuleError::CapacityExceeded => f.write_str("module table capacity exceeded"),
128        }
129    }
130}
131
132impl core::error::Error for ModuleError {}
133
134/// What is wrong, for a [`ValidationError`] (and for a [`BuildError::Invalid`]).
135///
136/// The enum is `#[non_exhaustive]`: new checks may add variants in a minor release.
137#[derive(Clone, PartialEq, Eq, Debug)]
138#[non_exhaustive]
139pub enum ValidationErrorKind {
140    // ----- module level -----
141    /// Two symbols share a name.
142    DuplicateName {
143        /// The name.
144        name: String,
145    },
146    /// A symbol name is empty.
147    EmptyName,
148    /// A signature handle is out of range.
149    UnknownSignature {
150        /// The handle.
151        sig: SigId,
152    },
153    /// A function handle is out of range.
154    UnknownFunction {
155        /// The handle.
156        func: FuncId,
157    },
158    /// A global handle is out of range.
159    UnknownGlobal {
160        /// The handle.
161        global: GlobalId,
162    },
163    /// An aggregate handle is out of range, or names a later aggregate.
164    UnknownAggregate {
165        /// The handle.
166        agg: AggId,
167    },
168    /// An aggregate's recorded layout is inconsistent or too large.
169    BadAggregate {
170        /// The aggregate.
171        agg: AggId,
172    },
173    /// An alignment is not a supported power of two.
174    BadAlignment {
175        /// The alignment.
176        align: u64,
177    },
178    /// A global's initializer or relocations are inconsistent with its size or
179    /// linkage.
180    BadGlobal {
181        /// The global.
182        global: GlobalId,
183    },
184    /// A defined (local or exported) function has no body.
185    MissingBody,
186    /// An imported function has a body.
187    ImportWithBody,
188
189    // ----- function structure -----
190    /// The entry block handle is out of range or the block was removed.
191    BadEntry,
192    /// The entry block's parameters do not match the signature's parameters.
193    EntryParams {
194        /// The signature's parameter types count.
195        expected: usize,
196        /// The entry block's parameter count.
197        found: usize,
198    },
199    /// A block has no terminator.
200    MissingTerminator,
201    /// An edge targets a block that does not exist or was removed.
202    BadBlockTarget {
203        /// The target.
204        target: Block,
205    },
206    /// An edge targets the entry block, which must have no predecessors.
207    EntryBranchTarget,
208    /// The block's instruction list is corrupt (a link is out of range, points
209    /// into another block, or loops).
210    CorruptLayout,
211    /// A value handle is out of range.
212    ValueOutOfRange {
213        /// The handle.
214        value: Value,
215    },
216    /// A value's recorded definition disagrees with the block parameter list or the
217    /// instruction result range that lists it.
218    InconsistentDefinition {
219        /// The value.
220        value: Value,
221    },
222    /// A value is listed as defined at two sites.
223    DefinedTwice {
224        /// The value.
225        value: Value,
226    },
227    /// A value whose definition was removed is still used.
228    UseOfDetached {
229        /// The value.
230        value: Value,
231    },
232    /// A value's forwarding (from `replace_all_uses`) is inconsistent: it loops or
233    /// changes type.
234    BadAlias {
235        /// The value.
236        value: Value,
237    },
238    /// A value is used where its definition does not dominate the use.
239    UseBeforeDef {
240        /// The value.
241        value: Value,
242    },
243    /// A stack slot handle is out of range, or a slot has a bad size or alignment.
244    BadSlot {
245        /// The slot.
246        slot: StackSlot,
247    },
248
249    // ----- typing -----
250    /// A value has the wrong type for its position.
251    TypeMismatch {
252        /// The value (or `None` for a terminator-produced edge argument).
253        value: Option<Value>,
254        /// The required type.
255        expected: Type,
256        /// The actual type.
257        found: Type,
258    },
259    /// An operand's type is not one the operation accepts.
260    BadOperandType {
261        /// The operation's opcode.
262        op: &'static str,
263        /// The operand's type.
264        found: Type,
265    },
266    /// A conversion between these types is not allowed for this conversion op.
267    BadConversion {
268        /// The conversion's opcode.
269        op: &'static str,
270        /// The source type.
271        from: Type,
272        /// The result type.
273        to: Type,
274    },
275    /// An operation instance does not carry exactly the policy fields it needs.
276    PolicyMismatch {
277        /// The operation's opcode.
278        op: &'static str,
279        /// The fields it must carry.
280        required: PolicyFields,
281    },
282    /// An operation that can raise an error appears as a plain instruction; it
283    /// must be the operation of a `check` terminator. Fallible means: a policy
284    /// field is `error`; `char_from_u32`; or, on a signed integer type, `pow`
285    /// under any `overflow` policy (a negative exponent) or `shl`/`shr` under
286    /// `shift = saturate` (a negative amount). [`BinaryOp::can_error`],
287    /// [`UnaryOp::can_error`], and [`ConvOp::can_error`] are the rule.
288    ///
289    /// [`BinaryOp::can_error`]: crate::BinaryOp::can_error
290    /// [`UnaryOp::can_error`]: crate::UnaryOp::can_error
291    /// [`ConvOp::can_error`]: crate::ConvOp::can_error
292    ErrorOutsideCheck {
293        /// The opcode.
294        op: &'static str,
295    },
296    /// A `check` terminator holds an operation that cannot raise an error, or an
297    /// instruction that is not a unary, binary, or conversion operation.
298    NotFallible {
299        /// The opcode.
300        op: &'static str,
301    },
302    /// A constant's bits do not fit its type (or a `ptr`/`ref` constant is not 0).
303    BadConst {
304        /// The type.
305        ty: Type,
306        /// The bits.
307        bits: u64,
308    },
309    /// A struct field index is out of range, or the aggregate is not a struct.
310    BadField {
311        /// The aggregate.
312        agg: AggId,
313        /// The field index.
314        field: u32,
315    },
316    /// An atomic operation has an ordering it may not have.
317    BadOrdering,
318    /// An instruction's recorded result values disagree with what it produces (count
319    /// or types).
320    ResultMismatch,
321    /// A call passes the wrong number of arguments, or a return passes the wrong
322    /// number of values.
323    ArgCount {
324        /// The required count.
325        expected: usize,
326        /// The supplied count.
327        found: usize,
328    },
329    /// An edge passes the wrong number of arguments for its target's parameters.
330    EdgeArgCount {
331        /// The target block.
332        target: Block,
333        /// The target's parameter count.
334        expected: usize,
335        /// The supplied count.
336        found: usize,
337    },
338    /// A switch has two cases with the same value, or a case value does not fit
339    /// the scrutinee's type.
340    BadSwitchCase {
341        /// The case value.
342        value: u64,
343    },
344    /// A tail call's callee returns or convention differ from this function's, or
345    /// a tail call carries a GC list.
346    BadTailCall,
347    /// A GC list entry is not a `ref`, or a `nogc` call carries a GC list.
348    BadGcList,
349
350    // ----- exception edges -----
351    /// An edge argument is a `Result`, `Exception`, or `ErrorCode` where that kind is
352    /// not allowed, or a `Result` index is out of range.
353    BadEdgeArg,
354    /// A landing pad (the target of an unwind edge) also has a non-unwind
355    /// predecessor, or an `invoke`'s normal and unwind edges target one block.
356    LandingPadPredecessor {
357        /// The landing pad.
358        block: Block,
359    },
360    /// A `nounwind` function contains a `resume`, or a call that may unwind
361    /// without an `invoke`.
362    UnwindInNounwind,
363    /// A `noreturn` function contains a `ret`.
364    ReturnInNoreturn,
365    /// A `nogc` function contains a safepoint.
366    SafepointInNogc,
367
368    // ----- safepoints -----
369    /// A `ref` value, or a pointer derived from one, is live across a safepoint
370    /// without being relocated through its GC list.
371    LiveAcrossSafepoint {
372        /// The value.
373        value: Value,
374    },
375    /// A pointer derived from a `ref` escapes tracking: it is stored to memory or
376    /// passed to a call that is a safepoint.
377    DerivedPointerEscapes {
378        /// The value.
379        value: Value,
380    },
381}
382
383impl fmt::Display for ValidationErrorKind {
384    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385        use ValidationErrorKind as K;
386        match self {
387            K::DuplicateName { name } => write!(f, "symbol `{name}` is declared twice"),
388            K::EmptyName => f.write_str("a symbol name is empty"),
389            K::UnknownSignature { sig } => write!(f, "unknown signature {sig}"),
390            K::UnknownFunction { func } => write!(f, "unknown function {func}"),
391            K::UnknownGlobal { global } => write!(f, "unknown global {global}"),
392            K::UnknownAggregate { agg } => write!(f, "unknown or forward aggregate {agg}"),
393            K::BadAggregate { agg } => write!(f, "aggregate {agg} has an invalid layout"),
394            K::BadAlignment { align } => write!(f, "alignment {align} is not supported"),
395            K::BadGlobal { global } => write!(f, "global {global} has an invalid initializer"),
396            K::MissingBody => f.write_str("a defined function has no body"),
397            K::ImportWithBody => f.write_str("an imported function has a body"),
398            K::BadEntry => f.write_str("the entry block is missing"),
399            K::EntryParams { expected, found } => write!(
400                f,
401                "the entry block has {found} parameters but the signature has {expected}"
402            ),
403            K::MissingTerminator => f.write_str("block has no terminator"),
404            K::BadBlockTarget { target } => write!(f, "edge targets missing block {target}"),
405            K::EntryBranchTarget => f.write_str("an edge targets the entry block"),
406            K::CorruptLayout => f.write_str("the block's instruction list is corrupt"),
407            K::ValueOutOfRange { value } => write!(f, "reference to nonexistent value {value}"),
408            K::InconsistentDefinition { value } => {
409                write!(f, "value {value} disagrees with its definition site")
410            }
411            K::DefinedTwice { value } => write!(f, "value {value} is defined twice"),
412            K::UseOfDetached { value } => write!(f, "value {value} is used but no longer defined"),
413            K::BadAlias { value } => write!(f, "value {value} has an invalid forwarding"),
414            K::UseBeforeDef { value } => {
415                write!(
416                    f,
417                    "value {value} is used where its definition does not dominate"
418                )
419            }
420            K::BadSlot { slot } => write!(f, "invalid stack slot {slot}"),
421            K::TypeMismatch {
422                value: Some(v),
423                expected,
424                found,
425            } => write!(f, "value {v} has type {found}, expected {expected}"),
426            K::TypeMismatch {
427                value: None,
428                expected,
429                found,
430            } => write!(f, "edge argument has type {found}, expected {expected}"),
431            K::BadOperandType { op, found } => write!(f, "`{op}` does not accept {found}"),
432            K::BadConversion { op, from, to } => write!(f, "`{op}` cannot convert {from} to {to}"),
433            K::PolicyMismatch { op, required } => {
434                write!(f, "`{op}` must carry exactly the policy fields {required}")
435            }
436            K::ErrorOutsideCheck { op } => write!(
437                f,
438                "`{op}` can raise an error and must be the operation of a `check` terminator"
439            ),
440            K::NotFallible { op } => write!(f, "`check` holds `{op}`, which cannot raise an error"),
441            K::BadConst { ty, bits } => write!(f, "constant {bits:#x} does not fit {ty}"),
442            K::BadField { agg, field } => write!(f, "{agg} has no field {field}"),
443            K::BadOrdering => f.write_str("invalid memory ordering for this atomic operation"),
444            K::ResultMismatch => {
445                f.write_str("the instruction's results disagree with what it produces")
446            }
447            K::ArgCount { expected, found } => {
448                write!(f, "expected {expected} arguments, found {found}")
449            }
450            K::EdgeArgCount {
451                target,
452                expected,
453                found,
454            } => write!(
455                f,
456                "edge to {target} passes {found} arguments for {expected} parameters"
457            ),
458            K::BadSwitchCase { value } => {
459                write!(f, "switch case {value} is repeated or out of range")
460            }
461            K::BadTailCall => {
462                f.write_str("tail call does not match this function's returns and convention")
463            }
464            K::BadGcList => f.write_str("invalid GC list"),
465            K::BadEdgeArg => f.write_str("edge argument not allowed on this edge"),
466            K::LandingPadPredecessor { block } => {
467                write!(f, "landing pad {block} is reached by a non-unwind edge")
468            }
469            K::UnwindInNounwind => f.write_str("a `nounwind` function may unwind"),
470            K::ReturnInNoreturn => f.write_str("a `noreturn` function returns"),
471            K::SafepointInNogc => f.write_str("a `nogc` function contains a safepoint"),
472            K::LiveAcrossSafepoint { value } => write!(
473                f,
474                "value {value} is a GC reference (or derived from one) live across a safepoint"
475            ),
476            K::DerivedPointerEscapes { value } => {
477                write!(f, "pointer {value} derived from a GC reference escapes")
478            }
479        }
480    }
481}
482
483/// Why a module is not well-formed: what is wrong, and where.
484///
485/// [`Module::validate`](crate::Module::validate) returns the first problem it finds
486/// in a fixed order (module declarations, then each function in handle order;
487/// within a function: structure, instructions, terminators, exception edges,
488/// dominance, safepoints).
489///
490/// # Examples
491///
492/// ```
493/// use ir_lang::{Linkage, Module, Signature, Type, ValidationErrorKind};
494///
495/// let mut m = Module::new("m");
496/// let f = m.declare_function("f", &Signature::new(&[], &[Type::I32]), Linkage::Export)?;
497/// // Exported but never given a body.
498/// let err = m.validate().unwrap_err();
499/// assert_eq!(err.func(), Some(f));
500/// assert_eq!(err.kind(), &ValidationErrorKind::MissingBody);
501/// # Ok::<(), Box<dyn std::error::Error>>(())
502/// ```
503#[derive(Clone, PartialEq, Eq, Debug)]
504pub struct ValidationError {
505    pub(crate) func: Option<FuncId>,
506    pub(crate) block: Option<Block>,
507    pub(crate) inst: Option<Inst>,
508    pub(crate) kind: ValidationErrorKind,
509}
510
511impl ValidationError {
512    pub(crate) fn module(kind: ValidationErrorKind) -> ValidationError {
513        ValidationError {
514            func: None,
515            block: None,
516            inst: None,
517            kind,
518        }
519    }
520
521    /// The function the problem is in, if it is inside one.
522    ///
523    /// See [`ValidationError`] for an example.
524    #[must_use]
525    pub fn func(&self) -> Option<FuncId> {
526        self.func
527    }
528
529    /// The block the problem is in, if any.
530    ///
531    /// # Examples
532    ///
533    /// ```
534    /// use ir_lang::{Linkage, Module, Signature};
535    ///
536    /// let mut m = Module::new("m");
537    /// let f = m.declare_function("f", &Signature::new(&[], &[]), Linkage::Export)?;
538    /// let _ = m.build(f)?; // the entry block is never terminated
539    /// let err = m.validate().unwrap_err();
540    /// assert_eq!(err.block().map(|b| b.index()), Some(0));
541    /// # Ok::<(), Box<dyn std::error::Error>>(())
542    /// ```
543    #[must_use]
544    pub fn block(&self) -> Option<Block> {
545        self.block
546    }
547
548    /// The instruction the problem is in, if it is in one (`None` for a terminator
549    /// or a block-level problem).
550    ///
551    /// # Examples
552    ///
553    /// ```
554    /// use ir_lang::{Linkage, Module, Signature};
555    ///
556    /// let mut m = Module::new("m");
557    /// let f = m.declare_function("f", &Signature::new(&[], &[]), Linkage::Export)?;
558    /// let _ = m.build(f)?;
559    /// assert_eq!(m.validate().unwrap_err().inst(), None);
560    /// # Ok::<(), Box<dyn std::error::Error>>(())
561    /// ```
562    #[must_use]
563    pub fn inst(&self) -> Option<Inst> {
564        self.inst
565    }
566
567    /// What is wrong.
568    ///
569    /// See [`ValidationError`] for an example.
570    #[must_use]
571    pub fn kind(&self) -> &ValidationErrorKind {
572        &self.kind
573    }
574}
575
576impl fmt::Display for ValidationError {
577    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578        let mut sep = "";
579        if let Some(func) = self.func {
580            write!(f, "{sep}{func}")?;
581            sep = " ";
582        }
583        if let Some(block) = self.block {
584            write!(f, "{sep}{block}")?;
585            sep = " ";
586        }
587        if let Some(inst) = self.inst {
588            write!(f, "{sep}{inst}")?;
589            sep = " ";
590        }
591        if sep.is_empty() {
592            write!(f, "{}", self.kind)
593        } else {
594            write!(f, ": {}", self.kind)
595        }
596    }
597}
598
599impl core::error::Error for ValidationError {}
600
601/// Why a [`Builder`](crate::Builder) operation was refused.
602///
603/// The builder checks each instruction and terminator as it is added, so a
604/// lowering bug is reported at the call that caused it. Nothing is ever changed by
605/// a refused operation.
606///
607/// # Examples
608///
609/// ```
610/// use ir_lang::{BuildError, Linkage, Module, Signature};
611///
612/// let mut m = Module::new("m");
613/// let f = m.declare_function("f", &Signature::new(&[], &[]), Linkage::Export)?;
614/// let mut b = m.build(f)?;
615/// b.ret(&[])?;
616/// // A terminator is never silently overwritten.
617/// let entry = b.func().entry();
618/// assert_eq!(b.unreachable(), Err(BuildError::AlreadyTerminated { block: entry }));
619/// # Ok::<(), Box<dyn std::error::Error>>(())
620/// ```
621#[derive(Clone, PartialEq, Eq, Debug)]
622#[non_exhaustive]
623pub enum BuildError {
624    /// The block already has a terminator. Remove it first
625    /// ([`Builder::remove_terminator`](crate::Builder::remove_terminator)) to replace it.
626    AlreadyTerminated {
627        /// The block.
628        block: Block,
629    },
630    /// The block handle is unknown or the block was removed.
631    UnknownBlock {
632        /// The block.
633        block: Block,
634    },
635    /// The instruction handle is unknown or the instruction was removed.
636    UnknownInst {
637        /// The instruction.
638        inst: Inst,
639    },
640    /// The value handle is unknown.
641    UnknownValue {
642        /// The value.
643        value: Value,
644    },
645    /// There is no function parameter at this index.
646    UnknownParam {
647        /// The index.
648        index: usize,
649    },
650    /// The entry block cannot be removed or split off.
651    EntryBlock,
652    /// A block parameter index is out of range.
653    UnknownBlockParam {
654        /// The block.
655        block: Block,
656        /// The index.
657        index: usize,
658    },
659    /// The instruction or terminator is ill-formed.
660    Invalid(ValidationErrorKind),
661    /// A function table reached its capacity (`u32::MAX - 1` entries).
662    CapacityExceeded,
663    /// The variable was not declared by this builder (or the builder's variable
664    /// state was dropped by [`compact`](crate::Builder::compact)).
665    UnknownVariable {
666        /// The variable.
667        var: Variable,
668    },
669    /// An edge targets a block that was sealed: every edge into it was declared
670    /// to exist already ([`Builder::seal_block`](crate::Builder::seal_block)).
671    SealedBlock {
672        /// The sealed block.
673        block: Block,
674    },
675    /// The value is not a parameter of a live block.
676    NotBlockParam {
677        /// The value.
678        value: Value,
679    },
680    /// The operation conflicts with variable construction in progress: splitting
681    /// a block while blocks are unsealed, or adding a parameter to a block the
682    /// construction already added parameters to.
683    VariablesActive,
684    /// A whole-function transformation would exceed its work budget
685    /// ([`Builder::insert_statepoints_within`](crate::Builder::insert_statepoints_within));
686    /// nothing was changed.
687    BudgetExceeded,
688}
689
690impl fmt::Display for BuildError {
691    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
692        match self {
693            BuildError::AlreadyTerminated { block } => {
694                write!(f, "block {block} already has a terminator")
695            }
696            BuildError::UnknownBlock { block } => write!(f, "unknown or removed block {block}"),
697            BuildError::UnknownInst { inst } => write!(f, "unknown or removed instruction {inst}"),
698            BuildError::UnknownValue { value } => write!(f, "unknown value {value}"),
699            BuildError::UnknownParam { index } => write!(f, "no function parameter {index}"),
700            BuildError::EntryBlock => f.write_str("the entry block cannot be removed or split off"),
701            BuildError::UnknownBlockParam { block, index } => {
702                write!(f, "block {block} has no parameter {index}")
703            }
704            BuildError::Invalid(kind) => write!(f, "{kind}"),
705            BuildError::CapacityExceeded => f.write_str("function table capacity exceeded"),
706            BuildError::UnknownVariable { var } => write!(f, "unknown variable {var}"),
707            BuildError::SealedBlock { block } => {
708                write!(f, "block {block} is sealed and cannot get a new edge")
709            }
710            BuildError::NotBlockParam { value } => {
711                write!(f, "value {value} is not a block parameter")
712            }
713            BuildError::VariablesActive => {
714                f.write_str("not allowed while variable construction is in progress")
715            }
716            BuildError::BudgetExceeded => f.write_str("the operation's work budget was exceeded"),
717        }
718    }
719}
720
721impl core::error::Error for BuildError {}
722
723impl From<ValidationErrorKind> for BuildError {
724    fn from(kind: ValidationErrorKind) -> Self {
725        BuildError::Invalid(kind)
726    }
727}
728
729#[cfg(test)]
730mod tests {
731    use super::*;
732    use alloc::string::ToString;
733
734    #[test]
735    fn test_validation_error_display_includes_location() {
736        let e = ValidationError {
737            func: Some(FuncId::from_u32(1)),
738            block: Some(Block::from_u32(2)),
739            inst: None,
740            kind: ValidationErrorKind::MissingTerminator,
741        };
742        assert_eq!(e.to_string(), "f1 b2: block has no terminator");
743        let m = ValidationError::module(ValidationErrorKind::EmptyName);
744        assert_eq!(m.to_string(), "a symbol name is empty");
745    }
746
747    #[test]
748    fn test_every_build_error_displays() {
749        let errs = [
750            BuildError::EntryBlock,
751            BuildError::CapacityExceeded,
752            BuildError::UnknownParam { index: 3 },
753            BuildError::Invalid(ValidationErrorKind::BadOrdering),
754            BuildError::UnknownVariable {
755                var: Variable::from_u32(1),
756            },
757            BuildError::SealedBlock {
758                block: Block::from_u32(1),
759            },
760            BuildError::NotBlockParam {
761                value: Value::from_u32(1),
762            },
763            BuildError::VariablesActive,
764            BuildError::BudgetExceeded,
765        ];
766        for e in errs {
767            assert!(!e.to_string().is_empty());
768        }
769    }
770}