use std::fmt;
use codespan_reporting::diagnostic::Diagnostic as CsDiagnostic;
use crate::common::{primary_label, secondary_label, Diagnostic, InternalError, BUG_URL};
use crate::common::{FileId, Span};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnsafePredicateKind {
Negation,
Comparison,
FnCall,
}
impl fmt::Display for UnsafePredicateKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Negation => write!(f, "negated atom"),
Self::Comparison => write!(f, "comparison"),
Self::FnCall => write!(f, "function call"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum CatalogError {
#[error("unsafe variable `{var}` in {kind} `{predicate}`")]
UnsafeVariable {
kind: UnsafePredicateKind,
predicate: String,
predicate_span: Span,
rule_span: Span,
var: String,
},
#[error(transparent)]
Internal(#[from] InternalError),
}
impl CatalogError {
pub(super) fn internal(detail: impl Into<String>) -> Self {
Self::Internal(InternalError::new("catalog", detail, BUG_URL))
}
}
impl Diagnostic for CatalogError {
fn to_diagnostic(&self) -> CsDiagnostic<FileId> {
match self {
CatalogError::UnsafeVariable {
predicate_span,
rule_span,
var,
..
} => {
let mut label_vec = Vec::new();
if let Some(l) = primary_label(*predicate_span) {
label_vec
.push(l.with_message(format!("`{var}` is never bound in a positive atom")));
}
if let Some(l) = secondary_label(*rule_span) {
label_vec.push(l.with_message("in this rule"));
}
CsDiagnostic::error()
.with_message(self.to_string())
.with_labels(label_vec)
.with_notes(vec![
"every variable in a body predicate must also appear in a \
positive atom, so the set of tuples it ranges over is finite"
.into(),
])
}
CatalogError::Internal(ie) => ie.to_diagnostic(),
}
}
fn is_internal(&self) -> bool {
matches!(self, CatalogError::Internal(_))
}
}