pub use crate::small_string::SmallString;
use crate::{Error, Errors, ParseMetaItem, ParseMode, Result, ToKeyString};
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::ToTokens;
use std::{
borrow::{Borrow, Cow},
collections::HashMap,
};
pub use syn::{
parse::Nothing,
token::{Brace, Bracket, Paren},
};
use syn::{
parse::{ParseBuffer, ParseStream},
spanned::Spanned,
Token,
};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum FieldStatus<T> {
None,
ParseError,
Some(T),
}
impl<T> FieldStatus<T> {
#[inline]
pub const fn as_ref(&self) -> FieldStatus<&T> {
match *self {
Self::Some(ref x) => FieldStatus::Some(x),
Self::ParseError => FieldStatus::ParseError,
Self::None => FieldStatus::None,
}
}
#[inline]
pub fn and<U>(self, b: FieldStatus<U>) -> FieldStatus<U> {
match self {
Self::Some(_) => b,
Self::ParseError => FieldStatus::ParseError,
Self::None => FieldStatus::None,
}
}
#[inline]
pub fn and_then<U, F: FnOnce(T) -> FieldStatus<U>>(self, f: F) -> FieldStatus<U> {
match self {
Self::Some(x) => f(x),
Self::ParseError => FieldStatus::ParseError,
Self::None => FieldStatus::None,
}
}
#[inline]
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> FieldStatus<U> {
match self {
Self::Some(x) => FieldStatus::Some(f(x)),
Self::ParseError => FieldStatus::ParseError,
Self::None => FieldStatus::None,
}
}
#[inline]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
#[inline]
pub const fn is_parse_error(&self) -> bool {
matches!(self, Self::ParseError)
}
#[inline]
pub const fn is_some(&self) -> bool {
matches!(self, Self::Some(_))
}
#[inline]
pub fn or(self, b: FieldStatus<T>) -> FieldStatus<T> {
match self {
Self::Some(x) => FieldStatus::Some(x),
Self::ParseError => FieldStatus::ParseError,
Self::None => b,
}
}
#[inline]
pub fn or_else<F: FnOnce() -> FieldStatus<T>>(self, f: F) -> FieldStatus<T> {
match self {
Self::Some(x) => FieldStatus::Some(x),
Self::ParseError => FieldStatus::ParseError,
Self::None => f(),
}
}
#[inline]
#[track_caller]
pub fn unwrap(self) -> T {
match self {
Self::Some(val) => val,
_ => panic!("called `FieldStatus::unwrap()` on a `None` value"),
}
}
#[inline]
pub fn unwrap_or(self, default: T) -> T {
match self {
Self::Some(x) => x,
_ => default,
}
}
#[inline]
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
match self {
Self::Some(x) => x,
_ => f(),
}
}
#[inline]
pub fn flatten<U>(self) -> FieldStatus<U>
where
T: Into<FieldStatus<U>>,
{
match self {
Self::Some(inner) => inner.into(),
Self::ParseError => FieldStatus::ParseError,
Self::None => FieldStatus::None,
}
}
#[inline]
pub fn parse_named_item(&mut self, name: &str, input: ParseStream, span: Span, errors: &Errors)
where
T: ParseMetaItem,
{
self.parse_named_item_with(name, input, span, errors, T::parse_meta_item_named)
}
#[inline]
pub fn parse_named_item_with<F>(
&mut self,
name: &str,
input: ParseStream,
span: Span,
errors: &Errors,
func: F,
) where
F: FnOnce(ParseStream, &str, Span) -> Result<T>,
{
if !self.is_none() {
errors.push(span, format_args!("duplicate attribute for `{name}`"));
}
match errors.push_result(func(input, name, span)) {
Some(v) => {
if self.is_none() {
*self = FieldStatus::Some(v)
}
}
None => {
if self.is_none() {
*self = FieldStatus::ParseError;
}
skip_meta_item(input);
}
}
}
#[inline]
pub fn parse_unnamed_item(&mut self, input: ParseStream, errors: &Errors)
where
T: ParseMetaItem,
{
self.parse_unnamed_item_with(input, errors, T::parse_meta_item)
}
#[inline]
pub fn parse_unnamed_item_with<F>(&mut self, input: ParseStream, errors: &Errors, func: F)
where
F: FnOnce(ParseStream, ParseMode) -> Result<T>,
{
match errors.push_result(func(input, ParseMode::Unnamed)) {
Some(v) => *self = FieldStatus::Some(v),
None => {
*self = FieldStatus::ParseError;
skip_meta_item(input);
}
}
}
#[inline]
pub fn into_option(self) -> Option<T> {
match self {
Self::Some(v) => Some(v),
_ => None,
}
}
}
impl<T> Default for FieldStatus<T> {
#[inline]
fn default() -> Self {
Self::None
}
}
impl<T> From<FieldStatus<T>> for Option<T> {
#[inline]
fn from(value: FieldStatus<T>) -> Self {
value.into_option()
}
}
impl<T> From<Option<T>> for FieldStatus<T> {
#[inline]
fn from(value: Option<T>) -> Self {
match value {
Some(v) => Self::Some(v),
None => Self::None,
}
}
}
mod sealed {
pub trait Sealed {}
impl Sealed for syn::token::Paren {}
impl Sealed for syn::token::Brace {}
impl Sealed for syn::token::Bracket {}
}
pub trait ParseDelimited: sealed::Sealed {
fn parse_delimited(input: ParseStream) -> Result<ParseBuffer>;
#[inline]
fn parse_delimited_with<T, F: FnOnce(ParseStream) -> Result<T>>(
input: ParseStream,
func: F,
) -> Result<T> {
let content = Self::parse_delimited(input)?;
let result = func(&content)?;
parse_eof_or_trailing_comma(&content)?;
Ok(result)
}
#[inline]
fn parse_delimited_meta_item<T: ParseMetaItem>(
input: ParseStream,
mode: ParseMode,
) -> Result<T> {
Self::parse_delimited_with(input, |inner| T::parse_meta_item_inline(&[inner], mode))
}
}
impl ParseDelimited for Paren {
#[inline]
fn parse_delimited(input: ParseStream) -> Result<ParseBuffer> {
let content;
syn::parenthesized!(content in input);
Ok(content)
}
}
impl ParseDelimited for Brace {
#[inline]
fn parse_delimited(input: ParseStream) -> Result<ParseBuffer> {
let content;
syn::braced!(content in input);
Ok(content)
}
}
impl ParseDelimited for Bracket {
#[inline]
fn parse_delimited(input: ParseStream) -> Result<ParseBuffer> {
let content;
syn::bracketed!(content in input);
Ok(content)
}
}
#[inline]
pub fn peek_eof_or_trailing_comma(input: ParseStream) -> bool {
parse_eof_or_trailing_comma(&input.fork()).is_ok()
}
#[inline]
pub fn parse_eof_or_trailing_comma(input: ParseStream) -> Result<()> {
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
if let Some((tree, _)) = input.cursor().token_tree() {
Err(syn::Error::new(
tree.span(),
format_args!("unexpected token `{tree}`"),
))
} else {
Ok(())
}
}
#[inline]
pub fn parse_empty<T, F>(span: proc_macro2::Span, func: F) -> Result<T>
where
F: FnOnce(ParseStream) -> Result<T>,
{
syn::parse::Parser::parse2(func, TokenStream::new()).map_err(|e| {
let mut iter = e.into_iter();
let mut err = Error::new(span, iter.next().unwrap());
for e in iter {
err.combine(e);
}
err
})
}
#[inline]
pub fn parse_empty_meta_item<T: ParseMetaItem>(
span: proc_macro2::Span,
mode: ParseMode,
) -> Result<T> {
parse_empty(span, |input| T::parse_meta_item_inline(&[input], mode))
}
pub fn parse_first<'s, T, F, S>(inputs: &[S], mode: ParseMode, func: F) -> Result<T>
where
F: FnOnce(ParseStream, ParseMode) -> Result<T>,
S: Borrow<ParseBuffer<'s>>,
{
let mut iter = inputs.iter();
if let Some(input) = iter.next() {
let input = input.borrow();
let ret = func(input, mode)?;
parse_eof_or_trailing_comma(input)?;
for next in iter {
next.borrow().parse::<Nothing>()?;
}
Ok(ret)
} else {
let span = match mode {
ParseMode::Named(span) => span,
_ => Span::call_site(),
};
parse_empty(span, |input| func(input, mode))
}
}
#[inline]
pub fn parse_named_meta_item<T: ParseMetaItem>(input: ParseStream, name_span: Span) -> Result<T> {
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
T::parse_meta_item(input, ParseMode::Named(name_span))
} else if input.peek(Paren) {
Paren::parse_delimited_meta_item(input, ParseMode::Named(name_span))
} else {
T::parse_meta_item_flag(name_span)
}
}
#[derive(Debug)]
pub enum NamedParse<'p> {
Equals,
Paren(ParseBuffer<'p>),
Flag,
}
#[inline]
pub fn try_parse_named_meta_item(input: ParseStream) -> Result<NamedParse> {
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
Ok(NamedParse::Equals)
} else if input.peek(Paren) {
let content = Paren::parse_delimited(input)?;
Ok(NamedParse::Paren(content))
} else {
Ok(NamedParse::Flag)
}
}
#[inline]
pub fn parse_named_meta_item_with<T, I, II, IF>(
input: ParseStream,
name_span: Span,
parse: I,
parse_inline: II,
parse_flag: IF,
) -> Result<T>
where
I: FnOnce(ParseStream, ParseMode) -> Result<T>,
II: FnOnce(&[ParseStream], ParseMode) -> Result<T>,
IF: FnOnce(proc_macro2::Span) -> Result<T>,
{
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
parse(input, ParseMode::Named(name_span))
} else if input.peek(Paren) {
let content = Paren::parse_delimited(input)?;
let result = parse_inline(&[&content], ParseMode::Named(name_span))?;
parse_eof_or_trailing_comma(&content)?;
Ok(result)
} else {
parse_flag(name_span)
}
}
#[macro_export]
macro_rules! parse_named_meta_item_with {
($input:expr, $name_span:expr, $($path:tt)* $(,)?) => {
$crate::parse_helpers::parse_named_meta_item_with(
$input,
$name_span,
$($path)* :: parse_meta_item,
|inputs, mode| $($path)* :: parse_meta_item_inline(inputs, mode),
$($path)* :: parse_meta_item_flag,
)
};
}
#[inline]
pub fn parse_tuple_struct<'s, F, S>(mut inputs: &[S], len: usize, mut func: F) -> Result<usize>
where
S: Borrow<ParseBuffer<'s>>,
F: FnMut(ParseStream, &[S], usize) -> Result<()>,
{
let mut counter = 0;
while let Some(input) = inputs.first() {
let input = input.borrow();
loop {
if input.is_empty() {
break;
}
if counter >= len {
return Ok(counter);
}
func(input, inputs, counter)?;
counter += 1;
if !input.is_empty() && counter < len {
input.parse::<Token![,]>()?;
}
}
inputs = &inputs[1..];
}
Ok(counter)
}
pub fn parse_any_path(input: ParseStream) -> Result<syn::Path> {
Ok(syn::Path {
leading_colon: input.parse()?,
segments: {
let mut segments = syn::punctuated::Punctuated::new();
loop {
if input.cursor().ident().is_none() {
break;
}
let ident = <syn::Ident as syn::ext::IdentExt>::parse_any(input)?;
segments.push_value(syn::PathSegment::from(ident));
if !input.peek(syn::Token![::]) {
break;
}
let punct = input.parse()?;
segments.push_punct(punct);
}
if segments.is_empty() {
return Err(input.error("expected path"));
} else if segments.trailing_punct() {
return Err(input.error("expected path segment"));
}
segments
},
})
}
#[inline]
pub fn parse_struct<'s, F, S>(inputs: &[S], mut func: F) -> Result<()>
where
S: Borrow<ParseBuffer<'s>>,
F: FnMut(ParseStream, &str, Span) -> Result<()>,
{
for input in inputs {
let input = input.borrow();
loop {
if input.is_empty() {
break;
}
let path = input.call(parse_any_path)?;
func(input, key_to_string(&path).as_str(), path.span())?;
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
}
Ok(())
}
#[inline]
pub fn skip_meta_item(input: ParseStream) {
input
.step(|cursor| {
let mut cur = *cursor;
while let Some((tt, next)) = cur.token_tree() {
if let TokenTree::Punct(punct) = tt {
if punct.as_char() == ',' {
break;
}
}
cur = next;
}
Ok(((), cur))
})
.ok();
}
#[inline]
pub fn skip_all<'s, S>(inputs: &[S])
where
S: Borrow<ParseBuffer<'s>>,
{
for input in inputs {
input
.borrow()
.step(|cursor| {
let mut cur = *cursor;
while let Some((_, next)) = cur.token_tree() {
cur = next;
}
Ok(((), cur))
})
.ok();
}
}
#[inline]
fn parse_tokens<T, F: FnOnce(ParseStream) -> Result<T>>(input: TokenStream, func: F) -> Result<T> {
let errors = Errors::new();
let mut ret = None;
syn::parse::Parser::parse2(
|stream: ParseStream<'_>| {
ret = match func(stream) {
Ok(ret) => Some(ret),
Err(err) => {
errors.push_syn(err);
None
}
};
if let Err(err) = parse_eof_or_trailing_comma(stream) {
errors.push_syn(err);
}
Ok(())
},
input,
)?;
errors.check()?;
Ok(ret.unwrap())
}
pub fn parse_struct_attr_tokens<T, I, F, R>(inputs: I, func: F) -> Result<R>
where
T: quote::ToTokens,
I: IntoIterator<Item = (T, SmallString<'static>, Span)>,
F: FnOnce(&[ParseBuffer], &[(SmallString<'static>, Span)]) -> Result<R>,
{
fn parse_next<T, I, F, R>(
mut iter: I,
buffers: Vec<ParseBuffer>,
mut paths: Vec<(SmallString<'static>, Span)>,
func: F,
) -> Result<R>
where
T: quote::ToTokens,
I: Iterator<Item = (T, SmallString<'static>, Span)>,
F: FnOnce(&[ParseBuffer], &[(SmallString<'static>, Span)]) -> Result<R>,
{
if let Some((tokens, name, span)) = iter.next() {
let tokens = tokens.into_token_stream();
parse_tokens(tokens, |stream| {
let mut buffers: Vec<ParseBuffer> = buffers; if stream.is_empty() {
buffers.push(stream.fork());
} else {
let content = Paren::parse_delimited(stream)?;
buffers.push(content);
}
paths.push((name, span));
parse_next(iter, buffers, paths, func)
})
} else {
let r = func(&buffers, &paths)?;
for buffer in buffers {
parse_eof_or_trailing_comma(&buffer)?;
}
Ok(r)
}
}
parse_next(inputs.into_iter(), Vec::new(), Vec::new(), func)
}
pub fn key_to_string<T: ToKeyString>(t: &T) -> SmallString<'static> {
struct FormatKey<'a, A>(&'a A);
impl<'a, A: ToKeyString> std::fmt::Display for FormatKey<'a, A> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt_key_string(f)
}
}
let mut ss = SmallString::new();
std::fmt::write(&mut ss, format_args!("{}", FormatKey(t))).unwrap();
ss
}
#[inline]
pub fn join_path<'path>(prefix: &str, path: &'path str) -> Cow<'path, str> {
if prefix.is_empty() {
Cow::Borrowed(path)
} else if prefix.ends_with("::") {
format!("{prefix}{path}").into()
} else {
format!("{prefix}::{path}").into()
}
}
#[inline]
pub fn join_prefix(prefix: &str, path: &str) -> String {
if prefix.is_empty() {
let mut out = path.to_string();
out.push_str("::");
out
} else {
let mut out = prefix.to_string();
if !prefix.ends_with("::") {
out.push_str("::");
}
out.push_str(path);
out.push_str("::");
out
}
}
#[inline]
pub fn join_paths<'s>(prefix: &str, paths: &[&'s str]) -> Vec<Cow<'s, str>> {
paths.iter().map(|p| join_path(prefix, p)).collect()
}
#[inline]
#[doc(hidden)]
pub fn extend_from_owned<'s>(vec: &mut Vec<&'s str>, owned: &'s [Cow<'s, str>]) {
vec.extend(owned.iter().map(|p| p.as_ref()));
}
pub fn path_matches(path: &syn::Path, segs: &[&str]) -> bool {
if path.segments.len() != segs.len() {
return false;
}
for (i, seg) in path.segments.iter().enumerate() {
if !matches!(seg.arguments, syn::PathArguments::None) {
return false;
}
if seg.ident != segs[i] {
return false;
}
}
true
}
#[inline]
pub fn has_paths(paths: &HashMap<SmallString<'static>, Span>, keys: &[&[&str]]) -> bool {
keys.iter()
.all(|ks| ks.iter().any(|i| paths.contains_key(*i)))
}
pub fn remove_paths(paths: &mut HashMap<SmallString<'static>, Span>, keys: &[(&str, &[&str])]) {
for (prefix, ks) in keys {
for path in *ks {
let path = join_path(prefix, path);
paths.remove(path.as_ref());
}
}
}
pub fn disallow_paths(
paths: &HashMap<SmallString<'static>, Span>,
keys: &[(&str, &[&str])],
cur: &str,
errors: &Errors,
) {
for (prefix, ks) in keys {
for path in *ks {
let path = join_path(prefix, path);
if let Some(span) = paths.get(path.as_ref()) {
errors.push(
*span,
format_args!("`{path}` not allowed with variant `{cur}`"),
);
}
}
}
}
#[inline]
pub fn inputs_span<'s, S: Borrow<ParseBuffer<'s>>>(inputs: &[S]) -> Span {
let mut iter = inputs.iter();
let first = iter.next();
if let Some(input) = first {
let mut span = input.borrow().span();
for next in iter {
if let Some(joined) = span.join(next.borrow().span()) {
span = joined;
}
}
span
} else {
Span::call_site()
}
}
#[inline]
pub fn missing_field_error(name: &str, span: Span) -> Error {
syn::Error::new(span, format_args!("missing required field {name}"))
}
#[inline]
pub fn flag_disallowed_error(span: Span) -> Error {
syn::Error::new(span, "unexpected flag, expected `=` or parentheses")
}
pub fn unknown_error(path: &str, span: Span, fields: &[&str]) -> Error {
let mut closest = None;
for field in fields {
let sim = strsim::jaro_winkler(path, field);
if sim > 0.8 && closest.as_ref().map(|(s, _)| sim > *s).unwrap_or(true) {
closest = Some((sim, *field));
}
}
if let Some((_, closest)) = closest {
Error::new(
span,
format!("unknown field `{path}`, did you mean `{closest}`?"),
)
} else {
Error::new(span, format!("unknown field `{path}`"))
}
}
#[inline]
pub fn check_unknown_attribute(path: &str, span: Span, fields: &[&str], errors: &Errors) -> bool {
if !fields.contains(&path) {
errors.push_syn(unknown_error(path, span, fields));
return true;
}
false
}
#[inline]
pub fn ref_tokens<'t, P, T>(
input: &'t T,
) -> impl Iterator<Item = (TokenStream, SmallString<'static>, Span)> + 't
where
P: crate::ParseAttributes<'t, T>,
T: crate::HasAttributes,
{
T::attrs(input).iter().filter_map(|a| {
P::path_matches(a.path()).then(|| {
let value = match &a.meta {
syn::Meta::Path(_) => Default::default(),
syn::Meta::List(list) => proc_macro2::TokenTree::Group(proc_macro2::Group::new(
match list.delimiter {
syn::MacroDelimiter::Paren(_) => proc_macro2::Delimiter::Parenthesis,
syn::MacroDelimiter::Brace(_) => proc_macro2::Delimiter::Brace,
syn::MacroDelimiter::Bracket(_) => proc_macro2::Delimiter::Bracket,
},
list.tokens.clone(),
))
.into(),
syn::Meta::NameValue(nv) => nv.value.to_token_stream(),
};
(value, key_to_string(a.path()), a.path().span())
})
})
}
#[inline]
pub fn take_tokens<E, T>(
input: &mut T,
) -> Result<impl Iterator<Item = (TokenStream, SmallString<'static>, Span)>>
where
E: crate::ExtractAttributes<T>,
T: crate::HasAttributes,
{
let attrs = T::attrs_mut(input)?;
let mut tokens = Vec::new();
let mut index = 0;
while index < attrs.len() {
if E::path_matches(attrs[index].path()) {
let attr = attrs.remove(index);
let span = attr.path().span();
let key = key_to_string(attr.path());
let value = match attr.meta {
syn::Meta::Path(_) => Default::default(),
syn::Meta::List(list) => proc_macro2::TokenTree::Group(proc_macro2::Group::new(
match list.delimiter {
syn::MacroDelimiter::Paren(_) => proc_macro2::Delimiter::Parenthesis,
syn::MacroDelimiter::Brace(_) => proc_macro2::Delimiter::Brace,
syn::MacroDelimiter::Bracket(_) => proc_macro2::Delimiter::Bracket,
},
list.tokens,
))
.into(),
syn::Meta::NameValue(nv) => nv.value.into_token_stream(),
};
tokens.push((value, key, span));
} else {
index += 1;
}
}
Ok(tokens.into_iter())
}
#[inline]
pub fn first_span(spans: &[(SmallString<'static>, Span)]) -> Span {
spans.first().map(|s| s.1).unwrap_or_else(Span::call_site)
}
#[inline]
pub fn first_path_name<'a>(spans: &'a [(SmallString<'static>, Span)]) -> Option<&'a str> {
spans.first().map(|s| s.0.as_str())
}
#[inline]
pub fn fork_inputs<'s, S>(inputs: &[S]) -> Vec<ParseBuffer<'s>>
where
S: Borrow<ParseBuffer<'s>>,
{
inputs.iter().map(|s| s.borrow().fork()).collect()
}
#[inline]
pub fn only_one_variant(span: Span, prefix: &str, (va, vb): (&str, &str), errors: &Errors) {
errors.push(
span,
std::format_args!(
"expected only one enum variant, got `{}` and `{}`",
join_path(prefix, va),
join_path(prefix, vb)
),
);
}
#[inline]
pub fn variant_required(span: Span, prefix: &str, variants: &[&[&[&str]]], errors: &Errors) {
errors.push(
span,
std::format_args!(
"expected one of {}",
variants
.iter()
.map(|keys| {
let mut iter = keys.iter().map(|idents| {
idents
.iter()
.map(|i| join_path(prefix, i).into_owned())
.collect::<Vec<_>>()
.join("|")
});
if keys.len() == 1 {
iter.next().unwrap()
} else {
format!("({})", iter.collect::<Vec<_>>().join(", "))
}
})
.collect::<Vec<_>>()
.join(", ")
),
);
}