Skip to main content

selene_gql/analyze/error/
context.rs

1//! Analyzer type-mismatch context diagnostics.
2
3use selene_core::DbString;
4
5use crate::{BinaryOp, GqlType, analyze::binding::BindingDeclKind};
6
7/// Operation or clause that produced a type mismatch.
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub enum TypeMismatchContext {
10    /// Binary arithmetic operator.
11    BinaryArithmetic {
12        /// Operator.
13        op: BinaryOp,
14        /// Offending operand side.
15        side: Side,
16    },
17    /// Binary comparison operator.
18    BinaryComparison {
19        /// Operator.
20        op: BinaryOp,
21        /// Offending operand side.
22        side: Side,
23    },
24    /// Binary boolean operator.
25    BinaryBoolean {
26        /// Operator.
27        op: BinaryOp,
28        /// Offending operand side.
29        side: Side,
30    },
31    /// String/list/bytes/path concatenation.
32    BinaryConcat {
33        /// Offending operand side.
34        side: Side,
35    },
36    /// String predicate operator.
37    BinaryStringPredicate {
38        /// Operator.
39        op: BinaryOp,
40        /// Offending operand side.
41        side: Side,
42    },
43    /// Unary numeric negation.
44    UnaryNegate,
45    /// Unary boolean negation.
46    UnaryNot,
47    /// `IS TRUE` / `IS FALSE` / `IS UNKNOWN` operand.
48    IsTruthValue,
49    /// Unsupported `IS TYPED` target.
50    IsTypedTarget,
51    /// `IS NORMALIZED` operand.
52    IsNormalized,
53    /// `NORMALIZE` source operand.
54    NormalizeFunction,
55    /// Explicit TRIM source operand.
56    TrimSource,
57    /// Explicit TRIM character operand.
58    TrimCharacter,
59    /// `PATH[...]` element expression.
60    PathConstructorElement {
61        /// Zero-based path element-list position.
62        position: usize,
63    },
64    /// CASE branch result unification failed.
65    CaseBranchUnification,
66    /// List literal element unification failed.
67    ListLiteralUnification,
68    /// IN-list value unification failed.
69    InListUnification,
70    /// Boolean condition clause check failed.
71    Condition {
72        /// Condition clause kind.
73        clause: ConditionClause,
74    },
75    /// Procedure argument did not match the registered parameter type.
76    ProcedureArgument {
77        /// Qualified procedure name.
78        procedure: Box<[DbString]>,
79        /// Declared parameter name.
80        parameter: DbString,
81        /// Zero-based positional argument index.
82        position: usize,
83    },
84    /// `LIMIT` / `OFFSET` parameter type declaration cannot produce an amount.
85    LimitAmount,
86}
87
88impl std::fmt::Display for TypeMismatchContext {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Self::BinaryArithmetic { op, side } => {
92                write!(f, "{side} operand of arithmetic operator {op:?}")
93            }
94            Self::BinaryComparison { op, side } => {
95                write!(f, "{side} operand of comparison operator {op:?}")
96            }
97            Self::BinaryBoolean { op, side } => {
98                write!(f, "{side} operand of boolean operator {op:?}")
99            }
100            Self::BinaryConcat { side } => write!(f, "{side} operand of concat operator"),
101            Self::BinaryStringPredicate { op, side } => {
102                write!(f, "{side} operand of string predicate {op:?}")
103            }
104            Self::UnaryNegate => f.write_str("operand of unary negate"),
105            Self::UnaryNot => f.write_str("operand of unary NOT"),
106            Self::IsTruthValue => f.write_str("IS <truth value> operand"),
107            Self::IsTypedTarget => f.write_str("IS TYPED target"),
108            Self::IsNormalized => f.write_str("IS NORMALIZED operand"),
109            Self::NormalizeFunction => f.write_str("NORMALIZE operand"),
110            Self::TrimSource => f.write_str("TRIM source operand"),
111            Self::TrimCharacter => f.write_str("TRIM character operand"),
112            Self::PathConstructorElement { position } => {
113                write!(f, "PATH element {position}")
114            }
115            Self::CaseBranchUnification => f.write_str("CASE branch result"),
116            Self::ListLiteralUnification => f.write_str("list literal element"),
117            Self::InListUnification => f.write_str("IN-list value"),
118            Self::Condition { clause } => write!(f, "{clause} condition"),
119            Self::ProcedureArgument {
120                procedure,
121                parameter,
122                position,
123            } => {
124                write!(f, "argument {position} ({parameter}) of ")?;
125                super::fmt_qualified_name(f, procedure)
126            }
127            Self::LimitAmount => f.write_str("LIMIT/OFFSET parameter"),
128        }
129    }
130}
131
132/// Expected type category for a type mismatch.
133#[derive(Clone, Debug, Eq, PartialEq)]
134pub enum ExpectedType {
135    /// Any numeric type.
136    Numeric,
137    /// Boolean.
138    Boolean,
139    /// String.
140    String,
141    /// String or bytes type.
142    StringOrBytes,
143    /// Comparable type.
144    Comparable,
145    /// List, string, bytes, or path type.
146    ListStringBytesOrPath,
147    /// List type.
148    List,
149    /// Non-negative integer amount.
150    LimitAmount,
151    /// One specific GQL type.
152    Specific(GqlType),
153}
154
155impl std::fmt::Display for ExpectedType {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        match self {
158            Self::Numeric => f.write_str("numeric"),
159            Self::Boolean => f.write_str("boolean"),
160            Self::String => f.write_str("string"),
161            Self::StringOrBytes => f.write_str("string or bytes"),
162            Self::Comparable => f.write_str("comparable"),
163            Self::ListStringBytesOrPath => f.write_str("list, string, bytes, or path"),
164            Self::List => f.write_str("list"),
165            Self::LimitAmount => f.write_str("non-negative integer"),
166            Self::Specific(ty) => write!(f, "{ty:?}"),
167        }
168    }
169}
170
171/// Operand side for binary type diagnostics.
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub enum Side {
174    /// Left-hand side.
175    Lhs,
176    /// Right-hand side.
177    Rhs,
178}
179
180impl std::fmt::Display for Side {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        f.write_str(match self {
183            Self::Lhs => "left",
184            Self::Rhs => "right",
185        })
186    }
187}
188
189/// Boolean condition clause kind.
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub enum ConditionClause {
192    /// `MATCH ... WHERE`.
193    MatchWhere,
194    /// Node/edge pattern inline `WHERE`.
195    InlineWhere,
196    /// `FILTER`.
197    Filter,
198    /// `HAVING`.
199    Having,
200    /// `WITH ... WHERE`.
201    WithWhere,
202    /// `CASE WHEN`.
203    CaseWhen,
204}
205
206impl std::fmt::Display for ConditionClause {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        f.write_str(match self {
209            Self::MatchWhere => "MATCH WHERE",
210            Self::InlineWhere => "inline WHERE",
211            Self::Filter => "FILTER",
212            Self::Having => "HAVING",
213            Self::WithWhere => "WITH WHERE",
214            Self::CaseWhen => "CASE WHEN",
215        })
216    }
217}
218
219/// Pattern element categories used by [`super::AnalysisError::PatternKindMismatch`].
220///
221/// The bind pass groups declaration sites by the graph element they introduce
222/// (node, edge, path, value). Cross-category reuse via the same name is a
223/// semantic error; same-category reuse is allowed (e.g., `MATCH (n)` followed
224/// by `INSERT (n)-[:K]->(m)` legitimately reuses `n` as a node variable).
225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
226pub enum PatternElementKind {
227    /// `MATCH (n)` / `INSERT (n)`.
228    Node,
229    /// `MATCH ()-[e]->()` / `INSERT ()-[e]->()`.
230    Edge,
231    /// `path = (...)`.
232    Path,
233}
234
235impl PatternElementKind {
236    /// Categorize a [`BindingDeclKind`] for compatibility checks.
237    #[must_use]
238    pub const fn from_decl_kind(kind: BindingDeclKind) -> Option<Self> {
239        match kind {
240            BindingDeclKind::NodePattern | BindingDeclKind::InsertNode => Some(Self::Node),
241            BindingDeclKind::EdgePattern | BindingDeclKind::InsertEdge => Some(Self::Edge),
242            BindingDeclKind::PathBinding => Some(Self::Path),
243            BindingDeclKind::LetAlias
244            | BindingDeclKind::ForAlias
245            | BindingDeclKind::ProjectionAlias
246            | BindingDeclKind::YieldColumn => None,
247        }
248    }
249}
250
251impl std::fmt::Display for PatternElementKind {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        f.write_str(match self {
254            Self::Node => "node variable",
255            Self::Edge => "edge variable",
256            Self::Path => "path variable",
257        })
258    }
259}