zerodds_ast/error.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Model error type. Spec §10.
4
5use crate::invariant::InvariantViolation;
6use crate::kind::NodeKind;
7use alloc::string::String;
8use core::fmt;
9use zerodds_ast_core::{AstCoreError, NodeId};
10
11/// Failure of a model operation.
12///
13/// `#[non_exhaustive]` so that later phases can append variants —
14/// invariant violations arrive with the edit surface — without that
15/// being a breaking change.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum AstError {
19 /// Infrastructure failure.
20 Core(AstCoreError),
21 /// No node carries that identity.
22 NotFound {
23 /// The identity looked up.
24 node: NodeId,
25 },
26 /// The node exists but is not the kind the operation required.
27 WrongKind {
28 /// The node.
29 node: NodeId,
30 /// Kind the operation required.
31 expected: NodeKind,
32 /// Kind actually found.
33 found: NodeKind,
34 },
35 /// A name collided within its scope.
36 DuplicateName {
37 /// Scope the collision occurred in.
38 scope: NodeId,
39 /// The colliding name.
40 name: String,
41 },
42 /// A commit broke one or more structural invariants and was rolled
43 /// back. The model is byte-identical to its state before the
44 /// transaction began. Spec §8.1.
45 Invariant(alloc::vec::Vec<InvariantViolation>),
46 /// A part was added to a type that does not hold that kind of part —
47 /// an enumerator on a struct, a member on an enum.
48 NotAnAggregate {
49 /// The target type.
50 node: NodeId,
51 /// What kind of type it actually is.
52 found: &'static str,
53 },
54 /// A type that already has a body was asked to receive one again.
55 /// Two definitions of one name are a duplicate, not a completion.
56 AlreadyComplete {
57 /// The type that already has a body.
58 node: NodeId,
59 },
60}
61
62impl From<AstCoreError> for AstError {
63 fn from(error: AstCoreError) -> Self {
64 Self::Core(error)
65 }
66}
67
68impl fmt::Display for AstError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Self::Core(error) => write!(f, "{error}"),
72 Self::NotFound { node } => write!(f, "no node with identity {node:?}"),
73 Self::WrongKind {
74 node,
75 expected,
76 found,
77 } => write!(
78 f,
79 "node {node:?} is a {}, expected a {}",
80 found.as_str(),
81 expected.as_str()
82 ),
83 Self::DuplicateName { scope, name } => {
84 write!(f, "name `{name}` already exists in scope {scope:?}")
85 }
86 Self::NotAnAggregate { node, found } => {
87 write!(f, "node {node:?} is a {found} and holds no such part")
88 }
89 Self::AlreadyComplete { node } => {
90 write!(f, "node {node:?} already has a body")
91 }
92 Self::Invariant(violations) => {
93 write!(f, "commit rejected, {} violation(s):", violations.len())?;
94 for violation in violations {
95 write!(f, " {violation};")?;
96 }
97 Ok(())
98 }
99 }
100 }
101}
102
103impl core::error::Error for AstError {}