use crate::span::{Span, Spanned};
use ariadne::{FnCache, Label, Report, ReportKind, Source};
use std::fmt::{Display, Formatter, Result, Write};
use thiserror::Error;
use yansi::{Color, Paint, Style};
pub struct WarningLabel {
pub(super) message: String,
pub(super) span: Span,
pub(super) color: Color,
}
#[derive(Error, Debug)]
pub enum Warning {
#[error("unneeded else branch")]
MatchUnneededElse { span: Span },
#[error("constraint is always `false`")]
AlwaysFalseConstraint { span: Span },
}
impl ReportableWarning for Warning {
fn labels(&self) -> Vec<WarningLabel> {
use Warning::*;
match self {
MatchUnneededElse { span } => vec![WarningLabel {
message: "`else` branch in match will never be evaluated".to_string(),
span: span.clone(),
color: Color::Yellow,
}],
AlwaysFalseConstraint { span } => vec![WarningLabel {
message: "this constraint always evaluates to `false` and can never be satisfied"
.to_string(),
span: span.clone(),
color: Color::Yellow,
}],
}
}
fn note(&self) -> Option<String> {
use Warning::*;
match self {
MatchUnneededElse { .. } | AlwaysFalseConstraint { .. } => None,
}
}
fn code(&self) -> Option<String> {
None
}
fn help(&self) -> Option<String> {
use Warning::*;
match self {
AlwaysFalseConstraint { .. } => {
Some("if this is intentional, consider removing the containing predicate because its constraints can never be satisfied".to_string())
}
MatchUnneededElse { .. } => None,
}
}
}
impl Spanned for Warning {
fn span(&self) -> &Span {
use Warning::*;
match self {
MatchUnneededElse { span } | AlwaysFalseConstraint { span } => span,
}
}
}
#[derive(Debug)]
pub struct Warnings(pub Vec<Warning>);
impl Display for Warnings {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(
f,
"{}",
self.0
.iter()
.map(|warning| warning.display_raw())
.collect::<String>()
.trim_end()
)
}
}
pub trait ReportableWarning
where
Self: std::fmt::Display + Spanned,
{
fn labels(&self) -> Vec<WarningLabel>;
fn note(&self) -> Option<String>;
fn code(&self) -> Option<String>;
fn help(&self) -> Option<String>;
fn print(&self) {
let filepaths_and_sources = self
.labels()
.iter()
.map(|label| {
let filepath = format!("{}", label.span.context().display());
let source = std::fs::read_to_string(filepath.clone()).unwrap_or("<none>".into());
(filepath, source)
})
.collect::<Vec<(String, String)>>();
let warning_file: &str = &format!("{}", self.span().context().display());
let mut report_builder = Report::build(
ReportKind::Warning,
(warning_file, self.span().range.clone()),
)
.with_message(format!("{}", self.bold()))
.with_labels(
self.labels()
.iter()
.enumerate()
.map(|(index, label)| {
let filepath: &str = &filepaths_and_sources[index].0;
let mut style = Style::new().bold();
style.foreground = Some(label.color);
Label::new((filepath, label.span.start()..label.span.end()))
.with_message(label.message.clone().paint(style))
.with_color(label.color)
})
.collect::<Vec<_>>(),
);
if let Some(code) = self.code() {
report_builder = report_builder.with_code(code);
}
if let Some(note) = self.note() {
report_builder = report_builder.with_note(note);
}
if let Some(help) = self.help() {
report_builder = report_builder.with_help(help);
}
report_builder
.finish()
.eprint(
FnCache::new(|id: &&str| Err(Box::new(format!("Failed to fetch source '{id}'"))))
.with_sources(
filepaths_and_sources
.iter()
.map(|(id, s)| (&id[..], Source::from(s)))
.collect(),
),
)
.unwrap();
}
fn display_raw(&self) -> String {
self.to_string()
+ "\n"
+ &self.labels().iter().fold(String::new(), |mut acc, label| {
writeln!(
&mut acc,
"@{}..{}: {}",
label.span.start(),
label.span.end(),
label.message
)
.expect("Failed to write label to string");
acc
})
+ &self
.note()
.map_or(String::new(), |note| format!("{note}\n"))
+ &self
.help()
.map_or(String::new(), |help| format!("{help}\n"))
}
}
pub fn print_warnings(warnings: &Warnings) {
for warning in &warnings.0 {
warning.print();
}
}