use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{Attribute, Error, ImplItem, ItemImpl, ItemTrait, LitStr, Token, TraitItem, Visibility};
mod kw {
syn::custom_keyword!(tag);
syn::custom_keyword!(content);
syn::custom_keyword!(name);
}
pub enum TraitArgs {
External,
Internal { tag: LitStr },
Adjacent { tag: LitStr, content: LitStr },
}
pub struct ImplArgs {
pub name: Option<LitStr>,
}
pub enum Input {
Trait(ItemTrait),
Impl(ItemImpl),
}
impl Parse for Input {
fn parse(input: ParseStream) -> Result<Self> {
let mut attrs = Attribute::parse_outer(input)?;
let ahead = input.fork();
ahead.parse::<Visibility>()?;
ahead.parse::<Option<Token![unsafe]>>()?;
if ahead.peek(Token![trait]) {
let mut item: ItemTrait = input.parse()?;
for assoc in &item.items {
if let TraitItem::Const(assoc) = assoc {
let const_token = assoc.const_token;
let semi_token = assoc.semi_token;
let span = quote!(#const_token #semi_token);
let msg = "typetag trait with associated const is not supported yet";
return Err(Error::new_spanned(span, msg));
} else if let TraitItem::Type(assoc) = assoc {
let type_token = assoc.type_token;
let semi_token = assoc.semi_token;
let span = quote!(#type_token #semi_token);
let msg = "typetag trait with associated type is not supported yet";
return Err(Error::new_spanned(span, msg));
}
}
attrs.extend(item.attrs);
item.attrs = attrs;
Ok(Input::Trait(item))
} else if ahead.peek(Token![impl]) {
let mut item: ItemImpl = input.parse()?;
if item.trait_.is_none() {
let impl_token = item.impl_token;
let ty = item.self_ty;
let span = quote!(#impl_token #ty);
let msg = "expected impl Trait for Type";
return Err(Error::new_spanned(span, msg));
}
for assoc in &item.items {
if let ImplItem::Const(assoc) = assoc {
let const_token = assoc.const_token;
let semi_token = assoc.semi_token;
let span = quote!(#const_token #semi_token);
let msg = "typetag trait with associated const is not supported yet";
return Err(Error::new_spanned(span, msg));
} else if let ImplItem::Type(assoc) = assoc {
let type_token = assoc.type_token;
let semi_token = assoc.semi_token;
let span = quote!(#type_token #semi_token);
let msg = "typetag trait with associated type is not supported yet";
return Err(Error::new_spanned(span, msg));
}
}
attrs.extend(item.attrs);
item.attrs = attrs;
Ok(Input::Impl(item))
} else {
Err(input.error("expected trait or impl block"))
}
}
}
impl Parse for TraitArgs {
fn parse(input: ParseStream) -> Result<Self> {
if input.is_empty() {
return Ok(TraitArgs::External);
}
input.parse::<kw::tag>()?;
input.parse::<Token![=]>()?;
let tag: LitStr = input.parse()?;
if input.is_empty() {
return Ok(TraitArgs::Internal { tag });
}
input.parse::<Token![,]>()?;
if input.is_empty() {
return Ok(TraitArgs::Internal { tag });
}
input.parse::<kw::content>()?;
input.parse::<Token![=]>()?;
let content: LitStr = input.parse()?;
input.parse::<Option<Token![,]>>()?;
Ok(TraitArgs::Adjacent { tag, content })
}
}
impl Parse for ImplArgs {
fn parse(input: ParseStream) -> Result<Self> {
let name = if input.is_empty() {
None
} else {
input.parse::<kw::name>()?;
input.parse::<Token![=]>()?;
let name: LitStr = input.parse()?;
input.parse::<Option<Token![,]>>()?;
Some(name)
};
Ok(ImplArgs { name })
}
}