use core::fmt;
use std::{borrow::Cow, mem::replace};
use proc_macro::Span;
use crate::{IntoTokens, Parser, ParserPos, ToSpan, TokenQueue};
macro_rules! cfg_select {
{
$(_ => { $($it:item)* })?
} => {
$($($it)*)?
};
{
$cfg:meta => { $($it:item)* }
$($r:tt)*
} => {
#[cfg($cfg)]
cfg_select! {
_ => {
$($it)*
}
}
#[cfg(not($cfg))]
cfg_select! {
$($r)*
}
};
}
cfg_select! {
any(exhaustive) => {
mod emit;
#[cfg(feature = "unstable-diagnostics-backend-format-json")]
#[allow(dead_code)]
mod emit_stdlib;
#[cfg(feature = "unstable-diagnostics-backend-stdlib")]
#[allow(dead_code)]
mod emit_format_json;
}
feature = "unstable-diagnostics-backend-stdlib" => {
#[path = "emit_stdlib.rs"]
mod emit;
}
feature = "unstable-diagnostics-backend-format-json" => {
#[path = "emit_format_json.rs"]
mod emit;
}
_ => {
mod emit;
}
}
#[allow(dead_code)]
pub(self) fn emit_compile_error_invocation(q: &mut TokenQueue, span: Span, msg: String) {
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, TokenStream, TokenTree};
macro_rules! quote_path {
() => {};
(:: $n:ident $(:: $r:ident)*) => {
let mut p = Punct::new(':', Spacing::Joint);
p.set_span(span);
q.push(p);
let mut p = Punct::new(':', Spacing::Alone);
p.set_span(span);
q.push(p);
q.push(Ident::new(stringify!($n), span));
quote_path!($(:: $r)*)
};
}
quote_path!(::core::compile_error);
q.push(Punct::new('!', Spacing::Alone));
let mut msg: TokenTree = Literal::string(&msg).into();
msg.set_span(span);
let mut group = Group::new(Delimiter::Parenthesis, TokenStream::from_iter([msg]));
group.set_span(span);
q.push(group);
q.push(Punct::new(';', Spacing::Alone));
}
pub type Result<T, E = Expected> = core::result::Result<T, E>;
#[derive(Debug, PartialEq, Eq)]
enum Syntax {
LiteralBox(Box<str>),
LiteralStr(&'static str),
NounBox(Box<str>),
NounStr(&'static str),
}
impl fmt::Display for Syntax {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Syntax::LiteralBox(s) => write!(f, "`{s}`"),
Syntax::LiteralStr(s) => write!(f, "`{s}`"),
Syntax::NounBox(s) => f.write_str(s),
Syntax::NounStr(s) => f.write_str(s),
}
}
}
impl Syntax {
#[inline]
fn lit(lit: Cow<'static, str>) -> Self {
match lit {
Cow::Borrowed(s) => Self::LiteralStr(s),
Cow::Owned(s) => Self::LiteralBox(s.into_boxed_str()),
}
}
#[inline]
fn noun(noun: Cow<'static, str>) -> Self {
match noun {
Cow::Borrowed(s) => Self::NounStr(s),
Cow::Owned(s) => Self::NounBox(s.into_boxed_str()),
}
}
}
#[derive(Debug)]
pub struct Expected {
pos: ParserPos,
syntaxes: smallvec::SmallVec<[Syntax; 1]>,
notes: Vec<Box<dyn DisplayDebug>>,
}
impl From<Expected> for ParserPos {
#[inline]
fn from(value: Expected) -> Self {
value.pos
}
}
impl ToSpan for Expected {
#[inline]
fn span(&self) -> Span {
self.pos.span()
}
}
impl PartialEq for Expected {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.syntaxes == other.syntaxes
}
}
impl Expected {
#[inline]
#[must_use]
pub fn nothing(pos: impl Into<ParserPos>) -> Self {
Self {
pos: pos.into(),
syntaxes: smallvec::SmallVec::new(),
notes: Vec::new(),
}
}
#[inline]
#[must_use]
pub fn lit(pos: impl Into<ParserPos>, lit: impl Into<Cow<'static, str>>) -> Self {
Self::nothing(pos).or_lit(lit)
}
#[inline]
pub fn noun(pos: impl Into<ParserPos>, noun: impl Into<Cow<'static, str>>) -> Self {
Self::nothing(pos).or_noun(noun)
}
#[inline]
pub fn push_lit(&mut self, lit: impl Into<Cow<'static, str>>) {
self.syntaxes.push(Syntax::lit(lit.into()));
}
#[inline]
pub fn push_noun(&mut self, noun: impl Into<Cow<'static, str>>) {
self.syntaxes.push(Syntax::noun(noun.into()));
}
#[inline]
#[must_use]
pub fn or_lit(mut self, lit: impl Into<Cow<'static, str>>) -> Self {
self.push_lit(lit);
self
}
#[inline]
#[must_use]
pub fn or_noun(mut self, noun: impl Into<Cow<'static, str>>) -> Self {
self.push_noun(noun);
self
}
#[inline]
pub fn recover(&self, cx: &mut Parser) {
cx.seek_to(&self.pos)
}
#[inline]
pub fn add_note(&mut self, note: impl fmt::Display + fmt::Debug + 'static) {
self.notes.push(Box::new(note));
}
#[inline]
#[must_use]
pub fn with_note(mut self, note: impl fmt::Display + fmt::Debug + 'static) -> Self {
self.add_note(note);
self
}
}
impl fmt::Display for Expected {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "expected ")?;
if let Some((last, rest)) = self.syntaxes.split_last() {
for syntax in rest {
write!(f, "{syntax}, ")?;
}
if self.syntaxes.len() > 1 {
write!(f, "or ")?;
}
write!(f, "{last}")?;
if self.pos.is_eos() {
write!(f, ", but found the end of input")?;
}
} else {
write!(f, "no tokens")?;
}
for note in &self.notes {
write!(f, "\nnote: {note}")?;
}
Ok(())
}
}
trait DisplayDebug: fmt::Debug + fmt::Display {}
impl<T: fmt::Debug + fmt::Display> DisplayDebug for T {}
#[derive(Debug)]
struct CustomDiagnostic {
level: DiagnosticLevel,
span: Span,
msg: Box<dyn DisplayDebug>,
}
impl PartialEq for CustomDiagnostic {
fn eq(&self, other: &Self) -> bool {
self.level == other.level
}
}
#[derive(Debug)]
enum DiagnosticKind {
Expected(Expected),
Custom(CustomDiagnostic),
Join(Vec<DiagnosticKind>),
}
impl PartialEq for DiagnosticKind {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(DiagnosticKind::Expected(a), DiagnosticKind::Expected(b)) => a == b,
(DiagnosticKind::Join(a), DiagnosticKind::Join(b)) => a == b,
(DiagnosticKind::Custom(a), DiagnosticKind::Custom(b)) => a == b,
_ => false,
}
}
}
#[cfg_attr(not(feature = "warnings"), doc = "warnings")]
#[cfg_attr(feature = "warnings", doc = "[warnings](DiagnosticLevel::Warning)")]
#[derive(Debug, PartialEq)]
pub struct Diagnostic {
kind: DiagnosticKind,
}
impl From<Expected> for Diagnostic {
#[inline]
fn from(value: Expected) -> Self {
Self {
kind: DiagnosticKind::Expected(value),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DiagnosticLevel {
Error,
#[cfg(feature = "warnings")]
Warning,
}
impl Diagnostic {
#[inline]
pub fn custom(
level: DiagnosticLevel,
span: impl ToSpan,
msg: impl fmt::Display + fmt::Debug + 'static,
) -> Diagnostic {
Diagnostic {
kind: DiagnosticKind::Custom(CustomDiagnostic {
level,
span: span.span(),
msg: Box::new(msg),
}),
}
}
pub fn and_many(errors: impl IntoIterator<Item = Diagnostic>) -> Diagnostic {
let errors = errors.into_iter();
let mut buf = Vec::with_capacity(errors.size_hint().0);
errors.for_each(|err| Self::extend(&mut buf, err.kind));
Self {
kind: DiagnosticKind::Join(buf),
}
}
#[inline]
fn extend(buf: &mut Vec<DiagnosticKind>, err: DiagnosticKind) {
match err {
DiagnosticKind::Join(errors) => buf.extend(errors),
_ => buf.push(err),
}
}
pub fn and(&mut self, b: impl Into<Diagnostic>) {
let (a, b) = (self, b.into());
match (&mut a.kind, b.kind) {
(DiagnosticKind::Join(a_buf), DiagnosticKind::Join(b_buf)) => {
a_buf.extend(b_buf);
}
(DiagnosticKind::Join(a_buf), b_kind) => {
Self::extend(a_buf, b_kind);
}
(_, DiagnosticKind::Join(b_buf)) => {
let old_a = replace(
&mut a.kind,
DiagnosticKind::Join(Vec::with_capacity(b_buf.len() + 1)),
);
let DiagnosticKind::Join(a_buf) = &mut a.kind else {
unreachable!()
};
Self::extend(a_buf, old_a);
a_buf.extend(b_buf);
}
(_, b_kind) => {
let old_a = replace(&mut a.kind, DiagnosticKind::Join(Vec::with_capacity(2)));
let DiagnosticKind::Join(a_buf) = &mut a.kind else {
unreachable!()
};
a_buf.extend([old_a, b_kind]);
}
}
}
#[must_use = "accumulated errors must be returned from the proc-macro."]
pub fn finish(self) -> impl IntoTokens {
self.kind
}
}
impl IntoTokens for DiagnosticKind {
fn extend_tokens(self, q: &mut TokenQueue) {
match self {
DiagnosticKind::Expected(exp) => {
emit::emit(q, DiagnosticLevel::Error, exp.pos.span(), exp.to_string())
}
DiagnosticKind::Custom(custom) => {
emit::emit(q, custom.level, custom.span, custom.msg.to_string())
}
DiagnosticKind::Join(errors) => {
for err in errors {
err.extend_tokens(q);
}
}
}
}
}