diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae44483e..4e55e2c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,76 +1,76 @@ name: CI on: - push: - pull_request: - schedule: [cron: "40 1 * * *"] + push: + pull_request: + schedule: [cron: "40 1 * * *"] env: - RUST_BACKTRACE: 1 + RUST_BACKTRACE: 1 jobs: - test: - name: Test Rust ${{ matrix.rust }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: nightly, os: ubuntu-latest } - - { rust: nightly, os: macos-latest } - - { rust: nightly, os: windows-latest } - - { rust: stable, os: ubuntu-latest } - - { rust: stable, os: macos-latest } - - { rust: stable, os: windows-latest } - - { rust: 1.31.0, os: ubuntu-latest } - - { rust: 1.31.0, os: windows-latest } - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.rust }} - - uses: Swatinem/rust-cache@v2 - - name: Check Cargo availability - run: cargo --version - - run: cargo test --verbose --all - - run: cargo test --verbose --manifest-path core/Cargo.toml --no-default-features + test: + name: Test Rust ${{ matrix.rust }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { rust: nightly, os: ubuntu-latest } + - { rust: nightly, os: macos-latest } + - { rust: nightly, os: windows-latest } + - { rust: stable, os: ubuntu-latest } + - { rust: stable, os: macos-latest } + - { rust: stable, os: windows-latest } + - { rust: 1.56.0, os: ubuntu-latest } + - { rust: 1.56.0, os: windows-latest } + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: Check Cargo availability + run: cargo --version + - run: cargo test --verbose --all + - run: cargo test --verbose --manifest-path core/Cargo.toml --no-default-features - # Diagnostics are remaining a nightly-only feature for the foreseeable future, but - # we don't want them to break without us realizing. - test_diagnostics: - name: Test nightly with diagnostics feature - runs-on: ubuntu-latest - continue-on-error: true - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 - - name: Check Cargo availability - run: cargo --version - - run: cargo test --verbose --workspace --features diagnostics + # Diagnostics are remaining a nightly-only feature for the foreseeable future, but + # we don't want them to break without us realizing. + test_diagnostics: + name: Test nightly with diagnostics feature + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + - name: Check Cargo availability + run: cargo --version + - run: cargo test --verbose --workspace --features diagnostics - clippy: - name: Lint with clippy - runs-on: ubuntu-latest - env: - RUSTFLAGS: -Dwarnings - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Run clippy --workspace --tests - run: cargo clippy --workspace --tests + clippy: + name: Lint with clippy + runs-on: ubuntu-latest + env: + RUSTFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Run clippy --workspace --tests + run: cargo clippy --workspace --tests - rustfmt: - name: Verify code formatting - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: Run fmt --all -- --check - run: cargo fmt --all -- --check + rustfmt: + name: Verify code formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Run fmt --all -- --check + run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index 6f9aa306..56751cda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "darling" -version = "0.14.4" +version = "0.20.0" authors = ["Ted Driggs "] repository = "https://github.com/TedDriggs/darling" -documentation = "https://docs.rs/darling/0.14.4" +documentation = "https://docs.rs/darling/0.20.0" description = """ A proc-macro library for reading attributes into structs when implementing custom derives. @@ -17,13 +17,13 @@ exclude = ["/.travis.yml", "/publish.sh", "/.github/**"] maintenance = { status = "actively-developed" } [dependencies] -darling_core = { version = "=0.14.4", path = "core" } -darling_macro = { version = "=0.14.4", path = "macro" } +darling_core = { version = "=0.20.0", path = "core" } +darling_macro = { version = "=0.20.0", path = "macro" } [dev-dependencies] proc-macro2 = "1.0.37" quote = "1.0.18" -syn = "1.0.91" +syn = "2.0.15" [target.'cfg(compiletests)'.dev-dependencies] rustversion = "1.0.9" diff --git a/README.md b/README.md index 5f7f9d5b..cc945841 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ -Darling -======= +# Darling [![Build Status](https://github.com/TedDriggs/darling/workflows/CI/badge.svg)](https://github.com/TedDriggs/darling/actions) [![Latest Version](https://img.shields.io/crates/v/darling.svg)](https://crates.io/crates/darling) -[![Rustc Version 1.31+](https://img.shields.io/badge/rustc-1.31+-lightgray.svg)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html) +[![Rustc Version 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)] `darling` is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by `serde` both in its internals and in its API. # Benefits -* Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment. -* Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields. + +- Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment. +- Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields. # Usage + `darling` provides a set of traits which can be derived or manually implemented. 1. `FromMeta` is used to extract values from a meta-item in an attribute. Implementations are likely reusable for many libraries, much like `FromStr` or `serde::Deserialize`. Trait implementations are provided for primitives, some std types, and some `syn` types. @@ -21,9 +22,10 @@ Darling 5. `FromAttributes` is a lower-level version of the more-specific `FromDeriveInput`, `FromField`, and `FromVariant` traits. Structs deriving this trait get a meta-item extractor and error collection which works for any syntax element, including traits, trait items, and functions. This is useful for non-derive proc macros. ## Additional Modules -* `darling::ast` provides generic types for representing the AST. -* `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum. -* `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`. + +- `darling::ast` provides generic types for representing the AST. +- `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum. +- `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`. # Example @@ -59,14 +61,17 @@ pub struct ConsumingType; ``` # Attribute Macros + Non-derive attribute macros are supported. To parse arguments for attribute macros, derive `FromMeta` on the argument receiver type, then pass `&syn::AttributeArgs` to the `from_list` method. This will produce a normal `darling::Result` that can be used the same as a result from parsing a `DeriveInput`. ## Macro Code + ```rust,ignore -use darling::FromMeta; -use syn::{AttributeArgs, ItemFn}; +use darling::{Error, FromMeta}; +use darling::ast::NestedMeta; +use syn::ItemFn; use proc_macro::TokenStream; #[derive(Debug, FromMeta)] @@ -76,10 +81,13 @@ pub struct MacroArgs { path: String, } -#[proc_macro_attribute] +// #[proc_macro_attribute] fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream { - let attr_args = parse_macro_input!(args as AttributeArgs); - let _input = parse_macro_input!(input as ItemFn); + let attr_args = match NestedMeta::parse_meta_list(args) { + Ok(v) => v, + Err(e) => { return TokenStream::from(Error::from(e).write_errors()); } + }; + let _input = syn::parse_macro_input!(input as ItemFn); let _args = match MacroArgs::from_list(&attr_args) { Ok(v) => v, @@ -92,6 +100,7 @@ fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream { ``` ## Consuming Code + ```rust,ignore use your_crate::your_attr; @@ -102,37 +111,39 @@ fn do_stuff() { ``` # Features + Darling's features are built to work well for real-world projects. -* **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`. - Additionally, `Option` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those. -* **Field Renaming**: Fields can have different names in usage vs. the backing code. -* **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`. -* **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct. -* **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items. -* **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`. -* **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases. -* **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields. +- **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`. + Additionally, `Option` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those. +- **Field Renaming**: Fields can have different names in usage vs. the backing code. +- **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`. +- **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct. +- **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items. +- **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`. +- **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases. +- **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields. ## Shape Validation + Some proc-macros only work on structs, while others need enums whose variants are either unit or newtype variants. Darling makes this sort of validation extremely simple. On the receiver that derives `FromDeriveInput`, add `#[darling(supports(...))]` and then list the shapes that your macro should accept. -|Name|Description| -|---|---| -|`any`|Accept anything| -|`struct_any`|Accept any struct| -|`struct_named`|Accept structs with named fields, e.g. `struct Example { field: String }`| -|`struct_newtype`|Accept newtype structs, e.g. `struct Example(String)`| -|`struct_tuple`|Accept tuple structs, e.g. `struct Example(String, String)`| -|`struct_unit`|Accept unit structs, e.g. `struct Example;`| -|`enum_any`|Accept any enum| -|`enum_named`|Accept enum variants with named fields| -|`enum_newtype`|Accept newtype enum variants| -|`enum_tuple`|Accept tuple enum variants| -|`enum_unit`|Accept unit enum variants| +| Name | Description | +| ---------------- | ------------------------------------------------------------------------- | +| `any` | Accept anything | +| `struct_any` | Accept any struct | +| `struct_named` | Accept structs with named fields, e.g. `struct Example { field: String }` | +| `struct_newtype` | Accept newtype structs, e.g. `struct Example(String)` | +| `struct_tuple` | Accept tuple structs, e.g. `struct Example(String, String)` | +| `struct_unit` | Accept unit structs, e.g. `struct Example;` | +| `enum_any` | Accept any enum | +| `enum_named` | Accept enum variants with named fields | +| `enum_newtype` | Accept newtype enum variants | +| `enum_tuple` | Accept tuple enum variants | +| `enum_unit` | Accept unit enum variants | Each one is additive, so listing `#[darling(supports(struct_any, enum_newtype))]` would accept all structs and any enum where every variant is a newtype variant. -This can also be used when deriving `FromVariant`, without the `enum_` prefix. \ No newline at end of file +This can also be used when deriving `FromVariant`, without the `enum_` prefix. diff --git a/clippy.toml b/clippy.toml index ce89b550..e221e795 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,2 @@ -msrv = "1.31.0" +msrv = "1.56.0" disallowed-names = [] # we want to be able to use placeholder names in tests \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index 677f9815..655e555b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "darling_core" -version = "0.14.4" +version = "0.20.0" authors = ["Ted Driggs "] repository = "https://github.com/TedDriggs/darling" description = """ @@ -18,6 +18,6 @@ suggestions = ["strsim"] ident_case = "1.0.1" proc-macro2 = "1.0.37" quote = "1.0.18" -syn = { version = "1.0.91", features = ["full", "extra-traits"] } +syn = { version = "2.0.15", features = ["full", "extra-traits"] } fnv = "1.0.7" strsim = { version = "0.10.0", optional = true } diff --git a/core/src/ast/data.rs b/core/src/ast/data.rs index 958c7be9..38b0190e 100644 --- a/core/src/ast/data.rs +++ b/core/src/ast/data.rs @@ -2,7 +2,10 @@ use std::{slice, vec}; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; +use syn::ext::IdentExt; +use syn::parse::Parser; use syn::spanned::Spanned; +use syn::Token; use crate::usage::{ self, IdentRefSet, IdentSet, LifetimeRefSet, LifetimeSet, UsesLifetimes, UsesTypeParams, @@ -410,6 +413,43 @@ impl<'a> From<&'a syn::Fields> for Style { } } +#[derive(Debug)] +pub enum NestedMeta { + Meta(syn::Meta), + Lit(syn::Lit), +} + +impl NestedMeta { + pub fn parse_meta_list(tokens: TokenStream) -> syn::Result> { + syn::punctuated::Punctuated::::parse_terminated + .parse2(tokens) + .map(|punctuated| punctuated.into_iter().collect()) + } +} + +impl syn::parse::Parse for NestedMeta { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(Token![=])) { + input.parse().map(NestedMeta::Lit) + } else if input.peek(syn::Ident::peek_any) + || input.peek(Token![::]) && input.peek3(syn::Ident::peek_any) + { + input.parse().map(NestedMeta::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), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/src/ast/generics.rs b/core/src/ast/generics.rs index 3de3c579..d4bf0c9b 100644 --- a/core/src/ast/generics.rs +++ b/core/src/ast/generics.rs @@ -14,7 +14,7 @@ use crate::{FromGenericParam, FromGenerics, FromTypeParam, Result}; pub trait GenericParamExt { /// The type this GenericParam uses to represent type params and their bounds type TypeParam; - type LifetimeDef; + type LifetimeParam; type ConstParam; /// If this GenericParam is a type param, get the underlying value. @@ -23,7 +23,7 @@ pub trait GenericParamExt { } /// If this GenericParam is a lifetime, get the underlying value. - fn as_lifetime_def(&self) -> Option<&Self::LifetimeDef> { + fn as_lifetime_param(&self) -> Option<&Self::LifetimeParam> { None } @@ -35,7 +35,7 @@ pub trait GenericParamExt { impl GenericParamExt for syn::GenericParam { type TypeParam = syn::TypeParam; - type LifetimeDef = syn::LifetimeDef; + type LifetimeParam = syn::LifetimeParam; type ConstParam = syn::ConstParam; fn as_type_param(&self) -> Option<&Self::TypeParam> { @@ -46,7 +46,7 @@ impl GenericParamExt for syn::GenericParam { } } - fn as_lifetime_def(&self) -> Option<&Self::LifetimeDef> { + fn as_lifetime_param(&self) -> Option<&Self::LifetimeParam> { if let syn::GenericParam::Lifetime(ref val) = *self { Some(val) } else { @@ -65,7 +65,7 @@ impl GenericParamExt for syn::GenericParam { impl GenericParamExt for syn::TypeParam { type TypeParam = syn::TypeParam; - type LifetimeDef = (); + type LifetimeParam = (); type ConstParam = (); fn as_type_param(&self) -> Option<&Self::TypeParam> { @@ -75,7 +75,7 @@ impl GenericParamExt for syn::TypeParam { /// A mirror of `syn::GenericParam` which is generic over all its contents. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum GenericParam { +pub enum GenericParam { Type(T), Lifetime(L), Const(C), @@ -103,7 +103,7 @@ impl FromGenericParam for GenericParam { impl GenericParamExt for GenericParam { type TypeParam = T; - type LifetimeDef = L; + type LifetimeParam = L; type ConstParam = C; fn as_type_param(&self) -> Option<&T> { @@ -114,7 +114,7 @@ impl GenericParamExt for GenericParam { } } - fn as_lifetime_def(&self) -> Option<&L> { + fn as_lifetime_param(&self) -> Option<&L> { if let GenericParam::Lifetime(ref val) = *self { Some(val) } else { diff --git a/core/src/codegen/attr_extractor.rs b/core/src/codegen/attr_extractor.rs index 521d01c5..4afb174b 100644 --- a/core/src/codegen/attr_extractor.rs +++ b/core/src/codegen/attr_extractor.rs @@ -55,13 +55,18 @@ pub trait ExtractAttribute { #(#attr_names)|* => { match ::darling::util::parse_attribute_to_meta_list(__attr) { ::darling::export::Ok(__data) => { - if __data.nested.is_empty() { - continue; + 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()); + } } - - let __items = &__data.nested; - - #core_loop } // 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 @@ -92,7 +97,7 @@ pub trait ExtractAttribute { for __attr in #attrs_accessor { // Filter attributes based on name - match ::darling::export::ToString::to_string(&__attr.path.clone().into_token_stream()).as_str() { + match ::darling::export::ToString::to_string(&__attr.path().clone().into_token_stream()).as_str() { #parse_handled #forward_unhandled } diff --git a/core/src/codegen/from_meta_impl.rs b/core/src/codegen/from_meta_impl.rs index af59f3b1..5a65c5d7 100644 --- a/core/src/codegen/from_meta_impl.rs +++ b/core/src/codegen/from_meta_impl.rs @@ -55,7 +55,7 @@ impl<'a> ToTokens for FromMetaImpl<'a> { let post_transform = base.post_transform_call(); quote!( - fn from_list(__items: &[::darling::export::syn::NestedMeta]) -> ::darling::Result { + fn from_list(__items: &[::darling::export::NestedMeta]) -> ::darling::Result { #decls @@ -91,13 +91,13 @@ impl<'a> ToTokens for FromMetaImpl<'a> { }; quote!( - fn from_list(__outer: &[::darling::export::syn::NestedMeta]) -> ::darling::Result { + fn from_list(__outer: &[::darling::export::NestedMeta]) -> ::darling::Result { // 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::syn::NestedMeta::Meta(ref __nested) = __outer[0] { + if let ::darling::export::NestedMeta::Meta(ref __nested) = __outer[0] { match ::darling::util::path_to_string(__nested.path()).as_ref() { #(#struct_arms)* __other => ::darling::export::Err(::darling::Error::#unknown_variant_err.with_span(__nested)) diff --git a/core/src/codegen/variant.rs b/core/src/codegen/variant.rs index 9f5902ba..ccbabad6 100644 --- a/core/src/codegen/variant.rs +++ b/core/src/codegen/variant.rs @@ -121,7 +121,8 @@ impl<'a> ToTokens for DataMatchArm<'a> { tokens.append_all(quote!( #name_in_attr => { if let ::darling::export::syn::Meta::List(ref __data) = *__nested { - let __items = &__data.nested; + let __items = ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone())?; + let __items = &__items; #declare_errors diff --git a/core/src/codegen/variant_data.rs b/core/src/codegen/variant_data.rs index d1c878e2..7c0063ea 100644 --- a/core/src/codegen/variant_data.rs +++ b/core/src/codegen/variant_data.rs @@ -60,14 +60,14 @@ impl<'a> FieldsGen<'a> { quote!( for __item in __items { match *__item { - ::darling::export::syn::NestedMeta::Meta(ref __inner) => { + ::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::syn::NestedMeta::Lit(ref __inner) => { + ::darling::export::NestedMeta::Lit(ref __inner) => { __errors.push(::darling::Error::unsupported_format("literal") .with_span(__inner)); } diff --git a/core/src/error/mod.rs b/core/src/error/mod.rs index 8d888645..6f2c0454 100644 --- a/core/src/error/mod.rs +++ b/core/src/error/mod.rs @@ -13,7 +13,7 @@ use std::iter::{self, Iterator}; use std::string::ToString; use std::vec; use syn::spanned::Spanned; -use syn::{Lit, LitStr, Path}; +use syn::{Expr, Lit, LitStr, Path}; #[cfg(feature = "diagnostics")] mod child; @@ -149,6 +149,53 @@ impl Error { Error::new(ErrorKind::UnexpectedType(ty.into())) } + pub fn unexpected_expr_type(expr: &Expr) -> Self { + Error::unexpected_type(match *expr { + Expr::Array(_) => "array", + Expr::Assign(_) => "assign", + Expr::Async(_) => "async", + Expr::Await(_) => "await", + Expr::Binary(_) => "binary", + Expr::Block(_) => "block", + Expr::Break(_) => "break", + Expr::Call(_) => "call", + Expr::Cast(_) => "cast", + Expr::Closure(_) => "closure", + Expr::Const(_) => "const", + Expr::Continue(_) => "continue", + Expr::Field(_) => "field", + Expr::ForLoop(_) => "for_loop", + Expr::Group(_) => "group", + Expr::If(_) => "if", + Expr::Index(_) => "index", + Expr::Infer(_) => "infer", + Expr::Let(_) => "let", + Expr::Lit(_) => "lit", + Expr::Loop(_) => "loop", + Expr::Macro(_) => "macro", + Expr::Match(_) => "match", + Expr::MethodCall(_) => "method_call", + Expr::Paren(_) => "paren", + Expr::Path(_) => "path", + Expr::Range(_) => "range", + Expr::Reference(_) => "reference", + Expr::Repeat(_) => "repeat", + Expr::Return(_) => "return", + Expr::Struct(_) => "struct", + Expr::Try(_) => "try", + Expr::TryBlock(_) => "try_block", + Expr::Tuple(_) => "tuple", + Expr::Unary(_) => "unary", + Expr::Unsafe(_) => "unsafe", + Expr::Verbatim(_) => "verbatim", + Expr::While(_) => "while", + Expr::Yield(_) => "yield", + // non-exhaustive enum + _ => "unknown", + }) + .with_span(expr) + } + /// Creates a new error for a field which has an unexpected literal type. This will automatically /// extract the literal type name from the passed-in `Lit` and set the span to encompass only the /// literal value. @@ -188,6 +235,8 @@ impl Error { Lit::Float(_) => "float", Lit::Bool(_) => "bool", Lit::Verbatim(_) => "verbatim", + // non-exhaustive enum + _ => "unknown", }) .with_span(lit) } diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index ac4f06c3..4c1ce245 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -7,9 +7,11 @@ use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::Arc; -use syn::{Expr, Lit, Meta, NestedMeta}; +use syn::{Expr, Lit, Meta}; -use crate::{util::path_to_string, Error, Result}; +use crate::ast::NestedMeta; +use crate::util::path_to_string; +use crate::{Error, Result}; /// Create an instance from an item in an attribute declaration. /// @@ -66,14 +68,10 @@ pub trait FromMeta: Sized { fn from_meta(item: &Meta) -> Result { (match *item { Meta::Path(_) => Self::from_word(), - Meta::List(ref value) => Self::from_list( - &value - .nested - .iter() - .cloned() - .collect::>()[..], - ), - Meta::NameValue(ref value) => Self::from_value(&value.lit), + Meta::List(ref value) => { + Self::from_list(&NestedMeta::parse_meta_list(value.tokens.clone())?[..]) + } + Meta::NameValue(ref value) => Self::from_expr(&value.value), }) .map_err(|e| e.with_span(item)) } @@ -121,6 +119,15 @@ pub trait FromMeta: Sized { .map_err(|e| e.with_span(value)) } + fn from_expr(expr: &Expr) -> Result { + match *expr { + Expr::Lit(ref lit) => Self::from_value(&lit.lit), + Expr::Group(ref group) => Self::from_expr(&group.expr), + _ => Err(Error::unexpected_expr_type(expr)), + } + .map_err(|e| e.with_span(expr)) + } + /// Create an instance from a char literal in a value position. #[allow(unused_variables)] fn from_char(value: char) -> Result { @@ -267,6 +274,74 @@ impl FromMeta for syn::punctuated::P } } +/// Support for arbitrary expressions as values in a meta item. +/// +/// For backwards-compatibility to versions of `darling` based on `syn` 1, +/// string literals will be "unwrapped" and their contents will be parsed +/// as an expression. +impl FromMeta for syn::Expr { + fn from_expr(expr: &Expr) -> Result { + if let syn::Expr::Lit(expr_lit) = expr { + if let syn::Lit::Str(_) = &expr_lit.lit { + return Self::from_value(&expr_lit.lit); + } + } + + Ok(expr.clone()) + } + + fn from_string(value: &str) -> Result { + syn::parse_str(value).map_err(|_| Error::unknown_value(value)) + } + + fn from_value(value: &::syn::Lit) -> Result { + if let ::syn::Lit::Str(ref v) = *value { + v.parse::() + .map_err(|_| Error::unknown_lit_str_value(v)) + } else { + Err(Error::unexpected_lit_type(value)) + } + } +} + +/// Adapter for various expression types. +/// +/// Prior to syn 2.0, darling supported arbitrary expressions as long as they +/// were wrapped in quotation marks. This was helpful for people writing +/// libraries that needed expressions, but it now creates an ambiguity when +/// parsing a meta item. +/// +/// To address this, the macro supports both formats; if it cannot parse the +/// item as an expression of the right type and the passed-in expression is +/// a string literal, it will fall back to parsing the string contents. +macro_rules! from_syn_expr_type { + ($ty:path, $variant:ident) => { + impl FromMeta for $ty { + fn from_expr(expr: &syn::Expr) -> Result { + if let syn::Expr::$variant(body) = expr { + Ok(body.clone()) + } else if let syn::Expr::Lit(expr_lit) = expr { + Self::from_value(&expr_lit.lit) + } else { + Err(Error::unexpected_expr_type(expr)) + } + } + + fn from_value(value: &::syn::Lit) -> Result { + if let syn::Lit::Str(body) = &value { + body.parse::<$ty>() + .map_err(|_| Error::unknown_lit_str_value(body)) + } else { + Err(Error::unexpected_lit_type(value)) + } + } + } + }; +} + +from_syn_expr_type!(syn::ExprArray, Array); +from_syn_expr_type!(syn::ExprPath, Path); + /// Adapter from `syn::parse::Parse` to `FromMeta`. /// /// This cannot be a blanket impl, due to the `syn::Lit` family's need to handle non-string values. @@ -291,9 +366,6 @@ macro_rules! from_syn_parse { } from_syn_parse!(syn::Ident); -from_syn_parse!(syn::Expr); -from_syn_parse!(syn::ExprArray); -from_syn_parse!(syn::ExprPath); from_syn_parse!(syn::Path); from_syn_parse!(syn::Type); from_syn_parse!(syn::TypeArray); @@ -318,11 +390,9 @@ macro_rules! from_numeric_array { ($ty:ident) => { /// Parsing an unsigned integer array, i.e. `example = "[1, 2, 3, 4]"`. impl FromMeta for Vec<$ty> { - fn from_value(value: &Lit) -> Result { - let expr_array = syn::ExprArray::from_value(value)?; - // To meet rust <1.36 borrow checker rules on expr_array.elems - let v = - expr_array + fn from_expr(expr: &syn::Expr) -> Result { + if let syn::Expr::Array(expr_array) = expr { + let v = expr_array .elems .iter() .map(|expr| match expr { @@ -331,7 +401,17 @@ macro_rules! from_numeric_array { .with_span(expr)), }) .collect::>>(); - v + v + } else if let syn::Expr::Lit(expr_lit) = expr { + Self::from_value(&expr_lit.lit) + } else { + Err(Error::unexpected_expr_type(expr)) + } + } + + fn from_value(value: &Lit) -> Result { + let expr_array = syn::ExprArray::from_value(value)?; + Self::from_expr(&syn::Expr::Array(expr_array)) } } }; @@ -519,7 +599,7 @@ impl KeyFromPath for syn::Ident { macro_rules! hash_map { ($key:ty) => { impl FromMeta for HashMap<$key, V, S> { - fn from_list(nested: &[syn::NestedMeta]) -> Result { + fn from_list(nested: &[NestedMeta]) -> Result { // Convert the nested meta items into a sequence of (path, value result) result tuples. // An outer Err means no (key, value) structured could be found, while an Err in the // second position of the tuple means that value was rejected by FromMeta. @@ -530,14 +610,14 @@ macro_rules! hash_map { .iter() .map(|item| -> Result<(&syn::Path, Result)> { match *item { - syn::NestedMeta::Meta(ref inner) => { + NestedMeta::Meta(ref inner) => { let path = inner.path(); Ok(( path, FromMeta::from_meta(inner).map_err(|e| e.at_path(&path)), )) } - syn::NestedMeta::Lit(_) => Err(Error::unsupported_format("literal")), + NestedMeta::Lit(_) => Err(Error::unsupported_format("expression")), } }); @@ -613,9 +693,10 @@ mod tests { /// parse a string as a syn::Meta instance. fn pm(tokens: TokenStream) -> ::std::result::Result { let attribute: syn::Attribute = parse_quote!(#[#tokens]); - attribute.parse_meta().map_err(|_| "Unable to parse".into()) + Ok(attribute.meta) } + #[track_caller] fn fm(tokens: TokenStream) -> T { FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input")) .expect("Tests should pass valid input") @@ -829,10 +910,7 @@ mod tests { #[test] fn test_number_array() { - assert_eq!( - fm::>(quote!(ignore = "[16, 0xff]")), - vec![0x10, 0xff] - ); + assert_eq!(fm::>(quote!(ignore = [16, 0xff])), vec![0x10, 0xff]); assert_eq!( fm::>(quote!(ignore = "[32, 0xffff]")), vec![0x20, 0xffff] diff --git a/core/src/options/core.rs b/core/src/options/core.rs index c79f7d5d..cc05f218 100644 --- a/core/src/options/core.rs +++ b/core/src/options/core.rs @@ -166,7 +166,7 @@ impl<'a> From<&'a Core> for codegen::TraitImpl<'a> { .map_enum_variants(|variant| variant.as_codegen_variant(&v.ident)), default: v.as_codegen_default(), post_transform: v.post_transform.as_ref(), - bound: v.bound.as_ref().map(|i| i.as_slice()), + bound: v.bound.as_deref(), allow_unknown_fields: v.allow_unknown_fields.unwrap_or_default(), } } diff --git a/core/src/options/forward_attrs.rs b/core/src/options/forward_attrs.rs index c72d009d..ac9f4e1a 100644 --- a/core/src/options/forward_attrs.rs +++ b/core/src/options/forward_attrs.rs @@ -1,5 +1,4 @@ -use syn::NestedMeta; - +use crate::ast::NestedMeta; use crate::util::PathList; use crate::{FromMeta, Result}; diff --git a/core/src/options/from_derive.rs b/core/src/options/from_derive.rs index 0f1e1c4d..71a3c3cb 100644 --- a/core/src/options/from_derive.rs +++ b/core/src/options/from_derive.rs @@ -52,13 +52,7 @@ impl ParseData for FdiOptions { } fn parse_field(&mut self, field: &syn::Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("vis") => { self.vis = field.ident.clone(); Ok(()) diff --git a/core/src/options/from_field.rs b/core/src/options/from_field.rs index fcfab755..75e5fed2 100644 --- a/core/src/options/from_field.rs +++ b/core/src/options/from_field.rs @@ -37,13 +37,7 @@ impl ParseData for FromFieldOptions { } fn parse_field(&mut self, field: &syn::Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("vis") => { self.vis = field.ident.clone(); Ok(()) diff --git a/core/src/options/from_type_param.rs b/core/src/options/from_type_param.rs index 5ab3241e..80178ff7 100644 --- a/core/src/options/from_type_param.rs +++ b/core/src/options/from_type_param.rs @@ -37,13 +37,7 @@ impl ParseData for FromTypeParamOptions { } fn parse_field(&mut self, field: &syn::Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("bounds") => { self.bounds = field.ident.clone(); Ok(()) diff --git a/core/src/options/from_variant.rs b/core/src/options/from_variant.rs index fabdae17..ad761ade 100644 --- a/core/src/options/from_variant.rs +++ b/core/src/options/from_variant.rs @@ -58,13 +58,7 @@ impl ParseAttribute for FromVariantOptions { impl ParseData for FromVariantOptions { fn parse_field(&mut self, field: &Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("discriminant") => { self.discriminant = field.ident.clone(); Ok(()) diff --git a/core/src/options/mod.rs b/core/src/options/mod.rs index d8a147d8..066aa1c1 100644 --- a/core/src/options/mod.rs +++ b/core/src/options/mod.rs @@ -1,6 +1,7 @@ use proc_macro2::Span; use syn::{parse_quote, spanned::Spanned}; +use crate::ast::NestedMeta; use crate::{Error, FromMeta, Result}; mod core; @@ -50,7 +51,7 @@ impl FromMeta for DefaultExpression { 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_value(&nv.lit), + syn::Meta::NameValue(nv) => Self::from_expr(&nv.value), } } @@ -66,7 +67,7 @@ pub trait ParseAttribute: Sized { fn parse_attributes(mut self, attrs: &[syn::Attribute]) -> Result { let mut errors = Error::accumulator(); for attr in attrs { - if attr.path == parse_quote!(darling) { + if attr.meta.path() == &parse_quote!(darling) { errors.handle(parse_attr(attr, &mut self)); } } @@ -80,10 +81,10 @@ pub trait ParseAttribute: Sized { fn parse_attr(attr: &syn::Attribute, target: &mut T) -> Result<()> { let mut errors = Error::accumulator(); - match attr.parse_meta().ok() { - Some(syn::Meta::List(data)) => { - for item in data.nested { - if let syn::NestedMeta::Meta(ref mi) = item { + 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); @@ -92,8 +93,7 @@ fn parse_attr(attr: &syn::Attribute, target: &mut T) -> Resul errors.finish() } - Some(ref item) => panic!("Wasn't able to parse: `{:?}`", item), - None => panic!("Unable to parse {:?}", attr), + item => panic!("Wasn't able to parse: `{:?}`", item), } } diff --git a/core/src/options/outer_from.rs b/core/src/options/outer_from.rs index 0a8088a0..492b0cd6 100644 --- a/core/src/options/outer_from.rs +++ b/core/src/options/outer_from.rs @@ -66,13 +66,7 @@ impl ParseAttribute for OuterFrom { impl ParseData for OuterFrom { fn parse_field(&mut self, field: &Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("ident") => { self.ident = field.ident.clone(); Ok(()) diff --git a/core/src/options/shape.rs b/core/src/options/shape.rs index 5245a00d..3e35a236 100644 --- a/core/src/options/shape.rs +++ b/core/src/options/shape.rs @@ -3,8 +3,9 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{parse_quote, Meta, NestedMeta}; +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 @@ -225,7 +226,7 @@ mod tests { /// parse a string as a syn::Meta instance. fn pm(tokens: TokenStream) -> ::std::result::Result { let attribute: syn::Attribute = parse_quote!(#[#tokens]); - attribute.parse_meta().map_err(|_| "Unable to parse".into()) + Ok(attribute.meta) } fn fm(tokens: TokenStream) -> T { diff --git a/core/src/usage/lifetimes.rs b/core/src/usage/lifetimes.rs index b940173c..b7124aa5 100644 --- a/core/src/usage/lifetimes.rs +++ b/core/src/usage/lifetimes.rs @@ -112,20 +112,20 @@ impl UsesLifetimes for Lifetime { } uses_lifetimes!(syn::AngleBracketedGenericArguments, args); +uses_lifetimes!(syn::AssocType, ty); uses_lifetimes!(syn::BareFnArg, ty); -uses_lifetimes!(syn::Binding, 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::LifetimeDef, lifetime, bounds); +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::PredicateEq, lhs_ty, rhs_ty); uses_lifetimes!(syn::PredicateLifetime, lifetime, bounds); uses_lifetimes!(syn::PredicateType, lifetimes, bounded_ty, bounds); uses_lifetimes!(syn::QSelf, ty); @@ -134,6 +134,7 @@ 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); @@ -245,7 +246,9 @@ impl UsesLifetimes for syn::WherePredicate { match *self { syn::WherePredicate::Type(ref v) => v.uses_lifetimes(options, lifetimes), syn::WherePredicate::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), - syn::WherePredicate::Eq(ref v) => v.uses_lifetimes(options, lifetimes), + // non-exhaustive enum + // TODO: replace panic with failible function + _ => panic!("Unknown syn::WherePredicate: {:?}", self), } } } @@ -258,10 +261,29 @@ impl UsesLifetimes for syn::GenericArgument { ) -> LifetimeRefSet<'a> { match *self { syn::GenericArgument::Type(ref v) => v.uses_lifetimes(options, lifetimes), - syn::GenericArgument::Binding(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::Const(_) => Default::default(), + 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), } } } @@ -275,6 +297,9 @@ impl UsesLifetimes for syn::TypeParamBound { 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), } } } @@ -282,8 +307,7 @@ impl UsesLifetimes for syn::TypeParamBound { #[cfg(test)] mod tests { use proc_macro2::Span; - use syn::parse_quote; - use syn::DeriveInput; + use syn::{parse_quote, DeriveInput}; use super::UsesLifetimes; use crate::usage::GenericsExt; diff --git a/core/src/usage/type_params.rs b/core/src/usage/type_params.rs index 152d218e..d4e7e799 100644 --- a/core/src/usage/type_params.rs +++ b/core/src/usage/type_params.rs @@ -87,8 +87,8 @@ impl UsesTypeParams for Punctuated { } uses_type_params!(syn::AngleBracketedGenericArguments, args); +uses_type_params!(syn::AssocType, ty); uses_type_params!(syn::BareFnArg, ty); -uses_type_params!(syn::Binding, ty); uses_type_params!(syn::Constraint, bounds); uses_type_params!(syn::DataEnum, variants); uses_type_params!(syn::DataStruct, fields); @@ -96,7 +96,6 @@ 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::PredicateEq, lhs_ty, rhs_ty); uses_type_params!(syn::PredicateType, bounded_ty, bounds); uses_type_params!(syn::QSelf, ty); uses_type_params!(syn::TraitBound, path); @@ -217,7 +216,9 @@ impl UsesTypeParams for syn::WherePredicate { match *self { syn::WherePredicate::Lifetime(_) => Default::default(), syn::WherePredicate::Type(ref v) => v.uses_type_params(options, type_set), - syn::WherePredicate::Eq(ref v) => v.uses_type_params(options, type_set), + // non-exhaustive enum + // TODO: replace panic with failible function + _ => panic!("Unknown syn::WherePredicate: {:?}", self), } } } @@ -226,11 +227,14 @@ 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::Binding(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::Const(_) | syn::GenericArgument::Lifetime(_) => { - Default::default() - } + 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), } } } @@ -240,6 +244,9 @@ impl UsesTypeParams for syn::TypeParamBound { 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), } } } @@ -323,7 +330,7 @@ mod tests { #[test] fn box_fn_output() { - let input: DeriveInput = parse_quote! { struct Foo(Box T>); }; + let input: DeriveInput = parse_quote! { struct Foo(Box T>); }; let generics = ident_set(vec!["T"]); let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); assert_eq!(matches.len(), 1); @@ -332,7 +339,7 @@ mod tests { #[test] fn box_fn_input() { - let input: DeriveInput = parse_quote! { struct Foo(Box ()>); }; + let input: DeriveInput = parse_quote! { struct Foo(Box ()>); }; let generics = ident_set(vec!["T"]); let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); assert_eq!(matches.len(), 1); diff --git a/core/src/util/flag.rs b/core/src/util/flag.rs index ade88988..66f2adc8 100644 --- a/core/src/util/flag.rs +++ b/core/src/util/flag.rs @@ -77,12 +77,6 @@ impl FromMeta for Flag { } } -impl Spanned for Flag { - fn span(&self) -> Span { - self.0.unwrap_or_else(Span::call_site) - } -} - impl From for bool { fn from(flag: Flag) -> Self { flag.is_present() diff --git a/core/src/util/over_ride.rs b/core/src/util/over_ride.rs index 92ce25a3..3de6ed53 100644 --- a/core/src/util/over_ride.rs +++ b/core/src/util/over_ride.rs @@ -1,7 +1,8 @@ use std::fmt; -use syn::{Lit, NestedMeta}; +use syn::Lit; +use crate::ast::NestedMeta; use crate::{FromMeta, Result}; use self::Override::*; diff --git a/core/src/util/parse_attribute.rs b/core/src/util/parse_attribute.rs index 8d8be5d7..747c30de 100644 --- a/core/src/util/parse_attribute.rs +++ b/core/src/util/parse_attribute.rs @@ -1,25 +1,34 @@ -use crate::{util::SpannedValue, Error, Result}; +use crate::{Error, Result}; use std::fmt; -use syn::{punctuated::Pair, spanned::Spanned, token, Attribute, Meta, MetaList, Path}; +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 { - match attr.parse_meta() { - Ok(Meta::List(list)) => Ok(list), - Ok(Meta::NameValue(nv)) => Err(Error::custom(format!( + 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)), - Ok(Meta::Path(path)) => Ok(MetaList { - path, - paren_token: token::Paren(attr.span()), - nested: Default::default(), + 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(), }), - Err(e) => Err(Error::custom(format!("Unable to parse attribute: {}", e)) - .with_span(&SpannedValue::new((), e.span()))), } } @@ -45,19 +54,23 @@ impl fmt::Display for DisplayPath<'_> { #[cfg(test)] mod tests { use super::parse_attribute_to_meta_list; - use syn::{parse_quote, spanned::Spanned, Ident}; + 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(); - assert_eq!(meta.nested.len(), 1); + 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!(meta.nested.is_empty()); + assert!(nested_meta.is_empty()); } #[test] diff --git a/core/src/util/path_list.rs b/core/src/util/path_list.rs index 18b0f630..bea25fd5 100644 --- a/core/src/util/path_list.rs +++ b/core/src/util/path_list.rs @@ -1,7 +1,8 @@ use std::ops::Deref; -use syn::{Meta, NestedMeta, Path}; +use syn::{Meta, Path}; +use crate::ast::NestedMeta; use crate::{Error, FromMeta, Result}; use super::path_to_string; @@ -72,7 +73,7 @@ mod tests { /// parse a string as a syn::Meta instance. fn pm(tokens: TokenStream) -> ::std::result::Result { let attribute: Attribute = parse_quote!(#[#tokens]); - attribute.parse_meta().map_err(|_| "Unable to parse".into()) + Ok(attribute.meta) } fn fm(tokens: TokenStream) -> T { diff --git a/core/src/util/spanned_value.rs b/core/src/util/spanned_value.rs index a97f9977..68dcc380 100644 --- a/core/src/util/spanned_value.rs +++ b/core/src/util/spanned_value.rs @@ -67,12 +67,6 @@ impl AsRef for SpannedValue { } } -impl Spanned for SpannedValue { - fn span(&self) -> Span { - self.span - } -} - macro_rules! spanned { ($trayt:ident, $method:ident, $syn:path) => { impl $trayt for SpannedValue { @@ -95,10 +89,10 @@ impl FromMeta for SpannedValue { syn::Meta::Path(path) => path.span(), // Example: `#[darling(attributes(Value))]` as a SpannedValue> // should have the span pointing to the list contents. - syn::Meta::List(list) => list.nested.span(), + syn::Meta::List(list) => list.tokens.span(), // Example: `#[darling(skip = true)]` as SpannedValue // should have the span pointing to the word `true`. - syn::Meta::NameValue(nv) => nv.lit.span(), + syn::Meta::NameValue(nv) => nv.value.span(), }; Ok(Self::new(value, span)) diff --git a/examples/fallible_read.rs b/examples/fallible_read.rs index 850465e8..32cc10c9 100644 --- a/examples/fallible_read.rs +++ b/examples/fallible_read.rs @@ -51,7 +51,7 @@ impl MyInputReceiver { // we'll go ahead and make it positive. let amplitude = match amplitude { Ok(amp) => amp, - Err(mi) => (i64::from_meta(&mi)?).abs() as u64, + Err(mi) => (i64::from_meta(&mi)?).unsigned_abs(), }; Ok(Self { diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 3f50ffa3..59fc63f5 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "darling_macro" -version = "0.14.4" +version = "0.20.0" authors = ["Ted Driggs "] repository = "https://github.com/TedDriggs/darling" description = """ @@ -12,8 +12,8 @@ edition = "2018" [dependencies] quote = "1.0.18" -syn = "1.0.91" -darling_core = { version = "=0.14.4", path = "../core" } +syn = "2.0.15" +darling_core = { version = "=0.20.0", path = "../core" } [lib] proc-macro = true diff --git a/macro/src/lib.rs b/macro/src/lib.rs index de9709fb..8da0272e 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -1,6 +1,3 @@ -// This is needed for 1.31.0 to keep compiling -extern crate proc_macro; - use darling_core::{derive, Error}; use proc_macro::TokenStream; use syn::parse_macro_input; diff --git a/src/lib.rs b/src/lib.rs index b2be36d0..657a6ffe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,8 @@ pub mod export { pub use darling_core::syn; pub use std::string::ToString; pub use std::vec::Vec; + + pub use crate::ast::NestedMeta; } #[macro_use] diff --git a/tests/from_generics.rs b/tests/from_generics.rs index 5cdd697d..e2cc99ee 100644 --- a/tests/from_generics.rs +++ b/tests/from_generics.rs @@ -48,8 +48,8 @@ fn expand_some() { .expect("Input is well-formed"); assert!(rec.generics.where_clause.is_none()); - // Make sure we've preserved the lifetime def, though we don't do anything with it. - assert!(rec.generics.params[0].as_lifetime_def().is_some()); + // Make sure we've preserved the lifetime param, though we don't do anything with it. + assert!(rec.generics.params[0].as_lifetime_param().is_some()); let mut ty_param_iter = rec.generics.type_params(); diff --git a/tests/hash_map.rs b/tests/hash_map.rs index 5d9a0114..881cd1cf 100644 --- a/tests/hash_map.rs +++ b/tests/hash_map.rs @@ -16,7 +16,7 @@ fn parse_map() { #[foo(first(name = "Hello", option), the::second(name = "Second"))] }; - let meta = attr.parse_meta().unwrap(); + let meta = attr.meta; let map: HashMap = FromMeta::from_meta(&meta).unwrap(); let comparison: HashMap = vec![ diff --git a/tests/unsupported_attributes.rs b/tests/unsupported_attributes.rs index 14edf2c8..98b190d5 100644 --- a/tests/unsupported_attributes.rs +++ b/tests/unsupported_attributes.rs @@ -20,8 +20,8 @@ fn non_meta_attribute_gets_own_error() { }; let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten(); - // The number of errors here is 1 for the bad attribute + 2 for the missing fields - assert_eq!(3, errors.len()); + // The number of errors here is 1 for the bad value of the `st` attribute + assert_eq!(1, errors.len()); // Make sure one of the errors propagates the syn error assert!(errors .into_iter() @@ -40,8 +40,8 @@ fn non_meta_attribute_does_not_block_others() { }; let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten(); - // The number of errors here is 1 for the bad attribute + 1 for the missing "st" field - assert_eq!(2, errors.len()); + // The number of errors here is 1 for the bad value of the `st` attribute + assert_eq!(1, errors.len()); // Make sure one of the errors propagates the syn error assert!(errors .into_iter()