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