use crate::replicate::respan::respan;
use crate::replicate::symbol::*;
use crate::replicate::{ungroup, Ctxt};
use proc_macro2::{Spacing, TokenStream, TokenTree};
use quote::ToTokens;
use std::collections::BTreeSet;
use syn;
use syn::parse::{self, Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::Meta::{List, NameValue};
use syn::NestedMeta::Meta;
use syn::Token;
struct Attr<'c, T> {
cx: &'c Ctxt,
name: Symbol,
tokens: TokenStream,
value: Option<T>,
}
impl<'c, T> Attr<'c, T> {
fn none(cx: &'c Ctxt, name: Symbol) -> Self {
Attr {
cx,
name,
tokens: TokenStream::new(),
value: None,
}
}
fn set<A: ToTokens>(&mut self, obj: A, value: T) {
let tokens = obj.into_token_stream();
if self.value.is_some() {
self.cx.error_spanned_by(
tokens,
format!("duplicate replicate attribute `{}`", self.name),
);
} else {
self.tokens = tokens;
self.value = Some(value);
}
}
fn set_opt<A: ToTokens>(&mut self, obj: A, value: Option<T>) {
if let Some(value) = value {
self.set(obj, value);
}
}
fn set_if_none(&mut self, value: T) {
if self.value.is_none() {
self.value = Some(value);
}
}
fn get(self) -> Option<T> {
self.value
}
fn get_with_tokens(self) -> Option<(TokenStream, T)> {
match self.value {
Some(v) => Some((self.tokens, v)),
None => None,
}
}
}
struct BoolAttr<'c>(Attr<'c, ()>);
impl<'c> BoolAttr<'c> {
fn none(cx: &'c Ctxt, name: Symbol) -> Self {
BoolAttr(Attr::none(cx, name))
}
fn set_true<A: ToTokens>(&mut self, obj: A) {
self.0.set(obj, ());
}
fn get(&self) -> bool {
self.0.value.is_some()
}
}
struct VecAttr<'c, T> {
cx: &'c Ctxt,
name: Symbol,
first_dup_tokens: TokenStream,
values: Vec<T>,
}
impl<'c, T> VecAttr<'c, T> {
fn none(cx: &'c Ctxt, name: Symbol) -> Self {
VecAttr {
cx,
name,
first_dup_tokens: TokenStream::new(),
values: Vec::new(),
}
}
fn insert<A: ToTokens>(&mut self, obj: A, value: T) {
if self.values.len() == 1 {
self.first_dup_tokens = obj.into_token_stream();
}
self.values.push(value);
}
fn at_most_one(mut self) -> Result<Option<T>, ()> {
if self.values.len() > 1 {
let dup_token = self.first_dup_tokens;
self.cx.error_spanned_by(
dup_token,
format!("duplicate replicate attribute `{}`", self.name),
);
Err(())
} else {
Ok(self.values.pop())
}
}
fn get(self) -> Vec<T> {
self.values
}
}
pub struct Container {
pub remote: Option<syn::Path>,
pub sabi_path: Option<syn::Path>,
}
impl Container {
pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self {
let mut remote = Attr::none(cx, REMOTE);
let mut sabi_path = Attr::none(cx, CRATE);
for meta_item in item
.attrs
.iter()
.flat_map(|attr| get_replicate_meta_items(cx, attr))
.flatten()
{
match meta_item {
Meta(NameValue(m)) if m.path == REMOTE => {
if let Ok(path) = parse_lit_into_path(cx, REMOTE, &m.lit) {
if is_primitive_path(&path, "Self") {
remote.set(&m.path, item.ident.clone().into());
} else {
remote.set(&m.path, path);
}
}
}
Meta(NameValue(m)) if m.path == CRATE => {
if let Ok(path) = parse_lit_into_path(cx, CRATE, &m.lit) {
sabi_path.set(&m.path, path);
}
}
meta => {
cx.error_spanned_by(meta, "unexpected attribute in replicate attribute");
}
}
}
Container {
remote: remote.get(),
sabi_path: sabi_path.get(),
}
}
}
pub fn get_replicate_meta_items(
cx: &Ctxt,
attr: &syn::Attribute,
) -> Result<Vec<syn::NestedMeta>, ()> {
if attr.path != REPLICATE {
return Ok(Vec::new());
}
match attr.parse_meta() {
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
Ok(other) => {
cx.error_spanned_by(other, "expected #[replicate(...)]");
Err(())
}
Err(err) => {
cx.syn_error(err);
Err(())
}
}
}
fn get_lit_str<'a>(cx: &Ctxt, attr_name: Symbol, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> {
get_lit_str2(cx, attr_name, attr_name, lit)
}
fn get_lit_str2<'a>(
cx: &Ctxt,
attr_name: Symbol,
meta_item_name: Symbol,
lit: &'a syn::Lit,
) -> Result<&'a syn::LitStr, ()> {
if let syn::Lit::Str(lit) = lit {
Ok(lit)
} else {
cx.error_spanned_by(
lit,
format!(
"expected replicate {} attribute to be a string: `{} = \"...\"`",
attr_name, meta_item_name
),
);
Err(())
}
}
fn parse_lit_into_path(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result<syn::Path, ()> {
let string = get_lit_str(cx, attr_name, lit)?;
parse_lit_str(string).map_err(|_| {
cx.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value()));
})
}
fn parse_lit_into_expr_path(
cx: &Ctxt,
attr_name: Symbol,
lit: &syn::Lit,
) -> Result<syn::ExprPath, ()> {
let string = get_lit_str(cx, attr_name, lit)?;
parse_lit_str(string).map_err(|_| {
cx.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value()));
})
}
fn parse_lit_into_where(
cx: &Ctxt,
attr_name: Symbol,
meta_item_name: Symbol,
lit: &syn::Lit,
) -> Result<Vec<syn::WherePredicate>, ()> {
let string = get_lit_str2(cx, attr_name, meta_item_name, lit)?;
if string.value().is_empty() {
return Ok(Vec::new());
}
let where_string = syn::LitStr::new(&format!("where {}", string.value()), string.span());
parse_lit_str::<syn::WhereClause>(&where_string)
.map(|wh| wh.predicates.into_iter().collect())
.map_err(|err| cx.error_spanned_by(lit, err))
}
fn parse_lit_into_ty(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result<syn::Type, ()> {
let string = get_lit_str(cx, attr_name, lit)?;
parse_lit_str(string).map_err(|_| {
cx.error_spanned_by(
lit,
format!("failed to parse type: {} = {:?}", attr_name, string.value()),
);
})
}
fn parse_lit_into_lifetimes(
cx: &Ctxt,
attr_name: Symbol,
lit: &syn::Lit,
) -> Result<BTreeSet<syn::Lifetime>, ()> {
let string = get_lit_str(cx, attr_name, lit)?;
if string.value().is_empty() {
cx.error_spanned_by(lit, "at least one lifetime must be borrowed");
return Err(());
}
struct BorrowedLifetimes(Punctuated<syn::Lifetime, Token![+]>);
impl Parse for BorrowedLifetimes {
fn parse(input: ParseStream) -> parse::Result<Self> {
Punctuated::parse_separated_nonempty(input).map(BorrowedLifetimes)
}
}
if let Ok(BorrowedLifetimes(lifetimes)) = parse_lit_str(string) {
let mut set = BTreeSet::new();
for lifetime in lifetimes {
if !set.insert(lifetime.clone()) {
cx.error_spanned_by(lit, format!("duplicate borrowed lifetime `{}`", lifetime));
}
}
return Ok(set);
}
cx.error_spanned_by(
lit,
format!("failed to parse borrowed lifetimes: {:?}", string.value()),
);
Err(())
}
fn is_implicitly_borrowed(ty: &syn::Type) -> bool {
is_implicitly_borrowed_reference(ty) || is_option(ty, is_implicitly_borrowed_reference)
}
fn is_implicitly_borrowed_reference(ty: &syn::Type) -> bool {
is_reference(ty, is_str) || is_reference(ty, is_slice_u8)
}
fn is_cow(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
let path = match ungroup(ty) {
syn::Type::Path(ty) => &ty.path,
_ => {
return false;
}
};
let seg = match path.segments.last() {
Some(seg) => seg,
None => {
return false;
}
};
let args = match &seg.arguments {
syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args,
_ => {
return false;
}
};
seg.ident == "Cow"
&& args.len() == 2
&& match (&args[0], &args[1]) {
(syn::GenericArgument::Lifetime(_), syn::GenericArgument::Type(arg)) => elem(arg),
_ => false,
}
}
fn is_option(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
let path = match ungroup(ty) {
syn::Type::Path(ty) => &ty.path,
_ => {
return false;
}
};
let seg = match path.segments.last() {
Some(seg) => seg,
None => {
return false;
}
};
let args = match &seg.arguments {
syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args,
_ => {
return false;
}
};
seg.ident == "Option"
&& args.len() == 1
&& match &args[0] {
syn::GenericArgument::Type(arg) => elem(arg),
_ => false,
}
}
fn is_reference(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
match ungroup(ty) {
syn::Type::Reference(ty) => ty.mutability.is_none() && elem(&ty.elem),
_ => false,
}
}
fn is_str(ty: &syn::Type) -> bool {
is_primitive_type(ty, "str")
}
fn is_slice_u8(ty: &syn::Type) -> bool {
match ungroup(ty) {
syn::Type::Slice(ty) => is_primitive_type(&ty.elem, "u8"),
_ => false,
}
}
fn is_primitive_type(ty: &syn::Type, primitive: &str) -> bool {
match ungroup(ty) {
syn::Type::Path(ty) => ty.qself.is_none() && is_primitive_path(&ty.path, primitive),
_ => false,
}
}
fn is_primitive_path(path: &syn::Path, primitive: &str) -> bool {
path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments[0].ident == primitive
&& path.segments[0].arguments.is_empty()
}
fn borrowable_lifetimes(
cx: &Ctxt,
name: &str,
field: &syn::Field,
) -> Result<BTreeSet<syn::Lifetime>, ()> {
let mut lifetimes = BTreeSet::new();
collect_lifetimes(&field.ty, &mut lifetimes);
if lifetimes.is_empty() {
cx.error_spanned_by(
field,
format!("field `{}` has no lifetimes to borrow", name),
);
Err(())
} else {
Ok(lifetimes)
}
}
fn collect_lifetimes(ty: &syn::Type, out: &mut BTreeSet<syn::Lifetime>) {
match ty {
syn::Type::Slice(ty) => {
collect_lifetimes(&ty.elem, out);
}
syn::Type::Array(ty) => {
collect_lifetimes(&ty.elem, out);
}
syn::Type::Ptr(ty) => {
collect_lifetimes(&ty.elem, out);
}
syn::Type::Reference(ty) => {
out.extend(ty.lifetime.iter().cloned());
collect_lifetimes(&ty.elem, out);
}
syn::Type::Tuple(ty) => {
for elem in &ty.elems {
collect_lifetimes(elem, out);
}
}
syn::Type::Path(ty) => {
if let Some(qself) = &ty.qself {
collect_lifetimes(&qself.ty, out);
}
for seg in &ty.path.segments {
if let syn::PathArguments::AngleBracketed(bracketed) = &seg.arguments {
for arg in &bracketed.args {
match arg {
syn::GenericArgument::Lifetime(lifetime) => {
out.insert(lifetime.clone());
}
syn::GenericArgument::Type(ty) => {
collect_lifetimes(ty, out);
}
syn::GenericArgument::Binding(binding) => {
collect_lifetimes(&binding.ty, out);
}
syn::GenericArgument::Constraint(_)
| syn::GenericArgument::Const(_) => {}
}
}
}
}
}
syn::Type::Paren(ty) => {
collect_lifetimes(&ty.elem, out);
}
syn::Type::Group(ty) => {
collect_lifetimes(&ty.elem, out);
}
syn::Type::Macro(ty) => {
collect_lifetimes_from_tokens(ty.mac.tokens.clone(), out);
}
syn::Type::BareFn(_)
| syn::Type::Never(_)
| syn::Type::TraitObject(_)
| syn::Type::ImplTrait(_)
| syn::Type::Infer(_)
| syn::Type::Verbatim(_) => {}
#[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))]
_ => {}
}
}
fn collect_lifetimes_from_tokens(tokens: TokenStream, out: &mut BTreeSet<syn::Lifetime>) {
let mut iter = tokens.into_iter();
while let Some(tt) = iter.next() {
match &tt {
TokenTree::Punct(op) if op.as_char() == '\'' && op.spacing() == Spacing::Joint => {
if let Some(TokenTree::Ident(ident)) = iter.next() {
out.insert(syn::Lifetime {
apostrophe: op.span(),
ident,
});
}
}
TokenTree::Group(group) => {
let tokens = group.stream();
collect_lifetimes_from_tokens(tokens, out);
}
_ => {}
}
}
}
fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>
where
T: Parse,
{
let tokens = spanned_tokens(s)?;
syn::parse2(tokens)
}
fn spanned_tokens(s: &syn::LitStr) -> parse::Result<TokenStream> {
let stream = syn::parse_str(&s.value())?;
Ok(respan(stream, s.span()))
}