Skip to main content

cyrs_plan/
error.rs

1//! Errors produced by HIR → Plan lowering (spec 0001 §12, bead cy-wlr).
2//!
3//! The [`lower_statement`] entry point in [`crate::lower`] validates its
4//! post-resolve, post-desugar preconditions before walking the HIR. Any
5//! violation surfaces as a [`PlanLowerError`] rather than a panic, so
6//! fuzz and agent-facing callers can fail cleanly.
7//!
8//! [`lower_statement`]: crate::lower::lower_statement
9
10use cyrs_hir::HirSpan;
11use smol_str::SmolStr;
12
13/// A precondition violation detected by HIR → Plan lowering.
14///
15/// `lower_statement` requires its input to have been name-resolved
16/// (`cyrs-sema::resolve` / cy-b4b) and desugared
17/// (`cyrs_hir::desugar::desugar_statement` / cy-mla). The variants below
18/// describe the kinds of precondition violation the entry point detects.
19///
20/// This enum is `#[non_exhaustive]`: callers must include a wildcard arm to
21/// remain forward-compatible (spec cy-2i9.1 precedent).
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum PlanLowerError {
25    /// An [`cyrs_hir::Expr::Unresolved`] node survived into plan
26    /// lowering — name resolution must run first (cy-b4b). The variable
27    /// name is preserved for diagnostics.
28    UnresolvedName {
29        /// The identifier that was never resolved to a [`cyrs_hir::VarId`].
30        name: SmolStr,
31        /// Approximate span of the offending clause. The HIR does not carry
32        /// per-expression spans, so this is the clause span that contained
33        /// the unresolved reference.
34        span: HirSpan,
35    },
36    /// An expression that must be desugared before plan lowering
37    /// survived into the entry point (cy-mla). Possible `kind` values
38    /// are `"ListComprehension"`, `"MapProjection"`, and
39    /// `"PatternPredicate"`.
40    UndesugaredExpr {
41        /// The name of the offending HIR expression variant.
42        kind: &'static str,
43        /// Approximate span of the offending clause (see
44        /// [`Self::UnresolvedName::span`] for why this is clause-scoped).
45        span: HirSpan,
46    },
47    /// A [`cyrs_hir::PatternPart`] reached the lowerer with no
48    /// [`cyrs_hir::PatternElement`]s, or with a malformed element
49    /// sequence the Source + Expand translation cannot walk (cy-f2t).
50    ///
51    /// The parser's error-recovery pass can emit such pattern parts for
52    /// inputs where the pattern is syntactically incomplete (e.g. a bare
53    /// `MATCH` keyword, or an open `MATCH (` with no close). The
54    /// previous lowerer assumed every part starts with a [`cyrs_hir::PatternElement::Node`]
55    /// and panicked on the empty / leading-`Rel` case; this variant
56    /// surfaces the recovery-produced shape as a clean error.
57    EmptyPatternPart {
58        /// Approximate span of the offending clause (see
59        /// [`Self::UnresolvedName::span`] for why this is clause-scoped).
60        span: HirSpan,
61    },
62}
63
64impl std::fmt::Display for PlanLowerError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            Self::UnresolvedName { name, .. } => write!(
68                f,
69                "cyrs-plan: unresolved variable `{name}` in HIR → Plan lowering; \
70                 run name resolution (cy-b4b) before calling lower_statement"
71            ),
72            Self::UndesugaredExpr { kind, .. } => write!(
73                f,
74                "cyrs-plan: un-desugared `{kind}` expression in HIR → Plan lowering; \
75                 run cyrs_hir::desugar::desugar_statement (cy-mla) first"
76            ),
77            Self::EmptyPatternPart { .. } => write!(
78                f,
79                "cyrs-plan: empty or malformed pattern part in HIR → Plan lowering; \
80                 parser error-recovery produced a pattern the lowerer cannot walk \
81                 (e.g. bare `MATCH`)"
82            ),
83        }
84    }
85}
86
87impl std::error::Error for PlanLowerError {}