use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{
meta::{parser as meta_parser, ParseNestedMeta},
parse::Parser,
parse2, parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
Attribute, Data, DeriveInput, Fields, GenericArgument, Ident, ItemEnum, ItemStruct, LitBool,
LitStr, PathArguments, Token, Type, Variant, Visibility,
};
fn is_span_ty(ty: &Type) -> bool {
let Type::Path(p) = ty else {
return false;
};
p.path
.segments
.last()
.is_some_and(|seg| seg.ident == "Span")
}
#[proc_macro_attribute]
pub fn lumi_diagnostic(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = match parse_diagnostic_attr(attr) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let input2 = TokenStream2::from(item.clone());
let en = match parse2::<ItemEnum>(input2) {
Ok(e) => e,
Err(_) => {
return syn::Error::new(
Span::call_site(),
"#[lumi_diagnostic] must be applied to an enum",
)
.to_compile_error()
.into();
}
};
let name = &en.ident;
let level_ident: Ident = attr.level.unwrap_or_else(|| parse_quote!(Error));
let kind_expr = if let Some(kind_ident) = attr.kind {
quote!(Some(lumi_core::diagnostics::DiagnosticKind::#kind_ident))
} else {
quote!(None)
};
TokenStream::from(quote! {
#en
impl #name {
#[doc = "Converts this error into a diagnostic entry."]
pub fn to_diagnostic(&self) -> lumi_core::diagnostics::Diagnostic {
lumi_core::diagnostics::Diagnostic {
msg: self.to_string(),
span: lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(self).copied(),
level: lumi_core::diagnostics::Level::#level_ident,
file: None,
kind: #kind_expr,
}
}
#[doc = "Emits this error as a diagnostic into the provided diagnostic list."]
pub fn emit_diagnostic(&self, diags: &mut lumi_core::diagnostics::DiagnosticList) {
let msg = self.to_string();
let span = lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(self).copied();
diags.push(lumi_core::diagnostics::Level::#level_ident, msg, span);
if let Some(last) = diags.list.last_mut() {
last.kind = #kind_expr;
}
}
}
})
}
#[derive(Debug, Default)]
struct LumiErrorAttr {
kind: Option<Ident>,
level: Option<Ident>,
suggestion: bool,
recoverable: bool,
}
fn parse_lumi_error_attr(attr: TokenStream) -> Result<LumiErrorAttr, syn::Error> {
let mut out = LumiErrorAttr {
kind: None,
level: None,
suggestion: true,
recoverable: true,
};
let parser = |meta: ParseNestedMeta<'_>| {
if meta.path.is_ident("kind") {
let ident: Ident = meta.value()?.parse()?;
out.kind = Some(ident);
return Ok(());
}
if meta.path.is_ident("level") {
let ident: Ident = meta.value()?.parse()?;
out.level = Some(ident);
return Ok(());
}
if meta.path.is_ident("suggestion") {
let lit: LitBool = meta.value()?.parse()?;
out.suggestion = lit.value;
return Ok(());
}
if meta.path.is_ident("recoverable") {
let lit: LitBool = meta.value()?.parse()?;
out.recoverable = lit.value;
return Ok(());
}
Err(meta.error("unknown #[lumi_error] option"))
};
meta_parser(parser).parse(attr)?;
Ok(out)
}
fn token_class_idents(attrs: &[Attribute]) -> Result<Vec<Ident>, syn::Error> {
let mut out = Vec::new();
for a in attrs {
if !a.path().is_ident("token_class") {
continue;
}
let idents = a.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)?;
out.extend(idents);
}
Ok(out)
}
#[proc_macro_derive(TokenClassify, attributes(token_class))]
pub fn token_classify_derive(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = input.ident;
let Data::Enum(en) = input.data else {
return syn::Error::new_spanned(name, "TokenClassify can only be derived for enums")
.to_compile_error()
.into();
};
let mut keyword_pats = Vec::new();
let mut operator_pats = Vec::new();
let mut delimiter_pats = Vec::new();
let mut literal_pats = Vec::new();
for variant in en.variants {
let v = &variant.ident;
let pat = match &variant.fields {
Fields::Unit => quote!(Self::#v),
Fields::Unnamed(_) => quote!(Self::#v ( .. )),
Fields::Named(_) => quote!(Self::#v { .. }),
};
let classes = match token_class_idents(&variant.attrs) {
Ok(c) => c,
Err(e) => return e.to_compile_error().into(),
};
for class in classes {
if class == "keyword" {
keyword_pats.push(pat.clone());
} else if class == "operator" {
operator_pats.push(pat.clone());
} else if class == "delimiter" {
delimiter_pats.push(pat.clone());
} else if class == "literal" {
literal_pats.push(pat.clone());
} else {
return syn::Error::new_spanned(class, "unknown token class")
.to_compile_error()
.into();
}
}
}
let is_keyword_body = if keyword_pats.is_empty() {
quote!(false)
} else {
quote!(matches!(self, #(#keyword_pats)|*))
};
let is_operator_body = if operator_pats.is_empty() {
quote!(false)
} else {
quote!(matches!(self, #(#operator_pats)|*))
};
let is_delimiter_body = if delimiter_pats.is_empty() {
quote!(false)
} else {
quote!(matches!(self, #(#delimiter_pats)|*))
};
let is_literal_body = if literal_pats.is_empty() {
quote!(false)
} else {
quote!(matches!(self, #(#literal_pats)|*))
};
TokenStream::from(quote! {
impl #name {
#[doc = "Reports whether this token kind is a language keyword."]
pub fn is_keyword(&self) -> bool {
#is_keyword_body
}
#[doc = "Reports whether this token kind is an operator."]
pub fn is_operator(&self) -> bool {
#is_operator_body
}
#[doc = "Reports whether this token kind is a delimiter."]
pub fn is_delimiter(&self) -> bool {
#is_delimiter_body
}
#[doc = "Reports whether this token kind is a literal."]
pub fn is_literal(&self) -> bool {
#is_literal_body
}
}
})
}
fn enum_variant_pat(variant: &Variant) -> TokenStream2 {
let v = &variant.ident;
match &variant.fields {
Fields::Unit => quote!(Self::#v),
Fields::Unnamed(_) => quote!(Self::#v ( .. )),
Fields::Named(_) => quote!(Self::#v { .. }),
}
}
#[derive(Debug, Default)]
struct LumiErrorWrapperAttr {
kind: Option<Ident>,
level: Option<Ident>,
}
fn parse_lumi_error_wrapper_attr(attr: TokenStream) -> Result<LumiErrorWrapperAttr, syn::Error> {
let mut out = LumiErrorWrapperAttr::default();
let parser = |meta: ParseNestedMeta<'_>| {
if meta.path.is_ident("kind") {
let ident: Ident = meta.value()?.parse()?;
out.kind = Some(ident);
return Ok(());
}
if meta.path.is_ident("level") {
let ident: Ident = meta.value()?.parse()?;
out.level = Some(ident);
return Ok(());
}
Err(meta.error("unknown #[lumi_error_wrapper] option"))
};
meta_parser(parser).parse(attr)?;
Ok(out)
}
#[derive(Debug, Default)]
struct WrapAttr {
span: bool,
recoverable: bool,
suggestion: bool,
}
fn parse_wrap_attr(attrs: &[Attribute]) -> Result<WrapAttr, syn::Error> {
let mut out = WrapAttr::default();
for a in attrs {
if !a.path().is_ident("wrap") {
continue;
}
if a.meta.require_path_only().is_ok() {
out.span = true;
continue;
}
let parser = |meta: ParseNestedMeta<'_>| {
if meta.path.is_ident("span") {
out.span = true;
return Ok(());
}
if meta.path.is_ident("recoverable") {
out.recoverable = true;
return Ok(());
}
if meta.path.is_ident("suggestion") {
out.suggestion = true;
return Ok(());
}
Err(meta.error("unknown #[wrap(...)] option"))
};
a.parse_args_with(meta_parser(parser))?;
}
Ok(out)
}
#[proc_macro_attribute]
pub fn lumi_error_wrapper(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = match parse_lumi_error_wrapper_attr(attr) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let input2 = TokenStream2::from(item.clone());
let mut en = match parse2::<ItemEnum>(input2) {
Ok(e) => e,
Err(_) => {
return syn::Error::new(
Span::call_site(),
"#[lumi_error_wrapper] must be applied to an enum",
)
.to_compile_error()
.into();
}
};
let name = &en.ident;
let level_ident: Ident = attr.level.unwrap_or_else(|| parse_quote!(Error));
let kind_expr = if let Some(kind_ident) = attr.kind {
quote!(Some(lumi_core::diagnostics::DiagnosticKind::#kind_ident))
} else {
quote!(None)
};
let mut span_arms = Vec::new();
let mut recoverable_arms = Vec::new();
let mut suggestion_arms = Vec::new();
for variant in &mut en.variants {
let v = &variant.ident;
let wrap = match parse_wrap_attr(&variant.attrs) {
Ok(w) => w,
Err(e) => return e.to_compile_error().into(),
};
variant.attrs.retain(|a| !a.path().is_ident("wrap"));
let pat = match &variant.fields {
Fields::Unit => {
if wrap.span || wrap.recoverable || wrap.suggestion {
return syn::Error::new_spanned(
variant,
"#[wrap(...)] is only supported on tuple variants with a single inner value",
)
.to_compile_error()
.into();
}
quote!(Self::#v)
}
Fields::Unnamed(unnamed) => {
if unnamed.unnamed.len() != 1 {
return syn::Error::new_spanned(
variant,
"#[lumi_error_wrapper] tuple variants must have exactly one field",
)
.to_compile_error()
.into();
}
quote!(Self::#v(inner))
}
Fields::Named(_) => {
return syn::Error::new_spanned(
variant,
"#[lumi_error_wrapper] only supports unit variants or tuple variants with a single field",
)
.to_compile_error()
.into();
}
};
if wrap.span {
span_arms
.push(quote!(#pat => lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(inner),));
} else {
span_arms.push(quote!(#pat => None,));
}
if wrap.recoverable {
recoverable_arms.push(quote!(#pat => inner.is_recoverable(),));
} else {
recoverable_arms.push(quote!(#pat => false,));
}
if wrap.suggestion {
suggestion_arms.push(quote!(#pat => inner.suggestion().map(|s| s.to_string()),));
} else {
suggestion_arms.push(quote!(#pat => None,));
}
}
TokenStream::from(quote! {
#en
impl #name {
#[doc = "Returns the source span covered by this value, if available."]
pub fn span_opt_ref(&self) -> Option<&lumi_core::diagnostics::Span> {
match self {
#(#span_arms)*
}
}
#[doc = "Returns the source span covered by this value."]
pub fn span(&self) -> lumi_core::diagnostics::Span {
self.span_opt_ref().copied().unwrap_or_default()
}
#[doc = "Returns the source span covered by this value by reference."]
pub fn span_ref(&self) -> &lumi_core::diagnostics::Span {
const DEFAULT_SPAN: lumi_core::diagnostics::Span = lumi_core::diagnostics::Span {
start: 0,
end: 0,
line: None,
column: None,
};
self.span_opt_ref().unwrap_or(&DEFAULT_SPAN)
}
#[doc = "Converts this error into a diagnostic entry."]
pub fn to_diagnostic(&self) -> lumi_core::diagnostics::Diagnostic {
lumi_core::diagnostics::Diagnostic {
msg: self.to_string(),
span: self.span_opt_ref().copied(),
level: lumi_core::diagnostics::Level::#level_ident,
file: None,
kind: #kind_expr,
}
}
#[doc = "Emits this error as a diagnostic into the provided diagnostic list."]
pub fn emit_diagnostic(&self, diags: &mut lumi_core::diagnostics::DiagnosticList) {
let msg = self.to_string();
let span = self.span_opt_ref().copied();
diags.push(lumi_core::diagnostics::Level::#level_ident, msg, span);
if let Some(last) = diags.list.last_mut() {
last.kind = #kind_expr;
}
}
#[doc = "Reports whether the error is potentially recoverable."]
pub fn is_recoverable(&self) -> bool {
match self {
#(#recoverable_arms)*
}
}
#[doc = "Returns a human-readable suggestion if available."]
pub fn suggestion(&self) -> Option<String> {
match self {
#(#suggestion_arms)*
}
}
}
impl lumi_core::diagnostics::HasSpanOptRef for #name {
fn span_opt_ref(&self) -> Option<&lumi_core::diagnostics::Span> {
#name::span_opt_ref(self)
}
}
impl lumi_core::diagnostics::HasSpan for #name {
fn span(&self) -> lumi_core::diagnostics::Span {
#name::span(self)
}
}
})
}
#[proc_macro_attribute]
pub fn lumi_error(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = match parse_lumi_error_attr(attr) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let input2 = TokenStream2::from(item.clone());
let mut en = match parse2::<ItemEnum>(input2) {
Ok(e) => e,
Err(_) => {
return syn::Error::new(
Span::call_site(),
"#[lumi_error] must be applied to an enum",
)
.to_compile_error()
.into();
}
};
let name = &en.ident;
let level_ident: Ident = attr.level.unwrap_or_else(|| parse_quote!(Error));
let kind_expr = if let Some(kind_ident) = attr.kind {
quote!(Some(lumi_core::diagnostics::DiagnosticKind::#kind_ident))
} else {
quote!(None)
};
let mut recoverable_pats = Vec::new();
let mut suggestion_arms = Vec::new();
for variant in &mut en.variants {
let pat = enum_variant_pat(variant);
let mut is_recoverable_variant = false;
let mut suggestion_lit: Option<LitStr> = None;
let mut new_attrs = Vec::new();
for a in variant.attrs.iter() {
if a.path().is_ident("recoverable") {
is_recoverable_variant = true;
continue;
}
if a.path().is_ident("suggestion") {
match a.parse_args::<LitStr>() {
Ok(lit) => suggestion_lit = Some(lit),
Err(e) => return e.to_compile_error().into(),
}
continue;
}
new_attrs.push(a.clone());
}
variant.attrs = new_attrs;
if is_recoverable_variant {
recoverable_pats.push(pat.clone());
}
if let Some(lit) = suggestion_lit {
suggestion_arms.push(quote!(#pat => Some(#lit),));
}
}
let recoverable_method = if attr.recoverable {
if recoverable_pats.is_empty() {
quote!(
#[doc = "Reports whether this error is potentially recoverable."]
pub fn is_recoverable(&self) -> bool {
false
}
)
} else {
quote!(
#[doc = "Reports whether this error is potentially recoverable."]
pub fn is_recoverable(&self) -> bool {
matches!(self, #(#recoverable_pats)|*)
}
)
}
} else {
quote!()
};
let suggestion_method = if attr.suggestion {
quote!(
#[doc = "Returns a human-readable suggestion if available."]
pub fn suggestion(&self) -> Option<&'static str> {
match self {
#(#suggestion_arms)*
_ => None,
}
}
)
} else {
quote!()
};
TokenStream::from(quote! {
#en
impl #name {
#[doc = "Converts this error into a diagnostic entry."]
pub fn to_diagnostic(&self) -> lumi_core::diagnostics::Diagnostic {
lumi_core::diagnostics::Diagnostic {
msg: self.to_string(),
span: lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(self).copied(),
level: lumi_core::diagnostics::Level::#level_ident,
file: None,
kind: #kind_expr,
}
}
#[doc = "Emits this error as a diagnostic into the provided diagnostic list."]
pub fn emit_diagnostic(&self, diags: &mut lumi_core::diagnostics::DiagnosticList) {
let msg = self.to_string();
let span = lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(self).copied();
diags.push(lumi_core::diagnostics::Level::#level_ident, msg, span);
if let Some(last) = diags.list.last_mut() {
last.kind = #kind_expr;
}
}
#recoverable_method
#suggestion_method
}
})
}
fn is_option_span_ty(ty: &Type) -> bool {
let Type::Path(p) = ty else {
return false;
};
let Some(last) = p.path.segments.last() else {
return false;
};
if last.ident != "Option" {
return false;
}
let PathArguments::AngleBracketed(args) = &last.arguments else {
return false;
};
args.args.iter().any(|arg| match arg {
GenericArgument::Type(t) => is_span_ty(t),
_ => false,
})
}
fn enum_span_match_arms(en: &ItemEnum, mode: SpanMode) -> Result<Vec<TokenStream2>, syn::Error> {
let mut arms = Vec::new();
for variant in &en.variants {
let v_ident = &variant.ident;
match &variant.fields {
Fields::Named(named) => {
let span_field = named
.named
.iter()
.find(|f| f.ident.as_ref().is_some_and(|id| id == "span"));
let Some(span_field) = span_field else {
return Err(syn::Error::new(
variant.span(),
"#[spanned*] requires a `span` field",
));
};
let ty = &span_field.ty;
let has_span = is_span_ty(ty);
let has_opt_span = is_option_span_ty(ty);
let pat = quote!(Self::#v_ident { span, .. });
let arm = match mode {
SpanMode::ByValue => {
if !has_span {
return Err(syn::Error::new(
ty.span(),
"#[spanned] requires `span: Span`",
));
}
quote!(#pat => *span)
}
SpanMode::ByRef => {
if !has_span {
return Err(syn::Error::new(
ty.span(),
"#[spanned_ref] requires `span: Span`",
));
}
quote!(#pat => span)
}
SpanMode::OptRef => {
if has_span {
quote!(#pat => Some(span))
} else if has_opt_span {
quote!(#pat => span.as_ref())
} else {
return Err(syn::Error::new(
ty.span(),
"#[spanned_opt_ref] requires `span: Span` or `span: Option<Span>`",
));
}
}
};
arms.push(quote!(#arm,));
}
Fields::Unnamed(unnamed) => {
let mut span_idx: Option<usize> = None;
let mut span_is_option = false;
for (i, f) in unnamed.unnamed.iter().enumerate() {
if is_span_ty(&f.ty) {
span_idx = Some(i);
span_is_option = false;
} else if is_option_span_ty(&f.ty) {
span_idx = Some(i);
span_is_option = true;
}
}
let Some(span_idx) = span_idx else {
if matches!(mode, SpanMode::OptRef) && unnamed.unnamed.len() == 1 {
arms.push(
quote!(Self::#v_ident(inner) => lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(inner),),
);
continue;
}
return Err(syn::Error::new(
variant.span(),
"#[span*] requires a `Span` (or `Option<Span>` for span_opt_ref) field, or a single wrapped value that implements HasSpanOptRef",
));
};
let pat_fields = (0..unnamed.unnamed.len()).map(|i| {
if i == span_idx {
quote!(span)
} else {
quote!(_)
}
});
let pat = quote!(Self::#v_ident( #(#pat_fields),* ));
let arm = match mode {
SpanMode::ByValue => {
if span_is_option {
return Err(syn::Error::new(
variant.span(),
"#[spanned] does not support `Option<Span>` fields",
));
}
quote!(#pat => *span)
}
SpanMode::ByRef => {
if span_is_option {
return Err(syn::Error::new(
variant.span(),
"#[spanned_ref] does not support `Option<Span>` fields",
));
}
quote!(#pat => span)
}
SpanMode::OptRef => {
if span_is_option {
quote!(#pat => span.as_ref())
} else {
quote!(#pat => Some(span))
}
}
};
arms.push(quote!(#arm,));
}
Fields::Unit => {
if matches!(mode, SpanMode::OptRef) {
arms.push(quote!(Self::#v_ident => None,));
} else {
return Err(syn::Error::new(
variant.span(),
"#[spanned]/#[spanned_ref] does not support unit variants",
));
}
}
}
}
Ok(arms)
}
fn struct_span_expr(st: &ItemStruct, mode: SpanMode) -> Result<TokenStream2, syn::Error> {
let fields = match &st.fields {
Fields::Named(named) => &named.named,
_ => {
return Err(syn::Error::new(
st.span(),
"#[spanned*] requires a struct with named fields",
));
}
};
let span_field = fields
.iter()
.find(|f| f.ident.as_ref().is_some_and(|id| id == "span"));
let Some(span_field) = span_field else {
return Err(syn::Error::new(
st.span(),
"#[spanned*] requires a `span` field",
));
};
let ty = &span_field.ty;
let has_span = is_span_ty(ty);
let has_opt_span = is_option_span_ty(ty);
match mode {
SpanMode::ByValue => {
if !has_span {
return Err(syn::Error::new(
ty.span(),
"#[spanned] requires `span: Span`",
));
}
Ok(quote!(self.span))
}
SpanMode::ByRef => {
if !has_span {
return Err(syn::Error::new(
ty.span(),
"#[spanned_ref] requires `span: Span`",
));
}
Ok(quote!(&self.span))
}
SpanMode::OptRef => {
if has_span {
Ok(quote!(Some(&self.span)))
} else if has_opt_span {
Ok(quote!(self.span.as_ref()))
} else {
Err(syn::Error::new(
ty.span(),
"#[spanned_opt_ref] requires `span: Span` or `span: Option<Span>`",
))
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SpanMode {
ByValue,
ByRef,
OptRef,
}
#[derive(Debug, Default)]
struct DiagnosticAttr {
kind: Option<Ident>,
level: Option<Ident>,
}
fn parse_diagnostic_attr(attr: TokenStream) -> Result<DiagnosticAttr, syn::Error> {
let mut out = DiagnosticAttr::default();
let parser = |meta: ParseNestedMeta<'_>| {
if meta.path.is_ident("kind") {
let ident: Ident = meta.value()?.parse()?;
out.kind = Some(ident);
return Ok(());
}
if meta.path.is_ident("level") {
let ident: Ident = meta.value()?.parse()?;
out.level = Some(ident);
return Ok(());
}
Err(meta.error("unknown #[lumi_diagnostic] option"))
};
meta_parser(parser).parse(attr)?;
Ok(out)
}
#[derive(Debug, Default)]
struct SpannedAttr {
method: Option<Ident>,
vis: Option<Visibility>,
}
fn parse_spanned_attr(attr: TokenStream) -> Result<SpannedAttr, syn::Error> {
let mut out = SpannedAttr::default();
let parser = |meta: ParseNestedMeta<'_>| {
if meta.path.is_ident("method") {
let lit: LitStr = meta.value()?.parse()?;
out.method = Some(Ident::new(&lit.value(), lit.span()));
return Ok(());
}
if meta.path.is_ident("vis") {
out.vis = Some(meta.value()?.parse()?);
return Ok(());
}
Err(meta.error("unknown #[span*]/#[spanned*] option"))
};
meta_parser(parser).parse(attr)?;
Ok(out)
}
fn spanned_impl(
attr: TokenStream,
item: TokenStream,
mode: SpanMode,
) -> Result<TokenStream, syn::Error> {
let attr = parse_spanned_attr(attr)?;
let input2 = TokenStream2::from(item.clone());
let vis: Visibility = attr.vis.clone().unwrap_or_else(|| parse_quote!(pub));
if let Ok(en) = parse2::<ItemEnum>(input2.clone()) {
let name = &en.ident;
let opt_arms = enum_span_match_arms(&en, SpanMode::OptRef)?;
let opt_body = quote!(match self { #(#opt_arms)* });
let ref_impl = if matches!(mode, SpanMode::ByValue | SpanMode::ByRef) {
let ref_arms = enum_span_match_arms(&en, SpanMode::ByRef)?;
let ref_body = quote!(match self { #(#ref_arms)* });
quote!(
impl lumi_core::diagnostics::HasSpanRef for #name {
fn span_ref(&self) -> &lumi_core::diagnostics::Span {
#ref_body
}
}
)
} else {
quote!()
};
Ok(TokenStream::from(quote! {
#en
impl #name {
#[doc = "Returns the source span covered by this value, if available."]
#vis fn span_opt_ref(&self) -> Option<&lumi_core::diagnostics::Span> {
#opt_body
}
#[doc = "Returns the source span covered by this value."]
#vis fn span(&self) -> lumi_core::diagnostics::Span {
self.span_opt_ref().copied().unwrap_or_default()
}
#[doc = "Returns the source span covered by this value by reference."]
#vis fn span_ref(&self) -> &lumi_core::diagnostics::Span {
const DEFAULT_SPAN: lumi_core::diagnostics::Span = lumi_core::diagnostics::Span {
start: 0,
end: 0,
line: None,
column: None,
};
self.span_opt_ref().unwrap_or(&DEFAULT_SPAN)
}
}
impl lumi_core::diagnostics::HasSpanOptRef for #name {
fn span_opt_ref(&self) -> Option<&lumi_core::diagnostics::Span> {
#opt_body
}
}
impl lumi_core::diagnostics::HasSpan for #name {
fn span(&self) -> lumi_core::diagnostics::Span {
lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(self)
.copied()
.unwrap_or_default()
}
}
#ref_impl
}))
} else if let Ok(st) = parse2::<ItemStruct>(input2) {
let name = &st.ident;
let opt_expr = struct_span_expr(&st, SpanMode::OptRef)?;
let ref_impl = if matches!(mode, SpanMode::ByValue | SpanMode::ByRef) {
let ref_expr = struct_span_expr(&st, SpanMode::ByRef)?;
quote!(
impl lumi_core::diagnostics::HasSpanRef for #name {
fn span_ref(&self) -> &lumi_core::diagnostics::Span {
#ref_expr
}
}
)
} else {
quote!()
};
Ok(TokenStream::from(quote! {
#st
impl #name {
#[doc = "Returns the source span covered by this value, if available."]
#vis fn span_opt_ref(&self) -> Option<&lumi_core::diagnostics::Span> {
#opt_expr
}
#[doc = "Returns the source span covered by this value."]
#vis fn span(&self) -> lumi_core::diagnostics::Span {
self.span_opt_ref().copied().unwrap_or_default()
}
#[doc = "Returns the source span covered by this value by reference."]
#vis fn span_ref(&self) -> &lumi_core::diagnostics::Span {
const DEFAULT_SPAN: lumi_core::diagnostics::Span = lumi_core::diagnostics::Span {
start: 0,
end: 0,
line: None,
column: None,
};
self.span_opt_ref().unwrap_or(&DEFAULT_SPAN)
}
}
impl lumi_core::diagnostics::HasSpanOptRef for #name {
fn span_opt_ref(&self) -> Option<&lumi_core::diagnostics::Span> {
#opt_expr
}
}
impl lumi_core::diagnostics::HasSpan for #name {
fn span(&self) -> lumi_core::diagnostics::Span {
lumi_core::diagnostics::HasSpanOptRef::span_opt_ref(self)
.copied()
.unwrap_or_default()
}
}
#ref_impl
}))
} else {
Err(syn::Error::new(
Span::call_site(),
"#[span*]/#[spanned*] must be applied to a struct or enum",
))
}
}
#[proc_macro_attribute]
pub fn spanned(attr: TokenStream, item: TokenStream) -> TokenStream {
match spanned_impl(attr, item, SpanMode::ByValue) {
Ok(ts) => ts,
Err(e) => e.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn span(attr: TokenStream, item: TokenStream) -> TokenStream {
spanned(attr, item)
}
#[proc_macro_attribute]
pub fn spanned_ref(attr: TokenStream, item: TokenStream) -> TokenStream {
match spanned_impl(attr, item, SpanMode::ByRef) {
Ok(ts) => ts,
Err(e) => e.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn span_ref(attr: TokenStream, item: TokenStream) -> TokenStream {
spanned_ref(attr, item)
}
#[proc_macro_attribute]
pub fn spanned_opt_ref(attr: TokenStream, item: TokenStream) -> TokenStream {
match spanned_impl(attr, item, SpanMode::OptRef) {
Ok(ts) => ts,
Err(e) => e.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn span_opt_ref(attr: TokenStream, item: TokenStream) -> TokenStream {
spanned_opt_ref(attr, item)
}