#![warn(rust_2018_idioms, unused_must_use)]
#![cfg_attr(feature = "span_errors", feature(proc_macro_diagnostic))]
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{parse_macro_input, Fields, Ident, ItemEnum};
fn identifier(identifier: &str) -> Ident {
Ident::new(identifier, Span::mixed_site())
}
fn module(vis: &syn::Visibility, name: Ident, content: TokenStream2) -> TokenStream2 {
quote! { #vis mod #name { #content } }
}
macro_rules! report {
($location:expr, $message:literal $(, $continuation:tt )?) => {
#[cfg(feature = "span_errors")]
{
$location
.span()
.unwrap()
.error($message)
.emit();
$( $continuation )?
}
#[cfg(not(feature = "span_errors"))]
return syn::parse::Error::new_spanned(&$location, $message)
.into_compile_error()
.into();
}
}
#[proc_macro_attribute]
pub fn tylift(attr: TokenStream, item: TokenStream) -> TokenStream {
let arguments = match Arguments::parse(attr) {
Ok(arguments) => arguments,
Err(_span) => panic!("invalid arguments"),
};
let scoped = arguments.scope.is_some();
let item = parse_macro_input!(item as ItemEnum);
if !item.generics.params.is_empty() {
#[allow(unused_imports)]
use syn::spanned::Spanned;
report!(
item.generics.params,
"type parameters cannot be lifted to the kind-level"
);
}
let mut variants = Vec::new();
for variant in &item.variants {
if variant.ident == item.ident {
report!(
variant.ident,
"name of variant matches name of enum",
continue
);
}
let mut field_names = Vec::new();
let mut field_types = Vec::new();
match &variant.fields {
Fields::Named(_) => {
report!(
variant.ident,
"variant must not have named fields",
continue
);
}
Fields::Unnamed(unnamed) => {
for (index, field) in unnamed.unnamed.iter().enumerate() {
field_names.push(identifier(&format!("T{}", index)));
field_types.push(&field.ty);
}
}
_ => {}
}
variants.push((&variant.attrs, &variant.ident, field_names, field_types));
}
let attributes = &item.attrs;
let visibility = &item.vis;
let kind = &item.ident;
let clause = item.generics.where_clause;
let kind_module = match arguments.scope {
None => identifier(&format!("__kind_{}", kind)),
Some(None) => kind.clone(),
Some(Some(ident)) => identifier(&ident.to_string()),
};
let sealed_module = identifier("sealed");
let sealed_trait = identifier("Sealed");
let mut output_stream = quote! {};
if !scoped {
output_stream.extend(quote! { #visibility use #kind_module::*; });
};
let mut kind_module_stream = quote! {
use super::*;
#(#attributes)*
pub trait #kind: #sealed_module::#sealed_trait #clause {}
};
let mut sealed_module_stream = quote! {
use super::*;
pub trait #sealed_trait {}
};
for (attributes, name, field_names, field_types) in &variants {
let &attributes = attributes;
let parameters = quote! { <#(#field_names: #field_types),*> };
let arguments = quote! { <#(#field_names),*> };
kind_module_stream.extend(quote! {
#(#attributes)*
pub struct #name #parameters (::core::marker::PhantomData <(#(#field_names),*)>);
impl #parameters #kind for #name #arguments {}
});
sealed_module_stream.extend(quote! {
impl #parameters #sealed_trait for #name #arguments {}
});
}
kind_module_stream.extend(module(
&syn::Visibility::Inherited,
sealed_module,
sealed_module_stream,
));
output_stream.extend(module(visibility, kind_module, kind_module_stream));
output_stream.into()
}
#[derive(Default)]
struct Arguments {
scope: Option<Option<proc_macro::Ident>>,
}
impl Arguments {
fn parse(tokens: TokenStream) -> Result<Self, proc_macro::Span> {
let mut tokens = tokens.into_iter();
let mut arguments = Self::default();
if let Some(token) = tokens.next() {
use proc_macro::TokenTree;
if let TokenTree::Ident(identifier) = token {
if identifier.to_string() != "mod" {
return Err(identifier.span());
}
arguments.scope = Some(None);
} else {
return Err(token.span());
}
if let Some(token) = tokens.next() {
if let TokenTree::Ident(identifier) = token {
arguments.scope = Some(Some(identifier));
} else {
return Err(token.span());
}
}
if let Some(token) = tokens.next() {
return Err(token.span());
}
}
Ok(arguments)
}
}