use crate::common::{primary_label, Diagnostic};
use crate::common::{FileId, Span};
use codespan_reporting::diagnostic::Diagnostic as CsDiagnostic;
use thiserror::Error;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum StratifyError {
#[error(
"recursive rules must be inside an explicit `loop`/`fixpoint` block, \
but recursion was found in plain rules"
)]
RecursionOutsideLoop {
rules: Vec<(usize, Span)>,
hint: &'static str,
},
#[error("`iterative` relation `{rel}` has no rule inside the loop body that derives it")]
IterativeNotInLoopHead {
rel: String,
decl_span: Span,
},
#[error(
"`iterative` relation `{rel}` is not recursive in this loop \
(never appears as a body atom)"
)]
IterativeNotRecursive {
rel: String,
decl_span: Span,
},
#[error(
"rule {rule} references relation `{rel}`, which is not yet defined \
at this point in the program"
)]
ForwardReference {
rule: usize,
span: Span,
rel: String,
},
#[error(
"recursive stratum #{stratum} has no recursive relations \
(no head relation appears as a body atom)"
)]
RecursiveStratumEmpty {
stratum: usize,
rules: Vec<(usize, Span)>,
},
#[error(
"loop until condition references relation `{rel}`, which has no \
rule inside the loop body that derives it"
)]
LoopConditionNotDerived {
rel: String,
span: Span,
},
#[error(
"loop until condition references relation `{rel}`, which does not \
depend on any recursive relation in this loop"
)]
LoopConditionNotRecursive {
rel: String,
span: Span,
},
}
impl Diagnostic for StratifyError {
fn to_diagnostic(&self) -> CsDiagnostic<FileId> {
let base = CsDiagnostic::error().with_message(self.to_string());
match self {
StratifyError::RecursionOutsideLoop { rules, hint } => {
let labels: Vec<_> = rules
.iter()
.filter_map(|(rid, span)| {
primary_label(*span).map(|l| l.with_message(format!("rule {rid}")))
})
.collect();
base.with_labels(labels).with_notes(vec![hint.to_string()])
}
StratifyError::IterativeNotInLoopHead { decl_span, .. } => base
.with_labels(primary_label(*decl_span).into_iter().collect())
.with_notes(vec![
"every relation declared `iterative` must appear as the head \
of at least one rule inside the fixpoint/loop block"
.into(),
]),
StratifyError::IterativeNotRecursive { decl_span, .. } => base
.with_labels(primary_label(*decl_span).into_iter().collect())
.with_notes(vec![
"only relations that feed back into their own derivation can \
use iterative (replacement) semantics"
.into(),
]),
StratifyError::ForwardReference { span, rel, .. } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![format!(
"`{rel}` appears to be defined in a later segment. \
Move the rule after the segment that derives `{rel}`."
)]),
StratifyError::RecursiveStratumEmpty { rules, .. } => {
let labels: Vec<_> = rules
.iter()
.filter_map(|(rid, span)| {
primary_label(*span).map(|l| l.with_message(format!("rule {rid}")))
})
.collect();
base.with_labels(labels).with_notes(vec![
"a `loop`/`fixpoint` block must contain at least one rule whose \
head relation also appears in a body atom within the same block"
.into(),
])
}
StratifyError::LoopConditionNotDerived { span, .. } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![
"the until-condition relation must appear as the head of \
at least one rule inside the loop block"
.into(),
]),
StratifyError::LoopConditionNotRecursive { span, .. } => base
.with_labels(primary_label(*span).into_iter().collect())
.with_notes(vec![
"an until-condition relation must be derived from the loop's \
recursive computation to be a meaningful termination signal"
.into(),
]),
}
}
}