Skip to main content

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 {}