#![cfg_attr(not(stable_assert_matches), feature(assert_matches))]
#![feature(never_type)]
#![feature(proc_macro_diagnostic)]
#![feature(try_trait_v2)]
#![feature(try_trait_v2_residual)]
use std::fmt::Debug;
extern crate proc_macro;
use proc_macro::TokenStream as TokenStream1;
use proc_macro2::Span;
use crate::DiagnosticResult_::{Error, Ok as Ok_, Warning};
use crate::internal::*;
pub mod prelude {
pub use super::AsDiagnostic;
pub use super::DiagnosticResult;
pub use super::DiagnosticStream;
pub use super::{Ok, error, error_spanned, warn_spanned};
}
pub type DiagnosticStream = DiagnosticResult<proc_macro2::TokenStream>;
#[derive(Clone, Debug)]
#[must_use = "this `DiagnosticResult` may be an error or a warning, which should be emitted"]
pub struct DiagnosticResult<T> {
inner: DiagnosticResult_<T>,
}
#[derive(Clone, Debug)]
pub enum DiagnosticResultKind {
Ok,
Warning,
Error,
}
#[derive(Clone, Debug)]
enum DiagnosticResult_<T> {
Ok(T),
Warning(T, Diagnostic),
Error(Diagnostic),
}
#[expect(non_snake_case, reason = "same feel as a Result type alias")]
pub fn Ok<T>(val: T) -> DiagnosticResult<T> {
DiagnosticResult { inner: Ok_(val) }
}
pub fn error<T, MSG: ToString>(message: MSG) -> DiagnosticResult<T> {
DiagnosticResult {
inner: Error(Diagnostic::new(Level::Error, Span::call_site(), message)),
}
}
pub fn error_spanned<T, MSG: ToString, SPN: MultiSpan>(
span: SPN,
message: MSG,
) -> DiagnosticResult<T> {
DiagnosticResult {
inner: Error(Diagnostic::new(Level::Error, span, message)),
}
}
pub fn warn_spanned<T, MSG: ToString, SPN: MultiSpan>(
value: T,
span: SPN,
message: MSG,
) -> DiagnosticResult<T> {
DiagnosticResult {
inner: Warning(value, Diagnostic::new(Level::Warning, span, message)),
}
}
pub trait AsDiagnostic<T> {
fn add_help<MSG: ToString, SPN: MultiSpan>(
self,
span: SPN,
message: MSG,
) -> DiagnosticResult<T>;
fn add_note<MSG: ToString, SPN: MultiSpan>(
self,
span: SPN,
message: MSG,
) -> DiagnosticResult<T>;
}
impl<T, E> AsDiagnostic<T> for Result<T, E>
where
E: Into<DiagnosticResult<T>>,
{
fn add_help<MSG: ToString, SPN: MultiSpan>(
self,
span: SPN,
message: MSG,
) -> DiagnosticResult<T> {
match self {
Result::Ok(val) => Ok(val),
Result::Err(e) => e.into().add_help(span, message),
}
}
fn add_note<MSG: ToString, SPN: MultiSpan>(
self,
span: SPN,
message: MSG,
) -> DiagnosticResult<T> {
match self {
Result::Ok(val) => Ok(val),
Result::Err(e) => e.into().add_note(span, message),
}
}
}
impl<T> DiagnosticResult<T> {
pub fn add_help<MSG: ToString, SPN: MultiSpan>(mut self, span: SPN, message: MSG) -> Self {
match self.inner {
Ok_(_) => self,
Warning(_, ref mut diagnostic) | Error(ref mut diagnostic) => {
diagnostic.add_help(span, message);
self
}
}
}
pub fn add_note<MSG: ToString, SPN: MultiSpan>(mut self, span: SPN, message: MSG) -> Self {
match self.inner {
Ok_(_) => self,
Warning(_, ref mut diagnostic) | Error(ref mut diagnostic) => {
diagnostic.add_note(span, message);
self
}
}
}
pub fn is_ok(&self) -> bool {
matches!(&self.kind(), DiagnosticResultKind::Ok)
}
pub fn is_warning(&self) -> bool {
matches!(&self.kind(), DiagnosticResultKind::Warning)
}
pub fn is_error(&self) -> bool {
matches!(&self.kind(), DiagnosticResultKind::Error)
}
pub fn kind(&self) -> DiagnosticResultKind {
match self.inner {
DiagnosticResult_::Ok(_) => DiagnosticResultKind::Ok,
DiagnosticResult_::Warning(_, _) => DiagnosticResultKind::Warning,
DiagnosticResult_::Error(_) => DiagnosticResultKind::Error,
}
}
pub fn unwrap(self) -> T
where
T: Debug,
{
match self.inner {
Ok_(t) => t,
Warning(val, warning) => panic!(
"Called unwrap on value {:?} with accompanying warning: {:?}",
val, warning
),
Error(error) => panic!("Called unwrap on an error: {:?}", error),
}
}
}
mod internal {
use std::fmt::Display;
use proc_macro2::Span;
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub level: Level,
pub message: String,
pub spans: Vec<Span>,
pub children: Vec<Diagnostic>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Level {
Error,
Warning,
Note,
Help,
}
impl Display for Level {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Level::Error => write!(f, "error"),
Level::Warning => write!(f, "warning"),
Level::Note => write!(f, "note"),
Level::Help => write!(f, "help"),
}
}
}
impl Diagnostic {
pub fn new<MSG: ToString, SPN: MultiSpan>(level: Level, span: SPN, message: MSG) -> Self {
Self {
level,
message: message.to_string(),
spans: span.into_spans(),
children: vec![],
}
}
pub fn push(&mut self, child: Diagnostic) {
self.children.push(child);
}
pub fn add_child<MSG: ToString, SPN: MultiSpan>(
&mut self,
level: Level,
span: SPN,
message: MSG,
) {
self.children.push(Diagnostic::new(level, span, message));
}
pub fn add_help<MSG: ToString, SPN: MultiSpan>(&mut self, span: SPN, message: MSG) {
self.add_child(Level::Help, span, message);
}
pub fn add_note<MSG: ToString, SPN: MultiSpan>(&mut self, span: SPN, message: MSG) {
self.add_child(Level::Note, span, message);
}
fn spans_call_site(&self) -> bool {
let call_site = Span::call_site();
let cs_file = call_site.local_file();
let cs_start = call_site.start();
let cs_end = call_site.end();
let is_call_site = |span: &Span| {
span.local_file() == cs_file && span.start() == cs_start && span.end() == cs_end
};
self.spans.iter().any(is_call_site)
|| self.children.iter().any(Diagnostic::spans_call_site)
}
}
impl From<syn::Error> for Diagnostic {
fn from(error: syn::Error) -> Self {
let mut diagnostic = Diagnostic::new(Level::Error, error.span(), error.to_string());
for err in error.into_iter().skip(1) {
diagnostic.push(err.into());
}
diagnostic
}
}
impl Diagnostic {
pub fn emit(mut self) {
if !self.spans_call_site() {
self.add_note(
Span::call_site(),
format!(
"this {} originates from the macro invocation here",
self.level
),
);
};
let spans = self.as_spans();
let mut pm_diagnostic =
proc_macro::Diagnostic::spanned(spans, self.level.into(), self.message);
for child in self.children {
pm_diagnostic = child.add_to_parent(pm_diagnostic);
}
pm_diagnostic.emit();
}
fn add_to_parent(self, parent: proc_macro::Diagnostic) -> proc_macro::Diagnostic {
let msg = self.message.clone();
match self.level {
Level::Error => parent.span_error(self.as_spans(), msg),
Level::Warning => parent.span_warning(self.as_spans(), msg),
Level::Note => parent.span_note(self.as_spans(), msg),
Level::Help => parent.span_help(self.as_spans(), msg),
}
}
fn as_spans(&self) -> Vec<proc_macro::Span> {
self.spans.iter().map(|span| span.unwrap()).collect()
}
}
impl From<Level> for proc_macro::Level {
fn from(level: Level) -> Self {
match level {
Level::Error => Self::Error,
Level::Help => Self::Help,
Level::Note => Self::Note,
Level::Warning => Self::Warning,
}
}
}
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 MultiSpan for &[Span] {
fn into_spans(self) -> Vec<Span> {
self.to_vec()
}
}
}
impl<T> std::ops::Try for DiagnosticResult<T> {
type Output = T;
type Residual = DiagnosticResult<!>;
fn from_output(output: Self::Output) -> Self {
Self { inner: Ok_(output) }
}
fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
match self.inner {
Ok_(t) => std::ops::ControlFlow::Continue(t),
Warning(t, d) => {
d.emit();
std::ops::ControlFlow::Continue(t)
}
Error(d) => std::ops::ControlFlow::Break(DiagnosticResult { inner: Error(d) }),
}
}
}
impl<T> std::ops::FromResidual<DiagnosticResult<!>> for DiagnosticResult<T> {
fn from_residual(residual: DiagnosticResult<!>) -> Self {
match residual.inner {
Error(residual) => Self {
inner: Error(residual),
},
}
}
}
impl<T> std::ops::Residual<T> for DiagnosticResult<!> {
type TryType = DiagnosticResult<T>;
}
impl<T> std::ops::FromResidual<Result<std::convert::Infallible, DiagnosticResult<!>>>
for DiagnosticResult<T>
{
fn from_residual(result: Result<std::convert::Infallible, DiagnosticResult<!>>) -> Self {
match result {
Result::Err(e) => match e.inner {
Error(diagnostic) => Self {
inner: DiagnosticResult_::Error(diagnostic),
},
},
}
}
}
impl<T, E> std::ops::FromResidual<Result<std::convert::Infallible, E>> for DiagnosticResult<T>
where
E: Into<Diagnostic>,
{
fn from_residual(result: Result<std::convert::Infallible, E>) -> Self {
match result {
Err(e) => DiagnosticResult {
inner: DiagnosticResult_::Error(e.into()),
},
}
}
}
impl<T, E> From<E> for DiagnosticResult<T>
where
E: Into<Diagnostic> + std::error::Error,
{
fn from(error: E) -> Self {
Self {
inner: DiagnosticResult_::Error(error.into()),
}
}
}
impl From<DiagnosticStream> for TokenStream1 {
fn from(result: DiagnosticStream) -> Self {
match result.inner {
Ok_(t) => t.into(),
Warning(t, warning) => {
warning.emit();
t.into()
}
Error(error) => {
error.emit();
TokenStream1::new()
}
}
}
}
#[cfg(test)]
mod tests {
#[cfg(assert_matches_in_module)]
use std::assert_matches::assert_matches;
#[cfg(assert_matches_in_root)]
use std::assert_matches;
use super::*;
#[test]
fn is_ok() {
assert!(Ok(()).is_ok());
}
#[test]
fn is_warning() {
assert!(warn_spanned((), Span::call_site(), "foo").is_warning());
}
#[test]
fn is_error() {
assert!(error::<(), &str>("foo").is_error());
}
#[test]
fn kind() {
match Ok(()).kind() {
DiagnosticResultKind::Ok => (),
DiagnosticResultKind::Warning => panic!("not a warning"),
DiagnosticResultKind::Error => panic!("not an error"),
}
}
#[test]
fn ok_with_help() {
assert_matches!(
Ok(()).add_help(Span::call_site(), "help text").kind(),
DiagnosticResultKind::Ok
)
}
}