feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+481
@@ -0,0 +1,481 @@
|
||||
use std::{slice, vec};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::usage::{
|
||||
self, IdentRefSet, IdentSet, LifetimeRefSet, LifetimeSet, UsesLifetimes, UsesTypeParams,
|
||||
};
|
||||
use crate::{Error, FromField, FromVariant, Result};
|
||||
|
||||
pub use nested_meta::NestedMeta;
|
||||
|
||||
mod nested_meta;
|
||||
|
||||
/// A struct or enum body.
|
||||
///
|
||||
/// `V` is the type which receives any encountered variants, and `F` receives struct fields.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Data<V, F> {
|
||||
Enum(Vec<V>),
|
||||
Struct(Fields<F>),
|
||||
}
|
||||
|
||||
impl<V, F> Data<V, F> {
|
||||
/// Creates an empty body of the same shape as the passed-in body.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function will panic if passed `syn::Data::Union`.
|
||||
pub fn empty_from(src: &syn::Data) -> Self {
|
||||
match *src {
|
||||
syn::Data::Enum(_) => Data::Enum(vec![]),
|
||||
syn::Data::Struct(ref vd) => Data::Struct(Fields::empty_from(&vd.fields)),
|
||||
syn::Data::Union(_) => panic!("Unions are not supported"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an empty body of the same shape as the passed-in body.
|
||||
///
|
||||
/// `darling` does not support unions; calling this function with a union body will return an error.
|
||||
pub fn try_empty_from(src: &syn::Data) -> Result<Self> {
|
||||
match *src {
|
||||
syn::Data::Enum(_) => Ok(Data::Enum(vec![])),
|
||||
syn::Data::Struct(ref vd) => Ok(Data::Struct(Fields::empty_from(&vd.fields))),
|
||||
// This deliberately doesn't set a span on the error message, as the error is most useful if
|
||||
// applied to the call site of the offending macro. Given that the message is very generic,
|
||||
// putting it on the union keyword ends up being confusing.
|
||||
syn::Data::Union(_) => Err(Error::custom("Unions are not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Data<&'a V, &'a F>` instance from `Data<V, F>`.
|
||||
pub fn as_ref(&self) -> Data<&V, &F> {
|
||||
match *self {
|
||||
Data::Enum(ref variants) => Data::Enum(variants.iter().collect()),
|
||||
Data::Struct(ref data) => Data::Struct(data.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function `V -> U` on enum variants, if this is an enum.
|
||||
pub fn map_enum_variants<T, U>(self, map: T) -> Data<U, F>
|
||||
where
|
||||
T: FnMut(V) -> U,
|
||||
{
|
||||
match self {
|
||||
Data::Enum(v) => Data::Enum(v.into_iter().map(map).collect()),
|
||||
Data::Struct(f) => Data::Struct(f),
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function `F -> U` on struct fields, if this is a struct.
|
||||
pub fn map_struct_fields<T, U>(self, map: T) -> Data<V, U>
|
||||
where
|
||||
T: FnMut(F) -> U,
|
||||
{
|
||||
match self {
|
||||
Data::Enum(v) => Data::Enum(v),
|
||||
Data::Struct(f) => Data::Struct(f.map(map)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function to the `Fields` if this is a struct.
|
||||
pub fn map_struct<T, U>(self, mut map: T) -> Data<V, U>
|
||||
where
|
||||
T: FnMut(Fields<F>) -> Fields<U>,
|
||||
{
|
||||
match self {
|
||||
Data::Enum(v) => Data::Enum(v),
|
||||
Data::Struct(f) => Data::Struct(map(f)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the `Data`, returning `Fields<F>` if it was a struct.
|
||||
pub fn take_struct(self) -> Option<Fields<F>> {
|
||||
match self {
|
||||
Data::Enum(_) => None,
|
||||
Data::Struct(f) => Some(f),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the `Data`, returning `Vec<V>` if it was an enum.
|
||||
pub fn take_enum(self) -> Option<Vec<V>> {
|
||||
match self {
|
||||
Data::Enum(v) => Some(v),
|
||||
Data::Struct(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this instance is `Data::Enum`.
|
||||
pub fn is_enum(&self) -> bool {
|
||||
match *self {
|
||||
Data::Enum(_) => true,
|
||||
Data::Struct(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this instance is `Data::Struct`.
|
||||
pub fn is_struct(&self) -> bool {
|
||||
!self.is_enum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromVariant, F: FromField> Data<V, F> {
|
||||
/// Attempt to convert from a `syn::Data` instance.
|
||||
pub fn try_from(body: &syn::Data) -> Result<Self> {
|
||||
match *body {
|
||||
syn::Data::Enum(ref data) => {
|
||||
let mut errors = Error::accumulator();
|
||||
let items = data
|
||||
.variants
|
||||
.iter()
|
||||
.filter_map(|v| errors.handle(FromVariant::from_variant(v)))
|
||||
.collect();
|
||||
|
||||
errors.finish_with(Data::Enum(items))
|
||||
}
|
||||
syn::Data::Struct(ref data) => Ok(Data::Struct(Fields::try_from(&data.fields)?)),
|
||||
// This deliberately doesn't set a span on the error message, as the error is most useful if
|
||||
// applied to the call site of the offending macro. Given that the message is very generic,
|
||||
// putting it on the union keyword ends up being confusing.
|
||||
syn::Data::Union(_) => Err(Error::custom("Unions are not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: UsesTypeParams, F: UsesTypeParams> UsesTypeParams for Data<V, F> {
|
||||
fn uses_type_params<'a>(
|
||||
&self,
|
||||
options: &usage::Options,
|
||||
type_set: &'a IdentSet,
|
||||
) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
Data::Struct(ref v) => v.uses_type_params(options, type_set),
|
||||
Data::Enum(ref v) => v.uses_type_params(options, type_set),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: UsesLifetimes, F: UsesLifetimes> UsesLifetimes for Data<V, F> {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &usage::Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
Data::Struct(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Data::Enum(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to `syn::Fields`, but replaces the AST element with a generic.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Fields<T> {
|
||||
pub style: Style,
|
||||
pub fields: Vec<T>,
|
||||
span: Option<Span>,
|
||||
__nonexhaustive: (),
|
||||
}
|
||||
|
||||
impl<T> Fields<T> {
|
||||
/// Creates a new [`Fields`] struct.
|
||||
pub fn new(style: Style, fields: Vec<T>) -> Self {
|
||||
Self {
|
||||
style,
|
||||
fields,
|
||||
span: None,
|
||||
__nonexhaustive: (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a [`Span`] to [`Fields`].
|
||||
pub fn with_span(mut self, span: Span) -> Self {
|
||||
if self.span.is_none() {
|
||||
self.span = Some(span);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn empty_from(vd: &syn::Fields) -> Self {
|
||||
Self::new(vd.into(), Vec::new())
|
||||
}
|
||||
|
||||
/// Splits the `Fields` into its style and fields for further processing.
|
||||
/// Returns an empty `Vec` for `Unit` data.
|
||||
pub fn split(self) -> (Style, Vec<T>) {
|
||||
(self.style, self.fields)
|
||||
}
|
||||
|
||||
/// Returns true if this variant's data makes it a newtype.
|
||||
pub fn is_newtype(&self) -> bool {
|
||||
self.style == Style::Tuple && self.len() == 1
|
||||
}
|
||||
|
||||
pub fn is_unit(&self) -> bool {
|
||||
self.style.is_unit()
|
||||
}
|
||||
|
||||
pub fn is_tuple(&self) -> bool {
|
||||
self.style.is_tuple()
|
||||
}
|
||||
|
||||
pub fn is_struct(&self) -> bool {
|
||||
self.style.is_struct()
|
||||
}
|
||||
|
||||
pub fn as_ref(&self) -> Fields<&T> {
|
||||
Fields {
|
||||
style: self.style,
|
||||
fields: self.fields.iter().collect(),
|
||||
span: self.span,
|
||||
__nonexhaustive: (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<F, U>(self, map: F) -> Fields<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
Fields {
|
||||
style: self.style,
|
||||
fields: self.fields.into_iter().map(map).collect(),
|
||||
span: self.span,
|
||||
__nonexhaustive: (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> slice::Iter<'_, T> {
|
||||
self.fields.iter()
|
||||
}
|
||||
|
||||
/// Returns the number of fields in the structure.
|
||||
pub fn len(&self) -> usize {
|
||||
self.fields.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the `Fields` contains no fields.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.fields.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FromField> Fields<F> {
|
||||
pub fn try_from(fields: &syn::Fields) -> Result<Self> {
|
||||
let mut errors = Error::accumulator();
|
||||
let items = {
|
||||
match &fields {
|
||||
syn::Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
errors.handle(FromField::from_field(field).map_err(|err| {
|
||||
// There should always be an ident here, since this is a collection
|
||||
// of named fields, but `syn` doesn't prevent someone from manually
|
||||
// constructing an invalid collection so a guard is still warranted.
|
||||
if let Some(ident) = &field.ident {
|
||||
err.at(ident)
|
||||
} else {
|
||||
err
|
||||
}
|
||||
}))
|
||||
})
|
||||
.collect(),
|
||||
syn::Fields::Unnamed(fields) => fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.filter_map(|field| errors.handle(FromField::from_field(field)))
|
||||
.collect(),
|
||||
syn::Fields::Unit => vec![],
|
||||
}
|
||||
};
|
||||
|
||||
errors.finish()?;
|
||||
|
||||
Ok(Self::new(fields.into(), items).with_span(fields.span()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToTokens> ToTokens for Fields<T> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let fields = &self.fields;
|
||||
// An unknown Span should be `Span::call_site()`;
|
||||
// https://docs.rs/syn/1.0.12/syn/spanned/trait.Spanned.html#tymethod.span
|
||||
let span = self.span.unwrap_or_else(Span::call_site);
|
||||
|
||||
match self.style {
|
||||
Style::Struct => {
|
||||
let trailing_comma = {
|
||||
if fields.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(,)
|
||||
}
|
||||
};
|
||||
|
||||
tokens.extend(quote_spanned![span => { #(#fields),* #trailing_comma }]);
|
||||
}
|
||||
Style::Tuple => {
|
||||
tokens.extend(quote_spanned![span => ( #(#fields),* )]);
|
||||
}
|
||||
Style::Unit => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Fields<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.style == other.style && self.fields == other.fields
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq> Eq for Fields<T> {}
|
||||
|
||||
impl<T> IntoIterator for Fields<T> {
|
||||
type Item = T;
|
||||
type IntoIter = vec::IntoIter<T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.fields.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Style> for Fields<T> {
|
||||
fn from(style: Style) -> Self {
|
||||
Self::new(style, Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Into<Vec<T>>> From<(Style, U)> for Fields<T> {
|
||||
fn from((style, fields): (Style, U)) -> Self {
|
||||
style.with_fields(fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesTypeParams> UsesTypeParams for Fields<T> {
|
||||
fn uses_type_params<'a>(
|
||||
&self,
|
||||
options: &usage::Options,
|
||||
type_set: &'a IdentSet,
|
||||
) -> IdentRefSet<'a> {
|
||||
self.fields.uses_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesLifetimes> UsesLifetimes for Fields<T> {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &usage::Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
self.fields.uses_lifetimes(options, lifetimes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
Tuple,
|
||||
Struct,
|
||||
Unit,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn is_unit(self) -> bool {
|
||||
self == Style::Unit
|
||||
}
|
||||
|
||||
pub fn is_tuple(self) -> bool {
|
||||
self == Style::Tuple
|
||||
}
|
||||
|
||||
pub fn is_struct(self) -> bool {
|
||||
self == Style::Struct
|
||||
}
|
||||
|
||||
/// Creates a new `Fields` of the specified style with the passed-in fields.
|
||||
fn with_fields<T, U: Into<Vec<T>>>(self, fields: U) -> Fields<T> {
|
||||
Fields::new(self, fields.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<syn::Fields> for Style {
|
||||
fn from(vd: syn::Fields) -> Self {
|
||||
(&vd).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&syn::Fields> for Style {
|
||||
fn from(vd: &syn::Fields) -> Self {
|
||||
match *vd {
|
||||
syn::Fields::Named(_) => Style::Struct,
|
||||
syn::Fields::Unnamed(_) => Style::Tuple,
|
||||
syn::Fields::Unit => Style::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// it is not possible to directly convert a TokenStream into syn::Fields, so you have
|
||||
// to convert the TokenStream into DeriveInput first and then pass the syn::Fields to
|
||||
// Fields::try_from.
|
||||
fn token_stream_to_fields(input: TokenStream) -> Fields<syn::Field> {
|
||||
Fields::try_from(&{
|
||||
if let syn::Data::Struct(s) = syn::parse2::<syn::DeriveInput>(input).unwrap().data {
|
||||
s.fields
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_eq() {
|
||||
// `Fields` implements `Eq` manually, so it has to be ensured, that all fields of `Fields`
|
||||
// implement `Eq`, this test would fail, if someone accidentally removed the Eq
|
||||
// implementation from `Style`.
|
||||
struct _AssertEq
|
||||
where
|
||||
Style: Eq;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fields_to_tokens_struct() {
|
||||
let reference = quote!(
|
||||
{
|
||||
executable: String,
|
||||
args: Vec<String>,
|
||||
env: Vec<String>,
|
||||
index: usize,
|
||||
optional: Option<String>,
|
||||
current_dir: String,
|
||||
}
|
||||
);
|
||||
let input = quote!(
|
||||
struct ExampleTest #reference
|
||||
);
|
||||
|
||||
let fields = token_stream_to_fields(input);
|
||||
|
||||
let mut result = quote!();
|
||||
fields.to_tokens(&mut result);
|
||||
assert_eq!(result.to_string(), reference.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fields_to_tokens_tuple() {
|
||||
let reference = quote!((u64, usize, &'a T));
|
||||
let input = quote!(
|
||||
struct ExampleTest #reference;
|
||||
);
|
||||
|
||||
let fields = token_stream_to_fields(input);
|
||||
|
||||
let mut result = quote!();
|
||||
fields.to_tokens(&mut result);
|
||||
assert_eq!(result.to_string(), reference.to_string());
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
use proc_macro2::{Delimiter, TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{discouraged::Speculative, ParseStream, Parser},
|
||||
punctuated::Punctuated,
|
||||
token::{self, Brace, Bracket, Paren},
|
||||
Expr, ExprLit, Ident, Lit, MacroDelimiter, Meta, MetaList, MetaNameValue, Path, PathSegment,
|
||||
Token,
|
||||
};
|
||||
|
||||
fn parse_meta_path<'a>(input: ParseStream<'a>) -> syn::Result<Path> {
|
||||
Ok(Path {
|
||||
leading_colon: input.parse()?,
|
||||
segments: {
|
||||
let mut segments = Punctuated::new();
|
||||
loop {
|
||||
// Allow all identifiers, including keywords.
|
||||
if !input.peek(Ident::peek_any) {
|
||||
break;
|
||||
}
|
||||
|
||||
let ident = Ident::parse_any(input)?;
|
||||
segments.push_value(PathSegment::from(ident));
|
||||
if !input.peek(Token![::]) {
|
||||
break;
|
||||
}
|
||||
let punct = input.parse()?;
|
||||
segments.push_punct(punct);
|
||||
}
|
||||
if segments.is_empty() {
|
||||
return Err(input.parse::<Ident>().unwrap_err());
|
||||
} else if segments.trailing_punct() {
|
||||
return Err(input.error("expected path segment after `::`"));
|
||||
}
|
||||
segments
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_meta_after_path<'a>(path: Path, input: ParseStream<'a>) -> syn::Result<Meta> {
|
||||
if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) {
|
||||
parse_meta_list_after_path(path, input).map(Meta::List)
|
||||
} else if input.peek(Token![=]) {
|
||||
parse_meta_name_value_after_path(path, input).map(Meta::NameValue)
|
||||
} else {
|
||||
Ok(Meta::Path(path))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_meta_list_after_path<'a>(path: Path, input: ParseStream<'a>) -> syn::Result<MetaList> {
|
||||
let (delimiter, tokens) = input.step(|cursor| {
|
||||
if let Some((TokenTree::Group(g), rest)) = cursor.token_tree() {
|
||||
let span = g.delim_span();
|
||||
let delimiter = match g.delimiter() {
|
||||
Delimiter::Parenthesis => MacroDelimiter::Paren(Paren(span)),
|
||||
Delimiter::Brace => MacroDelimiter::Brace(Brace(span)),
|
||||
Delimiter::Bracket => MacroDelimiter::Bracket(Bracket(span)),
|
||||
Delimiter::None => {
|
||||
return Err(cursor.error("expected delimiter"));
|
||||
}
|
||||
};
|
||||
Ok(((delimiter, g.stream()), rest))
|
||||
} else {
|
||||
Err(cursor.error("expected delimiter"))
|
||||
}
|
||||
})?;
|
||||
Ok(MetaList {
|
||||
path,
|
||||
delimiter,
|
||||
tokens,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_meta_name_value_after_path<'a>(
|
||||
path: Path,
|
||||
input: ParseStream<'a>,
|
||||
) -> syn::Result<MetaNameValue> {
|
||||
let eq_token: Token![=] = input.parse()?;
|
||||
let ahead = input.fork();
|
||||
let lit: Option<Lit> = ahead.parse()?;
|
||||
let value = if let (Some(lit), true) = (lit, ahead.is_empty()) {
|
||||
input.advance_to(&ahead);
|
||||
Expr::Lit(ExprLit {
|
||||
attrs: Vec::new(),
|
||||
lit,
|
||||
})
|
||||
} else if input.peek(Token![#]) && input.peek2(token::Bracket) {
|
||||
return Err(input.error("unexpected attribute inside of attribute"));
|
||||
} else {
|
||||
input.parse()?
|
||||
};
|
||||
Ok(MetaNameValue {
|
||||
path,
|
||||
eq_token,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// Addressing this would break many users of the crate.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum NestedMeta {
|
||||
Meta(syn::Meta),
|
||||
Lit(syn::Lit),
|
||||
}
|
||||
|
||||
impl NestedMeta {
|
||||
pub fn parse_meta_list(tokens: TokenStream) -> syn::Result<Vec<Self>> {
|
||||
syn::punctuated::Punctuated::<NestedMeta, Token![,]>::parse_terminated
|
||||
.parse2(tokens)
|
||||
.map(|punctuated| punctuated.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for NestedMeta {
|
||||
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
||||
// The difference from `syn::Meta` and `NestedMeta`:
|
||||
// 1. `syn::Meta` requires a path, named value, or meta list only.
|
||||
// 1. `outer(path)`
|
||||
// 2. `outer(key = "value")`, the identifier of the key cannot be strict keywords in rust, like `self`, `super`, `crate`, etc.
|
||||
// 3. `outer(inner(..))`, the identifier of the inner meta cannot be strict keywords in rust, like `self`, `super`, `crate`, etc.
|
||||
// 2. `NestedMeta` allows a literal, which is useful for attributes like `#[outer("foo")]`.
|
||||
// 1. `outer("foo")`
|
||||
// 2. `outer(42)`
|
||||
// 3. `outer(key = "value")`, the identifier of the key can be strict keywords in rust, like `self`, `super`, `crate`, etc.
|
||||
// 1. `outer(self = "value")`
|
||||
// 2. `outer(type = "Foo")`
|
||||
// 3. `outer(crate = "bar")`
|
||||
// 4. `outer(inner(..))`, the identifier of the inner meta can be strict keywords in rust, like `self`, `super`, `crate`, etc.
|
||||
// 1. `outer(self(..))`
|
||||
// 2. `outer(super(..))`
|
||||
// 3. `outer(crate(..))`
|
||||
if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(syn::Token![=])) {
|
||||
input.parse().map(Self::Lit)
|
||||
} else if input.peek(syn::Ident::peek_any) {
|
||||
let path = parse_meta_path(input)?;
|
||||
parse_meta_after_path(path, input).map(Self::Meta)
|
||||
} else {
|
||||
Err(input.error("expected identifier or literal"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for NestedMeta {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
NestedMeta::Meta(meta) => meta.to_tokens(tokens),
|
||||
NestedMeta::Lit(lit) => lit.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
//! Types for working with generics
|
||||
|
||||
use std::iter::Iterator;
|
||||
use std::slice::Iter;
|
||||
|
||||
use crate::{FromGenericParam, FromGenerics, FromTypeParam, Result};
|
||||
|
||||
/// Extension trait for `GenericParam` to support getting values by variant.
|
||||
///
|
||||
/// # Usage
|
||||
/// `darling::ast::Generics` needs a way to test its params array in order to iterate over type params.
|
||||
/// Rather than require callers to use `darling::ast::GenericParam` in all cases, this trait makes that
|
||||
/// polymorphic.
|
||||
pub trait GenericParamExt {
|
||||
/// The type this GenericParam uses to represent type params and their bounds
|
||||
type TypeParam;
|
||||
type LifetimeParam;
|
||||
type ConstParam;
|
||||
|
||||
/// If this GenericParam is a type param, get the underlying value.
|
||||
fn as_type_param(&self) -> Option<&Self::TypeParam> {
|
||||
None
|
||||
}
|
||||
|
||||
/// If this GenericParam is a lifetime, get the underlying value.
|
||||
fn as_lifetime_param(&self) -> Option<&Self::LifetimeParam> {
|
||||
None
|
||||
}
|
||||
|
||||
/// If this GenericParam is a const param, get the underlying value.
|
||||
fn as_const_param(&self) -> Option<&Self::ConstParam> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericParamExt for syn::GenericParam {
|
||||
type TypeParam = syn::TypeParam;
|
||||
type LifetimeParam = syn::LifetimeParam;
|
||||
type ConstParam = syn::ConstParam;
|
||||
|
||||
fn as_type_param(&self) -> Option<&Self::TypeParam> {
|
||||
if let syn::GenericParam::Type(ref val) = *self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_lifetime_param(&self) -> Option<&Self::LifetimeParam> {
|
||||
if let syn::GenericParam::Lifetime(ref val) = *self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_const_param(&self) -> Option<&Self::ConstParam> {
|
||||
if let syn::GenericParam::Const(ref val) = *self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericParamExt for syn::TypeParam {
|
||||
type TypeParam = syn::TypeParam;
|
||||
type LifetimeParam = ();
|
||||
type ConstParam = ();
|
||||
|
||||
fn as_type_param(&self) -> Option<&Self::TypeParam> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A mirror of `syn::GenericParam` which is generic over all its contents.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum GenericParam<T = syn::TypeParam, L = syn::LifetimeParam, C = syn::ConstParam> {
|
||||
Type(T),
|
||||
Lifetime(L),
|
||||
Const(C),
|
||||
}
|
||||
|
||||
impl<T: FromTypeParam> FromTypeParam for GenericParam<T> {
|
||||
fn from_type_param(type_param: &syn::TypeParam) -> Result<Self> {
|
||||
Ok(GenericParam::Type(FromTypeParam::from_type_param(
|
||||
type_param,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromTypeParam> FromGenericParam for GenericParam<T> {
|
||||
fn from_generic_param(param: &syn::GenericParam) -> Result<Self> {
|
||||
Ok(match *param {
|
||||
syn::GenericParam::Type(ref ty) => {
|
||||
GenericParam::Type(FromTypeParam::from_type_param(ty)?)
|
||||
}
|
||||
syn::GenericParam::Lifetime(ref val) => GenericParam::Lifetime(val.clone()),
|
||||
syn::GenericParam::Const(ref val) => GenericParam::Const(val.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, L, C> GenericParamExt for GenericParam<T, L, C> {
|
||||
type TypeParam = T;
|
||||
type LifetimeParam = L;
|
||||
type ConstParam = C;
|
||||
|
||||
fn as_type_param(&self) -> Option<&T> {
|
||||
if let GenericParam::Type(ref val) = *self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_lifetime_param(&self) -> Option<&L> {
|
||||
if let GenericParam::Lifetime(ref val) = *self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_const_param(&self) -> Option<&C> {
|
||||
if let GenericParam::Const(ref val) = *self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mirror of the `syn::Generics` type which can contain arbitrary representations
|
||||
/// of params and where clauses.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Generics<P, W = syn::WhereClause> {
|
||||
pub params: Vec<P>,
|
||||
pub where_clause: Option<W>,
|
||||
}
|
||||
|
||||
impl<P, W> Generics<P, W> {
|
||||
pub fn type_params(&self) -> TypeParams<'_, P> {
|
||||
TypeParams(self.params.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: FromGenericParam> FromGenerics for Generics<P> {
|
||||
fn from_generics(generics: &syn::Generics) -> Result<Self> {
|
||||
Ok(Generics {
|
||||
params: generics
|
||||
.params
|
||||
.iter()
|
||||
.map(FromGenericParam::from_generic_param)
|
||||
.collect::<Result<Vec<P>>>()?,
|
||||
where_clause: generics.where_clause.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypeParams<'a, P>(Iter<'a, P>);
|
||||
|
||||
impl<'a, P: GenericParamExt> Iterator for TypeParams<'a, P> {
|
||||
type Item = &'a <P as GenericParamExt>::TypeParam;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next = self.0.next();
|
||||
match next {
|
||||
None => None,
|
||||
Some(v) => match v.as_type_param() {
|
||||
Some(val) => Some(val),
|
||||
None => self.next(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::{GenericParam, Generics};
|
||||
use crate::FromGenerics;
|
||||
|
||||
#[test]
|
||||
fn generics() {
|
||||
let g: syn::Generics = parse_quote!(<T>);
|
||||
let deified: Generics<GenericParam<syn::Ident>> = FromGenerics::from_generics(&g).unwrap();
|
||||
assert!(deified.params.len() == 1);
|
||||
assert!(deified.where_clause.is_none());
|
||||
}
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
//! Utility types for working with the AST.
|
||||
|
||||
mod data;
|
||||
mod generics;
|
||||
|
||||
pub use self::data::*;
|
||||
pub use self::generics::{GenericParam, GenericParamExt, Generics};
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
use crate::util::PathList;
|
||||
|
||||
use super::ForwardAttrs;
|
||||
|
||||
/// Infrastructure for generating an attribute extractor.
|
||||
pub trait ExtractAttribute {
|
||||
/// A set of mutable declarations for all members of the implementing type.
|
||||
fn local_declarations(&self) -> TokenStream;
|
||||
|
||||
/// Gets the list of attribute names that should be parsed by the extractor.
|
||||
fn attr_names(&self) -> &PathList;
|
||||
|
||||
fn forward_attrs(&self) -> &ForwardAttrs<'_>;
|
||||
|
||||
/// Gets the name used by the generated impl to return to the `syn` item passed as input.
|
||||
fn param_name(&self) -> TokenStream;
|
||||
|
||||
/// Get the tokens to access a borrowed list of attributes where extraction will take place.
|
||||
///
|
||||
/// By default, this will be `&#input.attrs` where `#input` is `self.param_name()`.
|
||||
fn attrs_accessor(&self) -> TokenStream {
|
||||
let input = self.param_name();
|
||||
quote!(&#input.attrs)
|
||||
}
|
||||
|
||||
/// Gets the core from-meta-item loop that should be used on matching attributes.
|
||||
fn core_loop(&self) -> TokenStream;
|
||||
|
||||
/// Generates the main extraction loop.
|
||||
fn extractor(&self) -> TokenStream {
|
||||
let mut declarations = self.local_declarations();
|
||||
self.forward_attrs()
|
||||
.as_declaration()
|
||||
.to_tokens(&mut declarations);
|
||||
|
||||
let will_parse_any = !self.attr_names().is_empty();
|
||||
|
||||
// Forwarding requires both that there be some items we would forward,
|
||||
// and a place that will keep the forwarded items.
|
||||
let will_fwd_any = self.forward_attrs().will_forward_any();
|
||||
|
||||
if !(will_parse_any || will_fwd_any) {
|
||||
return quote! {
|
||||
#declarations
|
||||
};
|
||||
}
|
||||
|
||||
let attrs_accessor = self.attrs_accessor();
|
||||
|
||||
// The block for parsing attributes whose names have been claimed by the target
|
||||
// struct. If no attributes were claimed, this is a pass-through.
|
||||
let parse_handled = if will_parse_any {
|
||||
let attr_names = self.attr_names().to_strings();
|
||||
let core_loop = self.core_loop();
|
||||
quote!(
|
||||
#(#attr_names)|* => {
|
||||
match ::darling::util::parse_attribute_to_meta_list(__attr) {
|
||||
::darling::export::Ok(__data) => {
|
||||
match ::darling::export::NestedMeta::parse_meta_list(__data.tokens) {
|
||||
::darling::export::Ok(ref __items) => {
|
||||
if __items.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
#core_loop
|
||||
}
|
||||
::darling::export::Err(__err) => {
|
||||
__errors.push(__err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
// darling was asked to handle this attribute name, but the actual attribute
|
||||
// isn't one that darling can work with. This either indicates a typing error
|
||||
// or some misunderstanding of the meta attribute syntax; in either case, the
|
||||
// caller should get a useful error.
|
||||
::darling::export::Err(__err) => {
|
||||
__errors.push(__err);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let fwd_population = self.forward_attrs().as_value_populator();
|
||||
|
||||
// Specifies the behavior for unhandled attributes. They will either be silently ignored or
|
||||
// forwarded to the inner struct for later analysis.
|
||||
let forward_unhandled = self.forward_attrs().as_match_arms();
|
||||
|
||||
quote!(
|
||||
#declarations
|
||||
use ::darling::ToTokens;
|
||||
|
||||
for __attr in #attrs_accessor {
|
||||
// Filter attributes based on name
|
||||
match ::darling::export::ToString::to_string(&__attr.path().clone().into_token_stream()).as_str() {
|
||||
#parse_handled
|
||||
#forward_unhandled
|
||||
}
|
||||
}
|
||||
|
||||
#fwd_population
|
||||
)
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::options::{ForwardAttrsFilter, ForwardedField};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ForwardAttrs<'a> {
|
||||
pub filter: Option<&'a ForwardAttrsFilter>,
|
||||
pub field: Option<&'a ForwardedField>,
|
||||
}
|
||||
|
||||
impl ForwardAttrs<'_> {
|
||||
/// Check if this will forward any attributes; this requires both that
|
||||
/// there be a filter which can match some attributes and a field to receive them.
|
||||
pub fn will_forward_any(&self) -> bool {
|
||||
if let Some(filter) = self.filter {
|
||||
!filter.is_empty() && self.field.is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the field declarations to support attribute forwarding
|
||||
pub fn as_declaration(&self) -> Option<Declaration<'_>> {
|
||||
self.field.map(Declaration)
|
||||
}
|
||||
|
||||
/// Get the match arms for attribute matching
|
||||
pub fn as_match_arms(&self) -> MatchArms<'_> {
|
||||
MatchArms(self)
|
||||
}
|
||||
|
||||
/// Get the statement that will try to transform forwarded attributes into
|
||||
/// the result expected by the receiver field.
|
||||
pub fn as_value_populator(&self) -> Option<ValuePopulator<'_>> {
|
||||
self.field.map(ValuePopulator)
|
||||
}
|
||||
|
||||
/// Get the field initializer for use when building the deriving struct.
|
||||
pub fn as_initializer<'a>(&'a self) -> Option<impl 'a + ToTokens> {
|
||||
self.field.map(|f| f.as_initializer())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Declaration<'a>(pub &'a ForwardedField);
|
||||
|
||||
impl ToTokens for Declaration<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let ident = &self.0.ident;
|
||||
tokens.append_all(quote! {
|
||||
let mut __fwd_attrs: ::darling::export::Vec<::darling::export::syn::Attribute> = vec![];
|
||||
let mut #ident: ::darling::export::Option<_> = None;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValuePopulator<'a>(pub &'a ForwardedField);
|
||||
|
||||
impl ToTokens for ValuePopulator<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let ForwardedField { ident, with } = self.0;
|
||||
let initializer_expr = match with {
|
||||
Some(with) => quote_spanned!(with.span()=> __errors.handle(#with(__fwd_attrs))),
|
||||
None => quote!(::darling::export::Some(__fwd_attrs)),
|
||||
};
|
||||
tokens.append_all(quote!(#ident = #initializer_expr;));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MatchArms<'a>(&'a ForwardAttrs<'a>);
|
||||
|
||||
impl ToTokens for MatchArms<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
if !self.0.will_forward_any() {
|
||||
tokens.append_all(quote!(_ => continue));
|
||||
return;
|
||||
}
|
||||
|
||||
let push_command = quote!(__fwd_attrs.push(__attr.clone()));
|
||||
|
||||
tokens.append_all(
|
||||
match self
|
||||
.0
|
||||
.filter
|
||||
.expect("Can only forward attributes if filter is defined")
|
||||
{
|
||||
ForwardAttrsFilter::All => quote!(_ => #push_command),
|
||||
ForwardAttrsFilter::Only(idents) => {
|
||||
let names = idents.to_strings();
|
||||
quote! {
|
||||
#(#names)|* => #push_command,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::{spanned::Spanned, Ident, Path};
|
||||
|
||||
/// This will be in scope during struct initialization after option parsing.
|
||||
const DEFAULT_STRUCT_NAME: &str = "__default";
|
||||
|
||||
/// The fallback value for a field or container.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DefaultExpression<'a> {
|
||||
/// Only valid on fields, `Inherit` indicates that the value should be taken from a pre-constructed
|
||||
/// fallback object. The value in the variant is the ident of the field.
|
||||
Inherit(&'a Ident),
|
||||
Explicit(&'a Path),
|
||||
Trait {
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> DefaultExpression<'a> {
|
||||
pub fn as_declaration(&'a self) -> DefaultDeclaration<'a> {
|
||||
DefaultDeclaration(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for DefaultExpression<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.append_all(match *self {
|
||||
DefaultExpression::Inherit(ident) => {
|
||||
let dsn = Ident::new(DEFAULT_STRUCT_NAME, ::proc_macro2::Span::call_site());
|
||||
quote!(#dsn.#ident)
|
||||
}
|
||||
DefaultExpression::Explicit(path) => {
|
||||
// Use quote_spanned to properly set the span of the parentheses
|
||||
quote_spanned!(path.span()=>#path())
|
||||
}
|
||||
DefaultExpression::Trait { span } => {
|
||||
quote_spanned!(span=> ::darling::export::Default::default())
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Used only by containers, this wrapper type generates code to declare the fallback instance.
|
||||
pub struct DefaultDeclaration<'a>(&'a DefaultExpression<'a>);
|
||||
|
||||
impl ToTokens for DefaultDeclaration<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let name = Ident::new(DEFAULT_STRUCT_NAME, ::proc_macro2::Span::call_site());
|
||||
let expr = self.0;
|
||||
tokens.append_all(quote!(let #name: Self = #expr;));
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
|
||||
/// Declares the local variable into which errors will be accumulated.
|
||||
#[derive(Default)]
|
||||
pub struct ErrorDeclaration {
|
||||
__hidden: (),
|
||||
}
|
||||
|
||||
impl ToTokens for ErrorDeclaration {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.append_all(quote! {
|
||||
let mut __errors = ::darling::Error::accumulator();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns early if attribute or body parsing has caused any errors.
|
||||
#[derive(Default)]
|
||||
pub struct ErrorCheck<'a> {
|
||||
location: Option<&'a str>,
|
||||
__hidden: (),
|
||||
}
|
||||
|
||||
impl<'a> ErrorCheck<'a> {
|
||||
pub fn with_location(location: &'a str) -> Self {
|
||||
ErrorCheck {
|
||||
location: Some(location),
|
||||
__hidden: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ErrorCheck<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let at_call = if let Some(ref s) = self.location {
|
||||
quote!(.map_err(|e| e.at(#s)))
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__errors.finish() #at_call?;
|
||||
})
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::{spanned::Spanned, Ident, Type};
|
||||
|
||||
use crate::codegen::{DefaultExpression, PostfixTransform};
|
||||
use crate::usage::{self, IdentRefSet, IdentSet, UsesTypeParams};
|
||||
|
||||
/// Properties needed to generate code for a field in all the contexts
|
||||
/// where one may appear.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Field<'a> {
|
||||
/// The name presented to the user of the library. This will appear
|
||||
/// in error messages and will be looked when parsing names.
|
||||
pub name_in_attr: Cow<'a, str>,
|
||||
|
||||
/// The name presented to the author of the library. This will appear
|
||||
/// in the setters or temporary variables which contain the values.
|
||||
pub ident: &'a Ident,
|
||||
|
||||
/// The type of the field in the input.
|
||||
pub ty: &'a Type,
|
||||
pub default_expression: Option<DefaultExpression<'a>>,
|
||||
/// An expression that will be wrapped in a call to [`core::convert::identity`] and
|
||||
/// then used for converting a provided value into the field value _before_ postfix
|
||||
/// transforms are called.
|
||||
pub with_callable: Cow<'a, syn::Expr>,
|
||||
pub post_transform: Option<&'a PostfixTransform>,
|
||||
pub skip: bool,
|
||||
pub multiple: bool,
|
||||
/// If set, this field will be given all unclaimed meta items and will
|
||||
/// not be exposed as a standard named field.
|
||||
pub flatten: bool,
|
||||
}
|
||||
|
||||
impl<'a> Field<'a> {
|
||||
/// Get the name of the meta item that should be matched against input and should be used in diagnostics.
|
||||
///
|
||||
/// This will be `None` if the field is `skip` or `flatten`, as neither kind of field is addressable
|
||||
/// by name from the input meta.
|
||||
pub fn as_name(&'a self) -> Option<&'a str> {
|
||||
if self.skip || self.flatten {
|
||||
None
|
||||
} else {
|
||||
Some(&self.name_in_attr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_declaration(&'a self) -> Declaration<'a> {
|
||||
Declaration(self)
|
||||
}
|
||||
|
||||
pub fn as_flatten_initializer(
|
||||
&'a self,
|
||||
parent_field_names: Vec<&'a str>,
|
||||
) -> FlattenInitializer<'a> {
|
||||
FlattenInitializer {
|
||||
field: self,
|
||||
parent_field_names,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_match(&'a self) -> MatchArm<'a> {
|
||||
MatchArm(self)
|
||||
}
|
||||
|
||||
pub fn as_initializer(&'a self) -> Initializer<'a> {
|
||||
Initializer(self)
|
||||
}
|
||||
|
||||
pub fn as_presence_check(&'a self) -> CheckMissing<'a> {
|
||||
CheckMissing(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for Field<'_> {
|
||||
fn uses_type_params<'b>(
|
||||
&self,
|
||||
options: &usage::Options,
|
||||
type_set: &'b IdentSet,
|
||||
) -> IdentRefSet<'b> {
|
||||
self.ty.uses_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
|
||||
/// An individual field during variable declaration in the generated parsing method.
|
||||
pub struct Declaration<'a>(&'a Field<'a>);
|
||||
|
||||
impl ToTokens for Declaration<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let field = self.0;
|
||||
let ident = field.ident;
|
||||
let ty = field.ty;
|
||||
|
||||
tokens.append_all(if field.multiple {
|
||||
// This is NOT mutable, as it will be declared mutable only temporarily.
|
||||
quote!(let mut #ident: #ty = ::darling::export::Default::default();)
|
||||
} else {
|
||||
quote!(let mut #ident: (bool, ::darling::export::Option<#ty>) = (false, None);)
|
||||
});
|
||||
|
||||
// The flatten field additionally needs a place to buffer meta items
|
||||
// until attribute walking is done, so declare that now.
|
||||
//
|
||||
// We expect there can only be one field marked `flatten`, so it shouldn't
|
||||
// be possible for this to shadow another declaration.
|
||||
if field.flatten {
|
||||
tokens.append_all(quote! {
|
||||
let mut __flatten: Vec<::darling::ast::NestedMeta> = vec![];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FlattenInitializer<'a> {
|
||||
field: &'a Field<'a>,
|
||||
parent_field_names: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl ToTokens for FlattenInitializer<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
field,
|
||||
parent_field_names,
|
||||
} = self;
|
||||
let ident = field.ident;
|
||||
|
||||
let add_parent_fields = if parent_field_names.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
.map_err(|e| e.add_sibling_alts_for_unknown_field(&[#(#parent_field_names),*]))
|
||||
})
|
||||
};
|
||||
|
||||
tokens.append_all(quote! {
|
||||
#ident = (true,
|
||||
__errors.handle(
|
||||
::darling::FromMeta::from_list(&__flatten) #add_parent_fields
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an individual field in the match.
|
||||
pub struct MatchArm<'a>(&'a Field<'a>);
|
||||
|
||||
impl ToTokens for MatchArm<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let field = self.0;
|
||||
|
||||
// Skipped and flattened fields cannot be populated by a meta
|
||||
// with their name, so they do not have a match arm.
|
||||
if field.skip || field.flatten {
|
||||
return;
|
||||
}
|
||||
|
||||
let name_str = &field.name_in_attr;
|
||||
let ident = field.ident;
|
||||
let with_callable = &field.with_callable;
|
||||
let post_transform = field.post_transform.as_ref();
|
||||
|
||||
// Errors include the location of the bad input, so we compute that here.
|
||||
// Fields that take multiple values add the index of the error for convenience,
|
||||
// while single-value fields only expose the name in the input attribute.
|
||||
let location = if field.multiple {
|
||||
// we use the local variable `len` here because location is accessed via
|
||||
// a closure, and the borrow checker gets very unhappy if we try to immutably
|
||||
// borrow `#ident` in that closure when it was declared `mut` outside.
|
||||
quote!(&format!("{}[{}]", #name_str, __len))
|
||||
} else {
|
||||
quote!(#name_str)
|
||||
};
|
||||
|
||||
// Give darling's generated code the span of the `with_callable` so that if the target
|
||||
// type doesn't impl FromMeta, darling's immediate user gets a properly-spanned error.
|
||||
//
|
||||
// Within the generated code, add the span immediately on extraction failure, so that it's
|
||||
// as specific as possible.
|
||||
// The behavior of `with_span` makes this safe to do; if the child applied an
|
||||
// even-more-specific span, our attempt here will not overwrite that and will only cost
|
||||
// us one `if` check.
|
||||
let extractor = quote_spanned!(with_callable.span()=>
|
||||
::darling::export::identity::<fn(&::darling::export::syn::Meta) -> ::darling::Result<_>>(#with_callable)(__inner)
|
||||
#post_transform
|
||||
.map_err(|e| e.with_span(&__inner).at(#location))
|
||||
);
|
||||
|
||||
tokens.append_all(if field.multiple {
|
||||
quote!(
|
||||
#name_str => {
|
||||
// Store the index of the name we're assessing in case we need
|
||||
// it for error reporting.
|
||||
let __len = #ident.len();
|
||||
if let ::darling::export::Some(__val) = __errors.handle(#extractor) {
|
||||
#ident.push(__val)
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!(
|
||||
#name_str => {
|
||||
if !#ident.0 {
|
||||
#ident = (true, __errors.handle(#extractor));
|
||||
} else {
|
||||
__errors.push(::darling::Error::duplicate_field(#name_str).with_span(&__inner));
|
||||
}
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper to generate initialization code for a field.
|
||||
pub struct Initializer<'a>(&'a Field<'a>);
|
||||
|
||||
impl ToTokens for Initializer<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let field = self.0;
|
||||
let ident = field.ident;
|
||||
tokens.append_all(if field.multiple {
|
||||
if let Some(ref expr) = field.default_expression {
|
||||
quote_spanned!(expr.span()=> #ident: if !#ident.is_empty() {
|
||||
#ident
|
||||
} else {
|
||||
#expr
|
||||
})
|
||||
} else {
|
||||
quote!(#ident: #ident)
|
||||
}
|
||||
} else if let Some(ref expr) = field.default_expression {
|
||||
quote_spanned!(expr.span()=> #ident: if let Some(__val) = #ident.1 {
|
||||
__val
|
||||
} else {
|
||||
#expr
|
||||
})
|
||||
} else {
|
||||
quote!(#ident: #ident.1.expect("Uninitialized fields without defaults were already checked"))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an error if a field has no value and no default.
|
||||
pub struct CheckMissing<'a>(&'a Field<'a>);
|
||||
|
||||
impl ToTokens for CheckMissing<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
if !self.0.multiple && self.0.default_expression.is_none() {
|
||||
let ident = self.0.ident;
|
||||
let ty = self.0.ty;
|
||||
let name_in_attr = &self.0.name_in_attr;
|
||||
|
||||
// If `ty` does not impl FromMeta, the compiler error should point
|
||||
// at the offending type rather than at the derive-macro call site.
|
||||
let from_none_call =
|
||||
quote_spanned!(ty.span()=> <#ty as ::darling::FromMeta>::from_none());
|
||||
|
||||
tokens.append_all(quote! {
|
||||
if !#ident.0 {
|
||||
match #from_none_call {
|
||||
::darling::export::Some(__type_fallback) => {
|
||||
#ident.1 = ::darling::export::Some(__type_fallback);
|
||||
}
|
||||
::darling::export::None => {
|
||||
__errors.push(::darling::Error::missing_field(#name_in_attr))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
use crate::{
|
||||
ast::Data,
|
||||
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
|
||||
util::PathList,
|
||||
};
|
||||
|
||||
use super::ForwardAttrs;
|
||||
|
||||
pub struct FromAttributesImpl<'a> {
|
||||
pub base: TraitImpl<'a>,
|
||||
pub attr_names: &'a PathList,
|
||||
pub forward_attrs: ForwardAttrs<'a>,
|
||||
}
|
||||
|
||||
impl ToTokens for FromAttributesImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let ty_ident = self.base.ident;
|
||||
let input = self.param_name();
|
||||
let post_transform = self.base.post_transform_call();
|
||||
|
||||
if let Data::Struct(ref data) = self.base.data {
|
||||
if data.is_newtype() {
|
||||
self.wrap(
|
||||
quote! {
|
||||
fn from_attributes(#input: &[::darling::export::syn::Attribute]) -> ::darling::Result<Self> {
|
||||
::darling::export::Ok(
|
||||
#ty_ident(::darling::FromAttributes::from_attributes(#input)?)
|
||||
) #post_transform
|
||||
}
|
||||
},
|
||||
tokens,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let passed_attrs = self.forward_attrs.as_initializer();
|
||||
let inits = self.base.initializers();
|
||||
let default = self.base.fallback_decl();
|
||||
|
||||
let grab_attrs = self.extractor();
|
||||
|
||||
let declare_errors = self.base.declare_errors();
|
||||
let require_fields = self.base.require_fields();
|
||||
let check_errors = self.base.check_errors();
|
||||
|
||||
self.wrap(
|
||||
quote! {
|
||||
fn from_attributes(#input: &[::darling::export::syn::Attribute]) -> ::darling::Result<Self> {
|
||||
#declare_errors
|
||||
|
||||
#grab_attrs
|
||||
|
||||
#require_fields
|
||||
|
||||
#check_errors
|
||||
|
||||
#default
|
||||
|
||||
::darling::export::Ok(#ty_ident {
|
||||
#passed_attrs
|
||||
#inits
|
||||
}) #post_transform
|
||||
}
|
||||
},
|
||||
tokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractAttribute for FromAttributesImpl<'_> {
|
||||
fn local_declarations(&self) -> TokenStream {
|
||||
self.base.local_declarations()
|
||||
}
|
||||
|
||||
fn attr_names(&self) -> &PathList {
|
||||
self.attr_names
|
||||
}
|
||||
|
||||
fn forward_attrs(&self) -> &super::ForwardAttrs<'_> {
|
||||
&self.forward_attrs
|
||||
}
|
||||
|
||||
fn param_name(&self) -> TokenStream {
|
||||
quote!(__di)
|
||||
}
|
||||
|
||||
fn attrs_accessor(&self) -> TokenStream {
|
||||
self.param_name()
|
||||
}
|
||||
|
||||
fn core_loop(&self) -> TokenStream {
|
||||
self.base.core_loop()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for FromAttributesImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::FromAttributes)
|
||||
}
|
||||
|
||||
fn trait_bound(&self) -> syn::Path {
|
||||
path!(::darling::FromMeta)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::{
|
||||
ast::Data,
|
||||
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
|
||||
options::{DeriveInputShapeSet, ForwardedField},
|
||||
util::PathList,
|
||||
};
|
||||
|
||||
use super::ForwardAttrs;
|
||||
|
||||
pub struct FromDeriveInputImpl<'a> {
|
||||
pub ident: Option<&'a Ident>,
|
||||
pub generics: Option<&'a ForwardedField>,
|
||||
pub vis: Option<&'a Ident>,
|
||||
pub data: Option<&'a ForwardedField>,
|
||||
pub base: TraitImpl<'a>,
|
||||
pub attr_names: &'a PathList,
|
||||
pub forward_attrs: ForwardAttrs<'a>,
|
||||
pub from_ident: bool,
|
||||
pub supports: Option<&'a DeriveInputShapeSet>,
|
||||
}
|
||||
|
||||
impl ToTokens for FromDeriveInputImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let ty_ident = self.base.ident;
|
||||
let input = self.param_name();
|
||||
let post_transform = self.base.post_transform_call();
|
||||
|
||||
if let Data::Struct(ref data) = self.base.data {
|
||||
if data.is_newtype() {
|
||||
self.wrap(
|
||||
quote!{
|
||||
fn from_derive_input(#input: &::darling::export::syn::DeriveInput) -> ::darling::Result<Self> {
|
||||
::darling::export::Ok(
|
||||
#ty_ident(::darling::FromDeriveInput::from_derive_input(#input)?)
|
||||
) #post_transform
|
||||
}
|
||||
},
|
||||
tokens,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let passed_ident = self
|
||||
.ident
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.ident.clone(),));
|
||||
let passed_vis = self.vis.as_ref().map(|i| quote!(#i: #input.vis.clone(),));
|
||||
let passed_attrs = self.forward_attrs.as_initializer();
|
||||
|
||||
let read_generics = self.generics.map(|generics| {
|
||||
let ident = &generics.ident;
|
||||
let with = generics.with.as_ref().map(|p| quote!(#p)).unwrap_or_else(
|
||||
|| quote_spanned!(ident.span()=>::darling::FromGenerics::from_generics),
|
||||
);
|
||||
quote! {
|
||||
let #ident = __errors.handle(#with(&#input.generics));
|
||||
}
|
||||
});
|
||||
|
||||
let pass_generics_to_receiver = self.generics.map(|g| g.as_initializer());
|
||||
|
||||
let check_shape = self
|
||||
.supports
|
||||
.map(|s| s.validator_path().into_token_stream())
|
||||
.unwrap_or_else(|| quote!(::darling::export::Ok));
|
||||
|
||||
let read_data = self
|
||||
.data
|
||||
.as_ref()
|
||||
.map(|i| match &i.with {
|
||||
Some(p) => quote!(#p),
|
||||
None => quote_spanned!(i.ident.span()=> ::darling::ast::Data::try_from),
|
||||
})
|
||||
.unwrap_or_else(|| quote!(::darling::export::Ok));
|
||||
|
||||
let supports = self.supports;
|
||||
let validate_and_read_data = {
|
||||
// If the caller wants `data` read into a field, we can use `data` as the local variable name
|
||||
// because we know there are no other fields of that name.
|
||||
let let_binding = self.data.map(|d| {
|
||||
let ident = &d.ident;
|
||||
quote!(let #ident = )
|
||||
});
|
||||
quote! {
|
||||
#supports
|
||||
#let_binding __errors.handle(#check_shape(&#input.data).and_then(#read_data));
|
||||
}
|
||||
};
|
||||
|
||||
let pass_data_to_receiver = self.data.map(|f| f.as_initializer());
|
||||
|
||||
let inits = self.base.initializers();
|
||||
let default = if self.from_ident {
|
||||
quote!(let __default: Self = ::darling::export::From::from(#input.ident.clone());)
|
||||
} else {
|
||||
self.base.fallback_decl()
|
||||
};
|
||||
|
||||
let grab_attrs = self.extractor();
|
||||
|
||||
let declare_errors = self.base.declare_errors();
|
||||
let require_fields = self.base.require_fields();
|
||||
let check_errors = self.base.check_errors();
|
||||
|
||||
self.wrap(
|
||||
quote! {
|
||||
fn from_derive_input(#input: &::darling::export::syn::DeriveInput) -> ::darling::Result<Self> {
|
||||
#declare_errors
|
||||
|
||||
#grab_attrs
|
||||
|
||||
#validate_and_read_data
|
||||
|
||||
#read_generics
|
||||
|
||||
#require_fields
|
||||
|
||||
#check_errors
|
||||
|
||||
#default
|
||||
|
||||
::darling::export::Ok(#ty_ident {
|
||||
#passed_ident
|
||||
#pass_generics_to_receiver
|
||||
#passed_vis
|
||||
#passed_attrs
|
||||
#pass_data_to_receiver
|
||||
#inits
|
||||
}) #post_transform
|
||||
}
|
||||
},
|
||||
tokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractAttribute for FromDeriveInputImpl<'_> {
|
||||
fn attr_names(&self) -> &PathList {
|
||||
self.attr_names
|
||||
}
|
||||
|
||||
fn forward_attrs(&self) -> &ForwardAttrs<'_> {
|
||||
&self.forward_attrs
|
||||
}
|
||||
|
||||
fn param_name(&self) -> TokenStream {
|
||||
quote!(__di)
|
||||
}
|
||||
|
||||
fn core_loop(&self) -> TokenStream {
|
||||
self.base.core_loop()
|
||||
}
|
||||
|
||||
fn local_declarations(&self) -> TokenStream {
|
||||
self.base.local_declarations()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for FromDeriveInputImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::FromDeriveInput)
|
||||
}
|
||||
|
||||
fn trait_bound(&self) -> syn::Path {
|
||||
path!(::darling::FromMeta)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::{
|
||||
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
|
||||
util::PathList,
|
||||
};
|
||||
|
||||
use super::ForwardAttrs;
|
||||
|
||||
/// `impl FromField` generator. This is used for parsing an individual
|
||||
/// field and its attributes.
|
||||
pub struct FromFieldImpl<'a> {
|
||||
pub ident: Option<&'a Ident>,
|
||||
pub vis: Option<&'a Ident>,
|
||||
pub ty: Option<&'a Ident>,
|
||||
pub base: TraitImpl<'a>,
|
||||
pub attr_names: &'a PathList,
|
||||
pub forward_attrs: ForwardAttrs<'a>,
|
||||
pub from_ident: bool,
|
||||
}
|
||||
|
||||
impl ToTokens for FromFieldImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let input = self.param_name();
|
||||
|
||||
let error_declaration = self.base.declare_errors();
|
||||
let require_fields = self.base.require_fields();
|
||||
let error_check = self.base.check_errors();
|
||||
|
||||
let initializers = self.base.initializers();
|
||||
|
||||
let default = if self.from_ident {
|
||||
quote!(let __default: Self = ::darling::export::From::from(#input.ident.clone());)
|
||||
} else {
|
||||
self.base.fallback_decl()
|
||||
};
|
||||
|
||||
let passed_ident = self
|
||||
.ident
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.ident.clone(),));
|
||||
let passed_vis = self.vis.as_ref().map(|i| quote!(#i: #input.vis.clone(),));
|
||||
let passed_ty = self.ty.as_ref().map(|i| quote!(#i: #input.ty.clone(),));
|
||||
let passed_attrs = self.forward_attrs.as_initializer();
|
||||
|
||||
// Determine which attributes to forward (if any).
|
||||
let grab_attrs = self.extractor();
|
||||
let post_transform = self.base.post_transform_call();
|
||||
|
||||
self.wrap(
|
||||
quote! {
|
||||
fn from_field(#input: &::darling::export::syn::Field) -> ::darling::Result<Self> {
|
||||
#error_declaration
|
||||
|
||||
#grab_attrs
|
||||
|
||||
#require_fields
|
||||
|
||||
#error_check
|
||||
|
||||
#default
|
||||
|
||||
::darling::export::Ok(Self {
|
||||
#passed_ident
|
||||
#passed_ty
|
||||
#passed_vis
|
||||
#passed_attrs
|
||||
#initializers
|
||||
}) #post_transform
|
||||
|
||||
}
|
||||
},
|
||||
tokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractAttribute for FromFieldImpl<'_> {
|
||||
fn attr_names(&self) -> &PathList {
|
||||
self.attr_names
|
||||
}
|
||||
|
||||
fn forward_attrs(&self) -> &super::ForwardAttrs<'_> {
|
||||
&self.forward_attrs
|
||||
}
|
||||
|
||||
fn param_name(&self) -> TokenStream {
|
||||
quote!(__field)
|
||||
}
|
||||
|
||||
fn core_loop(&self) -> TokenStream {
|
||||
self.base.core_loop()
|
||||
}
|
||||
|
||||
fn local_declarations(&self) -> TokenStream {
|
||||
self.base.local_declarations()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for FromFieldImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::FromField)
|
||||
}
|
||||
|
||||
fn trait_bound(&self) -> syn::Path {
|
||||
path!(::darling::FromMeta)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::ast::{Data, Fields, Style};
|
||||
use crate::codegen::{Field, OuterFromImpl, TraitImpl, Variant};
|
||||
use crate::util::Callable;
|
||||
|
||||
pub struct FromMetaImpl<'a> {
|
||||
pub base: TraitImpl<'a>,
|
||||
pub from_word: Option<Cow<'a, Callable>>,
|
||||
pub from_none: Option<&'a Callable>,
|
||||
pub from_expr: Option<&'a Callable>,
|
||||
pub derive_syn_parse: bool,
|
||||
}
|
||||
|
||||
impl ToTokens for FromMetaImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let base = &self.base;
|
||||
|
||||
let from_word = self.from_word.as_ref().map(|body| {
|
||||
quote_spanned! {body.span()=>
|
||||
fn from_word() -> ::darling::Result<Self> {
|
||||
::darling::export::identity::<fn() -> ::darling::Result<Self>>(#body)()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let from_none = self.from_none.map(|body| {
|
||||
quote_spanned! {body.span()=>
|
||||
fn from_none() -> ::darling::export::Option<Self> {
|
||||
::darling::export::identity::<fn() -> ::darling::export::Option<Self>>(#body)()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let from_expr = self.from_expr.map(|body| {
|
||||
quote_spanned! {body.span()=>
|
||||
fn from_expr(expr: &::darling::export::syn::Expr) -> ::darling::Result<Self> {
|
||||
::darling::export::identity::<fn(&::darling::export::syn::Expr) -> ::darling::Result<Self>>(#body)(expr)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let impl_block = match base.data {
|
||||
// Unit structs allow empty bodies only.
|
||||
Data::Struct(ref vd) if vd.style.is_unit() => {
|
||||
let ty_ident = base.ident;
|
||||
quote!(
|
||||
fn from_word() -> ::darling::Result<Self> {
|
||||
::darling::export::Ok(#ty_ident)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Newtype structs proxy to the sole value they contain.
|
||||
Data::Struct(Fields {
|
||||
ref fields,
|
||||
style: Style::Tuple,
|
||||
..
|
||||
}) if fields.len() == 1 => {
|
||||
let ty_ident = base.ident;
|
||||
quote!(
|
||||
fn from_meta(__item: &::darling::export::syn::Meta) -> ::darling::Result<Self> {
|
||||
::darling::FromMeta::from_meta(__item)
|
||||
.map_err(|e| e.with_span(&__item))
|
||||
.map(#ty_ident)
|
||||
}
|
||||
)
|
||||
}
|
||||
Data::Struct(Fields {
|
||||
style: Style::Tuple,
|
||||
..
|
||||
}) => {
|
||||
panic!("Multi-field tuples are not supported");
|
||||
}
|
||||
Data::Struct(ref data) => {
|
||||
let inits = data.fields.iter().map(Field::as_initializer);
|
||||
let declare_errors = base.declare_errors();
|
||||
let require_fields = base.require_fields();
|
||||
let check_errors = base.check_errors();
|
||||
let decls = base.local_declarations();
|
||||
let core_loop = base.core_loop();
|
||||
let default = base.fallback_decl();
|
||||
let post_transform = base.post_transform_call();
|
||||
|
||||
quote!(
|
||||
#from_word
|
||||
|
||||
#from_none
|
||||
|
||||
#from_expr
|
||||
|
||||
fn from_list(__items: &[::darling::export::NestedMeta]) -> ::darling::Result<Self> {
|
||||
|
||||
#decls
|
||||
|
||||
#declare_errors
|
||||
|
||||
#core_loop
|
||||
|
||||
#require_fields
|
||||
|
||||
#check_errors
|
||||
|
||||
#default
|
||||
|
||||
::darling::export::Ok(Self {
|
||||
#(#inits),*
|
||||
}) #post_transform
|
||||
}
|
||||
)
|
||||
}
|
||||
Data::Enum(ref variants) => {
|
||||
let unit_arms = variants.iter().map(Variant::as_unit_match_arm);
|
||||
|
||||
let (unknown_variant_err, unknown_unit_variant_err) = if !variants.is_empty() {
|
||||
let names = variants.iter().map(Variant::as_name);
|
||||
let names = quote!(&[#(#names),*]);
|
||||
(
|
||||
quote! {
|
||||
unknown_field_with_alts(__other, #names)
|
||||
},
|
||||
quote! {
|
||||
unknown_value_with_alts(__other, #names)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote! {
|
||||
unknown_field(__other)
|
||||
},
|
||||
quote!(unknown_value(__other)),
|
||||
)
|
||||
};
|
||||
|
||||
let data_variants = variants.iter().map(Variant::as_data_match_arm);
|
||||
|
||||
quote!(
|
||||
fn from_list(__outer: &[::darling::export::NestedMeta]) -> ::darling::Result<Self> {
|
||||
// An enum must have exactly one value inside the parentheses if it's not a unit
|
||||
// match arm.
|
||||
match __outer.len() {
|
||||
0 => ::darling::export::Err(::darling::Error::too_few_items(1)),
|
||||
1 => {
|
||||
if let ::darling::export::NestedMeta::Meta(ref __nested) = __outer[0] {
|
||||
match ::darling::util::path_to_string(__nested.path()).as_ref() {
|
||||
#(#data_variants)*
|
||||
__other => ::darling::export::Err(::darling::Error::#unknown_variant_err.with_span(__nested))
|
||||
}
|
||||
} else {
|
||||
::darling::export::Err(::darling::Error::unsupported_format("literal"))
|
||||
}
|
||||
}
|
||||
_ => ::darling::export::Err(::darling::Error::too_many_items(1)),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_string(lit: &str) -> ::darling::Result<Self> {
|
||||
match lit {
|
||||
#(#unit_arms)*
|
||||
__other => ::darling::export::Err(::darling::Error::#unknown_unit_variant_err)
|
||||
}
|
||||
}
|
||||
|
||||
#from_word
|
||||
|
||||
#from_none
|
||||
|
||||
#from_expr
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
self.wrap(impl_block, tokens);
|
||||
if self.derive_syn_parse {
|
||||
ParseImpl(self).to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for FromMetaImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::FromMeta)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
|
||||
struct ParseImpl<'a>(&'a FromMetaImpl<'a>);
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for ParseImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::export::syn::parse::Parse)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.0.base
|
||||
}
|
||||
|
||||
fn trait_bound(&self) -> syn::Path {
|
||||
// Since the Parse impl delegates to FromMeta, that's the
|
||||
// trait bound we need to apply.
|
||||
self.0.trait_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ParseImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let from_meta = self.0.trait_path();
|
||||
let impl_block = quote! {
|
||||
fn parse(input: ::darling::export::syn::parse::ParseStream<'_>) -> ::darling::export::syn::Result<Self> {
|
||||
use ::darling::export::IntoIterator;
|
||||
|
||||
let items = ::darling::export::syn::punctuated::Punctuated::<::darling::export::NestedMeta, ::darling::export::syn::Token![,]>::parse_terminated(input)?
|
||||
.into_iter()
|
||||
.collect::<::darling::export::Vec<_>>();
|
||||
<Self as #from_meta>::from_list(&items).map_err(::darling::export::Into::into)
|
||||
}
|
||||
};
|
||||
|
||||
self.wrap(impl_block, tokens);
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::codegen::{ExtractAttribute, ForwardAttrs, OuterFromImpl, TraitImpl};
|
||||
use crate::util::PathList;
|
||||
|
||||
pub struct FromTypeParamImpl<'a> {
|
||||
pub base: TraitImpl<'a>,
|
||||
pub ident: Option<&'a Ident>,
|
||||
pub bounds: Option<&'a Ident>,
|
||||
pub default: Option<&'a Ident>,
|
||||
pub attr_names: &'a PathList,
|
||||
pub forward_attrs: ForwardAttrs<'a>,
|
||||
pub from_ident: bool,
|
||||
}
|
||||
|
||||
impl ToTokens for FromTypeParamImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let input = self.param_name();
|
||||
|
||||
let error_declaration = self.base.declare_errors();
|
||||
let grab_attrs = self.extractor();
|
||||
let require_fields = self.base.require_fields();
|
||||
let error_check = self.base.check_errors();
|
||||
|
||||
let default = if self.from_ident {
|
||||
quote!(let __default: Self = ::darling::export::From::from(#input.ident.clone());)
|
||||
} else {
|
||||
self.base.fallback_decl()
|
||||
};
|
||||
|
||||
let passed_ident = self
|
||||
.ident
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.ident.clone(),));
|
||||
let passed_attrs = self.forward_attrs.as_initializer();
|
||||
let passed_bounds = self
|
||||
.bounds
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.bounds.clone().into_iter().collect::<Vec<_>>(),));
|
||||
let passed_default = self
|
||||
.default
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.default.clone(),));
|
||||
let initializers = self.base.initializers();
|
||||
|
||||
let post_transform = self.base.post_transform_call();
|
||||
|
||||
self.wrap(
|
||||
quote! {
|
||||
fn from_type_param(#input: &::darling::export::syn::TypeParam) -> ::darling::Result<Self> {
|
||||
#error_declaration
|
||||
|
||||
#grab_attrs
|
||||
|
||||
#require_fields
|
||||
|
||||
#error_check
|
||||
|
||||
#default
|
||||
|
||||
::darling::export::Ok(Self {
|
||||
#passed_ident
|
||||
#passed_bounds
|
||||
#passed_default
|
||||
#passed_attrs
|
||||
#initializers
|
||||
}) #post_transform
|
||||
}
|
||||
},
|
||||
tokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractAttribute for FromTypeParamImpl<'_> {
|
||||
fn attr_names(&self) -> &PathList {
|
||||
self.attr_names
|
||||
}
|
||||
|
||||
fn forward_attrs(&self) -> &ForwardAttrs<'_> {
|
||||
&self.forward_attrs
|
||||
}
|
||||
|
||||
fn param_name(&self) -> TokenStream {
|
||||
quote!(__type_param)
|
||||
}
|
||||
|
||||
fn core_loop(&self) -> TokenStream {
|
||||
self.base.core_loop()
|
||||
}
|
||||
|
||||
fn local_declarations(&self) -> TokenStream {
|
||||
self.base.local_declarations()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for FromTypeParamImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::FromTypeParam)
|
||||
}
|
||||
|
||||
fn trait_bound(&self) -> syn::Path {
|
||||
path!(::darling::FromMeta)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::codegen::{ExtractAttribute, ForwardAttrs, OuterFromImpl, TraitImpl};
|
||||
use crate::options::DataShape;
|
||||
use crate::util::PathList;
|
||||
|
||||
pub struct FromVariantImpl<'a> {
|
||||
pub base: TraitImpl<'a>,
|
||||
/// If set, the ident of the field into which the variant ident should be placed.
|
||||
///
|
||||
/// This is one of `darling`'s "magic fields", which allow a type deriving a `darling`
|
||||
/// trait to get fields from the input `syn` element added to the deriving struct
|
||||
/// automatically.
|
||||
pub ident: Option<&'a Ident>,
|
||||
/// If set, the ident of the field into which the transformed output of the input
|
||||
/// variant's fields should be placed.
|
||||
///
|
||||
/// This is one of `darling`'s "magic fields".
|
||||
pub fields: Option<&'a Ident>,
|
||||
/// If set, the ident of the field into which the discriminant of the input variant
|
||||
/// should be placed. The receiving field must be an `Option` as not all enums have
|
||||
/// discriminants.
|
||||
///
|
||||
/// This is one of `darling`'s "magic fields".
|
||||
pub discriminant: Option<&'a Ident>,
|
||||
pub attr_names: &'a PathList,
|
||||
pub forward_attrs: ForwardAttrs<'a>,
|
||||
pub from_ident: bool,
|
||||
pub supports: Option<&'a DataShape>,
|
||||
}
|
||||
|
||||
impl ToTokens for FromVariantImpl<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let input = self.param_name();
|
||||
let extractor = self.extractor();
|
||||
let passed_ident = self
|
||||
.ident
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.ident.clone(),));
|
||||
let passed_discriminant = self
|
||||
.discriminant
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: #input.discriminant.as_ref().map(|(_, expr)| expr.clone()),));
|
||||
let passed_attrs = self.forward_attrs.as_initializer();
|
||||
let passed_fields = self
|
||||
.fields
|
||||
.as_ref()
|
||||
.map(|i| quote!(#i: ::darling::ast::Fields::try_from(&#input.fields)?,));
|
||||
|
||||
let inits = self.base.initializers();
|
||||
let post_transform = self.base.post_transform_call();
|
||||
|
||||
let default = if self.from_ident {
|
||||
quote!(let __default: Self = ::darling::export::From::from(#input.ident.clone());)
|
||||
} else {
|
||||
self.base.fallback_decl()
|
||||
};
|
||||
|
||||
let supports = self.supports.map(|i| {
|
||||
quote! {
|
||||
__errors.handle(#i.check(&#input.fields));
|
||||
}
|
||||
});
|
||||
|
||||
let error_declaration = self.base.declare_errors();
|
||||
let require_fields = self.base.require_fields();
|
||||
let error_check = self.base.check_errors();
|
||||
|
||||
self.wrap(
|
||||
quote!(
|
||||
fn from_variant(#input: &::darling::export::syn::Variant) -> ::darling::Result<Self> {
|
||||
#error_declaration
|
||||
|
||||
#extractor
|
||||
|
||||
#supports
|
||||
|
||||
#require_fields
|
||||
|
||||
#error_check
|
||||
|
||||
#default
|
||||
|
||||
::darling::export::Ok(Self {
|
||||
#passed_ident
|
||||
#passed_discriminant
|
||||
#passed_attrs
|
||||
#passed_fields
|
||||
#inits
|
||||
}) #post_transform
|
||||
}
|
||||
),
|
||||
tokens,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractAttribute for FromVariantImpl<'_> {
|
||||
fn local_declarations(&self) -> TokenStream {
|
||||
self.base.local_declarations()
|
||||
}
|
||||
|
||||
fn attr_names(&self) -> &PathList {
|
||||
self.attr_names
|
||||
}
|
||||
|
||||
fn forward_attrs(&self) -> &ForwardAttrs<'_> {
|
||||
&self.forward_attrs
|
||||
}
|
||||
|
||||
fn param_name(&self) -> TokenStream {
|
||||
quote!(__variant)
|
||||
}
|
||||
|
||||
fn core_loop(&self) -> TokenStream {
|
||||
self.base.core_loop()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OuterFromImpl<'a> for FromVariantImpl<'a> {
|
||||
fn trait_path(&self) -> syn::Path {
|
||||
path!(::darling::FromVariant)
|
||||
}
|
||||
|
||||
fn trait_bound(&self) -> syn::Path {
|
||||
path!(::darling::FromMeta)
|
||||
}
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a> {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
mod attr_extractor;
|
||||
mod attrs_field;
|
||||
mod default_expr;
|
||||
mod error;
|
||||
mod field;
|
||||
mod from_attributes_impl;
|
||||
mod from_derive_impl;
|
||||
mod from_field;
|
||||
mod from_meta_impl;
|
||||
mod from_type_param;
|
||||
mod from_variant_impl;
|
||||
mod outer_from_impl;
|
||||
mod postfix_transform;
|
||||
mod trait_impl;
|
||||
mod variant;
|
||||
mod variant_data;
|
||||
|
||||
pub(in crate::codegen) use self::attr_extractor::ExtractAttribute;
|
||||
pub use self::attrs_field::ForwardAttrs;
|
||||
pub use self::default_expr::DefaultExpression;
|
||||
pub use self::field::Field;
|
||||
pub use self::from_attributes_impl::FromAttributesImpl;
|
||||
pub use self::from_derive_impl::FromDeriveInputImpl;
|
||||
pub use self::from_field::FromFieldImpl;
|
||||
pub use self::from_meta_impl::FromMetaImpl;
|
||||
pub use self::from_type_param::FromTypeParamImpl;
|
||||
pub use self::from_variant_impl::FromVariantImpl;
|
||||
pub use self::outer_from_impl::OuterFromImpl;
|
||||
pub use self::postfix_transform::PostfixTransform;
|
||||
pub use self::trait_impl::TraitImpl;
|
||||
pub use self::variant::Variant;
|
||||
pub use self::variant_data::FieldsGen;
|
||||
@@ -0,0 +1,62 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{GenericParam, Generics, Path, TraitBound, TraitBoundModifier, TypeParamBound};
|
||||
|
||||
use crate::codegen::TraitImpl;
|
||||
use crate::usage::IdentSet;
|
||||
|
||||
/// Wrapper for "outer From" traits, such as `FromDeriveInput`, `FromVariant`, and `FromField`.
|
||||
pub trait OuterFromImpl<'a> {
|
||||
/// Gets the path of the trait being implemented.
|
||||
fn trait_path(&self) -> Path;
|
||||
|
||||
fn base(&'a self) -> &'a TraitImpl<'a>;
|
||||
|
||||
fn trait_bound(&self) -> Path {
|
||||
self.trait_path()
|
||||
}
|
||||
|
||||
fn wrap<T: ToTokens>(&'a self, body: T, tokens: &mut TokenStream) {
|
||||
let base = self.base();
|
||||
let trayt = self.trait_path();
|
||||
let ty_ident = base.ident;
|
||||
// The type parameters used in non-skipped, non-magic fields.
|
||||
// These must impl `FromMeta` unless they have custom bounds.
|
||||
let used = base.used_type_params();
|
||||
let generics = compute_impl_bounds(self.trait_bound(), base.generics.clone(), &used);
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
tokens.append_all(quote!(
|
||||
#[automatically_derived]
|
||||
#[allow(clippy::manual_unwrap_or_default)]
|
||||
impl #impl_generics #trayt for #ty_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
#body
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_impl_bounds(bound: Path, mut generics: Generics, applies_to: &IdentSet) -> Generics {
|
||||
if generics.params.is_empty() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
let added_bound = TypeParamBound::Trait(TraitBound {
|
||||
paren_token: None,
|
||||
modifier: TraitBoundModifier::None,
|
||||
lifetimes: None,
|
||||
path: bound,
|
||||
});
|
||||
|
||||
for param in generics.params.iter_mut() {
|
||||
if let GenericParam::Type(ref mut typ) = *param {
|
||||
if applies_to.contains(&typ.ident) {
|
||||
typ.bounds.push(added_bound.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generics
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{Ident, Path};
|
||||
|
||||
/// A method invocation applied to a value.
|
||||
///
|
||||
/// This is used for `map` and `and_then` transforms in derivations.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PostfixTransform {
|
||||
pub(crate) transformer: Ident,
|
||||
pub(crate) function: Path,
|
||||
}
|
||||
|
||||
impl PostfixTransform {
|
||||
pub fn new(transformer: Ident, function: Path) -> Self {
|
||||
Self {
|
||||
transformer,
|
||||
function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for PostfixTransform {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let Self {
|
||||
transformer,
|
||||
function,
|
||||
} = self;
|
||||
tokens.append_all(quote!(.#transformer(#function)))
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Generics, Ident};
|
||||
|
||||
use crate::ast::{Data, Fields};
|
||||
use crate::codegen::{
|
||||
error::{ErrorCheck, ErrorDeclaration},
|
||||
DefaultExpression, Field, FieldsGen, PostfixTransform, Variant,
|
||||
};
|
||||
use crate::usage::{CollectTypeParams, IdentSet, Purpose};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TraitImpl<'a> {
|
||||
pub ident: &'a Ident,
|
||||
pub generics: &'a Generics,
|
||||
pub data: Data<Variant<'a>, Field<'a>>,
|
||||
pub default: Option<DefaultExpression<'a>>,
|
||||
pub post_transform: Option<&'a PostfixTransform>,
|
||||
pub allow_unknown_fields: bool,
|
||||
}
|
||||
|
||||
impl<'a> TraitImpl<'a> {
|
||||
/// Get all declared type parameters.
|
||||
pub fn declared_type_params(&self) -> IdentSet {
|
||||
self.generics
|
||||
.type_params()
|
||||
.map(|tp| tp.ident.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the type parameters which are used by non-skipped, non-magic fields.
|
||||
/// These type parameters will have a `FromMeta` bound applied to them in emitted
|
||||
/// code.
|
||||
pub fn used_type_params(&self) -> IdentSet {
|
||||
self.type_params_matching(|f| !f.skip, |v| !v.skip)
|
||||
}
|
||||
|
||||
fn type_params_matching<F, V>(&self, field_filter: F, variant_filter: V) -> IdentSet
|
||||
where
|
||||
F: Fn(&&Field<'_>) -> bool,
|
||||
V: Fn(&&Variant<'_>) -> bool,
|
||||
{
|
||||
let declared = self.declared_type_params();
|
||||
match self.data {
|
||||
Data::Struct(ref v) => self.type_params_in_fields(v, &field_filter, &declared),
|
||||
Data::Enum(ref v) => {
|
||||
v.iter()
|
||||
.filter(variant_filter)
|
||||
.fold(Default::default(), |mut state, variant| {
|
||||
state.extend(self.type_params_in_fields(
|
||||
&variant.data,
|
||||
&field_filter,
|
||||
&declared,
|
||||
));
|
||||
state
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type parameters of all fields in a set matching some filter
|
||||
fn type_params_in_fields<'b, F>(
|
||||
&'b self,
|
||||
fields: &'b Fields<Field<'a>>,
|
||||
field_filter: F,
|
||||
declared: &IdentSet,
|
||||
) -> IdentSet
|
||||
where
|
||||
F: Fn(&&'b Field<'_>) -> bool,
|
||||
{
|
||||
fields
|
||||
.iter()
|
||||
.filter(field_filter)
|
||||
.collect_type_params_cloned(&Purpose::BoundImpl.into(), declared)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TraitImpl<'a> {
|
||||
/// Gets the `let` declaration for errors accumulated during parsing.
|
||||
pub fn declare_errors(&self) -> ErrorDeclaration {
|
||||
ErrorDeclaration::default()
|
||||
}
|
||||
|
||||
/// Gets the check which performs an early return if errors occurred during parsing.
|
||||
pub fn check_errors(&self) -> ErrorCheck<'_> {
|
||||
ErrorCheck::default()
|
||||
}
|
||||
|
||||
/// Generate local variable declarations for all fields.
|
||||
pub(in crate::codegen) fn local_declarations(&self) -> TokenStream {
|
||||
if let Data::Struct(ref vd) = self.data {
|
||||
let vdr = vd.as_ref().map(Field::as_declaration);
|
||||
let decls = vdr.fields.as_slice();
|
||||
quote!(#(#decls)*)
|
||||
} else {
|
||||
quote!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::codegen) fn post_transform_call(&self) -> Option<TokenStream> {
|
||||
self.post_transform.map(|pt| quote!(#pt))
|
||||
}
|
||||
|
||||
/// Generate local variable declaration and initialization for instance from which missing fields will be taken.
|
||||
pub(in crate::codegen) fn fallback_decl(&self) -> TokenStream {
|
||||
let default = self.default.as_ref().map(DefaultExpression::as_declaration);
|
||||
quote!(#default)
|
||||
}
|
||||
|
||||
pub fn require_fields(&self) -> TokenStream {
|
||||
if let Data::Struct(ref vd) = self.data {
|
||||
let check_nones = vd.as_ref().map(Field::as_presence_check);
|
||||
let checks = check_nones.fields.as_slice();
|
||||
|
||||
// If a field was marked `flatten`, now is the time to process any unclaimed meta items
|
||||
// and mark the field as having been seen.
|
||||
let flatten_field_init = vd.fields.iter().find(|f| f.flatten).map(|v| {
|
||||
v.as_flatten_initializer(vd.fields.iter().filter_map(Field::as_name).collect())
|
||||
});
|
||||
|
||||
quote! {
|
||||
#flatten_field_init
|
||||
#(#checks)*
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::codegen) fn initializers(&self) -> TokenStream {
|
||||
self.make_field_ctx().initializers()
|
||||
}
|
||||
|
||||
/// Generate the loop which walks meta items looking for property matches.
|
||||
pub(in crate::codegen) fn core_loop(&self) -> TokenStream {
|
||||
self.make_field_ctx().core_loop()
|
||||
}
|
||||
|
||||
fn make_field_ctx(&'a self) -> FieldsGen<'a> {
|
||||
match self.data {
|
||||
Data::Enum(_) => panic!("Core loop on enums isn't supported"),
|
||||
Data::Struct(ref data) => FieldsGen::new(data, self.allow_unknown_fields),
|
||||
}
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::ast::Fields;
|
||||
use crate::codegen::error::{ErrorCheck, ErrorDeclaration};
|
||||
use crate::codegen::{Field, FieldsGen};
|
||||
use crate::usage::{self, IdentRefSet, IdentSet, UsesTypeParams};
|
||||
|
||||
/// A variant of the enum which is deriving `FromMeta`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Variant<'a> {
|
||||
/// The name which will appear in code passed to the `FromMeta` input.
|
||||
pub name_in_attr: Cow<'a, str>,
|
||||
|
||||
/// The name of the variant which will be returned for a given `name_in_attr`.
|
||||
pub variant_ident: &'a Ident,
|
||||
|
||||
/// The name of the parent enum type.
|
||||
pub ty_ident: &'a Ident,
|
||||
|
||||
pub data: Fields<Field<'a>>,
|
||||
|
||||
/// Whether or not the variant should be skipped in the generated code.
|
||||
pub skip: bool,
|
||||
|
||||
pub allow_unknown_fields: bool,
|
||||
}
|
||||
|
||||
impl<'a> Variant<'a> {
|
||||
pub fn as_name(&'a self) -> &'a str {
|
||||
&self.name_in_attr
|
||||
}
|
||||
|
||||
pub fn as_unit_match_arm(&'a self) -> UnitMatchArm<'a> {
|
||||
UnitMatchArm(self)
|
||||
}
|
||||
|
||||
pub fn as_data_match_arm(&'a self) -> DataMatchArm<'a> {
|
||||
DataMatchArm(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for Variant<'_> {
|
||||
fn uses_type_params<'b>(
|
||||
&self,
|
||||
options: &usage::Options,
|
||||
type_set: &'b IdentSet,
|
||||
) -> IdentRefSet<'b> {
|
||||
self.data.uses_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
|
||||
/// Code generator for an enum variant in a unit match position.
|
||||
/// This is placed in generated `from_string` calls for the parent enum.
|
||||
/// Value-carrying variants wrapped in this type will emit code to produce an "unsupported format" error.
|
||||
pub struct UnitMatchArm<'a>(&'a Variant<'a>);
|
||||
|
||||
impl ToTokens for UnitMatchArm<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let val: &Variant<'_> = self.0;
|
||||
|
||||
if val.skip {
|
||||
return;
|
||||
}
|
||||
|
||||
let name_in_attr = &val.name_in_attr;
|
||||
|
||||
let unsupported_format_error = || {
|
||||
quote!(::darling::export::Err(
|
||||
::darling::Error::unsupported_format("literal")
|
||||
))
|
||||
};
|
||||
|
||||
if val.data.is_unit() {
|
||||
let variant_ident = val.variant_ident;
|
||||
let ty_ident = val.ty_ident;
|
||||
|
||||
tokens.append_all(quote!(
|
||||
#name_in_attr => ::darling::export::Ok(#ty_ident::#variant_ident),
|
||||
));
|
||||
} else if val.data.is_newtype() {
|
||||
let field = val
|
||||
.data
|
||||
.fields
|
||||
.first()
|
||||
.expect("Newtype should have exactly one field");
|
||||
let field_ty = field.ty;
|
||||
let ty_ident = val.ty_ident;
|
||||
let variant_ident = val.variant_ident;
|
||||
let unsupported_format = unsupported_format_error();
|
||||
|
||||
tokens.append_all(quote!{
|
||||
#name_in_attr => {
|
||||
match <#field_ty as ::darling::FromMeta>::from_none() {
|
||||
::darling::export::Some(__value) => ::darling::export::Ok(#ty_ident::#variant_ident(__value)),
|
||||
::darling::export::None => #unsupported_format,
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let unsupported_format = unsupported_format_error();
|
||||
tokens.append_all(quote!(
|
||||
#name_in_attr => #unsupported_format,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Code generator for an enum variant in a data-carrying match position.
|
||||
/// This is placed in generated `from_list` calls for the parent enum.
|
||||
/// Unit variants wrapped in this type will emit code to produce an "unsupported format" error.
|
||||
pub struct DataMatchArm<'a>(&'a Variant<'a>);
|
||||
|
||||
impl ToTokens for DataMatchArm<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let val: &Variant<'_> = self.0;
|
||||
|
||||
if val.skip {
|
||||
return;
|
||||
}
|
||||
|
||||
let name_in_attr = &val.name_in_attr;
|
||||
let variant_ident = val.variant_ident;
|
||||
let ty_ident = val.ty_ident;
|
||||
|
||||
if val.data.is_unit() {
|
||||
// Allow unit variants to match a list item if it's just a path with no associated
|
||||
// value, e.g. `volume(shout)` is allowed.
|
||||
tokens.append_all(quote!(
|
||||
#name_in_attr => {
|
||||
if let ::darling::export::syn::Meta::Path(_) = *__nested {
|
||||
::darling::export::Ok(#ty_ident::#variant_ident)
|
||||
} else {
|
||||
::darling::export::Err(::darling::Error::unsupported_format("non-path"))
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let vdg = FieldsGen::new(&val.data, val.allow_unknown_fields);
|
||||
|
||||
if val.data.is_struct() {
|
||||
let declare_errors = ErrorDeclaration::default();
|
||||
let check_errors = ErrorCheck::with_location(name_in_attr);
|
||||
let require_fields = vdg.require_fields();
|
||||
let decls = vdg.declarations();
|
||||
let core_loop = vdg.core_loop();
|
||||
let inits = vdg.initializers();
|
||||
|
||||
tokens.append_all(quote!(
|
||||
#name_in_attr => {
|
||||
if let ::darling::export::syn::Meta::List(ref __data) = *__nested {
|
||||
let __items = ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone())?;
|
||||
let __items = &__items;
|
||||
|
||||
#declare_errors
|
||||
|
||||
#decls
|
||||
|
||||
#core_loop
|
||||
|
||||
#require_fields
|
||||
|
||||
#check_errors
|
||||
|
||||
::darling::export::Ok(#ty_ident::#variant_ident {
|
||||
#inits
|
||||
})
|
||||
} else {
|
||||
::darling::export::Err(::darling::Error::unsupported_format("non-list"))
|
||||
}
|
||||
}
|
||||
));
|
||||
} else if val.data.is_newtype() {
|
||||
tokens.append_all(quote!(
|
||||
#name_in_attr => {
|
||||
::darling::export::Ok(
|
||||
#ty_ident::#variant_ident(
|
||||
::darling::FromMeta::from_meta(__nested)
|
||||
.map_err(|e| e.at(#name_in_attr))?)
|
||||
)
|
||||
}
|
||||
));
|
||||
} else {
|
||||
panic!("Match arms aren't supported for tuple variants yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::ast::{Fields, Style};
|
||||
use crate::codegen::Field;
|
||||
|
||||
pub struct FieldsGen<'a> {
|
||||
fields: &'a Fields<Field<'a>>,
|
||||
allow_unknown_fields: bool,
|
||||
}
|
||||
|
||||
impl<'a> FieldsGen<'a> {
|
||||
pub fn new(fields: &'a Fields<Field<'a>>, allow_unknown_fields: bool) -> Self {
|
||||
Self {
|
||||
fields,
|
||||
allow_unknown_fields,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create declarations for all the fields in the struct.
|
||||
pub(in crate::codegen) fn declarations(&self) -> TokenStream {
|
||||
match *self.fields {
|
||||
Fields {
|
||||
style: Style::Struct,
|
||||
ref fields,
|
||||
..
|
||||
} => {
|
||||
let vdr = fields.iter().map(Field::as_declaration);
|
||||
quote!(#(#vdr)*)
|
||||
}
|
||||
_ => panic!("FieldsGen doesn't support tuples yet"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the loop which walks meta items looking for property matches.
|
||||
pub(in crate::codegen) fn core_loop(&self) -> TokenStream {
|
||||
let arms = self.fields.as_ref().map(Field::as_match);
|
||||
// If there is a flatten field, buffer the unknown field so it can be passed
|
||||
// to the flatten function with all other unknown fields.
|
||||
let handle_unknown = if self.fields.iter().any(|f| f.flatten) {
|
||||
quote! {
|
||||
__flatten.push(::darling::ast::NestedMeta::Meta(__inner.clone()));
|
||||
}
|
||||
}
|
||||
// If we're allowing unknown fields, then handling one is a no-op.
|
||||
else if self.allow_unknown_fields {
|
||||
quote!()
|
||||
}
|
||||
// Otherwise, we're going to push a new spanned error pointing at the field.
|
||||
else {
|
||||
let mut names = self.fields.iter().filter_map(Field::as_name).peekable();
|
||||
// We can't call `unknown_field_with_alts` with an empty slice, or else it fails to
|
||||
// infer the type of the slice item.
|
||||
let err_fn = if names.peek().is_none() {
|
||||
quote!(unknown_field(__other))
|
||||
} else {
|
||||
quote!(unknown_field_with_alts(__other, &[#(#names),*]))
|
||||
};
|
||||
|
||||
quote! {
|
||||
__errors.push(::darling::Error::#err_fn.with_span(__inner));
|
||||
}
|
||||
};
|
||||
let arms = arms.iter();
|
||||
|
||||
quote!(
|
||||
for __item in __items {
|
||||
match *__item {
|
||||
::darling::export::NestedMeta::Meta(ref __inner) => {
|
||||
let __name = ::darling::util::path_to_string(__inner.path());
|
||||
match __name.as_str() {
|
||||
#(#arms)*
|
||||
__other => { #handle_unknown }
|
||||
}
|
||||
}
|
||||
::darling::export::NestedMeta::Lit(ref __inner) => {
|
||||
__errors.push(::darling::Error::unsupported_format("literal")
|
||||
.with_span(__inner));
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn require_fields(&self) -> TokenStream {
|
||||
match *self.fields {
|
||||
Fields {
|
||||
style: Style::Struct,
|
||||
ref fields,
|
||||
..
|
||||
} => {
|
||||
let checks = fields.iter().map(Field::as_presence_check);
|
||||
quote!(#(#checks)*)
|
||||
}
|
||||
_ => panic!("FieldsGen doesn't support tuples for requirement checks"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::codegen) fn initializers(&self) -> TokenStream {
|
||||
let inits = self.fields.as_ref().map(Field::as_initializer);
|
||||
let inits = inits.iter();
|
||||
|
||||
quote!(#(#inits),*)
|
||||
}
|
||||
}
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
//! Functions to derive `darling`'s traits from well-formed input, without directly depending
|
||||
//! on `proc_macro`.
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::DeriveInput;
|
||||
|
||||
use crate::options;
|
||||
|
||||
/// Run an expression which returns a `darling::Result`, then either return the tokenized
|
||||
/// representation of the `Ok` value, or the tokens of the compiler errors in the `Err` case.
|
||||
macro_rules! emit_impl_or_error {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(val) => val.into_token_stream(),
|
||||
Err(err) => err.write_errors(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Create tokens for a `darling::FromMeta` impl from a `DeriveInput`. If
|
||||
/// the input cannot produce a valid impl, the returned tokens will contain
|
||||
/// compile errors instead.
|
||||
pub fn from_meta(input: &DeriveInput) -> TokenStream {
|
||||
emit_impl_or_error!(options::FromMetaOptions::new(input))
|
||||
}
|
||||
|
||||
/// Create tokens for a `darling::FromAttributes` impl from a `DeriveInput`. If
|
||||
/// the input cannot produce a valid impl, the returned tokens will contain
|
||||
/// compile errors instead.
|
||||
pub fn from_attributes(input: &DeriveInput) -> TokenStream {
|
||||
emit_impl_or_error!(options::FromAttributesOptions::new(input))
|
||||
}
|
||||
|
||||
/// Create tokens for a `darling::FromDeriveInput` impl from a `DeriveInput`. If
|
||||
/// the input cannot produce a valid impl, the returned tokens will contain
|
||||
/// compile errors instead.
|
||||
pub fn from_derive_input(input: &DeriveInput) -> TokenStream {
|
||||
emit_impl_or_error!(options::FdiOptions::new(input))
|
||||
}
|
||||
|
||||
/// Create tokens for a `darling::FromField` impl from a `DeriveInput`. If
|
||||
/// the input cannot produce a valid impl, the returned tokens will contain
|
||||
/// compile errors instead.
|
||||
pub fn from_field(input: &DeriveInput) -> TokenStream {
|
||||
emit_impl_or_error!(options::FromFieldOptions::new(input))
|
||||
}
|
||||
|
||||
/// Create tokens for a `darling::FromTypeParam` impl from a `DeriveInput`. If
|
||||
/// the input cannot produce a valid impl, the returned tokens will contain
|
||||
/// compile errors instead.
|
||||
pub fn from_type_param(input: &DeriveInput) -> TokenStream {
|
||||
emit_impl_or_error!(options::FromTypeParamOptions::new(input))
|
||||
}
|
||||
|
||||
/// Create tokens for a `darling::FromVariant` impl from a `DeriveInput`. If
|
||||
/// the input cannot produce a valid impl, the returned tokens will contain
|
||||
/// compile errors instead.
|
||||
pub fn from_variant(input: &DeriveInput) -> TokenStream {
|
||||
emit_impl_or_error!(options::FromVariantOptions::new(input))
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
use proc_macro2::Span;
|
||||
|
||||
/// Exhaustive mirror of [`proc_macro::Level`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::error) enum Level {
|
||||
Error,
|
||||
Warning,
|
||||
Note,
|
||||
Help,
|
||||
}
|
||||
|
||||
/// Supplemental message for an [`Error`](super::Error) when it's emitted as a `Diagnostic`.
|
||||
///
|
||||
/// # Example Output
|
||||
/// The `note` and `help` lines below come from child diagnostics.
|
||||
///
|
||||
/// ```text
|
||||
/// error: My custom error
|
||||
/// --> my_project/my_file.rs:3:5
|
||||
/// |
|
||||
/// 13 | FooBar { value: String },
|
||||
/// | ^^^^^^
|
||||
/// |
|
||||
/// = note: My note on the macro usage
|
||||
/// = help: Try doing this instead
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::error) struct ChildDiagnostic {
|
||||
level: Level,
|
||||
span: Option<Span>,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl ChildDiagnostic {
|
||||
pub(in crate::error) fn new(level: Level, span: Option<Span>, message: String) -> Self {
|
||||
Self {
|
||||
level,
|
||||
span,
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildDiagnostic {
|
||||
/// Append this child diagnostic to a `Diagnostic`.
|
||||
///
|
||||
/// # Panics
|
||||
/// This method panics if `self` has a span and is being invoked outside of
|
||||
/// a proc-macro due to the behavior of [`Span::unwrap()`](Span).
|
||||
pub fn append_to(self, diagnostic: proc_macro::Diagnostic) -> proc_macro::Diagnostic {
|
||||
match self.level {
|
||||
Level::Error => {
|
||||
if let Some(span) = self.span {
|
||||
diagnostic.span_error(span.unwrap(), self.message)
|
||||
} else {
|
||||
diagnostic.error(self.message)
|
||||
}
|
||||
}
|
||||
Level::Warning => {
|
||||
if let Some(span) = self.span {
|
||||
diagnostic.span_warning(span.unwrap(), self.message)
|
||||
} else {
|
||||
diagnostic.warning(self.message)
|
||||
}
|
||||
}
|
||||
Level::Note => {
|
||||
if let Some(span) = self.span {
|
||||
diagnostic.span_note(span.unwrap(), self.message)
|
||||
} else {
|
||||
diagnostic.note(self.message)
|
||||
}
|
||||
}
|
||||
Level::Help => {
|
||||
if let Some(span) = self.span {
|
||||
diagnostic.span_help(span.unwrap(), self.message)
|
||||
} else {
|
||||
diagnostic.help(self.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
use std::{collections::BTreeSet, fmt};
|
||||
|
||||
use crate::error::{
|
||||
util::{write_delimited, Quoted},
|
||||
Error,
|
||||
};
|
||||
|
||||
type DeriveInputShape = String;
|
||||
type FieldName = String;
|
||||
type MetaFormat = String;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
// Don't want to publicly commit to ErrorKind supporting equality yet, but
|
||||
// not having it makes testing very difficult.
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(in crate::error) enum ErrorKind {
|
||||
/// An arbitrary error message.
|
||||
Custom(String),
|
||||
DuplicateField(FieldName),
|
||||
MissingField(FieldName),
|
||||
UnsupportedShape {
|
||||
observed: DeriveInputShape,
|
||||
expected: Option<String>,
|
||||
},
|
||||
UnknownField(Box<ErrorUnknownValue>),
|
||||
UnexpectedFormat(MetaFormat),
|
||||
UnexpectedType(String),
|
||||
UnknownValue(Box<ErrorUnknownValue>),
|
||||
TooFewItems(usize),
|
||||
TooManyItems(usize),
|
||||
/// A set of errors.
|
||||
Multiple(Vec<Error>),
|
||||
|
||||
// TODO make this variant take `!` so it can't exist
|
||||
#[doc(hidden)]
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
/// Deeply counts the number of errors this item represents.
|
||||
pub fn len(&self) -> usize {
|
||||
if let ErrorKind::Multiple(ref items) = *self {
|
||||
items.iter().map(Error::len).sum()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use self::ErrorKind::*;
|
||||
|
||||
match *self {
|
||||
Custom(ref s) => s.fmt(f),
|
||||
DuplicateField(ref field) => write!(f, "Duplicate field `{}`", field),
|
||||
MissingField(ref field) => write!(f, "Missing field `{}`", field),
|
||||
UnknownField(ref field) => field.fmt(f),
|
||||
UnsupportedShape {
|
||||
ref observed,
|
||||
ref expected,
|
||||
} => {
|
||||
write!(f, "Unsupported shape `{}`", observed)?;
|
||||
if let Some(expected) = &expected {
|
||||
write!(f, ". Expected {}.", expected)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
UnexpectedFormat(ref format) => write!(f, "Unexpected meta-item format `{}`", format),
|
||||
UnexpectedType(ref ty) => write!(f, "Unexpected type `{}`", ty),
|
||||
UnknownValue(ref val) => val.fmt(f),
|
||||
TooFewItems(ref min) => write!(f, "Too few items: Expected at least {}", min),
|
||||
TooManyItems(ref max) => write!(f, "Too many items: Expected no more than {}", max),
|
||||
Multiple(ref items) if items.len() == 1 => items[0].fmt(f),
|
||||
Multiple(ref items) => {
|
||||
write!(f, "Multiple errors: (")?;
|
||||
write_delimited(f, items, ", ")?;
|
||||
write!(f, ")")
|
||||
}
|
||||
__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(in crate::error) enum UnknownValuePosition {
|
||||
Field,
|
||||
Value,
|
||||
}
|
||||
|
||||
impl AsRef<str> for UnknownValuePosition {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
UnknownValuePosition::Field => "field",
|
||||
UnknownValuePosition::Value => "value",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UnknownValuePosition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorUnknownValue> for ErrorKind {
|
||||
fn from(value: ErrorUnknownValue) -> Self {
|
||||
match value.noun {
|
||||
UnknownValuePosition::Field => Self::UnknownField(Box::new(value)),
|
||||
UnknownValuePosition::Value => Self::UnknownValue(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error where an unknown value was seen in a given position,
|
||||
/// with a possible "did-you-mean" suggestion to get the user back on the right track.
|
||||
#[derive(Clone, Debug)]
|
||||
// Don't want to publicly commit to ErrorKind supporting equality yet, but
|
||||
// not having it makes testing very difficult.
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(in crate::error) struct ErrorUnknownValue {
|
||||
/// The thing whose value is unknown.
|
||||
noun: UnknownValuePosition,
|
||||
|
||||
value: String,
|
||||
/// The best suggestion of what field the caller could have meant, along with
|
||||
/// the similarity score between that best option and the actual caller-provided
|
||||
/// field name.
|
||||
did_you_mean: Option<(f64, String)>,
|
||||
/// Set of all known valid field names.
|
||||
///
|
||||
/// This is a `BTreeSet` so that names will be displayed in alphabetical order
|
||||
/// without needing display-time sorting.
|
||||
alts: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl ErrorUnknownValue {
|
||||
pub fn new<I: Into<String>>(noun: UnknownValuePosition, value: I) -> Self {
|
||||
ErrorUnknownValue {
|
||||
noun,
|
||||
value: value.into(),
|
||||
did_you_mean: None,
|
||||
alts: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_alts<'a, T, I>(noun: UnknownValuePosition, value: &str, alternates: I) -> Self
|
||||
where
|
||||
T: AsRef<str> + 'a,
|
||||
I: IntoIterator<Item = &'a T>,
|
||||
{
|
||||
let alts = alternates
|
||||
.into_iter()
|
||||
.map(|s| s.as_ref().to_string())
|
||||
.collect();
|
||||
Self {
|
||||
noun,
|
||||
value: value.into(),
|
||||
did_you_mean: did_you_mean(value, &alts),
|
||||
alts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add more alternate values to the error, updating the `did_you_mean` suggestion
|
||||
/// if a closer match to the unknown value is found.
|
||||
pub fn add_alts<'a, T, I>(&mut self, alternates: I)
|
||||
where
|
||||
T: AsRef<str> + 'a,
|
||||
I: IntoIterator<Item = &'a T>,
|
||||
{
|
||||
let alts = alternates.into_iter().collect::<Vec<_>>();
|
||||
self.alts
|
||||
.extend(alts.iter().map(|s| s.as_ref().to_string()));
|
||||
if let Some(bna) = did_you_mean(&self.value, alts) {
|
||||
if let Some(current) = &self.did_you_mean {
|
||||
if bna.0 > current.0 {
|
||||
self.did_you_mean = Some(bna);
|
||||
}
|
||||
} else {
|
||||
self.did_you_mean = Some(bna);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
pub fn into_diagnostic(self, span: Option<::proc_macro2::Span>) -> ::proc_macro::Diagnostic {
|
||||
let mut diag = span
|
||||
.unwrap_or_else(::proc_macro2::Span::call_site)
|
||||
.unwrap()
|
||||
.error(self.top_line());
|
||||
|
||||
if let Some((_, alt_name)) = self.did_you_mean {
|
||||
diag = diag.help(format!("did you mean `{}`?", alt_name));
|
||||
}
|
||||
|
||||
if !self.alts.is_empty() {
|
||||
let mut alts = String::new();
|
||||
|
||||
// Per documentation, formatting is infallible unless underlying stream closes
|
||||
// https://doc.rust-lang.org/std/fmt/struct.Error.html
|
||||
write_delimited(&mut alts, self.alts.iter().map(Quoted::backticks), ", ")
|
||||
.expect("writing to a string never fails");
|
||||
diag = diag.help(format!("available values: {}", alts));
|
||||
}
|
||||
|
||||
diag
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
fn top_line(&self) -> String {
|
||||
format!("Unknown {}: `{}`", self.noun, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorUnknownValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Unknown {}: `{}`", self.noun, self.value)?;
|
||||
|
||||
if let Some((_, ref did_you_mean)) = self.did_you_mean {
|
||||
write!(f, ". Did you mean `{}`?", did_you_mean)?;
|
||||
} else if !self.alts.is_empty() && self.alts.len() < 10 {
|
||||
write!(f, ". Available values: ")?;
|
||||
write_delimited(f, self.alts.iter().map(Quoted::backticks), ", ")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "suggestions")]
|
||||
fn did_you_mean<'a, T, I>(field: &str, alternates: I) -> Option<(f64, String)>
|
||||
where
|
||||
T: AsRef<str> + 'a,
|
||||
I: IntoIterator<Item = &'a T>,
|
||||
{
|
||||
let mut candidate: Option<(f64, &str)> = None;
|
||||
for pv in alternates {
|
||||
let confidence = ::strsim::jaro_winkler(field, pv.as_ref());
|
||||
if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence))
|
||||
{
|
||||
candidate = Some((confidence, pv.as_ref()));
|
||||
}
|
||||
}
|
||||
candidate.map(|(score, candidate)| (score, candidate.into()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "suggestions"))]
|
||||
fn did_you_mean<'a, T, I>(_field: &str, _alternates: I) -> Option<(f64, String)>
|
||||
where
|
||||
T: AsRef<str> + 'a,
|
||||
I: IntoIterator<Item = &'a T>,
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ErrorUnknownValue, UnknownValuePosition};
|
||||
|
||||
/// Make sure that an unknown field error with no alts or suggestions has
|
||||
/// only the relevant information and no fragments of other sentences.
|
||||
#[test]
|
||||
fn present_no_alts() {
|
||||
let err = ErrorUnknownValue::new(UnknownValuePosition::Field, "hello");
|
||||
assert_eq!(&err.to_string(), "Unknown field: `hello`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn present_few_alts() {
|
||||
let err = ErrorUnknownValue::with_alts(
|
||||
UnknownValuePosition::Field,
|
||||
"hello",
|
||||
&["world", "friend"],
|
||||
);
|
||||
assert!(err.to_string().contains("`friend`"));
|
||||
}
|
||||
}
|
||||
Vendored
+1076
File diff suppressed because it is too large.
Load diff
+49
@@ -0,0 +1,49 @@
|
||||
use std::fmt;
|
||||
|
||||
/// Represents something surrounded by opening and closing strings.
|
||||
pub(crate) struct Quoted<T> {
|
||||
open: &'static str,
|
||||
body: T,
|
||||
close: &'static str,
|
||||
}
|
||||
|
||||
impl<T> Quoted<T> {
|
||||
/// Creates a new instance with matching open and close strings.
|
||||
pub fn new(quote: &'static str, body: T) -> Self {
|
||||
Self {
|
||||
open: quote,
|
||||
body,
|
||||
close: quote,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new instance using a backtick as the open and close string.
|
||||
pub fn backticks(body: T) -> Self {
|
||||
Self::new("`", body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for Quoted<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}{}{}", self.open, self.body, self.close)
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate through a list of items, writing each of them to the target separated
|
||||
/// by a delimiter string.
|
||||
pub(crate) fn write_delimited<T: fmt::Display>(
|
||||
f: &mut impl fmt::Write,
|
||||
items: impl IntoIterator<Item = T>,
|
||||
delimiter: &str,
|
||||
) -> fmt::Result {
|
||||
let mut first = true;
|
||||
for item in items {
|
||||
if !first {
|
||||
write!(f, "{}", delimiter)?;
|
||||
}
|
||||
first = false;
|
||||
write!(f, "{}", item)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
use syn::Attribute;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// Create an instance by parsing a list of attributes.
|
||||
///
|
||||
/// This trait is useful when dealing with items such as traits on traits and impl blocks,
|
||||
/// for which `darling` does not provide dedicated traits.
|
||||
pub trait FromAttributes: Sized {
|
||||
/// Create an instance by parsing a list of attributes.
|
||||
///
|
||||
/// By convention, `FromAttributes` implementations should merge item
|
||||
/// declarations across attributes, so that the following forms are
|
||||
/// equivalent:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[derive(Serialize)]
|
||||
/// #[serde(rename_all = "camel_case")]
|
||||
/// #[serde(borrow)]
|
||||
/// pub struct SplitExample {}
|
||||
///
|
||||
/// #[derive(Serialize)]
|
||||
/// #[serde(borrow, rename_all = "camel_case")]
|
||||
/// pub struct JoinedExample {}
|
||||
/// ```
|
||||
fn from_attributes(attrs: &[Attribute]) -> Result<Self>;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
use syn::DeriveInput;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// Creates an instance by parsing an entire proc-macro `derive` input,
|
||||
/// including the, identity, generics, and visibility of the type.
|
||||
///
|
||||
/// This trait should either be derived or manually implemented by a type
|
||||
/// in the proc macro crate which is directly using `darling`. It is unlikely
|
||||
/// that these implementations will be reusable across crates.
|
||||
pub trait FromDeriveInput: Sized {
|
||||
/// Create an instance from `syn::DeriveInput`, or return an error.
|
||||
fn from_derive_input(input: &DeriveInput) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl FromDeriveInput for () {
|
||||
fn from_derive_input(_: &DeriveInput) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDeriveInput for DeriveInput {
|
||||
fn from_derive_input(input: &DeriveInput) -> Result<Self> {
|
||||
Ok(input.clone())
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
use syn::Field;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// Creates an instance by parsing an individual field and its attributes.
|
||||
pub trait FromField: Sized {
|
||||
fn from_field(field: &Field) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl FromField for () {
|
||||
fn from_field(_: &Field) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromField for Field {
|
||||
fn from_field(field: &Field) -> Result<Self> {
|
||||
Ok(field.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromField for syn::Type {
|
||||
fn from_field(field: &Field) -> Result<Self> {
|
||||
Ok(field.ty.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromField for syn::Visibility {
|
||||
fn from_field(field: &Field) -> Result<Self> {
|
||||
Ok(field.vis.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromField for Vec<syn::Attribute> {
|
||||
fn from_field(field: &Field) -> Result<Self> {
|
||||
Ok(field.attrs.clone())
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
use crate::Result;
|
||||
|
||||
/// Creates an instance by parsing a specific `syn::GenericParam`.
|
||||
/// This can be a type param, a lifetime, or a const param.
|
||||
pub trait FromGenericParam: Sized {
|
||||
fn from_generic_param(param: &syn::GenericParam) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl FromGenericParam for () {
|
||||
fn from_generic_param(_param: &syn::GenericParam) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromGenericParam for syn::GenericParam {
|
||||
fn from_generic_param(param: &syn::GenericParam) -> Result<Self> {
|
||||
Ok(param.clone())
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
use syn::Generics;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// Creates an instance by parsing an entire generics declaration, including the
|
||||
/// `where` clause.
|
||||
pub trait FromGenerics: Sized {
|
||||
fn from_generics(generics: &Generics) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl FromGenerics for () {
|
||||
fn from_generics(_generics: &Generics) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromGenerics for Generics {
|
||||
fn from_generics(generics: &Generics) -> Result<Self> {
|
||||
Ok(generics.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromGenerics> FromGenerics for Result<T> {
|
||||
fn from_generics(generics: &Generics) -> Result<Self> {
|
||||
Ok(FromGenerics::from_generics(generics))
|
||||
}
|
||||
}
|
||||
Vendored
+1300
File diff suppressed because it is too large.
Load diff
+32
@@ -0,0 +1,32 @@
|
||||
use syn::TypeParam;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// Creates an instance by parsing an individual type_param and its attributes.
|
||||
pub trait FromTypeParam: Sized {
|
||||
fn from_type_param(type_param: &TypeParam) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl FromTypeParam for () {
|
||||
fn from_type_param(_: &TypeParam) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromTypeParam for TypeParam {
|
||||
fn from_type_param(type_param: &TypeParam) -> Result<Self> {
|
||||
Ok(type_param.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromTypeParam for Vec<syn::Attribute> {
|
||||
fn from_type_param(type_param: &TypeParam) -> Result<Self> {
|
||||
Ok(type_param.attrs.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromTypeParam for syn::Ident {
|
||||
fn from_type_param(type_param: &TypeParam) -> Result<Self> {
|
||||
Ok(type_param.ident.clone())
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
use syn::Variant;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// Creates an instance from a specified `syn::Variant`.
|
||||
pub trait FromVariant: Sized {
|
||||
/// Create an instance from `syn::Variant`, or return an error.
|
||||
fn from_variant(variant: &Variant) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl FromVariant for () {
|
||||
fn from_variant(_: &Variant) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromVariant for Variant {
|
||||
fn from_variant(variant: &Variant) -> Result<Self> {
|
||||
Ok(variant.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromVariant for syn::Ident {
|
||||
fn from_variant(variant: &Variant) -> Result<Self> {
|
||||
Ok(variant.ident.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromVariant for Vec<syn::Attribute> {
|
||||
fn from_variant(variant: &Variant) -> Result<Self> {
|
||||
Ok(variant.attrs.clone())
|
||||
}
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
#![recursion_limit = "256"]
|
||||
#![cfg_attr(feature = "diagnostics", feature(proc_macro_diagnostic))]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
extern crate proc_macro;
|
||||
|
||||
#[macro_use]
|
||||
mod macros_private;
|
||||
#[macro_use]
|
||||
mod macros_public;
|
||||
|
||||
pub mod ast;
|
||||
pub(crate) mod codegen;
|
||||
pub mod derive;
|
||||
pub mod error;
|
||||
mod from_attributes;
|
||||
mod from_derive_input;
|
||||
mod from_field;
|
||||
mod from_generic_param;
|
||||
mod from_generics;
|
||||
mod from_meta;
|
||||
mod from_type_param;
|
||||
mod from_variant;
|
||||
pub(crate) mod options;
|
||||
pub mod usage;
|
||||
pub mod util;
|
||||
|
||||
pub use self::error::{Error, Result};
|
||||
pub use self::from_attributes::FromAttributes;
|
||||
pub use self::from_derive_input::FromDeriveInput;
|
||||
pub use self::from_field::FromField;
|
||||
pub use self::from_generic_param::FromGenericParam;
|
||||
pub use self::from_generics::FromGenerics;
|
||||
pub use self::from_meta::FromMeta;
|
||||
pub use self::from_type_param::FromTypeParam;
|
||||
pub use self::from_variant::FromVariant;
|
||||
|
||||
// Re-exports
|
||||
#[doc(hidden)]
|
||||
pub use quote::ToTokens;
|
||||
#[doc(hidden)]
|
||||
pub use syn;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
macro_rules! path {
|
||||
($($path:tt)+) => {
|
||||
::syn::parse_quote!($($path)+)
|
||||
//stringify!($($path)+).parse().unwrap()
|
||||
};
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
//! Macros that should be exported from both `darling_core` and `darling`.
|
||||
//! Note that these are **sym-linked** into the main code, and so cannot declare on items that are exported differently
|
||||
//! in `darling_core` vs. `darling`.
|
||||
|
||||
/// Generator for `UsesTypeParam` impls that unions the used type parameters of the selected fields.
|
||||
///
|
||||
/// # Usage
|
||||
/// The macro takes the type implementing the trait as the first argument, then a comma-separated list of
|
||||
/// fields for the rest of its arguments.
|
||||
///
|
||||
/// The type of each passed-in field must implement `UsesTypeParams`, or the resulting code won't compile.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate syn;
|
||||
/// # use darling_core::uses_type_params;
|
||||
/// #
|
||||
/// struct MyField {
|
||||
/// ty: syn::Type,
|
||||
/// }
|
||||
///
|
||||
/// uses_type_params!(MyField, ty);
|
||||
///
|
||||
/// fn main() {
|
||||
/// // no test run
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `darling` cannot derive this trait automatically, as it doesn't know which information extracted from
|
||||
/// proc-macro input is meant to constitute "using" the type parameter, but crate consumers should
|
||||
/// implement it by hand or using the macro.
|
||||
#[macro_export]
|
||||
macro_rules! uses_type_params {
|
||||
($impl_type:ty, $accessor:ident) => {
|
||||
impl $crate::usage::UsesTypeParams for $impl_type {
|
||||
fn uses_type_params<'gen>(
|
||||
&self,
|
||||
options: &$crate::usage::Options,
|
||||
type_set: &'gen $crate::usage::IdentSet
|
||||
) -> $crate::usage::IdentRefSet<'gen> {
|
||||
self.$accessor.uses_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
};
|
||||
($impl_type:ty, $first:ident, $($field:ident),+) => {
|
||||
impl $crate::usage::UsesTypeParams for $impl_type {
|
||||
fn uses_type_params<'gen>(
|
||||
&self,
|
||||
options: &$crate::usage::Options,
|
||||
type_set: &'gen $crate::usage::IdentSet
|
||||
) -> $crate::usage::IdentRefSet<'gen> {
|
||||
let mut hits = self.$first.uses_type_params(options, type_set);
|
||||
$(
|
||||
hits.extend(self.$field.uses_type_params(options, type_set));
|
||||
)*
|
||||
hits
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generator for `UsesLifetimes` impls that unions the used lifetimes of the selected fields.
|
||||
///
|
||||
/// # Usage
|
||||
/// The macro takes the type implementing the trait as the first argument, then a comma-separated list of
|
||||
/// fields for the rest of its arguments.
|
||||
///
|
||||
/// The type of each passed-in field must implement `UsesLifetimes`, or the resulting code won't compile.
|
||||
#[macro_export]
|
||||
macro_rules! uses_lifetimes {
|
||||
($impl_type:ty, $accessor:ident) => {
|
||||
impl $crate::usage::UsesLifetimes for $impl_type {
|
||||
fn uses_lifetimes<'gen>(
|
||||
&self,
|
||||
options: &$crate::usage::Options,
|
||||
type_set: &'gen $crate::usage::LifetimeSet
|
||||
) -> $crate::usage::LifetimeRefSet<'gen> {
|
||||
self.$accessor.uses_lifetimes(options, type_set)
|
||||
}
|
||||
}
|
||||
};
|
||||
($impl_type:ty, $first:ident, $($field:ident),+) => {
|
||||
impl $crate::usage::UsesLifetimes for $impl_type {
|
||||
fn uses_lifetimes<'gen>(
|
||||
&self,
|
||||
options: &$crate::usage::Options,
|
||||
type_set: &'gen $crate::usage::LifetimeSet
|
||||
) -> $crate::usage::LifetimeRefSet<'gen> {
|
||||
let mut hits = self.$first.uses_lifetimes(options, type_set);
|
||||
$(
|
||||
hits.extend(self.$field.uses_lifetimes(options, type_set));
|
||||
)*
|
||||
hits
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
use ident_case::RenameRule;
|
||||
|
||||
use crate::ast::{Data, Fields, Style};
|
||||
use crate::codegen;
|
||||
use crate::codegen::PostfixTransform;
|
||||
use crate::error::Accumulator;
|
||||
use crate::options::{DefaultExpression, InputField, InputVariant, ParseAttribute, ParseData};
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
/// A struct or enum which should have `FromMeta` or `FromDeriveInput` implementations
|
||||
/// generated.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Core {
|
||||
/// The type identifier.
|
||||
pub ident: syn::Ident,
|
||||
|
||||
/// The type's generics. If the type does not use any generics, this will
|
||||
/// be an empty instance.
|
||||
pub generics: syn::Generics,
|
||||
|
||||
/// Controls whether missing properties should cause errors or should be filled by
|
||||
/// the result of a function call. This can be overridden at the field level.
|
||||
pub default: Option<DefaultExpression>,
|
||||
|
||||
/// The rule that should be used to rename all fields/variants in the container.
|
||||
pub rename_rule: RenameRule,
|
||||
|
||||
/// A transform which will be called on `darling::Result<Self>`. It must either be
|
||||
/// an `FnOnce(T) -> T` when `map` is used, or `FnOnce(T) -> darling::Result<T>` when
|
||||
/// `and_then` is used.
|
||||
///
|
||||
/// `map` and `and_then` are mutually-exclusive to avoid confusion about the order in
|
||||
/// which the two are applied.
|
||||
pub post_transform: Option<codegen::PostfixTransform>,
|
||||
|
||||
/// The body of the _deriving_ type.
|
||||
pub data: Data<InputVariant, InputField>,
|
||||
|
||||
/// The custom bound to apply to the generated impl
|
||||
pub bound: Option<Vec<syn::WherePredicate>>,
|
||||
|
||||
/// Whether or not unknown fields should produce an error at compilation time.
|
||||
pub allow_unknown_fields: Option<bool>,
|
||||
}
|
||||
|
||||
impl Core {
|
||||
/// Partially initializes `Core` by reading the identity, generics, and body shape.
|
||||
pub fn start(di: &syn::DeriveInput) -> Result<Self> {
|
||||
Ok(Core {
|
||||
ident: di.ident.clone(),
|
||||
generics: di.generics.clone(),
|
||||
data: Data::try_empty_from(&di.data)?,
|
||||
default: Default::default(),
|
||||
// See https://github.com/TedDriggs/darling/issues/10: We default to snake_case
|
||||
// for enums to help authors produce more idiomatic APIs.
|
||||
rename_rule: if let syn::Data::Enum(_) = di.data {
|
||||
RenameRule::SnakeCase
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
post_transform: Default::default(),
|
||||
bound: Default::default(),
|
||||
allow_unknown_fields: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_codegen_default(&self) -> Option<codegen::DefaultExpression<'_>> {
|
||||
self.default.as_ref().map(|expr| match *expr {
|
||||
DefaultExpression::Explicit(ref path) => codegen::DefaultExpression::Explicit(path),
|
||||
DefaultExpression::Inherit => {
|
||||
// It should be impossible for any input to get here,
|
||||
// so panic rather than returning an error or pretending
|
||||
// everything is fine.
|
||||
panic!("DefaultExpression::Inherit is not valid at container level")
|
||||
}
|
||||
DefaultExpression::Trait { span } => codegen::DefaultExpression::Trait { span },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for Core {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
let path = mi.path();
|
||||
|
||||
if path.is_ident("default") {
|
||||
if self.default.is_some() {
|
||||
return Err(Error::duplicate_field("default").with_span(mi));
|
||||
}
|
||||
|
||||
self.default = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("rename_all") {
|
||||
// WARNING: This may have been set based on body shape previously,
|
||||
// so an overwrite may be permissible.
|
||||
self.rename_rule = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("map") || path.is_ident("and_then") {
|
||||
// This unwrap is safe because we just called is_ident above
|
||||
let transformer = path.get_ident().unwrap().clone();
|
||||
|
||||
if let Some(post_transform) = &self.post_transform {
|
||||
if transformer == post_transform.transformer {
|
||||
return Err(Error::duplicate_field(&transformer.to_string()).with_span(mi));
|
||||
} else {
|
||||
return Err(Error::custom(format!(
|
||||
"Options `{}` and `{}` are mutually exclusive",
|
||||
transformer, post_transform.transformer
|
||||
))
|
||||
.with_span(mi));
|
||||
}
|
||||
}
|
||||
|
||||
self.post_transform =
|
||||
Some(PostfixTransform::new(transformer, FromMeta::from_meta(mi)?));
|
||||
} else if path.is_ident("bound") {
|
||||
self.bound = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("allow_unknown_fields") {
|
||||
if self.allow_unknown_fields.is_some() {
|
||||
return Err(Error::duplicate_field("allow_unknown_fields").with_span(mi));
|
||||
}
|
||||
|
||||
self.allow_unknown_fields = FromMeta::from_meta(mi)?;
|
||||
} else {
|
||||
return Err(Error::unknown_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for Core {
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
let v = InputVariant::from_variant(variant, Some(self))?;
|
||||
|
||||
match self.data {
|
||||
Data::Enum(ref mut variants) => {
|
||||
variants.push(v);
|
||||
Ok(())
|
||||
}
|
||||
Data::Struct(_) => panic!("Core::parse_variant should never be called for a struct"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
let f = InputField::from_field(field, Some(self))?;
|
||||
|
||||
match self.data {
|
||||
Data::Struct(Fields {
|
||||
style: Style::Unit, ..
|
||||
}) => panic!("Core::parse_field should not be called on unit"),
|
||||
Data::Struct(Fields { ref mut fields, .. }) => {
|
||||
fields.push(f);
|
||||
Ok(())
|
||||
}
|
||||
Data::Enum(_) => panic!("Core::parse_field should never be called for an enum"),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut Accumulator) {
|
||||
if let Data::Struct(fields) = &self.data {
|
||||
let flatten_targets: Vec<_> = fields
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
if field.flatten.is_present() {
|
||||
Some(field.flatten)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if flatten_targets.len() > 1 {
|
||||
for flatten in flatten_targets {
|
||||
errors.push(
|
||||
Error::custom("`#[darling(flatten)]` can only be applied to one field")
|
||||
.with_span(&flatten.span()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Core> for codegen::TraitImpl<'a> {
|
||||
fn from(v: &'a Core) -> Self {
|
||||
codegen::TraitImpl {
|
||||
ident: &v.ident,
|
||||
generics: &v.generics,
|
||||
data: v
|
||||
.data
|
||||
.as_ref()
|
||||
.map_struct_fields(InputField::as_codegen_field)
|
||||
.map_enum_variants(|variant| variant.as_codegen_variant(&v.ident)),
|
||||
default: v.as_codegen_default(),
|
||||
post_transform: v.post_transform.as_ref(),
|
||||
allow_unknown_fields: v.allow_unknown_fields.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::ast::NestedMeta;
|
||||
use crate::util::PathList;
|
||||
use crate::{FromMeta, Result};
|
||||
|
||||
/// A rule about which attributes to forward to the generated struct.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ForwardAttrsFilter {
|
||||
All,
|
||||
Only(PathList),
|
||||
}
|
||||
|
||||
impl ForwardAttrsFilter {
|
||||
/// Returns `true` if this will not forward any attributes.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
ForwardAttrsFilter::All => false,
|
||||
ForwardAttrsFilter::Only(ref list) => list.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for ForwardAttrsFilter {
|
||||
fn from_word() -> Result<Self> {
|
||||
Ok(ForwardAttrsFilter::All)
|
||||
}
|
||||
|
||||
fn from_list(nested: &[NestedMeta]) -> Result<Self> {
|
||||
Ok(ForwardAttrsFilter::Only(PathList::from_list(nested)?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{Ident, Path};
|
||||
|
||||
use crate::{Error, FromField, FromMeta};
|
||||
|
||||
use super::ParseAttribute;
|
||||
|
||||
/// A forwarded field and attributes that influence its behavior.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ForwardedField {
|
||||
/// The ident of the field that will receive the forwarded value.
|
||||
pub ident: Ident,
|
||||
/// Path of the function that will be called to convert the forwarded value
|
||||
/// into the type expected by the field in `ident`.
|
||||
pub with: Option<Path>,
|
||||
}
|
||||
|
||||
impl ForwardedField {
|
||||
/// Returns a field initializer for this forwarded field.
|
||||
pub fn as_initializer(&self) -> Initializer<'_> {
|
||||
Initializer(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromField for ForwardedField {
|
||||
fn from_field(field: &syn::Field) -> crate::Result<Self> {
|
||||
let result = Self {
|
||||
ident: field.ident.clone().ok_or_else(|| {
|
||||
Error::custom("forwarded field must be named field").with_span(field)
|
||||
})?,
|
||||
with: None,
|
||||
};
|
||||
|
||||
result.parse_attributes(&field.attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for ForwardedField {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> crate::Result<()> {
|
||||
if mi.path().is_ident("with") {
|
||||
if self.with.is_some() {
|
||||
return Err(Error::duplicate_field_path(mi.path()).with_span(mi));
|
||||
}
|
||||
|
||||
self.with = FromMeta::from_meta(mi)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::unknown_field_path_with_alts(mi.path(), &["with"]).with_span(mi))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A field initializer that assumes:
|
||||
///
|
||||
/// 1. There is a local variable with the same ident as `self.ident`
|
||||
/// 2. That local variable is an `Option`
|
||||
/// 3. That any errors were already checked by an accumulator.
|
||||
pub struct Initializer<'a>(&'a ForwardedField);
|
||||
|
||||
impl ToTokens for Initializer<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let ident = &self.0.ident;
|
||||
tokens.append_all(quote!(#ident: #ident.expect("Errors were already checked"),));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use quote::ToTokens;
|
||||
|
||||
use crate::{ast::Data, codegen::FromAttributesImpl, Error, Result};
|
||||
|
||||
use super::{OuterFrom, ParseAttribute, ParseData};
|
||||
|
||||
/// Receiver for derived `FromAttributes` impls.
|
||||
pub struct FromAttributesOptions {
|
||||
// Note: FromAttributes has no behaviors beyond those common
|
||||
// to all the `OuterFrom` traits.
|
||||
pub base: OuterFrom,
|
||||
}
|
||||
|
||||
impl FromAttributesOptions {
|
||||
pub fn new(di: &syn::DeriveInput) -> Result<Self> {
|
||||
let opts = (Self {
|
||||
base: OuterFrom::start(di)?,
|
||||
})
|
||||
.parse_attributes(&di.attrs)?
|
||||
.parse_body(&di.data)?;
|
||||
|
||||
if !opts.is_newtype() && opts.base.attr_names.is_empty() {
|
||||
Err(Error::custom(
|
||||
"FromAttributes without attributes collects nothing",
|
||||
))
|
||||
} else {
|
||||
Ok(opts)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_newtype(&self) -> bool {
|
||||
if let Data::Struct(ref data) = self.base.container.data {
|
||||
data.is_newtype()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for FromAttributesOptions {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
self.base.parse_nested(mi)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for FromAttributesOptions {
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
self.base.parse_variant(variant)
|
||||
}
|
||||
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
self.base.parse_field(field)
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut crate::error::Accumulator) {
|
||||
self.base.validate_body(errors);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FromAttributesOptions> for FromAttributesImpl<'a> {
|
||||
fn from(v: &'a FromAttributesOptions) -> Self {
|
||||
FromAttributesImpl {
|
||||
base: (&v.base.container).into(),
|
||||
attr_names: &v.base.attr_names,
|
||||
forward_attrs: v.base.as_forward_attrs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FromAttributesOptions {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
FromAttributesImpl::from(self).to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::Ident;
|
||||
|
||||
use crate::codegen::FromDeriveInputImpl;
|
||||
use crate::options::{DeriveInputShapeSet, OuterFrom, ParseAttribute, ParseData};
|
||||
use crate::{FromField, FromMeta, Result};
|
||||
|
||||
use super::forwarded_field::ForwardedField;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FdiOptions {
|
||||
pub base: OuterFrom,
|
||||
|
||||
/// The field on the target struct which should receive the type visibility, if any.
|
||||
pub vis: Option<Ident>,
|
||||
|
||||
/// The field on the target struct which should receive the type generics, if any.
|
||||
pub generics: Option<ForwardedField>,
|
||||
|
||||
/// The field on the target struct which should receive the derive input body, if any.
|
||||
pub data: Option<ForwardedField>,
|
||||
|
||||
pub supports: Option<DeriveInputShapeSet>,
|
||||
}
|
||||
|
||||
impl FdiOptions {
|
||||
pub fn new(di: &syn::DeriveInput) -> Result<Self> {
|
||||
(FdiOptions {
|
||||
base: OuterFrom::start(di)?,
|
||||
vis: Default::default(),
|
||||
generics: Default::default(),
|
||||
data: Default::default(),
|
||||
supports: Default::default(),
|
||||
})
|
||||
.parse_attributes(&di.attrs)?
|
||||
.parse_body(&di.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for FdiOptions {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
if mi.path().is_ident("supports") {
|
||||
self.supports = FromMeta::from_meta(mi)?;
|
||||
Ok(())
|
||||
} else {
|
||||
self.base.parse_nested(mi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for FdiOptions {
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
self.base.parse_variant(variant)
|
||||
}
|
||||
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
match field.ident.as_ref().map(|v| v.to_string()).as_deref() {
|
||||
Some("vis") => {
|
||||
self.vis.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
Some("data") => {
|
||||
self.data = ForwardedField::from_field(field).map(Some)?;
|
||||
Ok(())
|
||||
}
|
||||
Some("generics") => {
|
||||
self.generics = ForwardedField::from_field(field).map(Some)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => self.base.parse_field(field),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut crate::error::Accumulator) {
|
||||
self.base.validate_body(errors);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FdiOptions> for FromDeriveInputImpl<'a> {
|
||||
fn from(v: &'a FdiOptions) -> Self {
|
||||
FromDeriveInputImpl {
|
||||
base: (&v.base.container).into(),
|
||||
attr_names: &v.base.attr_names,
|
||||
from_ident: v.base.from_ident,
|
||||
ident: v.base.ident.as_ref(),
|
||||
vis: v.vis.as_ref(),
|
||||
data: v.data.as_ref(),
|
||||
generics: v.generics.as_ref(),
|
||||
forward_attrs: v.base.as_forward_attrs(),
|
||||
supports: v.supports.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FdiOptions {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
FromDeriveInputImpl::from(self).to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::Ident;
|
||||
|
||||
use crate::codegen::FromFieldImpl;
|
||||
use crate::options::{OuterFrom, ParseAttribute, ParseData};
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FromFieldOptions {
|
||||
pub base: OuterFrom,
|
||||
pub vis: Option<Ident>,
|
||||
pub ty: Option<Ident>,
|
||||
}
|
||||
|
||||
impl FromFieldOptions {
|
||||
pub fn new(di: &syn::DeriveInput) -> Result<Self> {
|
||||
(FromFieldOptions {
|
||||
base: OuterFrom::start(di)?,
|
||||
vis: Default::default(),
|
||||
ty: Default::default(),
|
||||
})
|
||||
.parse_attributes(&di.attrs)?
|
||||
.parse_body(&di.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for FromFieldOptions {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
self.base.parse_nested(mi)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for FromFieldOptions {
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
self.base.parse_variant(variant)
|
||||
}
|
||||
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
match field.ident.as_ref().map(|v| v.to_string()).as_deref() {
|
||||
Some("vis") => {
|
||||
self.vis.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
Some("ty") => {
|
||||
self.ty.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
_ => self.base.parse_field(field),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut crate::error::Accumulator) {
|
||||
self.base.validate_body(errors);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FromFieldOptions> for FromFieldImpl<'a> {
|
||||
fn from(v: &'a FromFieldOptions) -> Self {
|
||||
FromFieldImpl {
|
||||
ident: v.base.ident.as_ref(),
|
||||
vis: v.vis.as_ref(),
|
||||
ty: v.ty.as_ref(),
|
||||
base: (&v.base.container).into(),
|
||||
attr_names: &v.base.attr_names,
|
||||
forward_attrs: v.base.as_forward_attrs(),
|
||||
from_ident: v.base.from_ident,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FromFieldOptions {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
FromFieldImpl::from(self).to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_quote;
|
||||
|
||||
use crate::ast::Data;
|
||||
use crate::codegen::FromMetaImpl;
|
||||
use crate::error::Accumulator;
|
||||
use crate::options::{Core, ParseAttribute, ParseData};
|
||||
use crate::util::Callable;
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
pub struct FromMetaOptions {
|
||||
base: Core,
|
||||
/// Override for the default [`FromMeta::from_word`] method.
|
||||
from_word: Option<Callable>,
|
||||
/// Override for the default [`FromMeta::from_none`] method.
|
||||
from_none: Option<Callable>,
|
||||
/// Override for the default [`FromMeta::from_expr`] method.
|
||||
from_expr: Option<Callable>,
|
||||
/// Whether or not to derive [`syn::parse::Parse`] in addition to deriving [`FromMeta`].
|
||||
derive_syn_parse: Option<bool>,
|
||||
}
|
||||
|
||||
impl FromMetaOptions {
|
||||
pub fn new(di: &syn::DeriveInput) -> Result<Self> {
|
||||
(FromMetaOptions {
|
||||
base: Core::start(di)?,
|
||||
from_word: None,
|
||||
from_none: None,
|
||||
from_expr: None,
|
||||
derive_syn_parse: None,
|
||||
})
|
||||
.parse_attributes(&di.attrs)?
|
||||
.parse_body(&di.data)
|
||||
}
|
||||
|
||||
/// Get the `from_word` method body, if one exists. This can come from direct use of
|
||||
/// `#[darling(from_word = ...)]` on the container or from use of `#[darling(word)]` on
|
||||
/// a unit variant.
|
||||
#[allow(
|
||||
clippy::wrong_self_convention,
|
||||
// The reason is commented out due to MSRV issues.
|
||||
// reason = "This matches the name of the input option and output method"
|
||||
)]
|
||||
fn from_word(&self) -> Option<Cow<'_, Callable>> {
|
||||
self.from_word.as_ref().map(Cow::Borrowed).or_else(|| {
|
||||
if let Data::Enum(ref variants) = self.base.data {
|
||||
// The first variant which has `word` set to `true`.
|
||||
// This assumes that validation has prevented multiple variants
|
||||
// from claiming `word`.
|
||||
let variant = variants
|
||||
.iter()
|
||||
.find(|v| v.word.map(|x| *x).unwrap_or_default())?;
|
||||
let variant_ident = &variant.ident;
|
||||
let closure: syn::ExprClosure = parse_quote! {
|
||||
|| ::darling::export::Ok(Self::#variant_ident)
|
||||
};
|
||||
Some(Cow::Owned(Callable::from(closure)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for FromMetaOptions {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
let path = mi.path();
|
||||
|
||||
if path.is_ident("from_word") {
|
||||
if self.from_word.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(path));
|
||||
}
|
||||
|
||||
self.from_word = FromMeta::from_meta(mi).map(Some)?;
|
||||
} else if path.is_ident("from_none") {
|
||||
if self.from_none.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(path));
|
||||
}
|
||||
|
||||
self.from_none = FromMeta::from_meta(mi).map(Some)?;
|
||||
} else if path.is_ident("from_expr") {
|
||||
if self.from_expr.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(path));
|
||||
}
|
||||
|
||||
self.from_expr = FromMeta::from_meta(mi).map(Some)?;
|
||||
} else if path.is_ident("derive_syn_parse") {
|
||||
if self.derive_syn_parse.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(path));
|
||||
}
|
||||
|
||||
self.derive_syn_parse = FromMeta::from_meta(mi).map(Some)?;
|
||||
} else {
|
||||
self.base.parse_nested(mi)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for FromMetaOptions {
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
self.base.parse_variant(variant)
|
||||
}
|
||||
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
self.base.parse_field(field)
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut Accumulator) {
|
||||
self.base.validate_body(errors);
|
||||
|
||||
match self.base.data {
|
||||
Data::Struct(ref data) => {
|
||||
if let Some(from_word) = &self.from_word {
|
||||
if data.is_unit() {
|
||||
errors.push(Error::custom("`from_word` cannot be used on unit structs because it conflicts with the generated impl").with_span(from_word));
|
||||
} else if data.is_newtype() {
|
||||
errors.push(Error::custom("`from_word` cannot be used on newtype structs because the implementation is entirely delegated to the inner type").with_span(from_word));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(from_expr) = &self.from_expr {
|
||||
if data.is_newtype() {
|
||||
errors.push(Error::custom("`from_expr` cannot be used on newtype structs because the implementation is entirely delegated to the inner type").with_span(from_expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::Enum(ref data) => {
|
||||
let word_variants: Vec<_> = data
|
||||
.iter()
|
||||
.filter_map(|variant| variant.word.as_ref())
|
||||
.collect();
|
||||
|
||||
if !word_variants.is_empty() {
|
||||
if let Some(from_word) = &self.from_word {
|
||||
errors.push(
|
||||
Error::custom(
|
||||
"`from_word` cannot be used with an enum that also uses `word`",
|
||||
)
|
||||
.with_span(from_word),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds errors for duplicate `#[darling(word)]` annotations across all variants.
|
||||
if word_variants.len() > 1 {
|
||||
for word in word_variants {
|
||||
errors.push(
|
||||
Error::custom("`#[darling(word)]` can only be applied to one variant")
|
||||
.with_span(&word.span()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(from_expr) = &self.from_expr {
|
||||
if data.iter().any(|v| v.is_unit_variant() && !v.is_skipped()) {
|
||||
errors.push(
|
||||
Error::custom("`from_expr` cannot be used on enums with non-skipped unit variants because it conflicts with the generated impl")
|
||||
.with_span(from_expr),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FromMetaOptions> for FromMetaImpl<'a> {
|
||||
fn from(v: &'a FromMetaOptions) -> Self {
|
||||
FromMetaImpl {
|
||||
base: (&v.base).into(),
|
||||
from_word: v.from_word(),
|
||||
from_none: v.from_none.as_ref(),
|
||||
from_expr: v.from_expr.as_ref(),
|
||||
derive_syn_parse: v.derive_syn_parse.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FromMetaOptions {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
FromMetaImpl::from(self).to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::Ident;
|
||||
|
||||
use crate::codegen::FromTypeParamImpl;
|
||||
use crate::options::{OuterFrom, ParseAttribute, ParseData};
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FromTypeParamOptions {
|
||||
pub base: OuterFrom,
|
||||
pub bounds: Option<Ident>,
|
||||
pub default: Option<Ident>,
|
||||
}
|
||||
|
||||
impl FromTypeParamOptions {
|
||||
pub fn new(di: &syn::DeriveInput) -> Result<Self> {
|
||||
(FromTypeParamOptions {
|
||||
base: OuterFrom::start(di)?,
|
||||
bounds: None,
|
||||
default: None,
|
||||
})
|
||||
.parse_attributes(&di.attrs)?
|
||||
.parse_body(&di.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for FromTypeParamOptions {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
self.base.parse_nested(mi)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for FromTypeParamOptions {
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
self.base.parse_variant(variant)
|
||||
}
|
||||
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
match field.ident.as_ref().map(|v| v.to_string()).as_deref() {
|
||||
Some("bounds") => {
|
||||
self.bounds.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
Some("default") => {
|
||||
self.default.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
_ => self.base.parse_field(field),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut crate::error::Accumulator) {
|
||||
self.base.validate_body(errors);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FromTypeParamOptions> for FromTypeParamImpl<'a> {
|
||||
fn from(v: &'a FromTypeParamOptions) -> Self {
|
||||
FromTypeParamImpl {
|
||||
base: (&v.base.container).into(),
|
||||
ident: v.base.ident.as_ref(),
|
||||
bounds: v.bounds.as_ref(),
|
||||
default: v.default.as_ref(),
|
||||
attr_names: &v.base.attr_names,
|
||||
forward_attrs: v.base.as_forward_attrs(),
|
||||
from_ident: v.base.from_ident,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FromTypeParamOptions {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
FromTypeParamImpl::from(self).to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::{DeriveInput, Field, Ident, Meta};
|
||||
|
||||
use crate::codegen::FromVariantImpl;
|
||||
use crate::options::{DataShape, OuterFrom, ParseAttribute, ParseData};
|
||||
use crate::{FromMeta, Result};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FromVariantOptions {
|
||||
pub base: OuterFrom,
|
||||
/// The field on the deriving struct into which the discriminant expression
|
||||
/// should be placed by the derived `FromVariant` impl.
|
||||
pub discriminant: Option<Ident>,
|
||||
pub fields: Option<Ident>,
|
||||
pub supports: Option<DataShape>,
|
||||
}
|
||||
|
||||
impl FromVariantOptions {
|
||||
pub fn new(di: &DeriveInput) -> Result<Self> {
|
||||
(FromVariantOptions {
|
||||
base: OuterFrom::start(di)?,
|
||||
discriminant: Default::default(),
|
||||
fields: Default::default(),
|
||||
supports: Default::default(),
|
||||
})
|
||||
.parse_attributes(&di.attrs)?
|
||||
.parse_body(&di.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a FromVariantOptions> for FromVariantImpl<'a> {
|
||||
fn from(v: &'a FromVariantOptions) -> Self {
|
||||
FromVariantImpl {
|
||||
base: (&v.base.container).into(),
|
||||
ident: v.base.ident.as_ref(),
|
||||
discriminant: v.discriminant.as_ref(),
|
||||
fields: v.fields.as_ref(),
|
||||
attr_names: &v.base.attr_names,
|
||||
forward_attrs: v.base.as_forward_attrs(),
|
||||
from_ident: v.base.from_ident,
|
||||
supports: v.supports.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for FromVariantOptions {
|
||||
fn parse_nested(&mut self, mi: &Meta) -> Result<()> {
|
||||
if mi.path().is_ident("supports") {
|
||||
self.supports = FromMeta::from_meta(mi)?;
|
||||
Ok(())
|
||||
} else {
|
||||
self.base.parse_nested(mi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for FromVariantOptions {
|
||||
fn parse_field(&mut self, field: &Field) -> Result<()> {
|
||||
match field.ident.as_ref().map(|v| v.to_string()).as_deref() {
|
||||
Some("discriminant") => {
|
||||
self.discriminant.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
Some("fields") => {
|
||||
self.fields.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
_ => self.base.parse_field(field),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut crate::error::Accumulator) {
|
||||
self.base.validate_body(errors);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FromVariantOptions {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
FromVariantImpl::from(self).to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use syn::{parse_quote_spanned, spanned::Spanned};
|
||||
|
||||
use crate::codegen;
|
||||
use crate::options::{Core, DefaultExpression, ParseAttribute};
|
||||
use crate::util::{Callable, Flag, SpannedValue};
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputField {
|
||||
pub ident: syn::Ident,
|
||||
pub attr_name: Option<String>,
|
||||
pub ty: syn::Type,
|
||||
pub default: Option<DefaultExpression>,
|
||||
pub with: Option<Callable>,
|
||||
|
||||
/// If `true`, generated code will not look for this field in the input meta item,
|
||||
/// instead always falling back to either `InputField::default` or `Default::default`.
|
||||
pub skip: Option<SpannedValue<bool>>,
|
||||
pub post_transform: Option<codegen::PostfixTransform>,
|
||||
pub multiple: Option<bool>,
|
||||
pub flatten: Flag,
|
||||
}
|
||||
|
||||
impl InputField {
|
||||
/// Generate a view into this field that can be used for code generation.
|
||||
pub fn as_codegen_field(&self) -> codegen::Field<'_> {
|
||||
codegen::Field {
|
||||
ident: &self.ident,
|
||||
name_in_attr: self
|
||||
.attr_name
|
||||
.as_deref()
|
||||
.map_or_else(|| Cow::Owned(self.ident.to_string()), Cow::Borrowed),
|
||||
ty: &self.ty,
|
||||
default_expression: self.as_codegen_default(),
|
||||
with_callable: self.with.as_ref().map(|w| w.as_ref()).map_or_else(
|
||||
|| {
|
||||
Cow::Owned(
|
||||
parse_quote_spanned!(self.ty.span()=> ::darling::FromMeta::from_meta),
|
||||
)
|
||||
},
|
||||
Cow::Borrowed,
|
||||
),
|
||||
skip: *self.skip.unwrap_or_default(),
|
||||
post_transform: self.post_transform.as_ref(),
|
||||
multiple: self.multiple.unwrap_or_default(),
|
||||
flatten: self.flatten.is_present(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a codegen::DefaultExpression for this field. This requires the field name
|
||||
/// in the `Inherit` case.
|
||||
fn as_codegen_default(&self) -> Option<codegen::DefaultExpression<'_>> {
|
||||
self.default.as_ref().map(|expr| match *expr {
|
||||
DefaultExpression::Explicit(ref path) => codegen::DefaultExpression::Explicit(path),
|
||||
DefaultExpression::Inherit => codegen::DefaultExpression::Inherit(&self.ident),
|
||||
DefaultExpression::Trait { span } => codegen::DefaultExpression::Trait { span },
|
||||
})
|
||||
}
|
||||
|
||||
fn new(ident: syn::Ident, ty: syn::Type) -> Self {
|
||||
InputField {
|
||||
ident,
|
||||
ty,
|
||||
attr_name: None,
|
||||
default: None,
|
||||
with: None,
|
||||
skip: None,
|
||||
post_transform: Default::default(),
|
||||
multiple: None,
|
||||
flatten: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_field(f: &syn::Field, parent: Option<&Core>) -> Result<Self> {
|
||||
let ident = f
|
||||
.ident
|
||||
.clone()
|
||||
.unwrap_or_else(|| syn::Ident::new("__unnamed", ::proc_macro2::Span::call_site()));
|
||||
let ty = f.ty.clone();
|
||||
let base = Self::new(ident, ty).parse_attributes(&f.attrs)?;
|
||||
|
||||
Ok(if let Some(container) = parent {
|
||||
base.with_inherited(container)
|
||||
} else {
|
||||
base
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply inherited settings from the container. This is done _after_ parsing
|
||||
/// to ensure deference to explicit field-level settings.
|
||||
fn with_inherited(mut self, parent: &Core) -> Self {
|
||||
// explicit renamings take precedence over rename rules on the container,
|
||||
// but in the absence of an explicit name we apply the rule.
|
||||
if self.attr_name.is_none() {
|
||||
self.attr_name = Some(parent.rename_rule.apply_to_field(self.ident.to_string()));
|
||||
}
|
||||
|
||||
// Determine the default expression for this field, based on three pieces of information:
|
||||
// 1. Will we look for this field in the attribute?
|
||||
// 1. Is there a locally-defined default?
|
||||
// 1. Did the parent define a default?
|
||||
self.default = match (&self.skip, self.default.is_some(), parent.default.is_some()) {
|
||||
// If we have a default, use it.
|
||||
(_, true, _) => self.default,
|
||||
|
||||
// If there isn't an explicit default but the struct sets a default, we'll
|
||||
// inherit from that.
|
||||
(_, false, true) => Some(DefaultExpression::Inherit),
|
||||
|
||||
// If we're skipping the field and no defaults have been expressed then we should
|
||||
// use the ::darling::export::Default trait, and set the span to the skip keyword
|
||||
// so that an error caused by the skipped field's type not implementing `Default`
|
||||
// will correctly identify why darling is trying to use `Default`.
|
||||
(Some(v), false, false) if **v => Some(DefaultExpression::Trait { span: v.span() }),
|
||||
|
||||
// If we don't have or need a default, then leave it blank.
|
||||
(_, false, false) => None,
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for InputField {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
let path = mi.path();
|
||||
|
||||
if path.is_ident("rename") {
|
||||
if self.attr_name.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.attr_name = FromMeta::from_meta(mi)?;
|
||||
|
||||
if self.flatten.is_present() {
|
||||
return Err(
|
||||
Error::custom("`flatten` and `rename` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
} else if path.is_ident("default") {
|
||||
if self.default.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
self.default = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("with") {
|
||||
if self.with.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.with = Some(FromMeta::from_meta(mi)?);
|
||||
|
||||
if self.flatten.is_present() {
|
||||
return Err(
|
||||
Error::custom("`flatten` and `with` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
} else if path.is_ident("skip") {
|
||||
if self.skip.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.skip = FromMeta::from_meta(mi)?;
|
||||
|
||||
if self.skip.map(|v| *v).unwrap_or_default() && self.flatten.is_present() {
|
||||
return Err(
|
||||
Error::custom("`flatten` and `skip` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
} else if path.is_ident("map") || path.is_ident("and_then") {
|
||||
let transformer = path.get_ident().unwrap().clone();
|
||||
if let Some(post_transform) = &self.post_transform {
|
||||
if transformer == post_transform.transformer {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
} else {
|
||||
return Err(Error::custom(format!(
|
||||
"Options `{}` and `{}` are mutually exclusive",
|
||||
transformer, post_transform.transformer
|
||||
))
|
||||
.with_span(mi));
|
||||
}
|
||||
}
|
||||
|
||||
self.post_transform = Some(codegen::PostfixTransform::new(
|
||||
transformer,
|
||||
FromMeta::from_meta(mi)?,
|
||||
));
|
||||
} else if path.is_ident("multiple") {
|
||||
if self.multiple.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.multiple = FromMeta::from_meta(mi)?;
|
||||
|
||||
if self.multiple == Some(true) && self.flatten.is_present() {
|
||||
return Err(
|
||||
Error::custom("`flatten` and `multiple` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
} else if path.is_ident("flatten") {
|
||||
if self.flatten.is_present() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.flatten = FromMeta::from_meta(mi)?;
|
||||
|
||||
let mut conflicts = Error::accumulator();
|
||||
|
||||
if self.multiple == Some(true) {
|
||||
conflicts.push(
|
||||
Error::custom("`flatten` and `multiple` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
|
||||
if self.attr_name.is_some() {
|
||||
conflicts.push(
|
||||
Error::custom("`flatten` and `rename` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
|
||||
if self.with.is_some() {
|
||||
conflicts.push(
|
||||
Error::custom("`flatten` and `with` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
|
||||
if self.skip.map(|v| *v).unwrap_or_default() {
|
||||
conflicts.push(
|
||||
Error::custom("`flatten` and `skip` cannot be used together").with_span(mi),
|
||||
);
|
||||
}
|
||||
|
||||
conflicts.finish()?;
|
||||
} else {
|
||||
return Err(Error::unknown_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::ast::Fields;
|
||||
use crate::codegen;
|
||||
use crate::options::{Core, InputField, ParseAttribute};
|
||||
use crate::util::SpannedValue;
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputVariant {
|
||||
pub ident: syn::Ident,
|
||||
attr_name: Option<String>,
|
||||
data: Fields<InputField>,
|
||||
skip: Option<bool>,
|
||||
/// Whether or not the variant should be used to create an instance for
|
||||
/// `FromMeta::from_word`.
|
||||
pub word: Option<SpannedValue<bool>>,
|
||||
/// Whether or not unknown fields are acceptable in this
|
||||
allow_unknown_fields: Option<bool>,
|
||||
}
|
||||
|
||||
impl InputVariant {
|
||||
pub fn is_unit_variant(&self) -> bool {
|
||||
self.data.is_unit()
|
||||
}
|
||||
|
||||
pub fn is_skipped(&self) -> bool {
|
||||
self.skip.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn as_codegen_variant<'a>(&'a self, ty_ident: &'a syn::Ident) -> codegen::Variant<'a> {
|
||||
codegen::Variant {
|
||||
ty_ident,
|
||||
variant_ident: &self.ident,
|
||||
name_in_attr: self
|
||||
.attr_name
|
||||
.as_deref()
|
||||
.map_or_else(|| Cow::Owned(self.ident.to_string()), Cow::Borrowed),
|
||||
data: self.data.as_ref().map(InputField::as_codegen_field),
|
||||
skip: self.is_skipped(),
|
||||
allow_unknown_fields: self.allow_unknown_fields.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_variant(v: &syn::Variant, parent: Option<&Core>) -> Result<Self> {
|
||||
let mut starter = (InputVariant {
|
||||
ident: v.ident.clone(),
|
||||
attr_name: Default::default(),
|
||||
data: Fields::empty_from(&v.fields),
|
||||
skip: Default::default(),
|
||||
word: Default::default(),
|
||||
allow_unknown_fields: None,
|
||||
})
|
||||
.parse_attributes(&v.attrs)?;
|
||||
|
||||
starter.data.fields = match v.fields {
|
||||
syn::Fields::Unit => vec![],
|
||||
syn::Fields::Unnamed(ref fields) => {
|
||||
let mut items = Vec::with_capacity(fields.unnamed.len());
|
||||
for item in &fields.unnamed {
|
||||
items.push(InputField::from_field(item, parent)?);
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
syn::Fields::Named(ref fields) => {
|
||||
let mut items = Vec::with_capacity(fields.named.len());
|
||||
for item in &fields.named {
|
||||
items.push(InputField::from_field(item, parent)?);
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
};
|
||||
|
||||
Ok(if let Some(p) = parent {
|
||||
starter.with_inherited(p)
|
||||
} else {
|
||||
starter
|
||||
})
|
||||
}
|
||||
|
||||
fn with_inherited(mut self, parent: &Core) -> Self {
|
||||
if self.attr_name.is_none() {
|
||||
self.attr_name = Some(parent.rename_rule.apply_to_variant(self.ident.to_string()));
|
||||
}
|
||||
|
||||
if self.allow_unknown_fields.is_none() {
|
||||
self.allow_unknown_fields = Some(parent.allow_unknown_fields.unwrap_or_default());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for InputVariant {
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
|
||||
let path = mi.path();
|
||||
if path.is_ident("rename") {
|
||||
if self.attr_name.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.attr_name = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("skip") {
|
||||
if self.skip.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
self.skip = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("word") {
|
||||
if self.word.is_some() {
|
||||
return Err(Error::duplicate_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
if !self.data.is_unit() {
|
||||
let note = "`#[darling(word)]` can only be applied to a unit variant";
|
||||
#[cfg(feature = "diagnostics")]
|
||||
let error = Error::unknown_field_path(path).note(note);
|
||||
#[cfg(not(feature = "diagnostics"))]
|
||||
let error = Error::custom(format!("Unexpected field: `word`. {}", note));
|
||||
|
||||
return Err(error.with_span(mi));
|
||||
}
|
||||
|
||||
self.word = FromMeta::from_meta(mi)?;
|
||||
} else {
|
||||
return Err(Error::unknown_field_path(path).with_span(mi));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
use proc_macro2::Span;
|
||||
use syn::{parse_quote, spanned::Spanned};
|
||||
|
||||
use crate::ast::NestedMeta;
|
||||
use crate::error::Accumulator;
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
mod core;
|
||||
mod forward_attrs;
|
||||
mod forwarded_field;
|
||||
mod from_attributes;
|
||||
mod from_derive;
|
||||
mod from_field;
|
||||
mod from_meta;
|
||||
mod from_type_param;
|
||||
mod from_variant;
|
||||
mod input_field;
|
||||
mod input_variant;
|
||||
mod outer_from;
|
||||
mod shape;
|
||||
|
||||
pub use self::core::Core;
|
||||
pub use self::forward_attrs::ForwardAttrsFilter;
|
||||
pub use self::forwarded_field::ForwardedField;
|
||||
pub use self::from_attributes::FromAttributesOptions;
|
||||
pub use self::from_derive::FdiOptions;
|
||||
pub use self::from_field::FromFieldOptions;
|
||||
pub use self::from_meta::FromMetaOptions;
|
||||
pub use self::from_type_param::FromTypeParamOptions;
|
||||
pub use self::from_variant::FromVariantOptions;
|
||||
pub use self::input_field::InputField;
|
||||
pub use self::input_variant::InputVariant;
|
||||
pub use self::outer_from::OuterFrom;
|
||||
pub use self::shape::{DataShape, DeriveInputShapeSet};
|
||||
|
||||
/// A default/fallback expression encountered in attributes during parsing.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DefaultExpression {
|
||||
/// The value should be taken from the `default` instance of the containing struct.
|
||||
/// This is not valid in container options.
|
||||
Inherit,
|
||||
Explicit(syn::Path),
|
||||
Trait {
|
||||
/// The input span that is responsible for the use of `Default::default`.
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromMeta for DefaultExpression {
|
||||
// Note: This cannot use `from_word` as it needs to capture the span
|
||||
// in the `Meta::Path` case.
|
||||
fn from_meta(item: &syn::Meta) -> Result<Self> {
|
||||
match item {
|
||||
syn::Meta::Path(_) => Ok(DefaultExpression::Trait { span: item.span() }),
|
||||
syn::Meta::List(nm) => Err(Error::unsupported_format("list").with_span(nm)),
|
||||
syn::Meta::NameValue(nv) => Self::from_expr(&nv.value),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_expr(expr: &syn::Expr) -> Result<Self> {
|
||||
syn::Path::from_expr(expr).map(DefaultExpression::Explicit)
|
||||
}
|
||||
|
||||
fn from_value(value: &syn::Lit) -> Result<Self> {
|
||||
syn::Path::from_value(value).map(DefaultExpression::Explicit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Middleware for extracting attribute values. Implementers are expected to override
|
||||
/// `parse_nested` so they can apply individual items to themselves, while `parse_attributes`
|
||||
/// is responsible for looping through distinct outer attributes and collecting errors.
|
||||
pub trait ParseAttribute: Sized {
|
||||
fn parse_attributes(mut self, attrs: &[syn::Attribute]) -> Result<Self> {
|
||||
let mut errors = Error::accumulator();
|
||||
for attr in attrs {
|
||||
if attr.meta.path() == &parse_quote!(darling) {
|
||||
errors.handle(parse_attr(attr, &mut self));
|
||||
}
|
||||
}
|
||||
|
||||
errors.finish_with(self)
|
||||
}
|
||||
|
||||
/// Read a meta-item, and apply its values to the current instance.
|
||||
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()>;
|
||||
}
|
||||
|
||||
fn parse_attr<T: ParseAttribute>(attr: &syn::Attribute, target: &mut T) -> Result<()> {
|
||||
let mut errors = Error::accumulator();
|
||||
match &attr.meta {
|
||||
syn::Meta::List(data) => {
|
||||
for item in NestedMeta::parse_meta_list(data.tokens.clone())? {
|
||||
if let NestedMeta::Meta(ref mi) = item {
|
||||
errors.handle(target.parse_nested(mi));
|
||||
} else {
|
||||
panic!("Wasn't able to parse: `{:?}`", item);
|
||||
}
|
||||
}
|
||||
|
||||
errors.finish()
|
||||
}
|
||||
item => panic!("Wasn't able to parse: `{:?}`", item),
|
||||
}
|
||||
}
|
||||
|
||||
/// Middleware for extracting values from the body of the derive input. Implementers are
|
||||
/// expected to override `parse_field` or `parse_variant` as appropriate for their use-case,
|
||||
/// while `parse_body` dispatches to the appropriate methods and handles error collection.
|
||||
pub trait ParseData: Sized {
|
||||
fn parse_body(mut self, body: &syn::Data) -> Result<Self> {
|
||||
use syn::{Data, Fields};
|
||||
|
||||
let mut errors = Error::accumulator();
|
||||
|
||||
match *body {
|
||||
Data::Struct(ref data) => match data.fields {
|
||||
Fields::Unit => {}
|
||||
Fields::Named(ref fields) => {
|
||||
for field in &fields.named {
|
||||
errors.handle(self.parse_field(field));
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(ref fields) => {
|
||||
for field in &fields.unnamed {
|
||||
errors.handle(self.parse_field(field));
|
||||
}
|
||||
}
|
||||
},
|
||||
Data::Enum(ref data) => {
|
||||
for variant in &data.variants {
|
||||
errors.handle(self.parse_variant(variant));
|
||||
}
|
||||
}
|
||||
Data::Union(_) => unreachable!(),
|
||||
};
|
||||
|
||||
self.validate_body(&mut errors);
|
||||
|
||||
errors.finish_with(self)
|
||||
}
|
||||
|
||||
/// Apply the next found variant to the object, returning an error
|
||||
/// if parsing goes wrong.
|
||||
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
|
||||
Err(Error::unsupported_format("enum variant").with_span(variant))
|
||||
}
|
||||
|
||||
/// Apply the next found struct field to the object, returning an error
|
||||
/// if parsing goes wrong.
|
||||
fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
|
||||
Err(Error::unsupported_format("struct field").with_span(field))
|
||||
}
|
||||
|
||||
/// Perform validation checks that require data from more than one field or variant.
|
||||
/// The default implementation does no validations.
|
||||
/// Implementors can override this method as appropriate for their use-case.
|
||||
#[allow(unused_variables)]
|
||||
fn validate_body(&self, errors: &mut Accumulator) {}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
use quote::ToTokens;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Field, Ident, Meta};
|
||||
|
||||
use crate::ast::Data;
|
||||
use crate::codegen::ForwardAttrs;
|
||||
use crate::options::{
|
||||
Core, DefaultExpression, ForwardAttrsFilter, ForwardedField, ParseAttribute, ParseData,
|
||||
};
|
||||
use crate::util::PathList;
|
||||
use crate::{Error, FromField, FromMeta, Result};
|
||||
|
||||
/// Reusable base for `FromDeriveInput`, `FromVariant`, `FromField`, and other top-level
|
||||
/// `From*` traits.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OuterFrom {
|
||||
/// The field on the target struct which should receive the type identifier, if any.
|
||||
pub ident: Option<Ident>,
|
||||
|
||||
/// The field on the target struct which should receive the type attributes, if any.
|
||||
pub attrs: Option<ForwardedField>,
|
||||
|
||||
pub container: Core,
|
||||
|
||||
/// The attribute names that should be searched.
|
||||
pub attr_names: PathList,
|
||||
|
||||
/// The attribute names that should be forwarded. The presence of the word with no additional
|
||||
/// filtering will cause _all_ attributes to be cloned and exposed to the struct after parsing.
|
||||
pub forward_attrs: Option<ForwardAttrsFilter>,
|
||||
|
||||
/// Whether or not the container can be made through conversion from the type `Ident`.
|
||||
pub from_ident: bool,
|
||||
}
|
||||
|
||||
impl OuterFrom {
|
||||
pub fn start(di: &syn::DeriveInput) -> Result<Self> {
|
||||
Ok(OuterFrom {
|
||||
container: Core::start(di)?,
|
||||
attrs: Default::default(),
|
||||
ident: Default::default(),
|
||||
attr_names: Default::default(),
|
||||
forward_attrs: Default::default(),
|
||||
from_ident: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_forward_attrs(&self) -> ForwardAttrs<'_> {
|
||||
ForwardAttrs {
|
||||
field: self.attrs.as_ref(),
|
||||
filter: self.forward_attrs.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseAttribute for OuterFrom {
|
||||
fn parse_nested(&mut self, mi: &Meta) -> Result<()> {
|
||||
let path = mi.path();
|
||||
if path.is_ident("attributes") {
|
||||
self.attr_names = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("forward_attrs") {
|
||||
self.forward_attrs = FromMeta::from_meta(mi)?;
|
||||
} else if path.is_ident("from_ident") {
|
||||
// HACK: Declaring that a default is present will cause fields to
|
||||
// generate correct code, but control flow isn't that obvious.
|
||||
self.container.default = Some(DefaultExpression::Trait {
|
||||
// Use the span of the `from_ident` keyword so that errors in generated code
|
||||
// caused by this will point back to the correct location.
|
||||
span: path.span(),
|
||||
});
|
||||
self.from_ident = true;
|
||||
} else {
|
||||
return self.container.parse_nested(mi);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseData for OuterFrom {
|
||||
fn parse_field(&mut self, field: &Field) -> Result<()> {
|
||||
match field.ident.as_ref().map(|v| v.to_string()).as_deref() {
|
||||
Some("ident") => {
|
||||
self.ident.clone_from(&field.ident);
|
||||
Ok(())
|
||||
}
|
||||
Some("attrs") => {
|
||||
self.attrs = ForwardedField::from_field(field).map(Some)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => self.container.parse_field(field),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_body(&self, errors: &mut crate::error::Accumulator) {
|
||||
self.container.validate_body(errors);
|
||||
if let Some(attrs) = &self.attrs {
|
||||
if self.forward_attrs.is_none() {
|
||||
let container_name = match &self.container.data {
|
||||
Data::Enum(_) => "enum",
|
||||
Data::Struct(_) => "struct",
|
||||
};
|
||||
errors.push(
|
||||
Error::custom(format!(
|
||||
"field will not be populated because `forward_attrs` is not set on the {}",
|
||||
container_name
|
||||
))
|
||||
.with_span(&attrs.ident),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ForwardAttrsFilter::Only(fwd)) = &self.forward_attrs {
|
||||
for path in fwd.intersection(&self.attr_names) {
|
||||
errors.push(
|
||||
Error::custom(format!(
|
||||
"attribute path `{}` will not be forwarded because it is also listed in `attributes`",
|
||||
path.to_token_stream()
|
||||
))
|
||||
.with_span(path),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
//! Types for "shape" validation. This allows types deriving `FromDeriveInput` etc. to declare
|
||||
//! that they only work on - for example - structs with named fields, or newtype enum variants.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{parse_quote, Meta};
|
||||
|
||||
use crate::ast::NestedMeta;
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
/// Receiver struct for shape validation. Shape validation allows a deriving type
|
||||
/// to declare that it only accepts - for example - named structs, or newtype enum
|
||||
/// variants.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[ignore(any, struct_named, enum_newtype)]
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeriveInputShapeSet {
|
||||
enum_values: DataShape,
|
||||
struct_values: DataShape,
|
||||
any: bool,
|
||||
}
|
||||
|
||||
impl DeriveInputShapeSet {
|
||||
fn validator_fn_ident(&self) -> syn::Ident {
|
||||
syn::Ident::new("__validate_body", Span::call_site())
|
||||
}
|
||||
|
||||
pub fn validator_path(&self) -> syn::Path {
|
||||
if self.any {
|
||||
parse_quote!(::darling::export::Ok)
|
||||
} else {
|
||||
self.validator_fn_ident().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DeriveInputShapeSet {
|
||||
fn default() -> Self {
|
||||
DeriveInputShapeSet {
|
||||
enum_values: DataShape::new("enum_"),
|
||||
struct_values: DataShape::new("struct_"),
|
||||
any: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for DeriveInputShapeSet {
|
||||
fn from_list(items: &[NestedMeta]) -> Result<Self> {
|
||||
let mut new = DeriveInputShapeSet::default();
|
||||
for item in items {
|
||||
if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
|
||||
let ident = &path.segments.first().unwrap().ident;
|
||||
let word = ident.to_string();
|
||||
if word == "any" {
|
||||
new.any = true;
|
||||
} else if word.starts_with("enum_") {
|
||||
new.enum_values
|
||||
.set_word(&word)
|
||||
.map_err(|e| e.with_span(&ident))?;
|
||||
} else if word.starts_with("struct_") {
|
||||
new.struct_values
|
||||
.set_word(&word)
|
||||
.map_err(|e| e.with_span(&ident))?;
|
||||
} else {
|
||||
return Err(Error::unknown_value(&word).with_span(&ident));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::unsupported_format("non-word").with_span(item));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a body-shape validator if and only if that validation could fail for some inputs.
|
||||
impl ToTokens for DeriveInputShapeSet {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
if self.any {
|
||||
return;
|
||||
}
|
||||
|
||||
let fn_body = {
|
||||
let en = &self.enum_values;
|
||||
let st = &self.struct_values;
|
||||
|
||||
quote! {
|
||||
{
|
||||
let struct_check = #st;
|
||||
let enum_check = #en;
|
||||
|
||||
match *__body {
|
||||
::darling::export::syn::Data::Enum(ref data) => {
|
||||
if enum_check.is_empty() {
|
||||
return ::darling::export::Err(
|
||||
::darling::Error::unsupported_shape_with_expected("enum", &format!("struct with {}", struct_check))
|
||||
);
|
||||
}
|
||||
|
||||
let mut variant_errors = ::darling::Error::accumulator();
|
||||
for variant in &data.variants {
|
||||
variant_errors.handle(enum_check.check(variant));
|
||||
}
|
||||
|
||||
variant_errors.finish_with(__body)
|
||||
}
|
||||
::darling::export::syn::Data::Struct(ref struct_data) => {
|
||||
if struct_check.is_empty() {
|
||||
return ::darling::export::Err(
|
||||
::darling::Error::unsupported_shape_with_expected("struct", &format!("enum with {}", enum_check))
|
||||
);
|
||||
}
|
||||
|
||||
struct_check.check(struct_data).and(::darling::export::Ok(__body))
|
||||
}
|
||||
::darling::export::syn::Data::Union(_) => {
|
||||
let expected = if enum_check.is_empty() {
|
||||
format!("struct with {}", struct_check)
|
||||
} else if struct_check.is_empty() {
|
||||
format!("enum with {}", enum_check)
|
||||
} else {
|
||||
format!("struct with {} or enum with {}", struct_check, enum_check)
|
||||
};
|
||||
::darling::export::Err(::darling::Error::unsupported_shape_with_expected("union", &expected))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let fn_ident = self.validator_fn_ident();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
fn #fn_ident(__body: &::darling::export::syn::Data) -> ::darling::Result<&::darling::export::syn::Data> {
|
||||
#fn_body
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Receiver for shape information within a struct or enum context. See `Shape` for more information
|
||||
/// on valid uses of shape validation.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct DataShape {
|
||||
/// The kind of shape being described. This can be `struct_` or `enum_`.
|
||||
prefix: &'static str,
|
||||
newtype: bool,
|
||||
named: bool,
|
||||
tuple: bool,
|
||||
unit: bool,
|
||||
any: bool,
|
||||
}
|
||||
|
||||
impl DataShape {
|
||||
fn new(prefix: &'static str) -> Self {
|
||||
DataShape {
|
||||
prefix,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn set_word(&mut self, word: &str) -> Result<()> {
|
||||
match word.trim_start_matches(self.prefix) {
|
||||
"newtype" => {
|
||||
self.newtype = true;
|
||||
Ok(())
|
||||
}
|
||||
"named" => {
|
||||
self.named = true;
|
||||
Ok(())
|
||||
}
|
||||
"tuple" => {
|
||||
self.tuple = true;
|
||||
Ok(())
|
||||
}
|
||||
"unit" => {
|
||||
self.unit = true;
|
||||
Ok(())
|
||||
}
|
||||
"any" => {
|
||||
self.any = true;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::unknown_value(word)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for DataShape {
|
||||
fn from_list(items: &[NestedMeta]) -> Result<Self> {
|
||||
let mut errors = Error::accumulator();
|
||||
let mut new = DataShape::default();
|
||||
|
||||
for item in items {
|
||||
if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
|
||||
errors.handle(new.set_word(&path.segments.first().unwrap().ident.to_string()));
|
||||
} else {
|
||||
errors.push(Error::unsupported_format("non-word").with_span(item));
|
||||
}
|
||||
}
|
||||
|
||||
errors.finish_with(new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for DataShape {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
any,
|
||||
named,
|
||||
tuple,
|
||||
unit,
|
||||
newtype,
|
||||
..
|
||||
} = *self;
|
||||
|
||||
let shape_path: syn::Path = parse_quote!(::darling::util::Shape);
|
||||
|
||||
let mut shapes = vec![];
|
||||
if any || named {
|
||||
shapes.push(quote!(#shape_path::Named));
|
||||
}
|
||||
|
||||
if any || tuple {
|
||||
shapes.push(quote!(#shape_path::Tuple));
|
||||
}
|
||||
|
||||
if any || newtype {
|
||||
shapes.push(quote!(#shape_path::Newtype));
|
||||
}
|
||||
|
||||
if any || unit {
|
||||
shapes.push(quote!(#shape_path::Unit));
|
||||
}
|
||||
|
||||
tokens.append_all(quote! {
|
||||
::darling::util::ShapeSet::new(vec![#(#shapes),*])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::DeriveInputShapeSet;
|
||||
use crate::FromMeta;
|
||||
|
||||
/// parse a string as a syn::Meta instance.
|
||||
fn pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String> {
|
||||
let attribute: syn::Attribute = parse_quote!(#[#tokens]);
|
||||
Ok(attribute.meta)
|
||||
}
|
||||
|
||||
fn fm<T: FromMeta>(tokens: TokenStream) -> T {
|
||||
FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
|
||||
.expect("Tests should pass valid input")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_any() {
|
||||
let decl = fm::<DeriveInputShapeSet>(quote!(ignore(any)));
|
||||
assert!(decl.any);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_struct() {
|
||||
let decl = fm::<DeriveInputShapeSet>(quote!(ignore(struct_any, struct_newtype)));
|
||||
assert!(decl.struct_values.any);
|
||||
assert!(decl.struct_values.newtype);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_mixed() {
|
||||
let decl =
|
||||
fm::<DeriveInputShapeSet>(quote!(ignore(struct_newtype, enum_newtype, enum_tuple)));
|
||||
assert!(decl.struct_values.newtype);
|
||||
assert!(decl.enum_values.newtype);
|
||||
assert!(decl.enum_values.tuple);
|
||||
assert!(!decl.struct_values.any);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
use syn::Generics;
|
||||
|
||||
use crate::usage::{IdentSet, LifetimeSet};
|
||||
|
||||
/// Extension trait for pulling specific generics data from a generics AST representation.
|
||||
pub trait GenericsExt {
|
||||
/// Get the set of all lifetimes declared by the syntax element.
|
||||
/// This does not look for usage of the lifetime; see `UsesLifetimes` for that.
|
||||
fn declared_lifetimes(&self) -> LifetimeSet;
|
||||
|
||||
/// Get the set of all type parameters declared by the syntax element.
|
||||
/// This does not look for usage of the type parameter; see `UsesTypeParams` for that.
|
||||
fn declared_type_params(&self) -> IdentSet;
|
||||
}
|
||||
|
||||
impl GenericsExt for Generics {
|
||||
fn declared_lifetimes(&self) -> LifetimeSet {
|
||||
self.lifetimes().map(|lt| lt.lifetime.clone()).collect()
|
||||
}
|
||||
|
||||
fn declared_type_params(&self) -> IdentSet {
|
||||
self.type_params().map(|tp| tp.ident.clone()).collect()
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
use fnv::FnvHashSet;
|
||||
use syn::Ident;
|
||||
|
||||
/// A set of idents.
|
||||
pub type IdentSet = FnvHashSet<Ident>;
|
||||
|
||||
/// A set of references to idents.
|
||||
pub type IdentRefSet<'a> = FnvHashSet<&'a Ident>;
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
use fnv::FnvHashSet;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Lifetime, Type};
|
||||
|
||||
use crate::usage::Options;
|
||||
|
||||
/// A set of lifetimes.
|
||||
pub type LifetimeSet = FnvHashSet<Lifetime>;
|
||||
|
||||
/// A set of references to lifetimes.
|
||||
pub type LifetimeRefSet<'a> = FnvHashSet<&'a Lifetime>;
|
||||
|
||||
/// Searcher for finding lifetimes in a syntax tree.
|
||||
/// This can be used to determine which lifetimes must be emitted in generated code.
|
||||
pub trait UsesLifetimes {
|
||||
/// Returns the subset of the queried lifetimes that are used by the implementing syntax element.
|
||||
///
|
||||
/// This method only accounts for direct usage by the element; indirect usage via bounds or `where`
|
||||
/// predicates are not detected.
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a>;
|
||||
|
||||
/// Find all used lifetimes, then clone them and return that set.
|
||||
fn uses_lifetimes_cloned(&self, options: &Options, lifetimes: &LifetimeSet) -> LifetimeSet {
|
||||
self.uses_lifetimes(options, lifetimes)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Searcher for finding lifetimes in an iterator.
|
||||
///
|
||||
/// This trait extends iterators, providing a way to turn a filtered list of fields or variants into a set
|
||||
/// of lifetimes.
|
||||
pub trait CollectLifetimes {
|
||||
/// Consume an iterator, accumulating all lifetimes in the elements which occur in `lifetimes`.
|
||||
fn collect_lifetimes<'a>(
|
||||
self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a>;
|
||||
|
||||
/// Consume an iterator using `collect_lifetimes`, then clone all found lifetimes and return that set.
|
||||
fn collect_lifetimes_cloned(self, options: &Options, lifetimes: &LifetimeSet) -> LifetimeSet;
|
||||
}
|
||||
|
||||
impl<'i, I, T> CollectLifetimes for T
|
||||
where
|
||||
T: IntoIterator<Item = &'i I>,
|
||||
I: 'i + UsesLifetimes,
|
||||
{
|
||||
fn collect_lifetimes<'a>(
|
||||
self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
self.into_iter()
|
||||
.fold(Default::default(), |mut state, value| {
|
||||
state.extend(value.uses_lifetimes(options, lifetimes));
|
||||
state
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_lifetimes_cloned(self, options: &Options, lifetimes: &LifetimeSet) -> LifetimeSet {
|
||||
self.collect_lifetimes(options, lifetimes)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesLifetimes> UsesLifetimes for Vec<T> {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
self.collect_lifetimes(options, lifetimes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesLifetimes, U> UsesLifetimes for Punctuated<T, U> {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
self.collect_lifetimes(options, lifetimes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesLifetimes> UsesLifetimes for Option<T> {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
self.as_ref()
|
||||
.map(|v| v.uses_lifetimes(options, lifetimes))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for Lifetime {
|
||||
fn uses_lifetimes<'a>(&self, _: &Options, lifetimes: &'a LifetimeSet) -> LifetimeRefSet<'a> {
|
||||
lifetimes.iter().filter(|lt| *lt == self).collect()
|
||||
}
|
||||
}
|
||||
|
||||
uses_lifetimes!(syn::AngleBracketedGenericArguments, args);
|
||||
uses_lifetimes!(syn::AssocType, ty);
|
||||
uses_lifetimes!(syn::BareFnArg, ty);
|
||||
uses_lifetimes!(syn::BoundLifetimes, lifetimes);
|
||||
uses_lifetimes!(syn::ConstParam, ty);
|
||||
uses_lifetimes!(syn::Constraint, bounds);
|
||||
uses_lifetimes!(syn::DataEnum, variants);
|
||||
uses_lifetimes!(syn::DataStruct, fields);
|
||||
uses_lifetimes!(syn::DataUnion, fields);
|
||||
uses_lifetimes!(syn::Field, ty);
|
||||
uses_lifetimes!(syn::FieldsNamed, named);
|
||||
uses_lifetimes!(syn::LifetimeParam, lifetime, bounds);
|
||||
uses_lifetimes!(syn::ParenthesizedGenericArguments, inputs, output);
|
||||
uses_lifetimes!(syn::Path, segments);
|
||||
uses_lifetimes!(syn::PathSegment, arguments);
|
||||
uses_lifetimes!(syn::PredicateLifetime, lifetime, bounds);
|
||||
uses_lifetimes!(syn::PredicateType, lifetimes, bounded_ty, bounds);
|
||||
uses_lifetimes!(syn::QSelf, ty);
|
||||
uses_lifetimes!(syn::TraitBound, path, lifetimes);
|
||||
uses_lifetimes!(syn::TypeArray, elem);
|
||||
uses_lifetimes!(syn::TypeBareFn, inputs, output);
|
||||
uses_lifetimes!(syn::TypeGroup, elem);
|
||||
uses_lifetimes!(syn::TypeImplTrait, bounds);
|
||||
uses_lifetimes!(syn::TypeParam, bounds);
|
||||
uses_lifetimes!(syn::TypeParen, elem);
|
||||
uses_lifetimes!(syn::TypePtr, elem);
|
||||
uses_lifetimes!(syn::TypeReference, lifetime, elem);
|
||||
uses_lifetimes!(syn::TypeSlice, elem);
|
||||
uses_lifetimes!(syn::TypeTuple, elems);
|
||||
uses_lifetimes!(syn::TypeTraitObject, bounds);
|
||||
uses_lifetimes!(syn::Variant, fields);
|
||||
|
||||
impl UsesLifetimes for syn::Data {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
syn::Data::Struct(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::Data::Enum(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::Data::Union(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for Type {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
Type::Slice(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Array(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Ptr(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Reference(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::BareFn(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Tuple(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Path(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Paren(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Group(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::TraitObject(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::ImplTrait(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
Type::Macro(_) | Type::Verbatim(_) | Type::Infer(_) | Type::Never(_) => {
|
||||
Default::default()
|
||||
}
|
||||
_ => panic!("Unknown syn::Type: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::Fields {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
self.collect_lifetimes(options, lifetimes)
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::TypePath {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
let mut hits = self.path.uses_lifetimes(options, lifetimes);
|
||||
|
||||
if options.include_type_path_qself() {
|
||||
hits.extend(self.qself.uses_lifetimes(options, lifetimes));
|
||||
}
|
||||
|
||||
hits
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::ReturnType {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
if let syn::ReturnType::Type(_, ref ty) = *self {
|
||||
ty.uses_lifetimes(options, lifetimes)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::PathArguments {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
syn::PathArguments::None => Default::default(),
|
||||
syn::PathArguments::AngleBracketed(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::PathArguments::Parenthesized(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::WherePredicate {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
syn::WherePredicate::Type(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::WherePredicate::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
// non-exhaustive enum
|
||||
// TODO: replace panic with failible function
|
||||
_ => panic!("Unknown syn::WherePredicate: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::GenericArgument {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
syn::GenericArgument::Type(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::GenericArgument::AssocType(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::GenericArgument::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::GenericArgument::Constraint(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::GenericArgument::AssocConst(_) | syn::GenericArgument::Const(_) => {
|
||||
Default::default()
|
||||
}
|
||||
// non-exhaustive enum
|
||||
// TODO: replace panic with failible function
|
||||
_ => panic!("Unknown syn::GenericArgument: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::GenericParam {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
syn::GenericParam::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::GenericParam::Type(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::GenericParam::Const(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for syn::TypeParamBound {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &Options,
|
||||
lifetimes: &'a LifetimeSet,
|
||||
) -> LifetimeRefSet<'a> {
|
||||
match *self {
|
||||
syn::TypeParamBound::Trait(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
syn::TypeParamBound::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes),
|
||||
// non-exhaustive enum
|
||||
// TODO: replace panic with failible function
|
||||
_ => panic!("Unknown syn::TypeParamBound: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proc_macro2::Span;
|
||||
use syn::{parse_quote, DeriveInput};
|
||||
|
||||
use super::UsesLifetimes;
|
||||
use crate::usage::GenericsExt;
|
||||
use crate::usage::Purpose::*;
|
||||
|
||||
#[test]
|
||||
fn struct_named() {
|
||||
let input: DeriveInput = parse_quote! {
|
||||
struct Foo<'a, 'b: 'a> {
|
||||
parent: &'b Bar,
|
||||
child: &'a Baz,
|
||||
}
|
||||
};
|
||||
let omitted = syn::Lifetime::new("'c", Span::call_site());
|
||||
|
||||
let lifetimes = {
|
||||
let mut lt = input.generics.declared_lifetimes();
|
||||
lt.insert(omitted);
|
||||
lt
|
||||
};
|
||||
|
||||
let matches = input.data.uses_lifetimes(&BoundImpl.into(), &lifetimes);
|
||||
assert_eq!(matches.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qself() {
|
||||
let input: DeriveInput = parse_quote! {
|
||||
struct Foo<'a, 'b: 'a> {
|
||||
parent: &'b Bar,
|
||||
child: <Bar<'a> as MyIterator>::Item,
|
||||
}
|
||||
};
|
||||
let lifetimes = input.generics.declared_lifetimes();
|
||||
let matches = input.data.uses_lifetimes(&BoundImpl.into(), &lifetimes);
|
||||
assert_eq!(matches.len(), 1);
|
||||
|
||||
let decl_matches = input.data.uses_lifetimes(&Declare.into(), &lifetimes);
|
||||
assert_eq!(decl_matches.len(), 2);
|
||||
}
|
||||
}
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
//! Traits and types used for tracking the usage of generic parameters through a proc-macro input.
|
||||
//!
|
||||
//! When generating trait impls, libraries often want to automatically figure out which type parameters
|
||||
//! are used in which fields, and then emit bounds that will produce the most permissive compilable
|
||||
//! code.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ## Example 1: Filtering
|
||||
//! This example accepts a proc-macro input, then finds all lifetimes and type parameters used
|
||||
//! by private fields.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate darling_core;
|
||||
//! # extern crate syn;
|
||||
//! #
|
||||
//! # // in real-world usage, import from `darling`
|
||||
//! # use darling_core::usage::{self, CollectLifetimes, CollectTypeParams, GenericsExt, Purpose};
|
||||
//! # use syn::{Data, DeriveInput, GenericParam, Generics, Visibility};
|
||||
//! #
|
||||
//! # #[allow(dead_code)]
|
||||
//! fn process(input: &DeriveInput) -> Generics {
|
||||
//! let type_params = input.generics.declared_type_params();
|
||||
//! let lifetimes = input.generics.declared_lifetimes();
|
||||
//!
|
||||
//! let mut ret_generics = input.generics.clone();
|
||||
//!
|
||||
//! if let Data::Struct(ref body) = input.data {
|
||||
//! let internal_fields = body
|
||||
//! .fields
|
||||
//! .iter()
|
||||
//! .filter(|field| field.vis == Visibility::Inherited)
|
||||
//! .collect::<Vec<_>>();
|
||||
//!
|
||||
//! let int_type_params = internal_fields
|
||||
//! .collect_type_params(&Purpose::BoundImpl.into(), &type_params);
|
||||
//!
|
||||
//! // We could reuse the vec from above, but here we'll instead
|
||||
//! // directly consume the chained iterator.
|
||||
//! let int_lifetimes = body
|
||||
//! .fields
|
||||
//! .iter()
|
||||
//! .filter(|field| field.vis == Visibility::Inherited)
|
||||
//! .collect_lifetimes(&Purpose::BoundImpl.into(), &lifetimes);
|
||||
//!
|
||||
//!
|
||||
//! ret_generics.params = ret_generics
|
||||
//! .params
|
||||
//! .into_iter()
|
||||
//! .filter(|gp| {
|
||||
//! match *gp {
|
||||
//! GenericParam::Type(ref ty) => int_type_params.contains(&ty.ident),
|
||||
//! GenericParam::Lifetime(ref lt) => int_lifetimes.contains(<.lifetime),
|
||||
//! _ => true,
|
||||
//! }
|
||||
//! })
|
||||
//! .collect();
|
||||
//! }
|
||||
//!
|
||||
//! ret_generics
|
||||
//! }
|
||||
//!
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! ## Example 2: Integrating with `FromDeriveInput`
|
||||
//! It is possible to use `darling`'s magic fields feature in tandem with the `usage` feature set.
|
||||
//! While there is no custom derive for `UsesTypeParams` or `UsesLifetimes`, there are macros to
|
||||
//! generate impls.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #![allow(dead_code)]
|
||||
//!
|
||||
//! #[derive(FromField)]
|
||||
//! #[darling(attributes(speak))]
|
||||
//! struct SpeakerField {
|
||||
//! ident: Option<syn::Ident>,
|
||||
//! ty: syn::Type,
|
||||
//! #[darling(default)]
|
||||
//! volume: Option<u32>,
|
||||
//! }
|
||||
//!
|
||||
//! uses_type_params!(SpeakerField, ty);
|
||||
//! uses_lifetimes!(SpeakerField, ty);
|
||||
//!
|
||||
//! #[derive(FromDeriveInput)]
|
||||
//! struct SpeakerOptions {
|
||||
//! generics: syn::Generics,
|
||||
//! data: darling::ast::Data<darling::util::Ignored, SpeakerField>,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! At this point, you are able to call `uses_type_params` on `SpeakerOptions.data`, or any filtered
|
||||
//! view of it. `darling` internally uses this in conjunction with the `skip` meta-item to determine
|
||||
//! which type parameters don't require the `FromMeta` bound in generated impls.
|
||||
//!
|
||||
//! **Note:** If you are performing operations referencing generic params in meta-items parsed by `darling`,
|
||||
//! you should determine if those impact the emitted code and wire up `UsesTypeParams` accordingly for
|
||||
//! your field/variant.
|
||||
|
||||
mod generics_ext;
|
||||
mod ident_set;
|
||||
mod lifetimes;
|
||||
mod options;
|
||||
mod type_params;
|
||||
|
||||
pub use self::generics_ext::GenericsExt;
|
||||
pub use self::ident_set::{IdentRefSet, IdentSet};
|
||||
pub use self::lifetimes::{CollectLifetimes, LifetimeRefSet, LifetimeSet, UsesLifetimes};
|
||||
pub use self::options::{Options, Purpose};
|
||||
pub use self::type_params::{CollectTypeParams, UsesTypeParams};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/// The goal of tracing generic parameter usage.
|
||||
///
|
||||
/// Not all uses of type parameters imply a need to add bounds to a generated trait impl.
|
||||
/// For example, a field of type `<Vec<T> as a::b::Trait>::Associated` does not need a
|
||||
/// `where T: Serialize` bound in `serde`.
|
||||
/// However, a proc macro that is attempting to generate a helper struct _would_ need to
|
||||
/// know about this usage, or else the generated code would reference an unknown type `T`
|
||||
/// and fail to compile.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Purpose {
|
||||
/// The tracing is being used to generate an `impl` block.
|
||||
///
|
||||
/// Uses such as `syn::TypePath.qself` will _not_ be returned.
|
||||
BoundImpl,
|
||||
/// The tracing is being used to generate a new struct or enum.
|
||||
///
|
||||
/// All uses will be returned.
|
||||
Declare,
|
||||
}
|
||||
|
||||
/// Control struct for searching type parameters.
|
||||
///
|
||||
/// This acts as the search context, preserving information that might have been
|
||||
/// kept on a visitor in a different implementation.
|
||||
/// Trait implementers are required to pass this through on any invocations they make.
|
||||
///
|
||||
/// # Usage
|
||||
/// For extensibility, `Options` hides all of its fields from consumers.
|
||||
/// To create an instance, use the `From<Purpose>` trait implementation:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use darling_core::usage::{Options, Purpose};
|
||||
/// let opts: Options = Purpose::BoundImpl.into();
|
||||
/// assert!(!opts.include_type_path_qself());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Options {
|
||||
purpose: Purpose,
|
||||
#[doc(hidden)]
|
||||
__nonexhaustive: (),
|
||||
}
|
||||
|
||||
impl From<Purpose> for Options {
|
||||
fn from(purpose: Purpose) -> Self {
|
||||
Self {
|
||||
purpose,
|
||||
__nonexhaustive: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Returns `true` if the implementer of `UseTypeParams` should search
|
||||
/// `<___ as ...>::...` when looking for type parameter uses.
|
||||
pub fn include_type_path_qself(&self) -> bool {
|
||||
self.purpose == Purpose::Declare
|
||||
}
|
||||
}
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Ident, Type};
|
||||
|
||||
use crate::usage::{IdentRefSet, IdentSet, Options};
|
||||
|
||||
/// Searcher for finding type params in a syntax tree.
|
||||
/// This can be used to determine if a given type parameter needs to be bounded in a generated impl.
|
||||
pub trait UsesTypeParams {
|
||||
/// Returns the subset of the queried type parameters that are used by the implementing syntax element.
|
||||
///
|
||||
/// This method only accounts for direct usage by the element; indirect usage via bounds or `where`
|
||||
/// predicates are not detected.
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>;
|
||||
|
||||
/// Find all type params using `uses_type_params`, then clone the found values and return the set.
|
||||
fn uses_type_params_cloned(&self, options: &Options, type_set: &IdentSet) -> IdentSet {
|
||||
self.uses_type_params(options, type_set)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Searcher for finding type params in an iterator.
|
||||
///
|
||||
/// This trait extends iterators, providing a way to turn a filtered list of fields or variants into a set
|
||||
/// of type parameter idents.
|
||||
pub trait CollectTypeParams {
|
||||
/// Consume an iterator, accumulating all type parameters in the elements which occur in `type_set`.
|
||||
fn collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a>;
|
||||
|
||||
/// Consume an iterator using `collect_type_params`, then clone all found type params and return that set.
|
||||
fn collect_type_params_cloned(self, options: &Options, type_set: &IdentSet) -> IdentSet;
|
||||
}
|
||||
|
||||
impl<'i, T, I> CollectTypeParams for T
|
||||
where
|
||||
T: IntoIterator<Item = &'i I>,
|
||||
I: 'i + UsesTypeParams,
|
||||
{
|
||||
fn collect_type_params<'a>(self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
self.into_iter().fold(
|
||||
IdentRefSet::with_capacity_and_hasher(type_set.len(), Default::default()),
|
||||
|state, value| union_in_place(state, value.uses_type_params(options, type_set)),
|
||||
)
|
||||
}
|
||||
|
||||
fn collect_type_params_cloned(self, options: &Options, type_set: &IdentSet) -> IdentSet {
|
||||
self.collect_type_params(options, type_set)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert the contents of `right` into `left`.
|
||||
fn union_in_place<'a>(mut left: IdentRefSet<'a>, right: IdentRefSet<'a>) -> IdentRefSet<'a> {
|
||||
left.extend(right);
|
||||
|
||||
left
|
||||
}
|
||||
|
||||
impl UsesTypeParams for () {
|
||||
fn uses_type_params<'a>(&self, _options: &Options, _type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesTypeParams> UsesTypeParams for Option<T> {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
self.as_ref()
|
||||
.map(|v| v.uses_type_params(options, type_set))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesTypeParams> UsesTypeParams for Vec<T> {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
self.collect_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UsesTypeParams, U> UsesTypeParams for Punctuated<T, U> {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
self.collect_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
|
||||
uses_type_params!(syn::AngleBracketedGenericArguments, args);
|
||||
uses_type_params!(syn::AssocType, ty);
|
||||
uses_type_params!(syn::BareFnArg, ty);
|
||||
uses_type_params!(syn::Constraint, bounds);
|
||||
uses_type_params!(syn::DataEnum, variants);
|
||||
uses_type_params!(syn::DataStruct, fields);
|
||||
uses_type_params!(syn::DataUnion, fields);
|
||||
uses_type_params!(syn::Field, ty);
|
||||
uses_type_params!(syn::FieldsNamed, named);
|
||||
uses_type_params!(syn::ParenthesizedGenericArguments, inputs, output);
|
||||
uses_type_params!(syn::PredicateType, bounded_ty, bounds);
|
||||
uses_type_params!(syn::QSelf, ty);
|
||||
uses_type_params!(syn::TraitBound, path);
|
||||
uses_type_params!(syn::TypeArray, elem);
|
||||
uses_type_params!(syn::TypeBareFn, inputs, output);
|
||||
uses_type_params!(syn::TypeGroup, elem);
|
||||
uses_type_params!(syn::TypeImplTrait, bounds);
|
||||
uses_type_params!(syn::TypeParen, elem);
|
||||
uses_type_params!(syn::TypePtr, elem);
|
||||
uses_type_params!(syn::TypeReference, elem);
|
||||
uses_type_params!(syn::TypeSlice, elem);
|
||||
uses_type_params!(syn::TypeTuple, elems);
|
||||
uses_type_params!(syn::TypeTraitObject, bounds);
|
||||
uses_type_params!(syn::Variant, fields);
|
||||
|
||||
impl UsesTypeParams for syn::Data {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
syn::Data::Struct(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::Data::Enum(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::Data::Union(ref v) => v.uses_type_params(options, type_set),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::Fields {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
self.collect_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an Ident exactly matches one of the sought-after type parameters.
|
||||
impl UsesTypeParams for Ident {
|
||||
fn uses_type_params<'a>(&self, _options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
type_set.iter().filter(|v| *v == self).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::ReturnType {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
if let syn::ReturnType::Type(_, ref ty) = *self {
|
||||
ty.uses_type_params(options, type_set)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for Type {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
Type::Slice(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Array(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Ptr(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Reference(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::BareFn(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Tuple(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Path(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Paren(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Group(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::TraitObject(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::ImplTrait(ref v) => v.uses_type_params(options, type_set),
|
||||
Type::Macro(_) | Type::Verbatim(_) | Type::Infer(_) | Type::Never(_) => {
|
||||
Default::default()
|
||||
}
|
||||
_ => panic!("Unknown syn::Type: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::TypePath {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
let hits = self.path.uses_type_params(options, type_set);
|
||||
|
||||
if options.include_type_path_qself() {
|
||||
union_in_place(hits, self.qself.uses_type_params(options, type_set))
|
||||
} else {
|
||||
hits
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::Path {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
// Not sure if this is even possible, but a path with no segments definitely
|
||||
// can't use type parameters.
|
||||
if self.segments.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
|
||||
// A path segment ident can only match if it is not global and it is the first segment
|
||||
// in the path.
|
||||
let ident_hits = if self.leading_colon.is_none() {
|
||||
self.segments[0].ident.uses_type_params(options, type_set)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// Merge ident hit, if any, with all hits from path arguments
|
||||
self.segments.iter().fold(ident_hits, |state, segment| {
|
||||
union_in_place(state, segment.arguments.uses_type_params(options, type_set))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::PathArguments {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
syn::PathArguments::None => Default::default(),
|
||||
syn::PathArguments::AngleBracketed(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::PathArguments::Parenthesized(ref v) => v.uses_type_params(options, type_set),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::WherePredicate {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
syn::WherePredicate::Lifetime(_) => Default::default(),
|
||||
syn::WherePredicate::Type(ref v) => v.uses_type_params(options, type_set),
|
||||
// non-exhaustive enum
|
||||
// TODO: replace panic with failible function
|
||||
_ => panic!("Unknown syn::WherePredicate: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::GenericArgument {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
syn::GenericArgument::Type(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::GenericArgument::AssocType(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::GenericArgument::Constraint(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::GenericArgument::AssocConst(_)
|
||||
| syn::GenericArgument::Const(_)
|
||||
| syn::GenericArgument::Lifetime(_) => Default::default(),
|
||||
// non-exhaustive enum
|
||||
// TODO: replace panic with failible function
|
||||
_ => panic!("Unknown syn::GenericArgument: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesTypeParams for syn::TypeParamBound {
|
||||
fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> {
|
||||
match *self {
|
||||
syn::TypeParamBound::Trait(ref v) => v.uses_type_params(options, type_set),
|
||||
syn::TypeParamBound::Lifetime(_) => Default::default(),
|
||||
// non-exhaustive enum
|
||||
// TODO: replace panic with failible function
|
||||
_ => panic!("Unknown syn::TypeParamBound: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proc_macro2::Span;
|
||||
use syn::{parse_quote, DeriveInput, Ident};
|
||||
|
||||
use super::UsesTypeParams;
|
||||
use crate::usage::IdentSet;
|
||||
use crate::usage::Purpose::*;
|
||||
|
||||
fn ident_set(idents: Vec<&str>) -> IdentSet {
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|s| Ident::new(s, Span::call_site()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_simple() {
|
||||
let input: DeriveInput = parse_quote! { struct Foo<T, U>(T, i32, A, U); };
|
||||
let generics = ident_set(vec!["T", "U", "X"]);
|
||||
let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
assert_eq!(matches.len(), 2);
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(T)));
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(U)));
|
||||
assert!(!matches.contains::<Ident>(&parse_quote!(X)));
|
||||
assert!(!matches.contains::<Ident>(&parse_quote!(A)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_named() {
|
||||
let input: DeriveInput = parse_quote! {
|
||||
struct Foo<T, U = usize> {
|
||||
bar: T,
|
||||
world: U,
|
||||
}
|
||||
};
|
||||
|
||||
let generics = ident_set(vec!["T", "U", "X"]);
|
||||
|
||||
let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
|
||||
assert_eq!(matches.len(), 2);
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(T)));
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(U)));
|
||||
assert!(!matches.contains::<Ident>(&parse_quote!(X)));
|
||||
assert!(!matches.contains::<Ident>(&parse_quote!(A)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_as_type_arg() {
|
||||
let input: DeriveInput = parse_quote! {
|
||||
struct Foo<T, U> {
|
||||
bar: T,
|
||||
world: Vec<U>,
|
||||
}
|
||||
};
|
||||
|
||||
let generics = ident_set(vec!["T", "U", "X"]);
|
||||
|
||||
let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
|
||||
assert_eq!(matches.len(), 2);
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(T)));
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(U)));
|
||||
assert!(!matches.contains::<Ident>(&parse_quote!(X)));
|
||||
assert!(!matches.contains::<Ident>(&parse_quote!(A)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn associated_type() {
|
||||
let input: DeriveInput =
|
||||
parse_quote! { struct Foo<'a, T> where T: Iterator { peek: T::Item } };
|
||||
let generics = ident_set(vec!["T", "INTO"]);
|
||||
let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
assert_eq!(matches.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_fn_output() {
|
||||
let input: DeriveInput = parse_quote! { struct Foo<T>(Box<dyn Fn() -> T>); };
|
||||
let generics = ident_set(vec!["T"]);
|
||||
let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
assert_eq!(matches.len(), 1);
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(T)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_fn_input() {
|
||||
let input: DeriveInput = parse_quote! { struct Foo<T>(Box<dyn Fn(&T) -> ()>); };
|
||||
let generics = ident_set(vec!["T"]);
|
||||
let matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
assert_eq!(matches.len(), 1);
|
||||
assert!(matches.contains::<Ident>(&parse_quote!(T)));
|
||||
}
|
||||
|
||||
/// Test that `syn::TypePath` is correctly honoring the different modes a
|
||||
/// search can execute in.
|
||||
#[test]
|
||||
fn qself_vec() {
|
||||
let input: DeriveInput =
|
||||
parse_quote! { struct Foo<T>(<Vec<T> as a::b::Trait>::AssociatedItem); };
|
||||
let generics = ident_set(vec!["T", "U"]);
|
||||
|
||||
let bound_matches = input.data.uses_type_params(&BoundImpl.into(), &generics);
|
||||
assert_eq!(bound_matches.len(), 0);
|
||||
|
||||
let declare_matches = input.data.uses_type_params(&Declare.into(), &generics);
|
||||
assert_eq!(declare_matches.len(), 1);
|
||||
assert!(declare_matches.contains::<Ident>(&parse_quote!(T)));
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
use quote::ToTokens;
|
||||
use syn::{Expr, ExprClosure, ExprLit, ExprPath, Lit, Path};
|
||||
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
/// Either a path or a closure.
|
||||
///
|
||||
/// This type is useful for options that historically took a path,
|
||||
/// e.g. `#[darling(with = ...)]` or `#[serde(skip_serializing_if = ...)]`
|
||||
/// and now want to also allow using a closure to avoid needing a separate
|
||||
/// function declaration.
|
||||
///
|
||||
/// In `darling`, this value is wrapped in [`core::convert::identity`] before usage;
|
||||
/// this allows treatment of the closure and path cases as equivalent, and prevents
|
||||
/// a closure from accessing locals in the generated code.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Callable {
|
||||
/// The callable
|
||||
call: Expr,
|
||||
}
|
||||
|
||||
impl AsRef<Expr> for Callable {
|
||||
fn as_ref(&self) -> &Expr {
|
||||
&self.call
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Path> for Callable {
|
||||
fn from(path: Path) -> Self {
|
||||
Self::from(ExprPath {
|
||||
attrs: vec![],
|
||||
qself: None,
|
||||
path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExprPath> for Callable {
|
||||
fn from(value: ExprPath) -> Self {
|
||||
Self {
|
||||
call: Expr::Path(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExprClosure> for Callable {
|
||||
fn from(value: ExprClosure) -> Self {
|
||||
Self {
|
||||
call: Expr::Closure(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Callable> for Expr {
|
||||
fn from(value: Callable) -> Self {
|
||||
value.call
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for Callable {
|
||||
fn from_expr(expr: &Expr) -> Result<Self> {
|
||||
match expr {
|
||||
Expr::Path(_) | Expr::Closure(_) => Ok(Self { call: expr.clone() }),
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s), ..
|
||||
}) => syn::parse_str::<Path>(&s.value())
|
||||
.map_err(|e| {
|
||||
Error::custom(format!("`with` must be a path if it's a string: {}", e))
|
||||
.with_span(s)
|
||||
})
|
||||
.map(Self::from),
|
||||
_ => Err(Error::unexpected_expr_type(expr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Callable {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
self.call.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
use proc_macro2::Span;
|
||||
use syn::{spanned::Spanned, Meta};
|
||||
|
||||
use crate::{FromMeta, Result};
|
||||
|
||||
/// A meta-item that can be present as a word - with no value - or absent.
|
||||
///
|
||||
/// # Defaulting
|
||||
/// Like `Option`, `Flag` does not require `#[darling(default)]` to be optional.
|
||||
/// If the caller does not include the property, then an absent `Flag` will be included
|
||||
/// in the receiver struct.
|
||||
///
|
||||
/// # Spans
|
||||
/// `Flag` keeps the span where its word was seen.
|
||||
/// This enables attaching custom error messages to the word, such as in the case of two
|
||||
/// conflicting flags being present.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// #[derive(FromMeta)]
|
||||
/// #[darling(and_then = Self::not_both)]
|
||||
/// struct Demo {
|
||||
/// flag_a: Flag,
|
||||
/// flag_b: Flag,
|
||||
/// }
|
||||
///
|
||||
/// impl Demo {
|
||||
/// fn not_both(self) -> Result<Self> {
|
||||
/// if self.flag_a.is_present() && self.flag_b.is_present() {
|
||||
/// Err(Error::custom("Cannot set flag_a and flag_b").with_span(&self.flag_b.span()))
|
||||
/// } else {
|
||||
/// Ok(self)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The above struct would then produce the following error.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[example(flag_a, flag_b)]
|
||||
/// // ^^^^^^ Cannot set flag_a and flag_b
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Flag(Option<Span>);
|
||||
|
||||
impl Flag {
|
||||
/// Creates a new `Flag` which corresponds to the presence of a value.
|
||||
pub fn present() -> Self {
|
||||
Flag(Some(Span::call_site()))
|
||||
}
|
||||
|
||||
/// Check if the flag is present.
|
||||
pub fn is_present(&self) -> bool {
|
||||
self.0.is_some()
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.14.0", note = "Use Flag::is_present")]
|
||||
pub fn is_some(&self) -> bool {
|
||||
self.is_present()
|
||||
}
|
||||
|
||||
/// Get the span of the flag, or [`Span::call_site`] if the flag was not present.
|
||||
pub fn span(&self) -> Span {
|
||||
self.0.unwrap_or_else(Span::call_site)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for Flag {
|
||||
fn from_none() -> Option<Self> {
|
||||
Some(Flag(None))
|
||||
}
|
||||
|
||||
fn from_meta(mi: &syn::Meta) -> Result<Self> {
|
||||
if let Meta::Path(p) = mi {
|
||||
Ok(Flag(Some(p.span())))
|
||||
} else {
|
||||
// The implementation for () will produce an error for all non-path meta items;
|
||||
// call it to make sure the span behaviors and error messages are the same.
|
||||
Err(<()>::from_meta(mi).unwrap_err())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Flag> for bool {
|
||||
fn from(flag: Flag) -> Self {
|
||||
flag.is_present()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Flag {
|
||||
fn from(v: bool) -> Self {
|
||||
if v {
|
||||
Flag::present()
|
||||
} else {
|
||||
Flag(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use syn::{Ident, Meta};
|
||||
|
||||
use crate::{FromMeta, Result};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
||||
|
||||
/// A wrapper for an `Ident` which also keeps the value as a string.
|
||||
///
|
||||
/// This struct can be used to perform string comparisons and operations.
|
||||
///
|
||||
/// With the optional `serde` feature, this will be serialized as a string and
|
||||
/// supports being deserialized from a string.
|
||||
#[derive(Clone, PartialOrd, Ord)]
|
||||
pub struct IdentString {
|
||||
ident: Ident,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl IdentString {
|
||||
/// Create a new `IdentString`.
|
||||
pub fn new(ident: Ident) -> Self {
|
||||
IdentString {
|
||||
string: ident.to_string(),
|
||||
ident,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ident as a `proc_macro2::Ident`.
|
||||
pub fn as_ident(&self) -> &Ident {
|
||||
&self.ident
|
||||
}
|
||||
|
||||
/// Get the ident as a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.string
|
||||
}
|
||||
|
||||
/// Get the location of this `Ident` in source.
|
||||
pub fn span(&self) -> Span {
|
||||
self.ident.span()
|
||||
}
|
||||
|
||||
/// Apply some transform to the ident's string representation.
|
||||
///
|
||||
/// # Panics
|
||||
/// This will panic if the transform produces an invalid ident.
|
||||
pub fn map<F, S>(self, map_fn: F) -> Self
|
||||
where
|
||||
F: FnOnce(String) -> S,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let span = self.span();
|
||||
let string = map_fn(self.string);
|
||||
Ident::new(string.as_ref(), span).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Ident> for IdentString {
|
||||
fn as_ref(&self) -> &Ident {
|
||||
self.as_ident()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for IdentString {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ident> for IdentString {
|
||||
fn from(ident: Ident) -> Self {
|
||||
IdentString::new(ident)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentString> for Ident {
|
||||
fn from(v: IdentString) -> Ident {
|
||||
v.ident
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentString> for String {
|
||||
fn from(v: IdentString) -> String {
|
||||
v.string
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IdentString {}
|
||||
|
||||
impl PartialEq for IdentString {
|
||||
fn eq(&self, rhs: &Self) -> bool {
|
||||
self.ident == rhs.ident
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for IdentString {
|
||||
fn eq(&self, rhs: &String) -> bool {
|
||||
self.as_str() == rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for IdentString {
|
||||
fn eq(&self, rhs: &&str) -> bool {
|
||||
self.as_str() == *rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for IdentString {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.ident.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for IdentString {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.ident.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for IdentString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.ident)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IdentString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.ident)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for IdentString {
|
||||
fn from_meta(item: &Meta) -> Result<Self> {
|
||||
Ident::from_meta(item).map(IdentString::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::IdentString;
|
||||
|
||||
#[test]
|
||||
fn convert() {
|
||||
let i_str = IdentString::new(parse_quote!(t));
|
||||
assert_eq!(i_str.as_str(), "t");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_transform() {
|
||||
let i = IdentString::new(parse_quote!(my));
|
||||
let after = i.map(|v| format!("var_{}", v));
|
||||
assert_eq!(after, "var_my");
|
||||
assert_eq!(after, String::from("var_my"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use syn::Ident;
|
||||
|
||||
use crate::util::IdentString;
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for IdentString {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(IdentStringVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct IdentStringVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for IdentStringVisitor {
|
||||
type Value = IdentString;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(formatter, "a valid ident")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let ident: Ident = syn::parse_str(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(IdentString::new(ident))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for IdentString {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let raw_ident = r#""ident""#;
|
||||
let deserialized: IdentString =
|
||||
serde_json::from_str(&raw_ident).expect("ident must be valid");
|
||||
let serialized = serde_json::to_string(&deserialized).expect("ident must be serializable");
|
||||
|
||||
assert_eq!(raw_ident, serialized);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
use crate::{
|
||||
usage::{self, UsesLifetimes, UsesTypeParams},
|
||||
FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta, FromTypeParam,
|
||||
FromVariant, Result,
|
||||
};
|
||||
|
||||
/// An efficient way of discarding data from a syntax element.
|
||||
///
|
||||
/// All syntax elements will be successfully read into
|
||||
/// the `Ignored` struct, with all properties discarded.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Ignored;
|
||||
|
||||
macro_rules! ignored {
|
||||
($trayt:ident, $method:ident, $syn:path) => {
|
||||
impl $trayt for Ignored {
|
||||
fn $method(_: &$syn) -> Result<Self> {
|
||||
Ok(Ignored)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ignored!(FromGenericParam, from_generic_param, syn::GenericParam);
|
||||
ignored!(FromGenerics, from_generics, syn::Generics);
|
||||
ignored!(FromTypeParam, from_type_param, syn::TypeParam);
|
||||
ignored!(FromMeta, from_meta, syn::Meta);
|
||||
ignored!(FromDeriveInput, from_derive_input, syn::DeriveInput);
|
||||
ignored!(FromField, from_field, syn::Field);
|
||||
ignored!(FromVariant, from_variant, syn::Variant);
|
||||
|
||||
impl UsesTypeParams for Ignored {
|
||||
fn uses_type_params<'a>(
|
||||
&self,
|
||||
_opts: &usage::Options,
|
||||
_: &'a usage::IdentSet,
|
||||
) -> usage::IdentRefSet<'a> {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl UsesLifetimes for Ignored {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
_opts: &usage::Options,
|
||||
_: &'a usage::LifetimeSet,
|
||||
) -> usage::LifetimeRefSet<'a> {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
//! Utility types for attribute parsing.
|
||||
|
||||
mod callable;
|
||||
mod flag;
|
||||
mod ident_string;
|
||||
mod ignored;
|
||||
mod over_ride;
|
||||
mod parse_attribute;
|
||||
pub mod parse_expr;
|
||||
mod path_list;
|
||||
mod path_to_string;
|
||||
mod preserved_str_expr;
|
||||
mod shape;
|
||||
mod spanned_value;
|
||||
mod with_original;
|
||||
|
||||
pub use self::callable::Callable;
|
||||
pub use self::flag::Flag;
|
||||
pub use self::ident_string::IdentString;
|
||||
pub use self::ignored::Ignored;
|
||||
pub use self::over_ride::Override;
|
||||
pub use self::parse_attribute::parse_attribute_to_meta_list;
|
||||
pub use self::path_list::PathList;
|
||||
pub use self::path_to_string::path_to_string;
|
||||
pub use self::preserved_str_expr::PreservedStrExpr;
|
||||
pub use self::shape::{AsShape, Shape, ShapeSet};
|
||||
pub use self::spanned_value::SpannedValue;
|
||||
pub use self::with_original::WithOriginal;
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
use std::fmt;
|
||||
|
||||
use syn::Lit;
|
||||
|
||||
use crate::ast::NestedMeta;
|
||||
use crate::{FromMeta, Result};
|
||||
|
||||
use self::Override::*;
|
||||
|
||||
/// A value which can inherit a default value or have an explicit value specified.
|
||||
///
|
||||
/// # Usage
|
||||
/// This type is meant for attributes like `default` in `darling`, which can take the following forms:
|
||||
///
|
||||
/// * `#[darling(default)]`
|
||||
/// * `#[darling(default="path::to::fn")]`
|
||||
///
|
||||
/// In a struct collecting input for this attribute, that would be written as:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use darling::{util::Override, FromField};
|
||||
/// #[derive(FromField)]
|
||||
/// #[darling(attributes(darling))]
|
||||
/// pub struct Options {
|
||||
/// default: Option<Override<syn::Path>>,
|
||||
/// }
|
||||
///
|
||||
/// impl Options {
|
||||
/// fn hydrate(self) -> Option<syn::Path> {
|
||||
/// self.default.map(|ov| ov.unwrap_or(syn::parse_path("::Default::default").unwrap()))
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `word` format (with no associated value), would produce `Override::Inherit`, while a list
|
||||
/// or value format would produce `Override::Explicit`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Override<T> {
|
||||
/// Inherit the eventual value from an external source.
|
||||
Inherit,
|
||||
|
||||
/// Explicitly set the value.
|
||||
Explicit(T),
|
||||
}
|
||||
|
||||
impl<T> Override<T> {
|
||||
/// Converts from `Override<T>` to `Override<&T>`.
|
||||
///
|
||||
/// Produces a new `Override`, containing a reference into the original, leaving the original in place.
|
||||
pub fn as_ref(&self) -> Override<&T> {
|
||||
match *self {
|
||||
Inherit => Inherit,
|
||||
Explicit(ref val) => Explicit(val),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts from `Override<T>` to `Override<&mut T>`.
|
||||
///
|
||||
/// Produces a new `Override`, containing a mutable reference into the original.
|
||||
pub fn as_mut(&mut self) -> Override<&mut T> {
|
||||
match *self {
|
||||
Inherit => Inherit,
|
||||
Explicit(ref mut val) => Explicit(val),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the override is an `Explicit` value.
|
||||
pub fn is_explicit(&self) -> bool {
|
||||
match *self {
|
||||
Inherit => false,
|
||||
Explicit(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts from `Override<T>` to `Option<T>`.
|
||||
pub fn explicit(self) -> Option<T> {
|
||||
match self {
|
||||
Inherit => None,
|
||||
Explicit(val) => Some(val),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps an override, yielding the content of an `Explicit`. Otherwise, it returns `optb`.
|
||||
pub fn unwrap_or(self, optb: T) -> T {
|
||||
match self {
|
||||
Inherit => optb,
|
||||
Explicit(val) => val,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps an override, yielding the content of an `Explicit`. Otherwise, it calls `op`.
|
||||
pub fn unwrap_or_else<F>(self, op: F) -> T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
match self {
|
||||
Inherit => op(),
|
||||
Explicit(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Override<T> {
|
||||
/// Returns the contained value or the default value of `T`.
|
||||
pub fn unwrap_or_default(self) -> T {
|
||||
match self {
|
||||
Inherit => Default::default(),
|
||||
Explicit(val) => val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Override<T> {
|
||||
fn default() -> Self {
|
||||
Inherit
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for Override<T> {
|
||||
fn from(v: Option<T>) -> Self {
|
||||
match v {
|
||||
None => Inherit,
|
||||
Some(val) => Explicit(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for Override<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Inherit => write!(f, "Inherit"),
|
||||
Explicit(ref val) => write!(f, "Explicit `{}`", val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a `Meta`. A bare word will produce `Override::Inherit`, while
|
||||
/// any value will be forwarded to `T::from_meta`.
|
||||
impl<T: FromMeta> FromMeta for Override<T> {
|
||||
fn from_meta(item: &syn::Meta) -> Result<Self> {
|
||||
match item {
|
||||
syn::Meta::Path(_) => Self::from_word(),
|
||||
_ => FromMeta::from_meta(item).map(Explicit),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_word() -> Result<Self> {
|
||||
Ok(Inherit)
|
||||
}
|
||||
|
||||
fn from_list(items: &[NestedMeta]) -> Result<Self> {
|
||||
Ok(Explicit(FromMeta::from_list(items)?))
|
||||
}
|
||||
|
||||
fn from_value(lit: &Lit) -> Result<Self> {
|
||||
Ok(Explicit(FromMeta::from_value(lit)?))
|
||||
}
|
||||
|
||||
fn from_expr(expr: &syn::Expr) -> Result<Self> {
|
||||
Ok(Explicit(FromMeta::from_expr(expr)?))
|
||||
}
|
||||
|
||||
fn from_char(value: char) -> Result<Self> {
|
||||
Ok(Explicit(FromMeta::from_char(value)?))
|
||||
}
|
||||
|
||||
fn from_string(value: &str) -> Result<Self> {
|
||||
Ok(Explicit(FromMeta::from_string(value)?))
|
||||
}
|
||||
|
||||
fn from_bool(value: bool) -> Result<Self> {
|
||||
Ok(Explicit(FromMeta::from_bool(value)?))
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use crate::{Error, Result};
|
||||
use std::fmt;
|
||||
use syn::punctuated::Pair;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{token, Attribute, Meta, MetaList, Path};
|
||||
|
||||
/// Try to parse an attribute into a meta list. Path-type meta values are accepted and returned
|
||||
/// as empty lists with their passed-in path. Name-value meta values and non-meta attributes
|
||||
/// will cause errors to be returned.
|
||||
pub fn parse_attribute_to_meta_list(attr: &Attribute) -> Result<MetaList> {
|
||||
match &attr.meta {
|
||||
Meta::List(list) => Ok(list.clone()),
|
||||
Meta::NameValue(nv) => Err(Error::custom(format!(
|
||||
"Name-value arguments are not supported. Use #[{}(...)]",
|
||||
DisplayPath(&nv.path)
|
||||
))
|
||||
.with_span(&nv)),
|
||||
Meta::Path(path) => Ok(MetaList {
|
||||
path: path.clone(),
|
||||
delimiter: syn::MacroDelimiter::Paren(token::Paren {
|
||||
span: {
|
||||
let mut group = proc_macro2::Group::new(
|
||||
proc_macro2::Delimiter::None,
|
||||
proc_macro2::TokenStream::new(),
|
||||
);
|
||||
group.set_span(attr.span());
|
||||
group.delim_span()
|
||||
},
|
||||
}),
|
||||
tokens: Default::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayPath<'a>(&'a Path);
|
||||
|
||||
impl fmt::Display for DisplayPath<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let path = self.0;
|
||||
if path.leading_colon.is_some() {
|
||||
write!(f, "::")?;
|
||||
}
|
||||
for segment in path.segments.pairs() {
|
||||
match segment {
|
||||
Pair::Punctuated(segment, _) => write!(f, "{}::", segment.ident)?,
|
||||
Pair::End(segment) => segment.ident.fmt(f)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_attribute_to_meta_list;
|
||||
use crate::ast::NestedMeta;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse_quote, Ident};
|
||||
|
||||
#[test]
|
||||
fn parse_list() {
|
||||
let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar(baz = 4)])).unwrap();
|
||||
let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap();
|
||||
assert_eq!(nested_meta.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_path_returns_empty_list() {
|
||||
let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar])).unwrap();
|
||||
let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap();
|
||||
assert!(meta.path.is_ident(&Ident::new("bar", meta.path.span())));
|
||||
assert!(nested_meta.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_name_value_returns_error() {
|
||||
parse_attribute_to_meta_list(&parse_quote!(#[bar = 4])).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_name_value_error_includes_example() {
|
||||
let err = parse_attribute_to_meta_list(&parse_quote!(#[bar = 4])).unwrap_err();
|
||||
assert!(err.to_string().contains("#[bar(...)]"));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//! Functions to use with `#[darling(with = "...")]` that control how quoted values
|
||||
//! in [`Meta`] instances are parsed into [`Expr`] fields.
|
||||
//!
|
||||
//! Version 1 of syn did not permit expressions on the right-hand side of the `=` in a
|
||||
//! [`MetaNameValue`](syn::MetaNameValue), so darling accepted string literals and then
|
||||
//! parsed their contents as expressions.
|
||||
//! Passing a string literal in this version would have required the use of a raw string
|
||||
//! to add quotation marks inside the literal.
|
||||
//!
|
||||
//! Version 2 of syn removes the requirement that the right-hand side be a literal.
|
||||
//! For most types, such as [`Path`](syn::Path), the [`FromMeta`] impl can accept the
|
||||
//! version without quotation marks without causing ambiguity; a path cannot start and
|
||||
//! end with quotation marks, so removal is automatic.
|
||||
//!
|
||||
//! [`Expr`] is the one type where this ambiguity is new and unavoidable. To address this,
|
||||
//! this module provides different functions for different expected behaviors.
|
||||
|
||||
use syn::{Expr, Meta};
|
||||
|
||||
use crate::{Error, FromMeta};
|
||||
|
||||
/// Parse a [`Meta`] to an [`Expr`]; if the value is a string literal, the emitted
|
||||
/// expression will be a string literal.
|
||||
pub fn preserve_str_literal(meta: &Meta) -> crate::Result<Expr> {
|
||||
match meta {
|
||||
Meta::Path(_) => Err(Error::unsupported_format("path").with_span(meta)),
|
||||
Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
|
||||
Meta::NameValue(nv) => Ok(nv.value.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a [`Meta`] to an [`Expr`]; if the value is a string literal, the string's
|
||||
/// contents will be parsed as an expression and emitted.
|
||||
pub fn parse_str_literal(meta: &Meta) -> crate::Result<Expr> {
|
||||
match meta {
|
||||
Meta::Path(_) => Err(Error::unsupported_format("path").with_span(meta)),
|
||||
Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
|
||||
Meta::NameValue(nv) => {
|
||||
if let Expr::Lit(expr_lit) = &nv.value {
|
||||
Expr::from_value(&expr_lit.lit)
|
||||
} else {
|
||||
Ok(nv.value.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! meta {
|
||||
($body:expr) => {
|
||||
{
|
||||
let attr: ::syn::Attribute = ::syn::parse_quote!(#[ignore = $body]);
|
||||
attr.meta
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserve_str() {
|
||||
assert_eq!(
|
||||
preserve_str_literal(&meta!("World")).unwrap(),
|
||||
parse_quote!("World")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserve_binary_exp() {
|
||||
assert_eq!(
|
||||
preserve_str_literal(&meta!("World" + 5)).unwrap(),
|
||||
parse_quote!("World" + 5)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ident() {
|
||||
assert_eq!(
|
||||
parse_str_literal(&meta!("world")).unwrap(),
|
||||
parse_quote!(world)
|
||||
)
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use syn::{Meta, Path};
|
||||
|
||||
use crate::ast::NestedMeta;
|
||||
use crate::{Error, FromMeta, Result};
|
||||
|
||||
use super::path_to_string;
|
||||
|
||||
/// A list of `syn::Path` instances. This type is used to extract a list of paths from an
|
||||
/// attribute.
|
||||
///
|
||||
/// # Usage
|
||||
/// An `PathList` field on a struct implementing `FromMeta` will turn `#[builder(derive(serde::Debug, Clone))]` into:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// StructOptions {
|
||||
/// derive: PathList(vec![syn::Path::new("serde::Debug"), syn::Path::new("Clone")])
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct PathList(Vec<Path>);
|
||||
|
||||
impl PathList {
|
||||
/// Create a new list.
|
||||
pub fn new<T: Into<Path>>(vals: Vec<T>) -> Self {
|
||||
PathList(vals.into_iter().map(T::into).collect())
|
||||
}
|
||||
|
||||
/// Create a new `Vec` containing the string representation of each path.
|
||||
pub fn to_strings(&self) -> Vec<String> {
|
||||
self.0.iter().map(path_to_string).collect()
|
||||
}
|
||||
|
||||
/// Visits the values representing the intersection, i.e., the values that are both in `self` and `other`.
|
||||
///
|
||||
/// Values will be returned in the order they appear in `self`.
|
||||
///
|
||||
/// Values that are present multiple times in `self` and present at least once in `other` will be returned multiple times.
|
||||
///
|
||||
/// # Performance
|
||||
/// This function runs in `O(n * m)` time.
|
||||
/// It is believed that path lists are usually short enough that this is better than allocating a set containing the values
|
||||
/// of `other` or adding a conditional solution.
|
||||
pub fn intersection<'a>(&'a self, other: &'a PathList) -> impl Iterator<Item = &'a Path> {
|
||||
self.0.iter().filter(|path| other.0.contains(path))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PathList {
|
||||
type Target = Vec<Path>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Path>> for PathList {
|
||||
fn from(v: Vec<Path>) -> Self {
|
||||
PathList(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for PathList {
|
||||
fn from_list(v: &[NestedMeta]) -> Result<Self> {
|
||||
let mut paths = Vec::with_capacity(v.len());
|
||||
for nmi in v {
|
||||
if let NestedMeta::Meta(Meta::Path(ref path)) = *nmi {
|
||||
paths.push(path.clone());
|
||||
} else {
|
||||
return Err(Error::unexpected_type("non-word").with_span(nmi));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PathList(paths))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PathList;
|
||||
use crate::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_quote, Attribute, Meta};
|
||||
|
||||
/// parse a string as a syn::Meta instance.
|
||||
fn pm(tokens: TokenStream) -> ::std::result::Result<Meta, String> {
|
||||
let attribute: Attribute = parse_quote!(#[#tokens]);
|
||||
Ok(attribute.meta)
|
||||
}
|
||||
|
||||
fn fm<T: FromMeta>(tokens: TokenStream) -> T {
|
||||
FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
|
||||
.expect("Tests should pass valid input")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds() {
|
||||
let paths = fm::<PathList>(quote!(ignore(Debug, Clone, Eq)));
|
||||
assert_eq!(
|
||||
paths.to_strings(),
|
||||
vec![
|
||||
String::from("Debug"),
|
||||
String::from("Clone"),
|
||||
String::from("Eq")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Check that the parser rejects non-word members of the list, and that the error
|
||||
/// has an associated span.
|
||||
#[test]
|
||||
fn fails_non_word() {
|
||||
let input = PathList::from_meta(&pm(quote!(ignore(Debug, Clone = false))).unwrap());
|
||||
let err = input.unwrap_err();
|
||||
assert!(err.has_span());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection() {
|
||||
let left = fm::<PathList>(quote!(ignore(Debug, Clone, Eq)));
|
||||
let right = fm::<PathList>(quote!(ignore(Clone, Eq, Clone)));
|
||||
assert_eq!(
|
||||
left.intersection(&right).cloned().collect::<Vec<_>>(),
|
||||
vec![parse_quote!(Clone), parse_quote!(Eq)],
|
||||
);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/// Transform Rust paths to a readable and comparable string.
|
||||
///
|
||||
/// # Limitations
|
||||
/// * Leading colons are ignored.
|
||||
/// * Angle brackets and `as` elements are ignored.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use darling_core::util::path_to_string;
|
||||
/// # use syn::parse_quote;
|
||||
/// assert_eq!(path_to_string(&parse_quote!(a::b)), "a::b");
|
||||
/// ```
|
||||
pub fn path_to_string(path: &syn::Path) -> String {
|
||||
path.segments
|
||||
.iter()
|
||||
.map(|s| s.ident.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("::")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::path_to_string;
|
||||
|
||||
#[test]
|
||||
fn simple_ident() {
|
||||
assert_eq!(path_to_string(&parse_quote!(a)), "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_path() {
|
||||
assert_eq!(path_to_string(&parse_quote!(a::b)), "a::b");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use crate::{FromMeta, Result};
|
||||
use syn::Expr;
|
||||
|
||||
/// A wrapper around [`Expr`] that preserves the original expression
|
||||
/// without evaluating it.
|
||||
///
|
||||
/// For compatibility reasons, `darling` evaluates the expression inside string
|
||||
/// literals, which might be undesirable. In many cases,
|
||||
/// [`darling::util::parse_expr::preserve_str_literal`] can be used. However,
|
||||
/// when using [`Expr`] inside a container (such as a
|
||||
/// [`HashMap`](std::collections::HashMap)), it is not possible to use it.
|
||||
///
|
||||
/// This wrapper preserves the original expression without evaluating it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[derive(FromMeta)]
|
||||
/// #[darling(attributes(demo))]
|
||||
/// struct Demo {
|
||||
/// option: Option<HashMap<syn::Ident, PreservedStrExpr>>,
|
||||
/// }
|
||||
/// ```
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct PreservedStrExpr(pub Expr);
|
||||
|
||||
impl FromMeta for PreservedStrExpr {
|
||||
fn from_expr(expr: &Expr) -> Result<Self> {
|
||||
Ok(Self(expr.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expr> for PreservedStrExpr {
|
||||
fn from(value: Expr) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PreservedStrExpr> for Expr {
|
||||
fn from(value: PreservedStrExpr) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for PreservedStrExpr {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
self.0.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use syn::{parse_quote, Meta, MetaNameValue};
|
||||
|
||||
#[test]
|
||||
fn preserved_str_expr_from_meta() {
|
||||
let name_value: MetaNameValue = parse_quote!(test = "Hello, world!");
|
||||
let preserved = PreservedStrExpr::from_meta(&Meta::NameValue(name_value)).unwrap();
|
||||
|
||||
assert_eq!(preserved.0, parse_quote!("Hello, world!"));
|
||||
}
|
||||
}
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
use std::{fmt, iter::FromIterator};
|
||||
|
||||
use crate::ast;
|
||||
|
||||
/// Get the "shape" of a fields container, such as a struct or variant.
|
||||
pub trait AsShape {
|
||||
/// Get the "shape" of a fields container.
|
||||
fn as_shape(&self) -> Shape;
|
||||
}
|
||||
|
||||
impl<T> AsShape for ast::Fields<T> {
|
||||
fn as_shape(&self) -> Shape {
|
||||
match self.style {
|
||||
ast::Style::Tuple if self.fields.len() == 1 => Shape::Newtype,
|
||||
ast::Style::Tuple => Shape::Tuple,
|
||||
ast::Style::Struct => Shape::Named,
|
||||
ast::Style::Unit => Shape::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsShape for syn::Fields {
|
||||
fn as_shape(&self) -> Shape {
|
||||
match self {
|
||||
syn::Fields::Named(fields) => fields.as_shape(),
|
||||
syn::Fields::Unnamed(fields) => fields.as_shape(),
|
||||
syn::Fields::Unit => Shape::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsShape for syn::FieldsNamed {
|
||||
fn as_shape(&self) -> Shape {
|
||||
Shape::Named
|
||||
}
|
||||
}
|
||||
|
||||
impl AsShape for syn::FieldsUnnamed {
|
||||
fn as_shape(&self) -> Shape {
|
||||
if self.unnamed.len() == 1 {
|
||||
Shape::Newtype
|
||||
} else {
|
||||
Shape::Tuple
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsShape for syn::DataStruct {
|
||||
fn as_shape(&self) -> Shape {
|
||||
self.fields.as_shape()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsShape for syn::Variant {
|
||||
fn as_shape(&self) -> Shape {
|
||||
self.fields.as_shape()
|
||||
}
|
||||
}
|
||||
|
||||
/// Description of how fields in a struct or variant are syntactically laid out.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Shape {
|
||||
/// A set of named fields, e.g. `{ field: String }`.
|
||||
Named,
|
||||
/// A list of unnamed fields, e.g. `(String, u64)`.
|
||||
Tuple,
|
||||
/// No fields, e.g. `struct Example;`
|
||||
Unit,
|
||||
/// A special case of [`Tuple`](Shape#variant.Tuple) with exactly one field, e.g. `(String)`.
|
||||
Newtype,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Shape::Named => "named fields",
|
||||
Shape::Tuple => "unnamed fields",
|
||||
Shape::Unit => "no fields",
|
||||
Shape::Newtype => "one unnamed field",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Shape {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsShape for Shape {
|
||||
fn as_shape(&self) -> Shape {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of [`Shape`] values, which correctly handles the relationship between
|
||||
/// [newtype](Shape#variant.Newtype) and [tuple](Shape#variant.Tuple) shapes.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use darling_core::util::{Shape, ShapeSet};
|
||||
/// let shape_set = ShapeSet::new(vec![Shape::Tuple]);
|
||||
///
|
||||
/// // This is correct, because all newtypes are single-field tuples.
|
||||
/// assert!(shape_set.contains(&Shape::Newtype));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ShapeSet {
|
||||
newtype: bool,
|
||||
named: bool,
|
||||
tuple: bool,
|
||||
unit: bool,
|
||||
}
|
||||
|
||||
impl ShapeSet {
|
||||
/// Create a new `ShapeSet` which includes the specified items.
|
||||
///
|
||||
/// # Exampe
|
||||
/// ```rust
|
||||
/// # use darling_core::util::{Shape, ShapeSet};
|
||||
/// let shape_set = ShapeSet::new(vec![Shape::Named, Shape::Newtype]);
|
||||
/// assert!(shape_set.contains(&Shape::Newtype));
|
||||
/// ```
|
||||
pub fn new(items: impl IntoIterator<Item = Shape>) -> Self {
|
||||
items.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Insert all possible shapes into the set.
|
||||
///
|
||||
/// This is equivalent to calling [`insert`](ShapeSet#method.insert) with every value of [`Shape`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use darling_core::util::{Shape, ShapeSet};
|
||||
/// let mut shape_set = ShapeSet::default();
|
||||
/// shape_set.insert_all();
|
||||
/// assert!(shape_set.contains(&Shape::Named));
|
||||
/// ```
|
||||
pub fn insert_all(&mut self) {
|
||||
self.insert(Shape::Named);
|
||||
self.insert(Shape::Newtype);
|
||||
self.insert(Shape::Tuple);
|
||||
self.insert(Shape::Unit);
|
||||
}
|
||||
|
||||
/// Insert a shape into the set, so that the set will match that shape
|
||||
pub fn insert(&mut self, shape: Shape) {
|
||||
match shape {
|
||||
Shape::Named => self.named = true,
|
||||
Shape::Tuple => self.tuple = true,
|
||||
Shape::Unit => self.unit = true,
|
||||
Shape::Newtype => self.newtype = true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this set is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
!self.named && !self.newtype && !self.tuple && !self.unit
|
||||
}
|
||||
|
||||
fn contains_shape(&self, shape: Shape) -> bool {
|
||||
match shape {
|
||||
Shape::Named => self.named,
|
||||
Shape::Tuple => self.tuple,
|
||||
Shape::Unit => self.unit,
|
||||
Shape::Newtype => self.newtype || self.tuple,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a fields container's shape is in this set.
|
||||
pub fn contains(&self, fields: &impl AsShape) -> bool {
|
||||
self.contains_shape(fields.as_shape())
|
||||
}
|
||||
|
||||
/// Check if a field container's shape is in this set of shapes, and produce
|
||||
/// an [`Error`](crate::Error) if it does not.
|
||||
pub fn check(&self, fields: &impl AsShape) -> crate::Result<()> {
|
||||
let shape = fields.as_shape();
|
||||
|
||||
if self.contains_shape(shape) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::Error::unsupported_shape_with_expected(
|
||||
shape.description(),
|
||||
self,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_vec(&self) -> Vec<Shape> {
|
||||
let mut shapes = Vec::with_capacity(3);
|
||||
|
||||
if self.named {
|
||||
shapes.push(Shape::Named);
|
||||
}
|
||||
|
||||
if self.tuple || self.newtype {
|
||||
shapes.push(if self.tuple {
|
||||
Shape::Tuple
|
||||
} else {
|
||||
Shape::Newtype
|
||||
});
|
||||
}
|
||||
|
||||
if self.unit {
|
||||
shapes.push(Shape::Unit)
|
||||
}
|
||||
|
||||
shapes
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ShapeSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let shapes = self.to_vec();
|
||||
|
||||
match shapes.len() {
|
||||
0 => write!(f, "nothing"),
|
||||
1 => write!(f, "{}", shapes[0]),
|
||||
2 => write!(f, "{} or {}", shapes[0], shapes[1]),
|
||||
3 => write!(f, "{}, {}, or {}", shapes[0], shapes[1], shapes[2]),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Shape> for ShapeSet {
|
||||
fn from_iter<T: IntoIterator<Item = Shape>>(iter: T) -> Self {
|
||||
let mut output = ShapeSet::default();
|
||||
for shape in iter.into_iter() {
|
||||
output.insert(shape);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn any_accepts_anything() {
|
||||
let mut filter = ShapeSet::default();
|
||||
filter.insert_all();
|
||||
let unit_struct: syn::DeriveInput = syn::parse_quote! {
|
||||
struct Example;
|
||||
};
|
||||
if let syn::Data::Struct(data) = unit_struct.data {
|
||||
assert!(filter.contains(&data));
|
||||
} else {
|
||||
panic!("Struct not parsed as struct");
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_accepts_newtype() {
|
||||
let filter = ShapeSet::new(vec![Shape::Tuple]);
|
||||
let newtype_struct: syn::DeriveInput = parse_quote! {
|
||||
struct Example(String);
|
||||
};
|
||||
|
||||
if let syn::Data::Struct(data) = newtype_struct.data {
|
||||
assert!(filter.contains(&data));
|
||||
} else {
|
||||
panic!("Struct not parsed as struct");
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_rejects_tuple() {
|
||||
let filter = ShapeSet::new(vec![Shape::Newtype]);
|
||||
let tuple_struct: syn::DeriveInput = parse_quote! {
|
||||
struct Example(String, u64);
|
||||
};
|
||||
|
||||
if let syn::Data::Struct(data) = tuple_struct.data {
|
||||
assert!(!filter.contains(&data));
|
||||
} else {
|
||||
panic!("Struct not parsed as struct");
|
||||
};
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
use proc_macro2::Span;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::{
|
||||
FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta, FromTypeParam,
|
||||
FromVariant, Result,
|
||||
};
|
||||
|
||||
/// A value and an associated position in source code. The main use case for this is
|
||||
/// to preserve position information to emit warnings from proc macros. You can use
|
||||
/// a `SpannedValue<T>` as a field in any struct that implements or derives any of
|
||||
/// `darling`'s core traits.
|
||||
///
|
||||
/// To access the underlying value, use the struct's `Deref` implementation.
|
||||
///
|
||||
/// # Defaulting
|
||||
/// This type is meant to be used in conjunction with attribute-extracted options,
|
||||
/// but the user may not always explicitly set those options in their source code.
|
||||
/// In this case, using `Default::default()` will create an instance which points
|
||||
/// to `Span::call_site()`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SpannedValue<T> {
|
||||
value: T,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<T> SpannedValue<T> {
|
||||
pub fn new(value: T, span: Span) -> Self {
|
||||
SpannedValue { value, span }
|
||||
}
|
||||
|
||||
/// Get the source code location referenced by this struct.
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
/// Apply a mapping function to a reference to the spanned value.
|
||||
pub fn map_ref<U>(&self, map_fn: impl FnOnce(&T) -> U) -> SpannedValue<U> {
|
||||
SpannedValue::new(map_fn(&self.value), self.span)
|
||||
}
|
||||
|
||||
/// Gets the inner value, consuming `self` in the process.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for SpannedValue<T> {
|
||||
fn default() -> Self {
|
||||
SpannedValue::new(Default::default(), Span::call_site())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for SpannedValue<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for SpannedValue<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for SpannedValue<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! spanned {
|
||||
($trayt:ident, $method:ident, $syn:path) => {
|
||||
impl<T: $trayt> $trayt for SpannedValue<T> {
|
||||
fn $method(value: &$syn) -> Result<Self> {
|
||||
Ok(SpannedValue::new(
|
||||
$trayt::$method(value).map_err(|e| e.with_span(value))?,
|
||||
value.span(),
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<T: FromMeta> FromMeta for SpannedValue<T> {
|
||||
fn from_meta(item: &syn::Meta) -> Result<Self> {
|
||||
let value = T::from_meta(item).map_err(|e| e.with_span(item))?;
|
||||
let span = match item {
|
||||
// Example: `#[darling(skip)]` as SpannedValue<bool>
|
||||
// should have the span pointing to the word `skip`.
|
||||
syn::Meta::Path(path) => path.span(),
|
||||
// Example: `#[darling(attributes(Value))]` as a SpannedValue<Vec<String>>
|
||||
// should have the span pointing to the list contents.
|
||||
syn::Meta::List(list) => list.tokens.span(),
|
||||
// Example: `#[darling(skip = true)]` as SpannedValue<bool>
|
||||
// should have the span pointing to the word `true`.
|
||||
syn::Meta::NameValue(nv) => nv.value.span(),
|
||||
};
|
||||
|
||||
Ok(Self::new(value, span))
|
||||
}
|
||||
|
||||
fn from_nested_meta(item: &crate::ast::NestedMeta) -> Result<Self> {
|
||||
T::from_nested_meta(item)
|
||||
.map(|value| Self::new(value, item.span()))
|
||||
.map_err(|e| e.with_span(item))
|
||||
}
|
||||
|
||||
fn from_value(literal: &syn::Lit) -> Result<Self> {
|
||||
T::from_value(literal)
|
||||
.map(|value| Self::new(value, literal.span()))
|
||||
.map_err(|e| e.with_span(literal))
|
||||
}
|
||||
|
||||
fn from_expr(expr: &syn::Expr) -> Result<Self> {
|
||||
T::from_expr(expr)
|
||||
.map(|value| Self::new(value, expr.span()))
|
||||
.map_err(|e| e.with_span(expr))
|
||||
}
|
||||
}
|
||||
|
||||
spanned!(FromGenericParam, from_generic_param, syn::GenericParam);
|
||||
spanned!(FromGenerics, from_generics, syn::Generics);
|
||||
spanned!(FromTypeParam, from_type_param, syn::TypeParam);
|
||||
spanned!(FromDeriveInput, from_derive_input, syn::DeriveInput);
|
||||
spanned!(FromField, from_field, syn::Field);
|
||||
spanned!(FromVariant, from_variant, syn::Variant);
|
||||
|
||||
impl<T: Spanned> From<T> for SpannedValue<T> {
|
||||
fn from(value: T) -> Self {
|
||||
let span = value.span();
|
||||
SpannedValue::new(value, span)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proc_macro2::Span;
|
||||
|
||||
/// Make sure that `SpannedValue` can be seamlessly used as its underlying type.
|
||||
#[test]
|
||||
fn deref() {
|
||||
let test = SpannedValue::new("hello", Span::call_site());
|
||||
assert_eq!("hello", test.trim());
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
use crate::{
|
||||
usage::{UsesLifetimes, UsesTypeParams},
|
||||
FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta, FromTypeParam,
|
||||
FromVariant, Result,
|
||||
};
|
||||
|
||||
/// A container to parse some syntax and retain access to the original.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WithOriginal<T, O> {
|
||||
pub parsed: T,
|
||||
pub original: O,
|
||||
}
|
||||
|
||||
impl<T, O> WithOriginal<T, O> {
|
||||
pub fn new(parsed: T, original: O) -> Self {
|
||||
WithOriginal { parsed, original }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! with_original {
|
||||
($trayt:ident, $func:ident, $syn:path) => {
|
||||
impl<T: $trayt> $trayt for WithOriginal<T, $syn> {
|
||||
fn $func(value: &$syn) -> Result<Self> {
|
||||
Ok(WithOriginal::new($trayt::$func(value)?, value.clone()))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
with_original!(FromDeriveInput, from_derive_input, syn::DeriveInput);
|
||||
with_original!(FromField, from_field, syn::Field);
|
||||
with_original!(FromGenerics, from_generics, syn::Generics);
|
||||
with_original!(FromGenericParam, from_generic_param, syn::GenericParam);
|
||||
with_original!(FromMeta, from_meta, syn::Meta);
|
||||
with_original!(FromTypeParam, from_type_param, syn::TypeParam);
|
||||
with_original!(FromVariant, from_variant, syn::Variant);
|
||||
|
||||
/// Get the lifetime usage of `parsed`.
|
||||
impl<P: UsesLifetimes, O> UsesLifetimes for WithOriginal<P, O> {
|
||||
fn uses_lifetimes<'a>(
|
||||
&self,
|
||||
options: &crate::usage::Options,
|
||||
lifetimes: &'a crate::usage::LifetimeSet,
|
||||
) -> crate::usage::LifetimeRefSet<'a> {
|
||||
self.parsed.uses_lifetimes(options, lifetimes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type param usage of `parsed`.
|
||||
impl<P: UsesTypeParams, O> UsesTypeParams for WithOriginal<P, O> {
|
||||
fn uses_type_params<'a>(
|
||||
&self,
|
||||
options: &crate::usage::Options,
|
||||
type_set: &'a crate::usage::IdentSet,
|
||||
) -> crate::usage::IdentRefSet<'a> {
|
||||
self.parsed.uses_type_params(options, type_set)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user