1use selene_core::{DbString, LabelSet, PropertyValueType};
24
25mod context;
26
27pub use context::{ConditionClause, ExpectedType, PatternElementKind, Side, TypeMismatchContext};
28
29use crate::{
30 GqlStatus, GqlType, PathMode, PathSelector, ProcedureMutability, SourceSpan,
31 analyze::binding::BindingDeclKind,
32};
33
34#[derive(Debug, thiserror::Error, miette::Diagnostic)]
36#[non_exhaustive]
37pub enum AnalysisError {
38 #[error("undefined reference: {name}")]
40 #[diagnostic(code(SLENE_GQL_42N03))]
41 UndefinedReference {
42 name: DbString,
44 #[label("not bound in scope")]
46 span: SourceSpan,
47 #[help]
49 hint: Option<String>,
50 },
51
52 #[error("binding {name} is already declared in this scope")]
54 #[diagnostic(code(SLENE_GQL_42N10))]
55 Shadow {
56 name: DbString,
58 #[label("conflicts with an earlier binding")]
60 span: SourceSpan,
61 #[label("first declared here")]
63 prior_span: SourceSpan,
64 },
65
66 #[error(
70 "pattern variable {name} is already bound as a {prior} and cannot be reused as a {current}"
71 )]
72 #[diagnostic(code(SLENE_GQL_42N10))]
73 PatternKindMismatch {
74 name: DbString,
76 prior: PatternElementKind,
78 current: PatternElementKind,
80 #[label("incompatible reuse")]
82 span: SourceSpan,
83 #[label("first declared here")]
85 prior_span: SourceSpan,
86 },
87
88 #[error(
90 "binding {name} is already bound as {prior_kind:?} and cannot be reused as a {new_kind}"
91 )]
92 #[diagnostic(code(SLENE_GQL_42N10))]
93 AliasReusedAsPatternBinding {
94 name: DbString,
96 prior_kind: BindingDeclKind,
98 new_kind: PatternElementKind,
100 #[label("alias cannot be reused as a pattern binding")]
102 span: SourceSpan,
103 },
104
105 #[error("not implemented: {message}")]
107 #[diagnostic(code(SLENE_GQL_42N01))]
108 NotImplemented {
109 message: String,
111 #[label("not implemented yet")]
113 span: SourceSpan,
114 #[help]
116 hint: Option<String>,
117 },
118
119 #[error(
121 "unbounded variable-length edge pattern requires a restrictive path mode, selective path selector, or DIFFERENT EDGES match mode"
122 )]
123 #[diagnostic(code(SLENE_GQL_42001))]
124 UnboundedRequiresGate {
125 mode: PathMode,
127 selector: Option<PathSelector>,
129 #[label("unbounded quantifier requires an ISO 16.4 gate")]
131 span: SourceSpan,
132 },
133
134 #[error("invalid VALUE subquery shape: {message}")]
136 #[diagnostic(code(SLENE_GQL_42001))]
137 ValueSubqueryShapeViolation {
138 message: String,
140 #[label("violates ISO 20.6 scalar value query expression shape")]
142 span: SourceSpan,
143 },
144
145 #[error("invalid aggregate expression: {message}")]
148 #[diagnostic(code(SLENE_GQL_42001))]
149 AggregateNestingViolation {
150 message: String,
152 #[label("aggregate cannot contain another aggregate")]
154 span: SourceSpan,
155 },
156
157 #[error("grouped projection item must be a grouping key or aggregate expression")]
160 #[diagnostic(code(SLENE_GQL_42001))]
161 GroupedProjectionItemNotGrouped {
162 #[label("not a grouping key or aggregate expression")]
164 span: SourceSpan,
165 },
166
167 #[error("RETURN * requires a non-unit incoming binding table")]
169 #[diagnostic(code(SLENE_GQL_42001))]
170 ReturnStarRequiresInput {
171 #[label("no incoming bindings to expand")]
173 span: SourceSpan,
174 },
175
176 #[error("ORDER BY sort key cannot contain a nested query specification")]
178 #[diagnostic(code(SLENE_GQL_42001))]
179 SortKeyContainsNestedQuery {
180 #[label("nested query specification is not allowed in a sort key")]
182 span: SourceSpan,
183 },
184
185 #[error("ORDER BY sort key cannot contain an aggregate function in this RETURN context")]
187 #[diagnostic(code(SLENE_GQL_42001))]
188 SortKeyContainsAggregate {
189 #[label("aggregate function is not allowed in this sort key")]
191 span: SourceSpan,
192 },
193
194 #[error("invalid reference: {message}")]
196 #[diagnostic(code(SLENE_GQL_42002))]
197 InvalidReference {
198 message: String,
200 #[label("invalid reference here")]
202 span: SourceSpan,
203 },
204
205 #[error("expression nesting depth {depth} exceeds analyzer limit")]
207 #[diagnostic(code(SLENE_GQL_5GQL1))]
208 RecursionLimitExceeded {
209 depth: u32,
211 },
212
213 #[error("{context}: expected {expected}, found {found:?}")]
215 #[diagnostic(code(SLENE_GQL_22G03))]
216 TypeMismatch {
217 context: TypeMismatchContext,
219 expected: ExpectedType,
221 found: GqlType,
223 #[label("incompatible type")]
225 span: SourceSpan,
226 },
227 #[error("conflicting declared types for parameter ${name}")]
229 #[diagnostic(code(SLENE_GQL_22G03))]
230 ConflictingParameterTypes {
231 name: DbString,
233 declarations: Vec<(GqlType, SourceSpan)>,
235 },
236 #[error("unknown procedure: {}", display_qualified_name(name))]
238 #[diagnostic(code(SLENE_GQL_42N04))]
239 UnknownProcedure {
240 name: Box<[DbString]>,
242 #[label("procedure is not registered")]
244 span: SourceSpan,
245 },
246
247 #[error(
249 "wrong argument count for {}: expected {}, found {actual}",
250 display_qualified_name(procedure),
251 display_argument_range(*minimum, *expected)
252 )]
253 #[diagnostic(code(SLENE_GQL_22G03))]
254 WrongArgumentCount {
255 procedure: Box<[DbString]>,
257 expected: usize,
259 minimum: usize,
261 actual: usize,
263 #[label("wrong number of arguments")]
265 span: SourceSpan,
266 },
267
268 #[error(
270 "unknown YIELD column {column} for procedure {}",
271 display_qualified_name(procedure)
272 )]
273 #[diagnostic(code(SLENE_GQL_42N03))]
274 UnknownYieldColumn {
275 procedure: Box<[DbString]>,
277 column: DbString,
279 #[label("column is not produced by this procedure")]
281 span: SourceSpan,
282 },
283
284 #[error(
287 "mutating procedure {} cannot be invoked in a read pipeline",
288 display_qualified_name(procedure)
289 )]
290 #[diagnostic(code(SLENE_GQL_25G02))]
291 MutatingProcedureInReadPipeline {
292 procedure: Box<[DbString]>,
294 mutability: ProcedureMutability,
296 #[label("read pipelines cannot invoke mutating procedures")]
298 span: SourceSpan,
299 },
300
301 #[error("{labels:?} does not match any node type in graph type {graph_type}")]
303 #[diagnostic(code(SLENE_A_010))]
304 SchemaUnknownNodeType {
305 labels: LabelSet,
307 graph_type: DbString,
309 #[label("unknown node type")]
311 span: SourceSpan,
312 },
313
314 #[error("edge label {label} does not match any edge type in graph type {graph_type}")]
316 #[diagnostic(code(SLENE_A_011))]
317 SchemaUnknownEdgeType {
318 label: DbString,
320 graph_type: DbString,
322 #[label("unknown edge type")]
324 span: SourceSpan,
325 },
326
327 #[error(
329 "edge label {label}: declared as {expected_source} -> {expected_target} but used as {observed_source:?} -> {observed_target:?}"
330 )]
331 #[diagnostic(code(SLENE_A_012))]
332 SchemaEdgeEndpointMismatch {
333 label: DbString,
335 expected_source: String,
337 expected_target: String,
339 observed_source: Box<LabelSet>,
347 observed_target: Box<LabelSet>,
350 #[label("endpoint types do not match edge declaration")]
352 span: SourceSpan,
353 },
354
355 #[error("property {property} is not declared by {declared_in} in graph type {graph_type}")]
357 #[diagnostic(code(SLENE_A_013))]
358 SchemaUndeclaredProperty {
359 property: DbString,
361 declared_in: DbString,
363 graph_type: DbString,
365 #[label("property is not declared")]
367 span: SourceSpan,
368 },
369
370 #[error("property {property} of {declared_in} declared {expected} but value is {found:?}")]
372 #[diagnostic(code(SLENE_A_014))]
373 SchemaPropertyTypeMismatch {
374 property: DbString,
376 declared_in: DbString,
378 expected: PropertyValueType,
380 found: GqlType,
382 #[label("value type is incompatible with property declaration")]
384 span: SourceSpan,
385 },
386
387 #[error("required property {property} of {declared_in} missing at INSERT site")]
389 #[diagnostic(code(SLENE_A_015))]
390 SchemaRequiredPropertyMissing {
391 property: DbString,
393 declared_in: DbString,
395 #[label("required property is not supplied")]
397 span: SourceSpan,
398 },
399
400 #[error("required property {property} of {declared_in} cannot be REMOVE'd")]
402 #[diagnostic(code(SLENE_A_016))]
403 SchemaRequiredPropertyRemoved {
404 property: DbString,
406 declared_in: DbString,
408 #[label("required property cannot be removed")]
410 span: SourceSpan,
411 },
412
413 #[error("INSERT requires a single label or label conjunction; {form} is not allowed")]
415 #[diagnostic(code(SLENE_A_017))]
416 SchemaInvalidInsertLabelExpr {
417 form: InvalidLabelForm,
419 #[label("invalid INSERT label expression")]
421 span: SourceSpan,
422 },
423
424 #[error("required edge label {label} of {declared_in} cannot be REMOVE'd")]
426 #[diagnostic(code(SLENE_A_018))]
427 SchemaRequiredEdgeLabelRemoved {
428 label: DbString,
430 declared_in: DbString,
432 #[label("edge label cannot be removed")]
434 span: SourceSpan,
435 },
436}
437
438#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
440pub enum InvalidLabelForm {
441 Disjunction,
443 Negation,
445 Wildcard,
447 Missing,
449}
450
451impl std::fmt::Display for InvalidLabelForm {
452 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453 formatter.write_str(match self {
454 Self::Disjunction => "label disjunction",
455 Self::Negation => "label negation",
456 Self::Wildcard => "label wildcard",
457 Self::Missing => "missing label",
458 })
459 }
460}
461
462fn display_qualified_name(name: &[DbString]) -> QualifiedNameDisplay<'_> {
463 QualifiedNameDisplay(name)
464}
465
466struct QualifiedNameDisplay<'a>(&'a [DbString]);
467
468impl std::fmt::Display for QualifiedNameDisplay<'_> {
469 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
470 fmt_qualified_name(f, self.0)
471 }
472}
473
474fn fmt_qualified_name(f: &mut std::fmt::Formatter<'_>, name: &[DbString]) -> std::fmt::Result {
475 let mut first = true;
476 for segment in name {
477 if !first {
478 f.write_str(".")?;
479 }
480 let text = segment.as_str();
481 if text.contains('.') || text.contains('"') {
482 write!(f, "\"{}\"", text.replace('"', "\"\""))?;
483 } else {
484 f.write_str(text)?;
485 }
486 first = false;
487 }
488 Ok(())
489}
490
491fn display_argument_range(minimum: usize, maximum: usize) -> String {
492 if minimum == maximum {
493 maximum.to_string()
494 } else {
495 format!("{minimum}..={maximum}")
496 }
497}
498
499impl AnalysisError {
500 #[must_use]
502 pub const fn gqlstatus(&self) -> GqlStatus {
503 match self {
504 Self::UndefinedReference { .. } => GqlStatus::UNDEFINED_REFERENCE,
505 Self::Shadow { .. }
506 | Self::PatternKindMismatch { .. }
507 | Self::AliasReusedAsPatternBinding { .. } => GqlStatus::DUPLICATE_OBJECT,
508 Self::NotImplemented { .. } => GqlStatus::FEATURE_NOT_SUPPORTED,
509 Self::UnboundedRequiresGate { .. } => GqlStatus::SYNTAX_ERROR,
510 Self::ValueSubqueryShapeViolation { .. } => GqlStatus::SYNTAX_ERROR,
511 Self::AggregateNestingViolation { .. } => GqlStatus::SYNTAX_ERROR,
512 Self::GroupedProjectionItemNotGrouped { .. } => GqlStatus::SYNTAX_ERROR,
513 Self::ReturnStarRequiresInput { .. } => GqlStatus::SYNTAX_ERROR,
514 Self::SortKeyContainsNestedQuery { .. } => GqlStatus::SYNTAX_ERROR,
515 Self::SortKeyContainsAggregate { .. } => GqlStatus::SYNTAX_ERROR,
516 Self::InvalidReference { .. } => GqlStatus::INVALID_REFERENCE,
517 Self::RecursionLimitExceeded { .. } => GqlStatus::PROGRAM_LIMIT_EXCEEDED,
518 Self::TypeMismatch { .. } | Self::ConflictingParameterTypes { .. } => {
519 GqlStatus::DATATYPE_MISMATCH
520 }
521 Self::UnknownProcedure { .. } => GqlStatus::UNKNOWN_PROCEDURE,
522 Self::WrongArgumentCount { .. } => GqlStatus::DATATYPE_MISMATCH,
523 Self::UnknownYieldColumn { .. } => GqlStatus::UNDEFINED_REFERENCE,
524 Self::MutatingProcedureInReadPipeline { .. } => {
525 GqlStatus::INVALID_TRANSACTION_STATE_MIXING
526 }
527 Self::SchemaUnknownNodeType { .. }
528 | Self::SchemaUnknownEdgeType { .. }
529 | Self::SchemaEdgeEndpointMismatch { .. }
530 | Self::SchemaUndeclaredProperty { .. }
531 | Self::SchemaPropertyTypeMismatch { .. }
532 | Self::SchemaRequiredPropertyMissing { .. }
533 | Self::SchemaRequiredPropertyRemoved { .. }
534 | Self::SchemaInvalidInsertLabelExpr { .. }
535 | Self::SchemaRequiredEdgeLabelRemoved { .. } => GqlStatus::GRAPH_TYPE_VIOLATION,
536 }
537 }
538
539 pub(crate) fn undefined_reference(name: DbString, span: SourceSpan) -> Self {
540 Self::UndefinedReference {
541 name,
542 span,
543 hint: Some("declare the variable before this reference".into()),
544 }
545 }
546}