use std::cell::{Cell, RefCell};
use proc_macro::{Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum Level {
Error,
Warning,
Note,
Help,
}
pub trait MultiSpan {
fn into_spans(self) -> Vec<Span>;
}
impl MultiSpan for Span {
fn into_spans(self) -> Vec<Span> {
vec![self]
}
}
impl MultiSpan for Vec<Span> {
fn into_spans(self) -> Vec<Span> {
self
}
}
impl<'a> MultiSpan for &'a [Span] {
fn into_spans(self) -> Vec<Span> {
self.to_vec()
}
}
#[derive(Clone, Debug)]
pub struct Diagnostic {
level: Level,
message: String,
spans: Vec<Span>,
children: Vec<Diagnostic>,
}
macro_rules! diagnostic_child_methods {
($spanned:ident, $regular:ident, $level:expr) => {
#[doc = concat!("Adds a new child diagnostics message to `self` with the [`",
stringify!($level), "`] level, and the given `spans` and `message`.")]
pub fn $spanned<S, T>(mut self, spans: S, message: T) -> Diagnostic
where
S: MultiSpan,
T: Into<String>,
{
self.children.push(Diagnostic::spanned(spans, $level, message));
self
}
#[doc = concat!("Adds a new child diagnostic message to `self` with the [`",
stringify!($level), "`] level, and the given `message`.")]
pub fn $regular<T: Into<String>>(mut self, message: T) -> Diagnostic {
self.children.push(Diagnostic::new($level, message));
self
}
};
}
#[derive(Debug, Clone)]
pub struct Children<'a>(std::slice::Iter<'a, Diagnostic>);
impl<'a> Iterator for Children<'a> {
type Item = &'a Diagnostic;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl Diagnostic {
pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic {
Diagnostic {
level,
message: message.into(),
spans: vec![],
children: vec![],
}
}
pub fn spanned<S, T>(spans: S, level: Level, message: T) -> Diagnostic
where
S: MultiSpan,
T: Into<String>,
{
Diagnostic {
level,
message: message.into(),
spans: spans.into_spans(),
children: vec![],
}
}
diagnostic_child_methods!(span_error, error, Level::Error);
diagnostic_child_methods!(span_warning, warning, Level::Warning);
diagnostic_child_methods!(span_note, note, Level::Note);
diagnostic_child_methods!(span_help, help, Level::Help);
pub fn level(&self) -> Level {
self.level
}
pub fn set_level(&mut self, level: Level) {
self.level = level;
}
pub fn message(&self) -> &str {
&self.message
}
pub fn set_message<T: Into<String>>(&mut self, message: T) {
self.message = message.into();
}
pub fn spans(&self) -> &[Span] {
&self.spans
}
pub fn set_spans<S: MultiSpan>(&mut self, spans: S) {
self.spans = spans.into_spans();
}
pub fn children(&self) -> Children<'_> {
Children(self.children.iter())
}
pub fn emit(self) {
assert!(
MYNT_ACTIVE.get(),
"mynt diagnostic emitted outside of mynt scope"
);
DIAGNOSTICS.with(|store| store.borrow_mut().push(self));
}
}
thread_local! {
static MYNT_ACTIVE: Cell<bool> = const { Cell::new(false) };
static DIAGNOSTICS: RefCell<Vec<Diagnostic>> = const { RefCell::new(Vec::new()) };
}
#[doc(hidden)]
pub fn enter_diagnostics() {
DIAGNOSTICS.with(|store| store.replace(Vec::new()));
MYNT_ACTIVE.replace(true);
}
fn write_diagnostic(tokens: &mut TokenStream, diagnostic: &Diagnostic) {
let (start, end, file) = match diagnostic.spans.first() {
Some(span) => (span.start(), span.end(), Some(span.file())),
None => (Span::call_site(), Span::call_site(), None),
};
match diagnostic.level {
Level::Error => {
tokens.push_tt(Ident::new("compile_error", start));
tokens.push_tt(Punct::new('!', Spacing::Alone).with_span(start));
let mut message_tokens = TokenStream::new();
message_tokens.push_tt(Literal::string(&diagnostic.message).with_span(start));
tokens.push_tt(Group::new(proc_macro::Delimiter::Brace, message_tokens).with_span(end));
}
Level::Warning => {
#[cfg(feature = "yansi")]
{
use yansi::Paint;
eprintln!(
"{}{}{}",
"warning".bright_yellow().bold(),
": ".bold(),
diagnostic.message.bold()
);
if let Some(file) = file {
eprintln!(
" {} {}:{}:{}",
"-->".blue().bold(),
file,
start.line(),
start.column()
);
}
}
#[cfg(not(feature = "yansi"))]
{
eprintln!("warning: {}", diagnostic.message);
if let Some(file) = file {
eprintln!(" --> {}:{}:{}", file, start.line(), start.column());
}
}
}
Level::Note => {
#[cfg(feature = "yansi")]
{
use yansi::Paint;
eprintln!(
"{}{}{}",
"note".bright_cyan().bold(),
": ".bold(),
diagnostic.message.bold()
);
if let Some(file) = file {
eprintln!(
" {} {}:{}:{}",
"-->".blue(),
file,
start.line(),
start.column()
);
}
}
#[cfg(not(feature = "yansi"))]
{
eprintln!("note: {}", diagnostic.message);
if let Some(file) = file {
eprintln!(" --> {}:{}:{}", file, start.line(), start.column());
}
}
}
Level::Help => {
#[cfg(feature = "yansi")]
{
use yansi::Paint;
eprintln!(
"{}{}{}",
"help".bright_white().bold(),
": ".bold(),
diagnostic.message.bold()
);
if let Some(file) = file {
eprintln!(
" {} {}:{}:{}",
"-->".blue(),
file,
start.line(),
start.column()
);
}
}
#[cfg(not(feature = "yansi"))]
{
eprintln!("help: {}", diagnostic.message);
if let Some(file) = file {
eprintln!(" --> {}:{}:{}", file, start.line(), start.column());
}
}
}
}
for child_diagnostic in &diagnostic.children {
write_diagnostic(tokens, child_diagnostic);
}
}
#[doc(hidden)]
pub fn exit_diagnostics(tokens: &mut TokenStream) {
MYNT_ACTIVE.replace(false);
DIAGNOSTICS.with(|store| {
for diagnostic in store.borrow().iter() {
write_diagnostic(tokens, diagnostic);
}
});
}
trait TokenTreeExt {
fn to_tt(self) -> TokenTree;
fn with_span(self, span: Span) -> Self;
}
impl TokenTreeExt for TokenTree {
fn to_tt(self) -> TokenTree {
self
}
fn with_span(mut self, span: Span) -> Self {
self.set_span(span);
self
}
}
impl TokenTreeExt for Group {
fn to_tt(self) -> TokenTree {
TokenTree::Group(self)
}
fn with_span(mut self, span: Span) -> Self {
self.set_span(span);
self
}
}
impl TokenTreeExt for Ident {
fn to_tt(self) -> TokenTree {
TokenTree::Ident(self)
}
fn with_span(mut self, span: Span) -> Self {
self.set_span(span);
self
}
}
impl TokenTreeExt for Punct {
fn to_tt(self) -> TokenTree {
TokenTree::Punct(self)
}
fn with_span(mut self, span: Span) -> Self {
self.set_span(span);
self
}
}
impl TokenTreeExt for Literal {
fn to_tt(self) -> TokenTree {
TokenTree::Literal(self)
}
fn with_span(mut self, span: Span) -> Self {
self.set_span(span);
self
}
}
trait PushTokenTreeExt {
fn push_tt(&mut self, tt: impl TokenTreeExt);
}
impl PushTokenTreeExt for TokenStream {
fn push_tt(&mut self, tt: impl TokenTreeExt) {
self.extend(Some(tt.to_tt()));
}
}