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
+105
@@ -0,0 +1,105 @@
|
||||
#![allow(dead_code)]
|
||||
//! These tests verify that multiple errors will be collected up from throughout
|
||||
//! the parsing process and returned correctly to the caller.
|
||||
|
||||
use darling::{ast, FromDeriveInput, FromField, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(accrue))]
|
||||
struct Lorem {
|
||||
ipsum: String,
|
||||
dolor: Dolor,
|
||||
data: ast::Data<(), LoremField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Dolor {
|
||||
sit: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromField)]
|
||||
#[darling(attributes(accrue))]
|
||||
struct LoremField {
|
||||
ident: Option<syn::Ident>,
|
||||
aliased_as: syn::Ident,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_type_and_missing_fields() {
|
||||
let input = parse_quote! {
|
||||
#[accrue(ipsum = true, dolor(amet = "Hi"))]
|
||||
pub struct NonConforming {
|
||||
foo: ()
|
||||
}
|
||||
};
|
||||
|
||||
let s_result: ::darling::Error = Lorem::from_derive_input(&input).unwrap_err();
|
||||
let err = s_result.flatten();
|
||||
println!("{}", err);
|
||||
// 3 errors from the struct-level `accrue` attribute
|
||||
// + 1 for the field `foo` missing `aliased_as`
|
||||
// = 4 total errors
|
||||
assert_eq!(4, err.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn body_only_issues() {
|
||||
let input = parse_quote! {
|
||||
#[accrue(ipsum = "Hello", dolor(sit))]
|
||||
pub struct NonConforming {
|
||||
foo: (),
|
||||
bar: bool,
|
||||
}
|
||||
};
|
||||
|
||||
let s_err = Lorem::from_derive_input(&input).unwrap_err();
|
||||
println!("{:?}", s_err);
|
||||
assert_eq!(2, s_err.len());
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
enum Week {
|
||||
Monday,
|
||||
Tuesday { morning: bool, afternoon: String },
|
||||
Wednesday(Dolor),
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(accrue))]
|
||||
struct Month {
|
||||
schedule: Week,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_in_enum_fields() {
|
||||
let input = parse_quote! {
|
||||
#[accrue(schedule(tuesday(morning = "yes")))]
|
||||
pub struct NonConforming {
|
||||
foo: (),
|
||||
bar: bool,
|
||||
}
|
||||
};
|
||||
|
||||
let s_err = Month::from_derive_input(&input).unwrap_err();
|
||||
assert_eq!(2, s_err.len());
|
||||
let err = s_err.flatten();
|
||||
// TODO add tests to check location path is correct
|
||||
println!("{}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_in_newtype_variant() {
|
||||
let input = parse_quote! {
|
||||
#[accrue(schedule(wednesday(sit = "yes")))]
|
||||
pub struct NonConforming {
|
||||
foo: (),
|
||||
bar: bool,
|
||||
}
|
||||
};
|
||||
|
||||
let s_err = Month::from_derive_input(&input).unwrap_err();
|
||||
assert_eq!(1, s_err.len());
|
||||
println!("{}", s_err);
|
||||
println!("{}", s_err.flatten());
|
||||
}
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use darling::{util, Error, FromDeriveInput, Result};
|
||||
use syn::{parse_quote, Attribute};
|
||||
|
||||
fn unique_idents(attrs: Vec<Attribute>) -> Result<BTreeSet<String>> {
|
||||
let mut errors = Error::accumulator();
|
||||
let idents = attrs
|
||||
.into_iter()
|
||||
.filter_map(|attr| {
|
||||
let path = attr.path();
|
||||
errors.handle(
|
||||
path.get_ident()
|
||||
.map(std::string::ToString::to_string)
|
||||
.ok_or_else(|| {
|
||||
Error::custom(format!("`{}` is not an ident", util::path_to_string(path)))
|
||||
.with_span(path)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
errors.finish_with(idents)
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(a), forward_attrs)]
|
||||
struct Receiver {
|
||||
#[darling(with = unique_idents)]
|
||||
attrs: BTreeSet<String>,
|
||||
other: Option<bool>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds_on_no_attrs() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(di.attrs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds_on_valid_input() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
#[allow(dead_code)]
|
||||
/// testing
|
||||
#[another]
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(di.attrs.len(), 3);
|
||||
assert!(di.attrs.contains("allow"));
|
||||
assert!(di.attrs.contains("another"));
|
||||
assert!(di.attrs.contains("doc"));
|
||||
assert_eq!(di.other, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_combined_with_others() {
|
||||
let e = Receiver::from_derive_input(&parse_quote! {
|
||||
#[path::to::attr(dead_code)]
|
||||
#[a(other = 5)]
|
||||
struct Demo;
|
||||
})
|
||||
.map(|_| "Should have failed")
|
||||
.unwrap_err();
|
||||
|
||||
let error = e.to_string();
|
||||
|
||||
assert_eq!(e.len(), 2);
|
||||
|
||||
// Look for the error on the field `other`
|
||||
assert!(error.contains("at other"));
|
||||
|
||||
// Look for the invalid path from attrs conversion
|
||||
assert!(error.contains("`path::to::attr`"));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#[derive(darling::FromDeriveInput)]
|
||||
#[darling(attributes(example), forward_attrs(example))]
|
||||
pub struct Example {
|
||||
ignored: String,
|
||||
}
|
||||
|
||||
#[derive(darling::FromField)]
|
||||
#[darling(attributes(example), forward_attrs(example))]
|
||||
pub struct ExampleField {
|
||||
ignored: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: attribute path `example` will not be forwarded because it is also listed in `attributes`
|
||||
--> tests/compile-fail/attr_forwarded_and_parsed.rs:2:46
|
||||
|
|
||||
2 | #[darling(attributes(example), forward_attrs(example))]
|
||||
| ^^^^^^^
|
||||
|
||||
error: attribute path `example` will not be forwarded because it is also listed in `attributes`
|
||||
--> tests/compile-fail/attr_forwarded_and_parsed.rs:8:46
|
||||
|
|
||||
8 | #[darling(attributes(example), forward_attrs(example))]
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1,15 @@
|
||||
use darling::FromDeriveInput;
|
||||
use syn::Attribute;
|
||||
|
||||
fn bad_converter(attrs: Vec<Attribute>) -> Vec<Attribute> {
|
||||
attrs
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(forward_attrs)]
|
||||
struct Receiver {
|
||||
#[darling(with = bad_converter)]
|
||||
attrs: Vec<Attribute>,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,20 @@
|
||||
error[E0308]: mismatched types
|
||||
--> tests/compile-fail/attrs_with_bad_fn.rs:11:22
|
||||
|
|
||||
11 | #[darling(with = bad_converter)]
|
||||
| ^^^^^^^^^^^^^
|
||||
| |
|
||||
| expected `Result<_, Error>`, found `Vec<Attribute>`
|
||||
| arguments to this method are incorrect
|
||||
|
|
||||
= note: expected enum `Result<_, darling::Error>`
|
||||
found struct `Vec<Attribute>`
|
||||
note: method defined here
|
||||
--> core/src/error/mod.rs
|
||||
|
|
||||
| pub fn handle<T>(&mut self, result: Result<T>) -> Option<T> {
|
||||
| ^^^^^^
|
||||
help: try wrapping the expression in `Ok`
|
||||
|
|
||||
11 | #[darling(with = Ok(bad_converter))]
|
||||
| +++ +
|
||||
@@ -0,0 +1,10 @@
|
||||
use darling::FromDeriveInput;
|
||||
use syn::{Attribute, Ident};
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
struct HelloArgs {
|
||||
ident: Ident,
|
||||
attrs: Vec<Attribute>,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: field will not be populated because `forward_attrs` is not set on the struct
|
||||
--> tests/compile-fail/attrs_without_forward_attrs.rs:7:5
|
||||
|
|
||||
7 | attrs: Vec<Attribute>,
|
||||
| ^^^^^
|
||||
@@ -0,0 +1,12 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Receiver {
|
||||
#[darling(default = "usize::default")]
|
||||
not_u32: String,
|
||||
|
||||
#[darling(multiple, default = "usize::default")]
|
||||
also_not_u32: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,16 @@
|
||||
error[E0308]: mismatched types
|
||||
--> tests/compile-fail/default_expr_wrong_type.rs:5:25
|
||||
|
|
||||
5 | #[darling(default = "usize::default")]
|
||||
| ^^^^^^^^^^^^^^^^- help: try using a conversion method: `.to_string()`
|
||||
| |
|
||||
| expected `String`, found `usize`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/compile-fail/default_expr_wrong_type.rs:8:35
|
||||
|
|
||||
8 | #[darling(multiple, default = "usize::default")]
|
||||
| ^^^^^^^^^^^^^^^^ expected `Vec<String>`, found `usize`
|
||||
|
|
||||
= note: expected struct `Vec<String>`
|
||||
found type `usize`
|
||||
@@ -0,0 +1,12 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
enum Choice {
|
||||
#[darling(word)]
|
||||
A,
|
||||
#[darling(word)]
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: `#[darling(word)]` can only be applied to one variant
|
||||
--> tests/compile-fail/duplicate_word_across_variants.rs:5:15
|
||||
|
|
||||
5 | #[darling(word)]
|
||||
| ^^^^
|
||||
|
||||
error: `#[darling(word)]` can only be applied to one variant
|
||||
--> tests/compile-fail/duplicate_word_across_variants.rs:7:15
|
||||
|
|
||||
7 | #[darling(word)]
|
||||
| ^^^^
|
||||
@@ -0,0 +1,10 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
enum Choice {
|
||||
#[darling(word, word)]
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Duplicate field `word`
|
||||
--> tests/compile-fail/duplicate_word_on_variant.rs:5:21
|
||||
|
|
||||
5 | #[darling(word, word)]
|
||||
| ^^^^
|
||||
@@ -0,0 +1,21 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Inner {
|
||||
left: String,
|
||||
right: String,
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Outer {
|
||||
#[darling(flatten, multiple, with = demo, skip = true)]
|
||||
field: Inner,
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct ThisIsFine {
|
||||
#[darling(flatten, multiple = false)]
|
||||
field: Inner,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,17 @@
|
||||
error: `flatten` and `multiple` cannot be used together
|
||||
--> tests/compile-fail/flatten_meta_conflicts.rs:11:24
|
||||
|
|
||||
11 | #[darling(flatten, multiple, with = demo, skip = true)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: `flatten` and `with` cannot be used together
|
||||
--> tests/compile-fail/flatten_meta_conflicts.rs:11:34
|
||||
|
|
||||
11 | #[darling(flatten, multiple, with = demo, skip = true)]
|
||||
| ^^^^
|
||||
|
||||
error: `flatten` and `skip` cannot be used together
|
||||
--> tests/compile-fail/flatten_meta_conflicts.rs:11:47
|
||||
|
|
||||
11 | #[darling(flatten, multiple, with = demo, skip = true)]
|
||||
| ^^^^
|
||||
@@ -0,0 +1,28 @@
|
||||
//! Test that multiple fields cannot be marked `flatten` at once.
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Inner {
|
||||
left: String,
|
||||
right: String,
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
pub struct Example {
|
||||
#[darling(flatten)]
|
||||
first: Inner,
|
||||
#[darling(flatten)]
|
||||
last: Inner,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
pub struct FdiExample {
|
||||
ident: syn::Ident,
|
||||
#[darling(flatten)]
|
||||
first: Inner,
|
||||
#[darling(flatten)]
|
||||
last: Inner,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,23 @@
|
||||
error: `#[darling(flatten)]` can only be applied to one field
|
||||
--> tests/compile-fail/flatten_multiple_fields.rs:13:15
|
||||
|
|
||||
13 | #[darling(flatten)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: `#[darling(flatten)]` can only be applied to one field
|
||||
--> tests/compile-fail/flatten_multiple_fields.rs:15:15
|
||||
|
|
||||
15 | #[darling(flatten)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: `#[darling(flatten)]` can only be applied to one field
|
||||
--> tests/compile-fail/flatten_multiple_fields.rs:22:15
|
||||
|
|
||||
22 | #[darling(flatten)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: `#[darling(flatten)]` can only be applied to one field
|
||||
--> tests/compile-fail/flatten_multiple_fields.rs:24:15
|
||||
|
|
||||
24 | #[darling(flatten)]
|
||||
| ^^^^^^^
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
/// This usage of `from_expr` is VALID because there are no unit variants that should conflict with the
|
||||
/// implementation.
|
||||
#[derive(FromMeta)]
|
||||
#[darling(from_expr = |expr| Ok(HasNoUnits::Variant2 { other: format!("{:?}", expr) }))]
|
||||
enum HasNoUnits {
|
||||
Variant1 { field: String },
|
||||
Variant2 { other: String },
|
||||
}
|
||||
|
||||
/// This usage of `from_expr` is invalid because unit variants already generate a from_expr
|
||||
/// method, and we don't allow using the from_expr override when it conflicts with the macro's
|
||||
/// "normal" operation.
|
||||
#[derive(FromMeta)]
|
||||
#[darling(from_expr = |expr| Ok(HasUnits::Variant2))]
|
||||
enum HasUnits {
|
||||
Variant1 { field: String },
|
||||
Variant2,
|
||||
}
|
||||
|
||||
fn newtype_from_expr(_expr: &syn::Expr) -> darling::Result<Newtype> {
|
||||
Ok(Newtype(true))
|
||||
}
|
||||
|
||||
// This usage of `from_expr` is invalid because newtype structs call the inner type's `from_meta`
|
||||
// directly from their `from_meta`, so the custom `from_expr` will never be called in normal usage.
|
||||
#[derive(FromMeta)]
|
||||
#[darling(from_expr = newtype_from_expr)]
|
||||
struct Newtype(bool);
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: `from_expr` cannot be used on enums with non-skipped unit variants because it conflicts with the generated impl
|
||||
--> tests/compile-fail/from_expr.rs:16:23
|
||||
|
|
||||
16 | #[darling(from_expr = |expr| Ok(HasUnits::Variant2))]
|
||||
| ^
|
||||
|
||||
error: `from_expr` cannot be used on newtype structs because the implementation is entirely delegated to the inner type
|
||||
--> tests/compile-fail/from_expr.rs:29:23
|
||||
|
|
||||
29 | #[darling(from_expr = newtype_from_expr)]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
// This usage of `from_word` is invalid because unit structs already generate a from_word
|
||||
// method, and we don't allow using the from_word override when it conflicts with the macro's
|
||||
// "normal" operation.
|
||||
#[derive(FromMeta)]
|
||||
#[darling(from_word = || Ok(Unit))]
|
||||
struct Unit;
|
||||
|
||||
fn newtype_from_word() -> darling::Result<Newtype> {
|
||||
Ok(Newtype(true))
|
||||
}
|
||||
|
||||
// This usage of `from_word` is invalid because newtype structs call the inner type's `from_meta`
|
||||
// directly from their `from_meta`, so the custom `from_word` will never be called in normal usage.
|
||||
#[derive(FromMeta)]
|
||||
#[darling(from_word = newtype_from_word)]
|
||||
struct Newtype(bool);
|
||||
|
||||
#[derive(FromMeta)]
|
||||
#[darling(from_word = || Ok(Wordy::Options { thing: "Hello".to_string() }))]
|
||||
enum Wordy {
|
||||
#[darling(word)]
|
||||
Normal,
|
||||
Options {
|
||||
thing: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,17 @@
|
||||
error: `from_word` cannot be used on unit structs because it conflicts with the generated impl
|
||||
--> tests/compile-fail/from_word.rs:7:23
|
||||
|
|
||||
7 | #[darling(from_word = || Ok(Unit))]
|
||||
| ^
|
||||
|
||||
error: `from_word` cannot be used on newtype structs because the implementation is entirely delegated to the inner type
|
||||
--> tests/compile-fail/from_word.rs:17:23
|
||||
|
|
||||
17 | #[darling(from_word = newtype_from_word)]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: `from_word` cannot be used with an enum that also uses `word`
|
||||
--> tests/compile-fail/from_word.rs:21:23
|
||||
|
|
||||
21 | #[darling(from_word = || Ok(Wordy::Options { thing: "Hello".to_string() }))]
|
||||
| ^
|
||||
@@ -0,0 +1,16 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
struct NotImplFm;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct OuterFm {
|
||||
inner: NotImplFm,
|
||||
}
|
||||
|
||||
#[derive(darling::FromDeriveInput)]
|
||||
#[darling(attributes(hello))]
|
||||
struct OuterFdi {
|
||||
inner: NotImplFm,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,33 @@
|
||||
error[E0277]: the trait bound `NotImplFm: FromMeta` is not satisfied
|
||||
--> tests/compile-fail/not_impl_from_meta.rs:7:12
|
||||
|
|
||||
7 | inner: NotImplFm,
|
||||
| ^^^^^^^^^ the trait `FromMeta` is not implemented for `NotImplFm`
|
||||
|
|
||||
= help: the following other types implement trait `FromMeta`:
|
||||
bool
|
||||
char
|
||||
isize
|
||||
i8
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
i128
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `NotImplFm: FromMeta` is not satisfied
|
||||
--> tests/compile-fail/not_impl_from_meta.rs:13:12
|
||||
|
|
||||
13 | inner: NotImplFm,
|
||||
| ^^^^^^^^^ the trait `FromMeta` is not implemented for `NotImplFm`
|
||||
|
|
||||
= help: the following other types implement trait `FromMeta`:
|
||||
bool
|
||||
char
|
||||
isize
|
||||
i8
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
i128
|
||||
and $N others
|
||||
@@ -0,0 +1,18 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct NoDefault(String);
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Recevier {
|
||||
#[darling(skip)]
|
||||
skipped: NoDefault,
|
||||
|
||||
#[darling(skip = true)]
|
||||
explicitly_skipped: NoDefault,
|
||||
|
||||
#[darling(skip = false)]
|
||||
not_skipped_no_problem: NoDefault,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,23 @@
|
||||
error[E0277]: the trait bound `NoDefault: std::default::Default` is not satisfied
|
||||
--> tests/compile-fail/skip_field_not_impl_default.rs:8:15
|
||||
|
|
||||
8 | #[darling(skip)]
|
||||
| ^^^^ the trait `std::default::Default` is not implemented for `NoDefault`
|
||||
|
|
||||
help: consider annotating `NoDefault` with `#[derive(Default)]`
|
||||
|
|
||||
4 + #[derive(Default)]
|
||||
5 | struct NoDefault(String);
|
||||
|
|
||||
|
||||
error[E0277]: the trait bound `NoDefault: std::default::Default` is not satisfied
|
||||
--> tests/compile-fail/skip_field_not_impl_default.rs:11:22
|
||||
|
|
||||
11 | #[darling(skip = true)]
|
||||
| ^^^^ the trait `std::default::Default` is not implemented for `NoDefault`
|
||||
|
|
||||
help: consider annotating `NoDefault` with `#[derive(Default)]`
|
||||
|
|
||||
4 + #[derive(Default)]
|
||||
5 | struct NoDefault(String);
|
||||
|
|
||||
@@ -0,0 +1,21 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(demo))]
|
||||
pub struct Receiver {
|
||||
example1: String,
|
||||
#[darling(
|
||||
// This should fail because `example1` is a local that's been captured
|
||||
// from the `FromDeriveInput` impl. That's disallowed because exposing
|
||||
// those internals would make any change to the derived method body a
|
||||
// potentially-breaking change.
|
||||
with = |m| Ok(
|
||||
String::from_meta(m)?.to_uppercase()
|
||||
+ example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
|
||||
),
|
||||
default
|
||||
)]
|
||||
example2: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,30 @@
|
||||
error[E0308]: mismatched types
|
||||
--> tests/compile-fail/with_closure_capture.rs:12:16
|
||||
|
|
||||
12 | with = |m| Ok(
|
||||
| ^ arguments to this function are incorrect
|
||||
| ________________|
|
||||
| |
|
||||
13 | | String::from_meta(m)?.to_uppercase()
|
||||
14 | | + example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
|
||||
15 | | ),
|
||||
| |_________^ expected fn pointer, found closure
|
||||
|
|
||||
= note: expected fn pointer `for<'a> fn(&'a syn::attr::Meta) -> Result<String, darling::Error>`
|
||||
found closure `{closure@$DIR/tests/compile-fail/with_closure_capture.rs:12:16: 12:19}`
|
||||
note: closures can only be coerced to `fn` types if they do not capture any variables
|
||||
--> tests/compile-fail/with_closure_capture.rs:14:15
|
||||
|
|
||||
14 | + example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
|
||||
| ^^^^^^^^ `example1` captured here
|
||||
help: the return type of this call is `{closure@$DIR/tests/compile-fail/with_closure_capture.rs:12:16: 12:19}` due to the type of the argument passed
|
||||
--> tests/compile-fail/with_closure_capture.rs:12:16
|
||||
|
|
||||
12 | with = |m| Ok(
|
||||
| ________________^
|
||||
13 | | String::from_meta(m)?.to_uppercase()
|
||||
14 | | + example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
|
||||
15 | | ),
|
||||
| |_________- this argument influences the return type of `{{root}}`
|
||||
note: function defined here
|
||||
--> $RUST/core/src/convert/mod.rs
|
||||
@@ -0,0 +1,10 @@
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
enum Meta {
|
||||
Unit,
|
||||
#[darling(word)]
|
||||
NotUnit(String)
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Unexpected field: `word`. `#[darling(word)]` can only be applied to a unit variant
|
||||
--> tests/compile-fail/word_on_wrong_variant_type.rs:6:15
|
||||
|
|
||||
6 | #[darling(word)]
|
||||
| ^^^^
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
#![cfg(compiletests)]
|
||||
|
||||
#[rustversion::stable(1.77)]
|
||||
#[test]
|
||||
fn compile_test() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/compile-fail/*.rs");
|
||||
}
|
||||
|
||||
#[rustversion::not(stable(1.77))]
|
||||
#[test]
|
||||
fn wrong_rustc_version() {
|
||||
panic!(
|
||||
"This is not the expected version of rustc. Error messages vary across compiler versions so tests may produce spurious errors"
|
||||
);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
|
||||
fn parse<T: FromDeriveInput>(src: &str) -> T {
|
||||
let ast = syn::parse_str(src).unwrap();
|
||||
FromDeriveInput::from_derive_input(&ast).unwrap()
|
||||
}
|
||||
|
||||
#[derive(FromMeta, PartialEq, Eq, Debug)]
|
||||
enum Volume {
|
||||
Whisper,
|
||||
Talk,
|
||||
Shout,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(speak))]
|
||||
struct SpeakingOptions<T: Default, U> {
|
||||
max_volume: U,
|
||||
#[darling(skip)]
|
||||
#[allow(dead_code)]
|
||||
additional_data: T,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Phoneme {
|
||||
#[allow(dead_code)]
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skipped_field() {
|
||||
let parsed: SpeakingOptions<Phoneme, Volume> = parse(
|
||||
r#"
|
||||
#[derive(Speak)]
|
||||
#[speak(max_volume = "shout")]
|
||||
enum HtmlElement {
|
||||
Div(String)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
assert_eq!(parsed.max_volume, Volume::Shout);
|
||||
}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::ops::Add;
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
|
||||
#[derive(Debug, Clone, FromMeta)]
|
||||
#[darling(bound = "T: FromMeta + Add")]
|
||||
struct Wrapper<T>(pub T);
|
||||
|
||||
impl<T: Add> Add for Wrapper<T> {
|
||||
type Output = Wrapper<<T as Add>::Output>;
|
||||
fn add(self, rhs: Self) -> Wrapper<<T as Add>::Output> {
|
||||
Wrapper(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(hello), bound = "Wrapper<T>: Add, T: FromMeta")]
|
||||
struct Foo<T> {
|
||||
lorem: Wrapper<T>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expansion() {}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use darling::{Error, FromDeriveInput, Result};
|
||||
use syn::parse_quote;
|
||||
|
||||
fn field_names(data: &syn::Data) -> Result<BTreeSet<String>> {
|
||||
let fields = match data {
|
||||
syn::Data::Struct(data) => data.fields.iter(),
|
||||
syn::Data::Enum(_) => return Err(Error::custom("Expected struct or union")),
|
||||
syn::Data::Union(data) => data.fields.named.iter(),
|
||||
};
|
||||
|
||||
Ok(fields
|
||||
.filter_map(|f| f.ident.clone())
|
||||
.map(|i| i.to_string())
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(a), forward_attrs)]
|
||||
struct Receiver {
|
||||
#[darling(with = field_names)]
|
||||
data: BTreeSet<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds_on_no_fields() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(di.data.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds_on_valid_input() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
struct Demo {
|
||||
hello: String,
|
||||
world: String,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(di.data.len(), 2);
|
||||
assert!(di.data.contains("hello"));
|
||||
assert!(di.data.contains("world"));
|
||||
}
|
||||
Vendored
+189
@@ -0,0 +1,189 @@
|
||||
use darling::FromDeriveInput;
|
||||
use syn::parse_quote;
|
||||
|
||||
mod foo {
|
||||
pub mod bar {
|
||||
pub fn init() -> String {
|
||||
String::from("hello")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(speak))]
|
||||
pub struct SpeakerOpts {
|
||||
#[darling(default = foo::bar::init)]
|
||||
first_word: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_default() {
|
||||
let speaker: SpeakerOpts = FromDeriveInput::from_derive_input(&parse_quote! {
|
||||
struct Foo;
|
||||
})
|
||||
.expect("Unit struct with no attrs should parse");
|
||||
|
||||
assert_eq!(speaker.first_word, "hello");
|
||||
}
|
||||
|
||||
/// Tests in this module capture the somewhat-confusing behavior observed when defaults
|
||||
/// are set at both the field and container level.
|
||||
///
|
||||
/// The general rule is that more-specific declarations preempt less-specific ones; this is
|
||||
/// unsurprising and allows for granular control over what happens when parsing an AST.
|
||||
mod stacked_defaults {
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
fn jane() -> String {
|
||||
"Jane".into()
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
#[darling(default)]
|
||||
struct PersonName {
|
||||
#[darling(default = "jane")]
|
||||
first: String,
|
||||
#[darling(default)]
|
||||
middle: String,
|
||||
last: String,
|
||||
}
|
||||
|
||||
impl Default for PersonName {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first: "John".into(),
|
||||
middle: "T".into(),
|
||||
last: "Doe".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(person))]
|
||||
struct Person {
|
||||
#[darling(default)]
|
||||
name: PersonName,
|
||||
age: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_first_only() {
|
||||
let person = Person::from_derive_input(&parse_quote! {
|
||||
#[person(name(first = "Bill"), age = 5)]
|
||||
struct Foo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(person.name.first, "Bill");
|
||||
assert_eq!(
|
||||
person.name.middle, "",
|
||||
"Explicit field-level default should preempt container-level default"
|
||||
);
|
||||
assert_eq!(
|
||||
person.name.last, "Doe",
|
||||
"Absence of a field-level default falls back to container-level default"
|
||||
);
|
||||
}
|
||||
|
||||
/// This is the most surprising case. The presence of `name()` means we invoke
|
||||
/// `PersonName::from_list(&[])`. When that finishes parsing each of the zero nested
|
||||
/// items it has received, it will then start filling in missing fields, using the
|
||||
/// explicit field-level defaults for `first` and `middle`, while for `last` it will
|
||||
/// use the `last` field from the container-level default.
|
||||
#[test]
|
||||
fn name_empty_list() {
|
||||
let person = Person::from_derive_input(&parse_quote! {
|
||||
#[person(name(), age = 5)]
|
||||
struct Foo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(person.name.first, "Jane");
|
||||
assert_eq!(person.name.middle, "");
|
||||
assert_eq!(person.name.last, "Doe");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_name() {
|
||||
let person = Person::from_derive_input(&parse_quote! {
|
||||
#[person(age = 5)]
|
||||
struct Foo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(person.age, 5);
|
||||
assert_eq!(
|
||||
person.name.first, "John",
|
||||
"If `name` is not specified, `Person`'s field-level default should be used"
|
||||
);
|
||||
assert_eq!(person.name.middle, "T");
|
||||
assert_eq!(person.name.last, "Doe");
|
||||
}
|
||||
}
|
||||
|
||||
mod implicit_default {
|
||||
use darling::{util::Flag, FromDeriveInput};
|
||||
use syn::parse_quote;
|
||||
|
||||
// No use of `darling(default)` here at all!
|
||||
// This struct will fill in missing fields using FromMeta::from_none.
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(person))]
|
||||
struct Person {
|
||||
first_name: String,
|
||||
last_name: Option<String>,
|
||||
lefty: Flag,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_fields_fill() {
|
||||
let person = Person::from_derive_input(&parse_quote! {
|
||||
#[person(first_name = "James")]
|
||||
struct Foo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(person.first_name, "James");
|
||||
assert_eq!(person.last_name, None);
|
||||
assert!(!person.lefty.is_present());
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that a field-level implicit default using FromMeta::from_none is superseded
|
||||
/// by the parent declaring `#[darling(default)]`.
|
||||
mod overridden_implicit_default {
|
||||
use darling::{util::Flag, FromDeriveInput};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(default, attributes(person))]
|
||||
struct Person {
|
||||
first_name: String,
|
||||
last_name: Option<String>,
|
||||
lefty: Flag,
|
||||
}
|
||||
|
||||
impl Default for Person {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first_name: "Jane".into(),
|
||||
last_name: Some("Doe".into()),
|
||||
lefty: Flag::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_missing() {
|
||||
let person = Person::from_derive_input(&parse_quote!(
|
||||
#[person(last_name = "Archer")]
|
||||
struct Foo;
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(person.first_name, "Jane");
|
||||
assert_eq!(person.last_name, Some("Archer".into()));
|
||||
assert!(!person.lefty.is_present());
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta, PartialEq, Eq)]
|
||||
enum Dolor {
|
||||
Sit,
|
||||
#[darling(word)]
|
||||
Amet,
|
||||
}
|
||||
|
||||
impl Default for Dolor {
|
||||
fn default() -> Self {
|
||||
Dolor::Sit
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(hello))]
|
||||
struct Receiver {
|
||||
#[darling(default)]
|
||||
example: Dolor,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_meta() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
#[hello]
|
||||
struct Example;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Dolor::Sit, di.example);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_meta() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
#[hello(example)]
|
||||
struct Example;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Dolor::Amet, di.example);
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, FromMeta)]
|
||||
#[darling(default)]
|
||||
pub struct Amet {
|
||||
hello: bool,
|
||||
world: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, FromMeta)]
|
||||
#[darling(rename_all = "snake_case")]
|
||||
pub enum Lorem {
|
||||
Ipsum(bool),
|
||||
Dolor(String),
|
||||
OptDolor(Option<String>),
|
||||
Sit(Amet),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, FromDeriveInput)]
|
||||
#[darling(attributes(hello))]
|
||||
pub struct Holder {
|
||||
lorem: Lorem,
|
||||
}
|
||||
|
||||
impl PartialEq<Lorem> for Holder {
|
||||
fn eq(&self, other: &Lorem) -> bool {
|
||||
self.lorem == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_word() {
|
||||
let di = parse_quote! {
|
||||
#[hello(lorem(ipsum))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
let pr = Holder::from_derive_input(&di).unwrap();
|
||||
assert_eq!(pr, Lorem::Ipsum(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_literal() {
|
||||
let di = parse_quote! {
|
||||
#[hello(lorem(ipsum = false))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
let pr = Holder::from_derive_input(&di).unwrap();
|
||||
assert_eq!(pr, Lorem::Ipsum(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_literal() {
|
||||
let di = parse_quote! {
|
||||
#[hello(lorem(dolor = "Hello"))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
let pr = Holder::from_derive_input(&di).unwrap();
|
||||
assert_eq!(pr, Lorem::Dolor("Hello".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_literal() {
|
||||
let holder = Holder::from_derive_input(&parse_quote! {
|
||||
#[hello(lorem(opt_dolor = "Hello"))]
|
||||
struct Bar;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(holder.lorem, Lorem::OptDolor(Some("Hello".into())));
|
||||
}
|
||||
|
||||
/// Make sure newtype variants whose field's type's `from_none` return
|
||||
/// a `Some` can be used in key-value form.
|
||||
#[test]
|
||||
fn option_word_only() {
|
||||
let holder = Holder::from_derive_input(&parse_quote! {
|
||||
#[hello(lorem = "opt_dolor")]
|
||||
struct Bar;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(holder.lorem, Lorem::OptDolor(None));
|
||||
}
|
||||
|
||||
/// Make sure that newtype variants which don't produce a from_none value
|
||||
/// do not allow the word form.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn word_only_fails_for_non_option() {
|
||||
Holder::from_derive_input(&parse_quote! {
|
||||
#[hello(lorem = "dolor")]
|
||||
struct Bar;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_nested() {
|
||||
let di = parse_quote! {
|
||||
#[hello(lorem(sit(world = "Hello", hello = false)))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
let pr = Holder::from_derive_input(&di).unwrap();
|
||||
assert_eq!(
|
||||
pr,
|
||||
Lorem::Sit(Amet {
|
||||
hello: false,
|
||||
world: "Hello".to_string(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn format_mismatch() {
|
||||
let di = parse_quote! {
|
||||
#[hello(lorem(dolor(world = "Hello", hello = false)))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
Holder::from_derive_input(&di).unwrap();
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Test expansion of enums which have struct variants.
|
||||
|
||||
use darling::FromMeta;
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[darling(rename_all = "snake_case")]
|
||||
enum Message {
|
||||
Hello { user: String, silent: bool },
|
||||
Ping,
|
||||
Goodbye { user: String },
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expansion() {}
|
||||
Vendored
+152
@@ -0,0 +1,152 @@
|
||||
//! Test expansion of enum variants which have no associated data.
|
||||
|
||||
use darling::{ast::NestedMeta, FromMeta};
|
||||
use syn::{parse_quote, spanned::Spanned, Meta};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[darling(rename_all = "snake_case")]
|
||||
enum Pattern {
|
||||
Owned,
|
||||
Immutable,
|
||||
Mutable,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expansion() {}
|
||||
|
||||
#[test]
|
||||
fn unknown_value_includes_suggestion() {
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[allow(dead_code)]
|
||||
struct Test {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
let small_err = Test::from_meta(&parse_quote!(na(pattern = "oned")))
|
||||
.expect_err("`oned` should not be a valid pattern")
|
||||
.to_string();
|
||||
|
||||
// Make sure we're referring to it as a value and not a field
|
||||
assert!(small_err.contains("Unknown value"));
|
||||
|
||||
// If did-you-mean suggestions are enabled, then this one won't include all the alternates,
|
||||
// but it will contain the close value.
|
||||
assert!(small_err.contains("`owned`"));
|
||||
|
||||
let big_err = Test::from_meta(&parse_quote!(na(pattern = "fail")))
|
||||
.expect_err("`fail` should not be a valid pattern`")
|
||||
.to_string();
|
||||
|
||||
// Regardless of whether suggestions are enabled, this should include all the alternates.
|
||||
assert!(big_err.contains("`owned`"));
|
||||
assert!(big_err.contains("`mutable`"));
|
||||
assert!(big_err.contains("`immutable`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejected_in_unit_enum_variants() {
|
||||
#[derive(Debug, FromMeta, PartialEq)]
|
||||
struct Opts {
|
||||
choices: Choices,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Choices {
|
||||
values: Vec<Choice>,
|
||||
}
|
||||
|
||||
impl FromMeta for Choices {
|
||||
fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
|
||||
let values = items
|
||||
.iter()
|
||||
.map(|item| match item {
|
||||
NestedMeta::Meta(meta) => match meta {
|
||||
Meta::Path(path) => Choice::from_string(
|
||||
&path
|
||||
.get_ident()
|
||||
.ok_or(
|
||||
darling::Error::custom("choice must be an ident (no colon)")
|
||||
.with_span(path),
|
||||
)?
|
||||
.to_string(),
|
||||
)
|
||||
.map_err(|e| e.with_span(path)),
|
||||
Meta::List(list) => Choice::from_list(std::slice::from_ref(item))
|
||||
.map_err(|e| e.with_span(&list.span())),
|
||||
Meta::NameValue(n) => Err(darling::Error::custom(
|
||||
"choice options are not set as name-value, use parentheses",
|
||||
)
|
||||
.with_span(&n.eq_token)),
|
||||
},
|
||||
_ => {
|
||||
Err(darling::Error::custom("literal is not a valid choice").with_span(item))
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(Self { values })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta, PartialEq)]
|
||||
enum Choice {
|
||||
One(One),
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta, PartialEq)]
|
||||
struct One {
|
||||
foo: String,
|
||||
}
|
||||
|
||||
for (tokens, expected) in [
|
||||
(
|
||||
parse_quote! {
|
||||
choices(one(foo = "bar"))
|
||||
},
|
||||
Ok(Opts {
|
||||
choices: Choices {
|
||||
values: vec![Choice::One(One {
|
||||
foo: "bar".to_string(),
|
||||
})],
|
||||
},
|
||||
}),
|
||||
),
|
||||
(
|
||||
parse_quote! {
|
||||
choices(other)
|
||||
},
|
||||
Ok(Opts {
|
||||
choices: Choices {
|
||||
values: vec![Choice::Other],
|
||||
},
|
||||
}),
|
||||
),
|
||||
(
|
||||
parse_quote! {
|
||||
choices(other, one(foo = "bar"))
|
||||
},
|
||||
Ok(Opts {
|
||||
choices: Choices {
|
||||
values: vec![
|
||||
Choice::Other,
|
||||
Choice::One(One {
|
||||
foo: "bar".to_string(),
|
||||
}),
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
(
|
||||
parse_quote! {
|
||||
choices(other(foo = "bar"))
|
||||
},
|
||||
Err("Unexpected meta-item format `non-path` at choices".to_string()),
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
Opts::from_list(&NestedMeta::parse_meta_list(tokens).unwrap())
|
||||
.map_err(|e| e.to_string()),
|
||||
expected
|
||||
)
|
||||
}
|
||||
}
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
//! In case of bad input, parsing should fail. The error should have locations set in derived implementations.
|
||||
|
||||
// The use of fields in debug print commands does not count as "used",
|
||||
// which causes the fields to trigger an unwanted dead code warning.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Dolor {
|
||||
#[darling(rename = "amet")]
|
||||
sit: bool,
|
||||
world: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(from_ident, attributes(hello))]
|
||||
struct Lorem {
|
||||
ident: syn::Ident,
|
||||
ipsum: Dolor,
|
||||
}
|
||||
|
||||
impl From<syn::Ident> for Lorem {
|
||||
fn from(ident: syn::Ident) -> Self {
|
||||
Lorem {
|
||||
ident,
|
||||
ipsum: Dolor {
|
||||
sit: false,
|
||||
world: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_fail() {
|
||||
let di = parse_quote! {
|
||||
#[hello(ipsum(amet = "yes", world = false))]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
println!("{}", Lorem::from_derive_input(&di).unwrap_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_field() {
|
||||
let di = parse_quote! {
|
||||
#[hello(ipsum(amet = true))]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
println!("{}", Lorem::from_derive_input(&di).unwrap_err());
|
||||
}
|
||||
Vendored
+237
@@ -0,0 +1,237 @@
|
||||
use darling::{util::Flag, FromDeriveInput, FromMeta};
|
||||
use proc_macro2::Ident;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Vis {
|
||||
public: Flag,
|
||||
private: Flag,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(sample))]
|
||||
struct Example {
|
||||
ident: Ident,
|
||||
label: String,
|
||||
#[darling(flatten)]
|
||||
visibility: Vis,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let di = Example::from_derive_input(&parse_quote! {
|
||||
#[sample(label = "Hello", public)]
|
||||
struct Demo {}
|
||||
});
|
||||
|
||||
let parsed = di.unwrap();
|
||||
assert_eq!(parsed.ident, "Demo");
|
||||
assert_eq!(&parsed.label, "Hello");
|
||||
assert!(parsed.visibility.public.is_present());
|
||||
assert!(!parsed.visibility.private.is_present());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_field_errors() {
|
||||
let errors = Example::from_derive_input(&parse_quote! {
|
||||
#[sample(label = "Hello", republic)]
|
||||
struct Demo {}
|
||||
})
|
||||
.map(|_| "Should have failed")
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(errors.len(), 1);
|
||||
}
|
||||
|
||||
/// This test demonstrates flatten being used recursively.
|
||||
/// Fields are expected to be consumed by the outermost matching struct.
|
||||
#[test]
|
||||
fn recursive_flattening() {
|
||||
#[derive(FromMeta)]
|
||||
struct Nested2 {
|
||||
above: isize,
|
||||
below: isize,
|
||||
port: Option<isize>,
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Nested1 {
|
||||
port: isize,
|
||||
starboard: isize,
|
||||
#[darling(flatten)]
|
||||
z_axis: Nested2,
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Nested0 {
|
||||
fore: isize,
|
||||
aft: isize,
|
||||
#[darling(flatten)]
|
||||
cross_section: Nested1,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(boat))]
|
||||
struct BoatPosition {
|
||||
#[darling(flatten)]
|
||||
pos: Nested0,
|
||||
}
|
||||
|
||||
let parsed = BoatPosition::from_derive_input(&parse_quote! {
|
||||
#[boat(fore = 1, aft = 1, port = 10, starboard = 50, above = 20, below = -3)]
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(parsed.pos.fore, 1);
|
||||
assert_eq!(parsed.pos.aft, 1);
|
||||
|
||||
assert_eq!(parsed.pos.cross_section.port, 10);
|
||||
assert_eq!(parsed.pos.cross_section.starboard, 50);
|
||||
|
||||
assert_eq!(parsed.pos.cross_section.z_axis.above, 20);
|
||||
assert_eq!(parsed.pos.cross_section.z_axis.below, -3);
|
||||
// This should be `None` because the `port` field in `Nested1` consumed
|
||||
// the field before the leftovers were passed to `Nested2::from_list`.
|
||||
assert_eq!(parsed.pos.cross_section.z_axis.port, None);
|
||||
}
|
||||
|
||||
/// This test confirms that a collection - in this case a HashMap - can
|
||||
/// be used with `flatten`.
|
||||
#[test]
|
||||
fn flattening_into_hashmap() {
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(ca))]
|
||||
struct Catchall {
|
||||
hello: String,
|
||||
volume: usize,
|
||||
#[darling(flatten)]
|
||||
others: std::collections::HashMap<String, String>,
|
||||
}
|
||||
|
||||
let parsed = Catchall::from_derive_input(&parse_quote! {
|
||||
#[ca(hello = "World", volume = 10, first_name = "Alice", second_name = "Bob")]
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(parsed.hello, "World");
|
||||
assert_eq!(parsed.volume, 10);
|
||||
assert_eq!(parsed.others.len(), 2);
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
#[allow(dead_code)]
|
||||
struct Person {
|
||||
first: String,
|
||||
last: String,
|
||||
parent: Option<Box<Person>>,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(v))]
|
||||
#[allow(dead_code)]
|
||||
struct Outer {
|
||||
#[darling(flatten)]
|
||||
owner: Person,
|
||||
#[darling(default)]
|
||||
blast: bool,
|
||||
}
|
||||
|
||||
/// This test makes sure that field names from parent structs are not inappropriately
|
||||
/// offered as alternates for unknown field errors in child structs.
|
||||
///
|
||||
/// A naive implementation that tried to offer all the flattened fields for "did you mean"
|
||||
/// could inspect all errors returned by the flattened field's `from_list` call and add the
|
||||
/// parent's field names as alternates to all unknown field errors.
|
||||
///
|
||||
/// THIS WOULD BE INCORRECT. Those unknown field errors may have already come from
|
||||
/// child fields within the flattened struct, where the parent's field names are not valid.
|
||||
#[test]
|
||||
fn do_not_suggest_invalid_alts() {
|
||||
let errors = Outer::from_derive_input(&parse_quote! {
|
||||
#[v(first = "Hello", last = "World", parent(first = "Hi", last = "Earth", blasts = "off"))]
|
||||
struct Demo;
|
||||
})
|
||||
.map(|_| "Should have failed")
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(
|
||||
!errors.contains("`blast`"),
|
||||
"Should not contain `blast`: {}",
|
||||
errors
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "suggestions")]
|
||||
#[track_caller]
|
||||
fn assert_error_has_suggestion(error: &darling::Error, field: &str) {
|
||||
let error = error.to_string();
|
||||
let backtick_field = format!("`{}`", field);
|
||||
assert!(
|
||||
error.contains(&backtick_field),
|
||||
"Should contain {} as did-you-mean suggestion: {}",
|
||||
backtick_field,
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "suggestions")]
|
||||
fn suggest_valid_parent_alts() {
|
||||
let errors = Outer::from_derive_input(&parse_quote! {
|
||||
#[v(first = "Hello", bladt = false, last = "World", parent(first = "Hi", last = "Earth"))]
|
||||
struct Demo;
|
||||
})
|
||||
.map(|_| "Should have failed")
|
||||
.unwrap_err();
|
||||
assert_error_has_suggestion(&errors, "blast");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "suggestions")]
|
||||
fn suggest_alts_for_multiple_errors() {
|
||||
let errors = Example::from_derive_input(&parse_quote! {
|
||||
#[sample(label = "Hello", republic, lab)]
|
||||
struct Demo {}
|
||||
})
|
||||
.map(|_| "Should have failed")
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(errors.len(), 2);
|
||||
let mut errors = errors.into_iter();
|
||||
assert_error_has_suggestion(
|
||||
&errors.next().expect("length is 2, so first `next` returns"),
|
||||
"public",
|
||||
);
|
||||
assert_error_has_suggestion(
|
||||
&errors
|
||||
.next()
|
||||
.expect("length is 2, so second `next` returns"),
|
||||
"label",
|
||||
);
|
||||
}
|
||||
|
||||
/// Make sure that flatten works with smart pointer types, e.g. `Box`.
|
||||
///
|
||||
/// The generated `flatten` impl directly calls `FromMeta::from_list`
|
||||
/// rather than calling `from_meta`, and the default impl of `from_list`
|
||||
/// will return an unsupported format error; this test ensures that the
|
||||
/// smart pointer type is properly forwarding the `from_list` call.
|
||||
#[test]
|
||||
fn flattening_to_box() {
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(v))]
|
||||
struct Example {
|
||||
#[darling(flatten)]
|
||||
items: Box<Vis>,
|
||||
}
|
||||
|
||||
let when_omitted = Example::from_derive_input(&parse_quote! {
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(!when_omitted.items.public.is_present());
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use darling::{util::Flag, Error, FromDeriveInput, FromMeta};
|
||||
use proc_macro2::Ident;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
#[darling(and_then = Self::validate)]
|
||||
struct Vis {
|
||||
public: Flag,
|
||||
private: Flag,
|
||||
}
|
||||
|
||||
impl Vis {
|
||||
fn validate(self) -> darling::Result<Self> {
|
||||
if self.public.is_present() && self.private.is_present() {
|
||||
return Err(Error::custom("Cannot be both public and private"));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(sample))]
|
||||
#[allow(dead_code)]
|
||||
struct Example {
|
||||
ident: Ident,
|
||||
label: String,
|
||||
volume: usize,
|
||||
#[darling(flatten)]
|
||||
visibility: Vis,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_errors() {
|
||||
let e = Example::from_derive_input(&parse_quote! {
|
||||
#[sample(volume = 10, public, private)]
|
||||
struct Demo {}
|
||||
})
|
||||
.map(|_| "Should have failed")
|
||||
.unwrap_err();
|
||||
|
||||
// We are expecting an error from the Vis::validate method and an error for the
|
||||
// missing `label` field.
|
||||
assert_eq!(e.len(), 2);
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
use darling::{ast, util::Ignored, FromDeriveInput, FromField, FromMeta};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Vis {
|
||||
#[darling(default)]
|
||||
public: bool,
|
||||
#[darling(default)]
|
||||
private: bool,
|
||||
}
|
||||
|
||||
#[derive(FromField)]
|
||||
#[darling(attributes(v))]
|
||||
struct Field {
|
||||
ident: Option<Ident>,
|
||||
example: Option<String>,
|
||||
#[darling(flatten)]
|
||||
visibility: Vis,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(v))]
|
||||
struct Input {
|
||||
data: ast::Data<Ignored, Field>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_flattens() {
|
||||
let di = Input::from_derive_input(&parse_quote! {
|
||||
struct Demo {
|
||||
#[v(public, example = "world")]
|
||||
hello: String
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let fields = di.data.take_struct().unwrap();
|
||||
let first_field = fields.into_iter().next().unwrap();
|
||||
assert_eq!(
|
||||
first_field.ident,
|
||||
Some(Ident::new("hello", Span::call_site()))
|
||||
);
|
||||
assert!(first_field.visibility.public);
|
||||
assert!(!first_field.visibility.private);
|
||||
assert_eq!(first_field.example.unwrap(), "world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_flattens_with_no_field_level_attributes() {
|
||||
let di = Input::from_derive_input(&parse_quote! {
|
||||
struct Demo {
|
||||
hello: String
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let fields = di.data.take_struct().unwrap();
|
||||
let first_field = fields.into_iter().next().unwrap();
|
||||
assert_eq!(
|
||||
first_field.ident,
|
||||
Some(Ident::new("hello", Span::call_site()))
|
||||
);
|
||||
assert!(!first_field.visibility.public);
|
||||
assert!(!first_field.visibility.private);
|
||||
assert_eq!(first_field.example, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_flattens_across_attributes() {
|
||||
let di = Input::from_derive_input(&parse_quote! {
|
||||
struct Demo {
|
||||
#[v(public)]
|
||||
#[v(private)]
|
||||
hello: String
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let fields = di.data.take_struct().unwrap();
|
||||
let first_field = fields.into_iter().next().unwrap();
|
||||
assert!(first_field.visibility.public);
|
||||
assert!(first_field.visibility.private);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use darling::FromAttributes;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Default, darling::FromAttributes)]
|
||||
#[darling(attributes(builder), forward_attrs)]
|
||||
struct Params {
|
||||
default: Option<syn::Expr>,
|
||||
attrs: Vec<syn::Attribute>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forward_attrs_with_field() {
|
||||
let input: syn::DeriveInput = parse_quote! {
|
||||
#[doc = "Hello"]
|
||||
#[builder(default = 15)]
|
||||
struct Example;
|
||||
};
|
||||
|
||||
let parsed = Params::from_attributes(&input.attrs).unwrap();
|
||||
assert!(parsed.default.is_some());
|
||||
assert_eq!(parsed.attrs.len(), 1);
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
//! Tests for `FromGenerics`, and - indirectly - `FromGenericParam`.
|
||||
//! These tests assume `FromTypeParam` is working and only look at whether the wrappers for magic
|
||||
//! fields are working as expected.
|
||||
|
||||
use darling::{
|
||||
ast::{self, GenericParamExt},
|
||||
util::{Ignored, WithOriginal},
|
||||
FromDeriveInput, FromTypeParam, Result,
|
||||
};
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(lorem))]
|
||||
struct MyReceiver {
|
||||
pub generics: ast::Generics<ast::GenericParam<MyTypeParam>>,
|
||||
}
|
||||
|
||||
#[derive(FromTypeParam)]
|
||||
#[darling(attributes(lorem))]
|
||||
struct MyTypeParam {
|
||||
pub ident: syn::Ident,
|
||||
#[darling(default)]
|
||||
pub foo: bool,
|
||||
pub bar: Option<String>,
|
||||
}
|
||||
|
||||
fn fdi<T: FromDeriveInput>(src: &str) -> Result<T> {
|
||||
FromDeriveInput::from_derive_input(&syn::parse_str(src).expect("Source parses"))
|
||||
}
|
||||
|
||||
/// Verify that `ast::Generics` is populated correctly when there is no generics declaration
|
||||
#[test]
|
||||
fn no_generics() {
|
||||
let rec: MyReceiver = fdi("struct Baz;").expect("Input is well-formed");
|
||||
assert!(rec.generics.where_clause.is_none());
|
||||
assert_eq!(rec.generics.params.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
fn expand_some() {
|
||||
let rec: MyReceiver = fdi(r#"
|
||||
struct Baz<
|
||||
'a,
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized
|
||||
>(&'a T, U);
|
||||
"#)
|
||||
.expect("Input is well-formed");
|
||||
assert!(rec.generics.where_clause.is_none());
|
||||
|
||||
// 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();
|
||||
|
||||
let first = ty_param_iter
|
||||
.next()
|
||||
.expect("type_params should not be empty");
|
||||
assert!(first.bar.is_none());
|
||||
assert!(first.foo);
|
||||
assert_eq!(first.ident, "T");
|
||||
|
||||
let second = ty_param_iter
|
||||
.next()
|
||||
.expect("type_params should have a second value");
|
||||
assert_eq!(
|
||||
second
|
||||
.bar
|
||||
.as_ref()
|
||||
.expect("Second type param should set bar"),
|
||||
"x"
|
||||
);
|
||||
assert_eq!(second.foo, false);
|
||||
assert_eq!(second.ident, "U");
|
||||
}
|
||||
|
||||
/// Verify ≤0.4.1 behavior - where `generics` had to be `syn::Generics` - keeps working.
|
||||
#[test]
|
||||
fn passthrough() {
|
||||
#[derive(FromDeriveInput)]
|
||||
struct PassthroughReceiver {
|
||||
pub generics: syn::Generics,
|
||||
}
|
||||
|
||||
let rec: PassthroughReceiver = fdi(r#"
|
||||
struct Baz<
|
||||
'a,
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized
|
||||
>(&'a T, U);
|
||||
"#)
|
||||
.expect("Input is well-formed");
|
||||
|
||||
let mut type_param_iter = rec.generics.type_params();
|
||||
assert!(type_param_iter.next().is_some());
|
||||
}
|
||||
|
||||
/// Verify that `where_clause` is passed through when it exists.
|
||||
/// As of 0.4.1, there is no `FromWhereClause` trait, so other types aren't supported
|
||||
/// for that field.
|
||||
#[test]
|
||||
fn where_clause() {
|
||||
let rec: MyReceiver = fdi(r#"
|
||||
struct Baz<
|
||||
'a,
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized
|
||||
>(&'a T, U) where T: Into<String>;
|
||||
"#)
|
||||
.expect("Input is well-formed");
|
||||
|
||||
assert!(rec.generics.where_clause.is_some());
|
||||
}
|
||||
|
||||
/// Test that `WithOriginal` works for generics.
|
||||
#[test]
|
||||
fn with_original() {
|
||||
#[derive(FromDeriveInput)]
|
||||
struct WorigReceiver {
|
||||
generics: WithOriginal<ast::Generics<ast::GenericParam<MyTypeParam>>, syn::Generics>,
|
||||
}
|
||||
|
||||
let rec: WorigReceiver = fdi(r#"
|
||||
struct Baz<
|
||||
'a,
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized
|
||||
>(&'a T, U) where T: Into<String>;
|
||||
"#)
|
||||
.expect("Input is well-formed");
|
||||
|
||||
// Make sure we haven't lost anything in the conversion
|
||||
assert_eq!(rec.generics.parsed.params.len(), 3);
|
||||
assert_eq!(rec.generics.original.params.len(), 3);
|
||||
|
||||
let parsed_t: &MyTypeParam = rec.generics.parsed.params[1]
|
||||
.as_type_param()
|
||||
.expect("Second argument should be type param");
|
||||
|
||||
// Make sure the first type param in each case is T
|
||||
assert_eq!(parsed_t.ident, "T");
|
||||
assert_eq!(
|
||||
rec.generics
|
||||
.original
|
||||
.type_params()
|
||||
.next()
|
||||
.expect("First type param should exist")
|
||||
.ident,
|
||||
"T"
|
||||
);
|
||||
|
||||
// Make sure we actually parsed the first type param
|
||||
assert!(parsed_t.foo);
|
||||
assert!(parsed_t.bar.is_none());
|
||||
}
|
||||
|
||||
/// Make sure generics can be ignored
|
||||
#[test]
|
||||
fn ignored() {
|
||||
#[derive(FromDeriveInput)]
|
||||
struct IgnoredReceiver {
|
||||
generics: Ignored,
|
||||
}
|
||||
|
||||
let rec: IgnoredReceiver = fdi(r#"
|
||||
struct Baz<
|
||||
'a,
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized
|
||||
>(&'a T, U) where T: Into<String>;
|
||||
"#)
|
||||
.expect("Input is well-formed");
|
||||
|
||||
assert_eq!(Ignored, rec.generics);
|
||||
}
|
||||
Vendored
+414
@@ -0,0 +1,414 @@
|
||||
use darling::{Error, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[darling(derive_syn_parse)]
|
||||
struct Meta {
|
||||
#[darling(default)]
|
||||
meta1: Option<String>,
|
||||
#[darling(default)]
|
||||
meta2: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_meta_meta_value() {
|
||||
let meta = Meta::from_list(&[parse_quote! {
|
||||
meta1 = "thefeature"
|
||||
}])
|
||||
.unwrap();
|
||||
assert_eq!(meta.meta1, Some("thefeature".to_string()));
|
||||
assert!(!meta.meta2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_meta_meta_bool() {
|
||||
let meta = Meta::from_list(&[parse_quote! {
|
||||
meta2
|
||||
}])
|
||||
.unwrap();
|
||||
assert_eq!(meta.meta1, None);
|
||||
assert!(meta.meta2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_meta_lit_string_errors() {
|
||||
let err = Meta::from_list(&[parse_quote! {
|
||||
"meta2"
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
Error::unsupported_format("literal").to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_meta_lit_integer_errors() {
|
||||
let err = Meta::from_list(&[parse_quote! {
|
||||
2
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
Error::unsupported_format("literal").to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_meta_lit_bool_errors() {
|
||||
let err = Meta::from_list(&[parse_quote! {
|
||||
true
|
||||
}])
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
Error::unsupported_format("literal").to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_impl() {
|
||||
let meta = parse_quote! {
|
||||
meta1 = "thefeature",
|
||||
meta2
|
||||
};
|
||||
let parsed_meta: Meta = syn::parse2(meta).unwrap();
|
||||
assert_eq!(parsed_meta.meta1, Some("thefeature".to_string()));
|
||||
assert!(parsed_meta.meta2);
|
||||
}
|
||||
|
||||
/// Tests behavior of FromMeta implementation for enums.
|
||||
mod enum_impl {
|
||||
use darling::{Error, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
/// A playback volume.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromMeta)]
|
||||
enum Volume {
|
||||
Normal,
|
||||
Low,
|
||||
High,
|
||||
#[darling(rename = "dB")]
|
||||
Decibels(u8),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_for_unit_variant() {
|
||||
let volume = Volume::from_string("low").unwrap();
|
||||
assert_eq!(volume, Volume::Low);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_value_list() {
|
||||
let unit_variant = Volume::from_list(&[parse_quote!(high)]).unwrap();
|
||||
assert_eq!(unit_variant, Volume::High);
|
||||
|
||||
let newtype_variant = Volume::from_list(&[parse_quote!(dB = 100)]).unwrap();
|
||||
assert_eq!(newtype_variant, Volume::Decibels(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_list_errors() {
|
||||
let err = Volume::from_list(&[]).unwrap_err();
|
||||
assert_eq!(err.to_string(), Error::too_few_items(1).to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_values_list_errors() {
|
||||
let err = Volume::from_list(&[parse_quote!(low), parse_quote!(dB = 20)]).unwrap_err();
|
||||
assert_eq!(err.to_string(), Error::too_many_items(1).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
mod keyword {
|
||||
use darling::FromMeta;
|
||||
use quote::quote;
|
||||
use syn::{parse2, parse_quote, Path, Type};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Keyword {
|
||||
#[darling(rename = "type")]
|
||||
ty: Type,
|
||||
#[darling(rename = "fn")]
|
||||
func: Path,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct FlattenKeyword {
|
||||
#[darling(rename = "ref")]
|
||||
reference: Type,
|
||||
#[darling(flatten)]
|
||||
keyword: Keyword,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, FromMeta)]
|
||||
enum UnitEnumKeyword {
|
||||
#[darling(rename = "enum")]
|
||||
Enum,
|
||||
#[darling(rename = "struct")]
|
||||
Struct,
|
||||
#[darling(rename = "trait")]
|
||||
Trait,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct FlattenEnumKeyword {
|
||||
#[darling(rename = "ref")]
|
||||
reference: Type,
|
||||
#[darling(flatten)]
|
||||
keyword: UnitEnumKeyword,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keywords() {
|
||||
let meta = quote! {
|
||||
outer(type = "u32", fn = foo)
|
||||
};
|
||||
|
||||
let keyword = Keyword::from_meta(&parse2(meta).unwrap()).unwrap();
|
||||
assert_eq!(keyword.ty, parse_quote!(u32));
|
||||
assert_eq!(keyword.func, parse_quote!(foo));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_keywords() {
|
||||
let meta = quote! {
|
||||
outer(ref = "u32", type = "i32", fn = bar)
|
||||
};
|
||||
|
||||
let keyword = FlattenKeyword::from_meta(&parse2(meta).unwrap()).unwrap();
|
||||
assert_eq!(keyword.reference, parse_quote!(u32));
|
||||
assert_eq!(keyword.keyword.ty, parse_quote!(i32));
|
||||
assert_eq!(keyword.keyword.func, parse_quote!(bar));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_keywords() {
|
||||
let enum_ = quote! {
|
||||
outer(enum)
|
||||
};
|
||||
|
||||
let unit_enum = UnitEnumKeyword::from_meta(&parse2(enum_).unwrap()).unwrap();
|
||||
assert_eq!(unit_enum, UnitEnumKeyword::Enum);
|
||||
|
||||
let struct_ = quote! {
|
||||
outer(struct)
|
||||
};
|
||||
let unit_enum = UnitEnumKeyword::from_meta(&parse2(struct_).unwrap()).unwrap();
|
||||
assert_eq!(unit_enum, UnitEnumKeyword::Struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_enum_keywords() {
|
||||
let meta = quote! {
|
||||
outer(ref = "u32", enum)
|
||||
};
|
||||
|
||||
let keyword = FlattenEnumKeyword::from_meta(&parse2(meta).unwrap()).unwrap();
|
||||
assert_eq!(keyword.reference, parse_quote!(u32));
|
||||
assert_eq!(keyword.keyword, UnitEnumKeyword::Enum);
|
||||
|
||||
let meta = quote! {
|
||||
outer(ref = "u32", struct)
|
||||
};
|
||||
|
||||
let keyword = FlattenEnumKeyword::from_meta(&parse2(meta).unwrap()).unwrap();
|
||||
assert_eq!(keyword.reference, parse_quote!(u32));
|
||||
assert_eq!(keyword.keyword, UnitEnumKeyword::Struct);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_none_struct_closure {
|
||||
use darling::FromMeta;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Outer {
|
||||
// Do NOT add `darling(default)` here; this is testing the `from_none` fallback
|
||||
// invoked when a field is not declared and no `default` is specified.
|
||||
speech: Example,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[darling(from_none = || Some(Default::default()))]
|
||||
struct Example {
|
||||
max_volume: u32,
|
||||
}
|
||||
|
||||
impl Default for Example {
|
||||
fn default() -> Self {
|
||||
Example { max_volume: 3 }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absent_gets_from_none() {
|
||||
let thing = Outer::from_list(&[]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_errors() {
|
||||
let error = Outer::from_list(&[parse_quote!(speech)])
|
||||
.expect_err("speech should require its fields if declared");
|
||||
assert_eq!(error.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_sets_field() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 5);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_none_struct_path {
|
||||
use darling::FromMeta;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Outer {
|
||||
// Do NOT add `darling(default)` here; this is testing the `from_none` fallback
|
||||
// invoked when a field is not declared and no `default` is specified.
|
||||
speech: Example,
|
||||
}
|
||||
|
||||
fn from_none_fallback() -> Option<Example> {
|
||||
Some(Example { max_volume: 3 })
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[darling(from_none = from_none_fallback)]
|
||||
struct Example {
|
||||
max_volume: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absent_gets_from_none() {
|
||||
let thing = Outer::from_list(&[]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_errors() {
|
||||
let error = Outer::from_list(&[parse_quote!(speech)])
|
||||
.expect_err("speech should require its fields if declared");
|
||||
assert_eq!(error.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_sets_field() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 5);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_word_struct_closure {
|
||||
use darling::FromMeta;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Outer {
|
||||
#[darling(default)]
|
||||
speech: Example,
|
||||
}
|
||||
|
||||
#[derive(FromMeta, Default)]
|
||||
#[darling(from_word = || Ok(Example { max_volume: 10 }))]
|
||||
struct Example {
|
||||
max_volume: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absent_gets_default() {
|
||||
let thing = Outer::from_list(&[]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_gets_value() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech)]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_sets_field() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 5);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_word_struct_path {
|
||||
use darling::FromMeta;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Outer {
|
||||
#[darling(default)]
|
||||
speech: Example,
|
||||
}
|
||||
|
||||
fn max_volume_10() -> darling::Result<Example> {
|
||||
Ok(Example { max_volume: 10 })
|
||||
}
|
||||
|
||||
#[derive(FromMeta, Default)]
|
||||
#[darling(from_word = max_volume_10)]
|
||||
struct Example {
|
||||
max_volume: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absent_gets_default() {
|
||||
let thing = Outer::from_list(&[]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_gets_value() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech)]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_sets_field() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
|
||||
assert_eq!(thing.speech.max_volume, 5);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_word_enum_closure {
|
||||
use darling::FromMeta;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Outer {
|
||||
speech: Example,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta, PartialEq, Eq)]
|
||||
#[darling(from_word = || Ok(Example::Left { max_volume: 10 }))]
|
||||
enum Example {
|
||||
Left { max_volume: u32 },
|
||||
Right { speed: u32 },
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_gets_value() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech)]).unwrap();
|
||||
assert_eq!(thing.speech, Example::Left { max_volume: 10 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_sets_field() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech(left(max_volume = 5)))]).unwrap();
|
||||
assert_eq!(thing.speech, Example::Left { max_volume: 5 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variant_word_fails() {
|
||||
let thing = Outer::from_list(&[parse_quote!(speech(left))]).expect_err(
|
||||
"A variant word is an error because from_word applies at the all-up enum level",
|
||||
);
|
||||
assert_eq!(thing.len(), 1);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
use darling::FromTypeParam;
|
||||
use syn::{parse_quote, DeriveInput, GenericParam, Ident, TypeParam};
|
||||
|
||||
#[derive(FromTypeParam)]
|
||||
#[darling(attributes(lorem), from_ident)]
|
||||
struct Lorem {
|
||||
ident: Ident,
|
||||
bounds: Vec<syn::TypeParamBound>,
|
||||
foo: bool,
|
||||
bar: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Ident> for Lorem {
|
||||
fn from(ident: Ident) -> Self {
|
||||
Lorem {
|
||||
ident,
|
||||
foo: false,
|
||||
bar: None,
|
||||
bounds: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_type(param: &GenericParam) -> &TypeParam {
|
||||
match *param {
|
||||
GenericParam::Type(ref ty) => ty,
|
||||
_ => unreachable!("Not a type param"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
fn expand_many() {
|
||||
let di: DeriveInput = parse_quote! {
|
||||
struct Baz<
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized
|
||||
>(T, U);
|
||||
};
|
||||
|
||||
let params = di.generics.params;
|
||||
|
||||
{
|
||||
let ty = extract_type(¶ms[0]);
|
||||
let lorem = Lorem::from_type_param(ty).unwrap();
|
||||
assert_eq!(lorem.ident, "T");
|
||||
assert_eq!(lorem.foo, true);
|
||||
assert_eq!(lorem.bar, None);
|
||||
}
|
||||
|
||||
{
|
||||
let ty = extract_type(¶ms[1]);
|
||||
let lorem = Lorem::from_type_param(ty).unwrap();
|
||||
assert_eq!(lorem.ident, "U");
|
||||
assert_eq!(lorem.foo, false);
|
||||
assert_eq!(lorem.bar, Some("x".to_string()));
|
||||
assert_eq!(lorem.bounds.len(), 2);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use darling::FromTypeParam;
|
||||
use syn::{parse_quote, DeriveInput, GenericParam, TypeParam};
|
||||
|
||||
#[derive(Default, FromTypeParam)]
|
||||
#[darling(attributes(lorem), default)]
|
||||
struct Lorem {
|
||||
foo: bool,
|
||||
bar: Option<String>,
|
||||
default: Option<syn::Type>,
|
||||
}
|
||||
|
||||
fn extract_type(param: &GenericParam) -> &TypeParam {
|
||||
match *param {
|
||||
GenericParam::Type(ref ty) => ty,
|
||||
_ => unreachable!("Not a type param"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
fn expand_many() {
|
||||
let di: DeriveInput = parse_quote! {
|
||||
struct Baz<
|
||||
#[lorem(foo)] T,
|
||||
#[lorem(bar = "x")] U: Eq + ?Sized,
|
||||
#[lorem(foo = false)] V = (),
|
||||
>(T, U, V);
|
||||
};
|
||||
let params = di.generics.params;
|
||||
|
||||
{
|
||||
let ty = extract_type(¶ms[0]);
|
||||
let lorem = Lorem::from_type_param(ty).unwrap();
|
||||
assert_eq!(lorem.foo, true);
|
||||
assert_eq!(lorem.bar, None);
|
||||
}
|
||||
|
||||
{
|
||||
let ty = extract_type(¶ms[1]);
|
||||
let lorem = Lorem::from_type_param(ty).unwrap();
|
||||
assert_eq!(lorem.foo, false);
|
||||
assert_eq!(lorem.bar, Some("x".to_string()));
|
||||
assert!(lorem.default.is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let ty = extract_type(¶ms[2]);
|
||||
let lorem = Lorem::from_type_param(ty).unwrap();
|
||||
assert_eq!(lorem.foo, false);
|
||||
assert_eq!(lorem.bar, None);
|
||||
assert!(lorem.default.is_some());
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
use darling::FromVariant;
|
||||
use syn::{spanned::Spanned, Expr, ExprLit, LitInt};
|
||||
|
||||
#[derive(FromVariant)]
|
||||
#[darling(from_ident, attributes(hello))]
|
||||
#[allow(dead_code)]
|
||||
pub struct Lorem {
|
||||
ident: syn::Ident,
|
||||
into: Option<bool>,
|
||||
skip: Option<bool>,
|
||||
discriminant: Option<syn::Expr>,
|
||||
fields: darling::ast::Fields<syn::Type>,
|
||||
}
|
||||
|
||||
impl From<syn::Ident> for Lorem {
|
||||
fn from(ident: syn::Ident) -> Self {
|
||||
Lorem {
|
||||
ident,
|
||||
into: Default::default(),
|
||||
skip: Default::default(),
|
||||
discriminant: None,
|
||||
fields: darling::ast::Style::Unit.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discriminant() {
|
||||
let input: syn::DeriveInput = syn::parse_str(
|
||||
r#"
|
||||
pub enum Test {
|
||||
Works = 1,
|
||||
AlsoWorks = 2,
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let span = input.span();
|
||||
if let syn::Data::Enum(enm) = input.data {
|
||||
let lorem = Lorem::from_variant(
|
||||
enm.variants
|
||||
.first()
|
||||
.expect("Hardcoded input has one variant"),
|
||||
)
|
||||
.expect("FromVariant can process the discriminant");
|
||||
assert_eq!(
|
||||
lorem.discriminant,
|
||||
Some(Expr::Lit(ExprLit {
|
||||
attrs: vec![],
|
||||
lit: LitInt::new("1", span).into(),
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
panic!("Data should be enum");
|
||||
}
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, Clone, FromMeta)]
|
||||
struct Wrapper<T>(pub T);
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(hello))]
|
||||
struct Foo<T> {
|
||||
lorem: Wrapper<T>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expansion() {
|
||||
let di = parse_quote! {
|
||||
#[hello(lorem = "Hello")]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
Foo::<String>::from_derive_input(&di).unwrap();
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use darling::{Error, FromDeriveInput, Result};
|
||||
use syn::{parse_quote, Ident};
|
||||
|
||||
fn check_ident(ident: &Ident) -> Result<String> {
|
||||
let s = ident.to_string();
|
||||
if s.len() < 2 {
|
||||
Err(Error::custom("generics must be at least 2 characters").with_span(ident))
|
||||
} else {
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn long_generic_names(generics: &syn::Generics) -> Result<BTreeSet<String>> {
|
||||
let mut errors = Error::accumulator();
|
||||
let valid = generics
|
||||
.type_params()
|
||||
.filter_map(|c| errors.handle(check_ident(&c.ident)))
|
||||
.collect();
|
||||
errors.finish_with(valid)
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(a))]
|
||||
struct Receiver {
|
||||
#[darling(with = long_generic_names)]
|
||||
generics: BTreeSet<String>,
|
||||
surname: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds_on_no_generics() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
#[a(surname = "Smith")]
|
||||
struct Demo;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(di.generics.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succeeds_on_valid_generics() {
|
||||
let di = Receiver::from_derive_input(&parse_quote! {
|
||||
#[a(surname = "Smith")]
|
||||
struct Demo<Greeting> {
|
||||
hello: Greeting,
|
||||
world: String,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(di.generics.len(), 1);
|
||||
assert!(di.generics.contains("Greeting"));
|
||||
assert_eq!(di.surname, "Smith");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_input() {
|
||||
let err = Receiver::from_derive_input(&parse_quote! {
|
||||
struct Demo<G, S> {
|
||||
hello: G,
|
||||
world: S,
|
||||
}
|
||||
})
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.len(),
|
||||
// 2 errors from short type param names
|
||||
2 +
|
||||
// error for missing field `surname`
|
||||
1,
|
||||
"errors should have accumulated, and body checking should have occurred"
|
||||
);
|
||||
}
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Default, FromMeta, PartialEq, Debug)]
|
||||
#[darling(default)]
|
||||
struct Lorem {
|
||||
ipsum: bool,
|
||||
dolor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput, PartialEq, Debug)]
|
||||
#[darling(attributes(darling_demo))]
|
||||
struct Core {
|
||||
ident: syn::Ident,
|
||||
vis: syn::Visibility,
|
||||
generics: syn::Generics,
|
||||
lorem: Lorem,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput, PartialEq, Debug)]
|
||||
#[darling(attributes(darling_demo))]
|
||||
struct TraitCore {
|
||||
ident: syn::Ident,
|
||||
generics: syn::Generics,
|
||||
lorem: Lorem,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let di = parse_quote! {
|
||||
#[derive(Foo)]
|
||||
#[darling_demo(lorem(ipsum))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Core::from_derive_input(&di).unwrap(),
|
||||
Core {
|
||||
ident: parse_quote!(Bar),
|
||||
vis: parse_quote!(pub),
|
||||
generics: Default::default(),
|
||||
lorem: Lorem {
|
||||
ipsum: true,
|
||||
dolor: None,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_type() {
|
||||
let di = parse_quote! {
|
||||
#[derive(Foo)]
|
||||
#[darling_demo(lorem(dolor = "hello"))]
|
||||
pub struct Bar;
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
TraitCore::from_derive_input(&di).unwrap(),
|
||||
TraitCore {
|
||||
ident: parse_quote!(Bar),
|
||||
generics: Default::default(),
|
||||
lorem: Lorem {
|
||||
ipsum: false,
|
||||
dolor: Some("hello".to_owned()),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use darling::FromMeta;
|
||||
use syn::{parse_quote, Attribute, Path};
|
||||
|
||||
#[derive(Debug, FromMeta, PartialEq, Eq)]
|
||||
struct MapValue {
|
||||
name: String,
|
||||
#[darling(default)]
|
||||
option: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_map() {
|
||||
let attr: Attribute = parse_quote! {
|
||||
#[foo(first(name = "Hello", option), the::second(name = "Second"))]
|
||||
};
|
||||
|
||||
let meta = attr.meta;
|
||||
let map: HashMap<Path, MapValue> = FromMeta::from_meta(&meta).unwrap();
|
||||
|
||||
let comparison: HashMap<Path, MapValue> = vec![
|
||||
(
|
||||
parse_quote!(first),
|
||||
MapValue {
|
||||
name: "Hello".into(),
|
||||
option: true,
|
||||
},
|
||||
),
|
||||
(
|
||||
parse_quote!(the::second),
|
||||
MapValue {
|
||||
name: "Second".into(),
|
||||
option: false,
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
assert_eq!(comparison, map);
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
use darling::{util::parse_expr, FromDeriveInput, FromMeta};
|
||||
use syn::{parse_quote, Expr};
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(demo))]
|
||||
pub struct Receiver {
|
||||
#[darling(with = parse_expr::preserve_str_literal, map = Some)]
|
||||
example1: Option<Expr>,
|
||||
#[darling(
|
||||
with = |m| Ok(String::from_meta(m)?.to_uppercase()),
|
||||
map = Some
|
||||
)]
|
||||
example2: Option<String>,
|
||||
// This is deliberately strange - it keeps the field name, and ignores
|
||||
// the rest of the attribute. In normal operation, this is strongly discouraged.
|
||||
// It's used here to verify that the parameter type is known even if it can't be
|
||||
// inferred from usage within the closure.
|
||||
#[darling(with = |m| Ok(m.path().clone()))]
|
||||
example3: syn::Path,
|
||||
#[darling(with = "example4_parser")]
|
||||
example4: String,
|
||||
}
|
||||
|
||||
// the custom parser function referred to by string
|
||||
fn example4_parser(meta: &syn::Meta) -> darling::Result<String> {
|
||||
match meta {
|
||||
syn::Meta::NameValue(nv) => match &nv.value {
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(s),
|
||||
..
|
||||
}) => Ok(s.value()),
|
||||
other => Err(darling::Error::unexpected_expr_type(other)),
|
||||
},
|
||||
_ => Err(darling::Error::unexpected_type("name-value")),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_all_cases() {
|
||||
let input = Receiver::from_derive_input(&parse_quote! {
|
||||
#[demo(example1 = test::path, example2 = "hello", example3, example4 = "world")]
|
||||
struct Example;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(input.example1, Some(parse_quote!(test::path)));
|
||||
assert_eq!(input.example2, Some("HELLO".to_string()));
|
||||
assert_eq!(input.example3, parse_quote!(example3));
|
||||
assert_eq!(input.example4, "world".to_string());
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(hello))]
|
||||
#[allow(dead_code)]
|
||||
struct Lorem {
|
||||
ident: syn::Ident,
|
||||
ipsum: Ipsum,
|
||||
}
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Ipsum {
|
||||
#[darling(multiple)]
|
||||
dolor: Vec<String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_many() {
|
||||
let di = parse_quote! {
|
||||
#[hello(ipsum(dolor = "Hello", dolor = "World"))]
|
||||
pub struct Baz;
|
||||
};
|
||||
|
||||
let lorem: Lorem = Lorem::from_derive_input(&di).unwrap();
|
||||
assert_eq!(
|
||||
lorem.ipsum.dolor,
|
||||
vec!["Hello".to_string(), "World".to_string()]
|
||||
);
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
//! A newtype struct should be able to derive `FromMeta` if its member implements it.
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromMeta, PartialEq, Eq)]
|
||||
struct Lorem(bool);
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(newtype))]
|
||||
struct DemoContainer {
|
||||
lorem: Lorem,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated() {
|
||||
let di = parse_quote! {
|
||||
#[derive(Baz)]
|
||||
#[newtype(lorem = false)]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
let c = DemoContainer::from_derive_input(&di).unwrap();
|
||||
|
||||
assert_eq!(c.lorem, Lorem(false));
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
//! This is a regression test for issue [#371](https://github.com/TedDriggs/darling/issues/371).
|
||||
|
||||
use darling::util::{Callable, Override};
|
||||
use darling::FromDeriveInput;
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(test))]
|
||||
struct Test1 {
|
||||
#[allow(dead_code)]
|
||||
func: Callable,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(test))]
|
||||
struct Test2 {
|
||||
func: Override<Callable>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_closure() {
|
||||
let input = syn::parse_quote! {
|
||||
#[test(func = || 1 + 1)]
|
||||
struct Foo;
|
||||
};
|
||||
assert!(Test1::from_derive_input(&input).is_ok());
|
||||
assert!(Test2::from_derive_input(&input).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_path() {
|
||||
let input = syn::parse_quote! {
|
||||
#[test(func = foo::bar)]
|
||||
struct Foo;
|
||||
};
|
||||
assert!(Test1::from_derive_input(&input).is_ok());
|
||||
assert!(Test2::from_derive_input(&input).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inherit() {
|
||||
let input = syn::parse_quote! {
|
||||
#[test(func)]
|
||||
struct Foo;
|
||||
};
|
||||
assert!(Test1::from_derive_input(&input).is_err());
|
||||
assert!(matches!(
|
||||
Test2::from_derive_input(&input).unwrap().func,
|
||||
Override::Inherit
|
||||
));
|
||||
}
|
||||
Vendored
+74
@@ -0,0 +1,74 @@
|
||||
//! Test that skipped fields are not read into structs when they appear in input.
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, FromDeriveInput)]
|
||||
#[darling(attributes(skip_test))]
|
||||
pub struct Lorem {
|
||||
ipsum: String,
|
||||
|
||||
#[darling(skip)]
|
||||
dolor: u8,
|
||||
}
|
||||
|
||||
/// Verify variant-level and field-level skip work correctly for enums.
|
||||
#[derive(Debug, FromMeta)]
|
||||
pub enum Sit {
|
||||
Amet(bool),
|
||||
|
||||
#[darling(skip)]
|
||||
Foo {
|
||||
hello: bool,
|
||||
},
|
||||
|
||||
Bar {
|
||||
hello: bool,
|
||||
#[darling(skip)]
|
||||
world: u8,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_skipped_field_not_required() {
|
||||
let di = parse_quote! {
|
||||
#[skip_test(ipsum = "Hello")]
|
||||
struct Baz;
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Lorem::from_derive_input(&di).unwrap(),
|
||||
Lorem {
|
||||
ipsum: "Hello".to_string(),
|
||||
dolor: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// This test verifies that a skipped field will still prefer an explicit default
|
||||
/// over the default that would come from its field type. It would be incorrect for
|
||||
/// `Defaulting::from_derive_input` to fail here, and it would be wrong for the value
|
||||
/// of `dolor` to be `None`.
|
||||
#[test]
|
||||
fn verify_default_supersedes_from_none() {
|
||||
fn default_dolor() -> Option<u8> {
|
||||
Some(2)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, FromDeriveInput)]
|
||||
#[darling(attributes(skip_test))]
|
||||
pub struct Defaulting {
|
||||
#[darling(skip, default = "default_dolor")]
|
||||
dolor: Option<u8>,
|
||||
}
|
||||
|
||||
let di = parse_quote! {
|
||||
#[skip_test]
|
||||
struct Baz;
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Defaulting::from_derive_input(&di).unwrap(),
|
||||
Defaulting { dolor: Some(2) }
|
||||
)
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
use darling::{
|
||||
util::{Override, SpannedValue},
|
||||
FromAttributes,
|
||||
};
|
||||
|
||||
use quote::quote;
|
||||
use syn::{parse::Parser, Attribute, Lit};
|
||||
|
||||
use Override::{Explicit, Inherit};
|
||||
|
||||
#[derive(FromAttributes, Default)]
|
||||
#[darling(attributes(test_attr))]
|
||||
struct SpannedValueOverPrimitive {
|
||||
string: SpannedValue<String>,
|
||||
optional_int: Option<SpannedValue<u64>>,
|
||||
override_literal: Override<SpannedValue<Lit>>,
|
||||
optional_override_char: Option<Override<SpannedValue<char>>>,
|
||||
}
|
||||
|
||||
macro_rules! test_cases {
|
||||
($(
|
||||
$name:ident : #[test_attr($($tok:tt)*)] => $result:ident $( {
|
||||
string: $string:expr,
|
||||
int: $int:expr,
|
||||
literal: $literal:pat,
|
||||
char: $char:expr,
|
||||
} )?;
|
||||
)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
let quoted = quote! {
|
||||
#[test_attr(
|
||||
$($tok)*
|
||||
)]
|
||||
};
|
||||
|
||||
let attrs = Attribute::parse_outer
|
||||
.parse2(quoted)
|
||||
.expect("failed to parse tokens as an attribute list");
|
||||
|
||||
let result = SpannedValueOverPrimitive::from_attributes(&attrs);
|
||||
|
||||
test_case_result!(result, $result $({
|
||||
string: $string,
|
||||
int: $int,
|
||||
literal: $literal,
|
||||
char: $char,
|
||||
})?);
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
macro_rules! test_case_result {
|
||||
($value:expr, Err) => {
|
||||
assert!($value.is_err())
|
||||
};
|
||||
|
||||
($value:expr, Ok{
|
||||
string: $string:expr,
|
||||
int: $int:expr,
|
||||
literal: $literal:pat,
|
||||
char: $char:expr,
|
||||
}) => {{
|
||||
let out = $value.expect("failed to parse with from_attributes");
|
||||
|
||||
assert_eq!(out.string.as_ref(), $string);
|
||||
assert_eq!(out.optional_int.map(|i| *i.as_ref()), $int);
|
||||
assert!(matches!(out.override_literal, $literal));
|
||||
assert_eq!(
|
||||
out.optional_override_char.map(|c| match c {
|
||||
Inherit => Inherit,
|
||||
Explicit(value) => Explicit(*value.as_ref()),
|
||||
}),
|
||||
$char
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
test_cases! {
|
||||
basic: #[test_attr(
|
||||
string = "Hello",
|
||||
optional_int = 23,
|
||||
override_literal = "foo",
|
||||
optional_override_char = 'c'
|
||||
)] => Ok{
|
||||
string: "Hello",
|
||||
int: Some(23),
|
||||
literal: Explicit(_),
|
||||
char: Some(Override::Explicit('c')),
|
||||
};
|
||||
|
||||
omit_optionals: #[test_attr(
|
||||
string = "Hello",
|
||||
override_literal = "foo",
|
||||
)] => Ok{
|
||||
string: "Hello",
|
||||
int: None,
|
||||
literal: Explicit(_),
|
||||
char: None,
|
||||
};
|
||||
|
||||
omit_overrides: #[test_attr(
|
||||
string = "Hello",
|
||||
override_literal,
|
||||
optional_override_char,
|
||||
)] => Ok{
|
||||
string: "Hello",
|
||||
int: None,
|
||||
literal: Inherit,
|
||||
char: Some(Inherit),
|
||||
};
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
//! When input is split across multiple attributes on one element,
|
||||
//! darling should collapse that into one struct.
|
||||
|
||||
use darling::{Error, FromDeriveInput};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromDeriveInput, PartialEq, Eq)]
|
||||
#[darling(attributes(split))]
|
||||
struct Lorem {
|
||||
foo: String,
|
||||
bar: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_attributes_accrue_to_instance() {
|
||||
let di = parse_quote! {
|
||||
#[split(foo = "Hello")]
|
||||
#[split(bar)]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
let parsed = Lorem::from_derive_input(&di).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
Lorem {
|
||||
foo: "Hello".to_string(),
|
||||
bar: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicates_across_split_attrs_error() {
|
||||
let di = parse_quote! {
|
||||
#[split(foo = "Hello")]
|
||||
#[split(foo = "World", bar)]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
let pr = Lorem::from_derive_input(&di).unwrap_err();
|
||||
assert!(pr.has_span());
|
||||
assert_eq!(pr.to_string(), Error::duplicate_field("foo").to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_errors_accrue_to_instance() {
|
||||
let di = parse_quote! {
|
||||
#[split(foo = "Hello")]
|
||||
#[split(foo = "World")]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
let pr = Lorem::from_derive_input(&di);
|
||||
let err: Error = pr.unwrap_err();
|
||||
assert_eq!(2, err.len());
|
||||
let mut errs = err.into_iter().peekable();
|
||||
assert_eq!(
|
||||
errs.peek().unwrap().to_string(),
|
||||
Error::duplicate_field("foo").to_string()
|
||||
);
|
||||
assert!(errs.next().unwrap().has_span());
|
||||
assert_eq!(
|
||||
errs.next().unwrap().to_string(),
|
||||
Error::missing_field("bar").to_string()
|
||||
);
|
||||
assert!(errs.next().is_none());
|
||||
}
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
#![allow(dead_code)]
|
||||
#![cfg(feature = "suggestions")]
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(suggest))]
|
||||
struct Lorem {
|
||||
ipsum: String,
|
||||
dolor: Dolor,
|
||||
// This field is included to make sure that skipped fields aren't suggested.
|
||||
#[darling(skip)]
|
||||
amet: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Dolor {
|
||||
sit: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_dolor() {
|
||||
let input: syn::DeriveInput = parse_quote! {
|
||||
#[suggest(ipsum = "Hello", dolorr(sit))]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
let result = Lorem::from_derive_input(&input).unwrap_err();
|
||||
assert_eq!(2, result.len());
|
||||
assert!(format!("{}", result).contains("Did you mean"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_suggest_skipped_field() {
|
||||
let input: syn::DeriveInput = parse_quote! {
|
||||
#[suggest(ipsum = "Hello", dolor(sit), amt)]
|
||||
pub struct Foo;
|
||||
};
|
||||
|
||||
let result = Lorem::from_derive_input(&input).unwrap_err();
|
||||
assert_eq!(1, result.len());
|
||||
assert!(!format!("{}", result).contains("amet"));
|
||||
}
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
use darling::{ast, FromDeriveInput, FromField, FromVariant};
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(from_variants), supports(enum_any))]
|
||||
pub struct Container {
|
||||
// The second type parameter can be anything that implements FromField, since
|
||||
// FromDeriveInput will produce an error if given a struct.
|
||||
data: ast::Data<Variant, Panic>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, FromVariant)]
|
||||
#[darling(default, attributes(from_variants), supports(newtype, unit))]
|
||||
pub struct Variant {
|
||||
into: Option<bool>,
|
||||
skip: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(from_struct), supports(struct_named))]
|
||||
pub struct StructContainer {
|
||||
// The second type parameter can be anything that implements FromVariant, since
|
||||
// FromDeriveInput will produce an error if given an enum.
|
||||
data: ast::Data<Panic, syn::Field>,
|
||||
}
|
||||
|
||||
/// A struct that will panic if construction is attempted via `FromVariant` or `FromField`.
|
||||
///
|
||||
/// These tests use this to ensure no attempts are made to read fields or variants if
|
||||
/// shape validation has failed. Failure to do this could cause panics or spurious errors
|
||||
/// to be emitted by derived `FromDeriveInput` impls, which breaks library author's trust
|
||||
/// in `darling` to emit great error messages.
|
||||
#[derive(Debug)]
|
||||
struct Panic;
|
||||
|
||||
impl FromVariant for Panic {
|
||||
fn from_variant(variant: &syn::Variant) -> darling::Result<Self> {
|
||||
panic!("Should not have called from_variant on {}", variant.ident);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromField for Panic {
|
||||
fn from_field(_field: &syn::Field) -> darling::Result<Self> {
|
||||
panic!("Should not have called from_field");
|
||||
}
|
||||
}
|
||||
|
||||
mod source {
|
||||
use syn::{parse_quote, DeriveInput};
|
||||
|
||||
pub fn newtype_enum() -> DeriveInput {
|
||||
parse_quote! {
|
||||
enum Hello {
|
||||
World(bool),
|
||||
String(String),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn named_field_enum() -> DeriveInput {
|
||||
parse_quote! {
|
||||
enum Hello {
|
||||
Foo(u16),
|
||||
World {
|
||||
name: String
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_enum() -> DeriveInput {
|
||||
parse_quote! {
|
||||
enum Hello {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn named_struct() -> DeriveInput {
|
||||
parse_quote! {
|
||||
struct Hello {
|
||||
world: bool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tuple_struct() -> DeriveInput {
|
||||
parse_quote! { struct Hello(String, bool); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_newtype_or_unit() {
|
||||
// Should pass
|
||||
let container = Container::from_derive_input(&source::newtype_enum()).unwrap();
|
||||
assert!(container.data.is_enum());
|
||||
|
||||
// Should error
|
||||
Container::from_derive_input(&source::named_field_enum()).unwrap_err();
|
||||
Container::from_derive_input(&source::named_struct()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_named() {
|
||||
// Should pass
|
||||
let container = StructContainer::from_derive_input(&source::named_struct()).unwrap();
|
||||
assert!(container.data.is_struct());
|
||||
|
||||
// Should fail
|
||||
StructContainer::from_derive_input(&source::tuple_struct()).unwrap_err();
|
||||
StructContainer::from_derive_input(&source::named_field_enum()).unwrap_err();
|
||||
StructContainer::from_derive_input(&source::newtype_enum()).unwrap_err();
|
||||
StructContainer::from_derive_input(&source::empty_enum()).unwrap_err();
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
use darling::FromDeriveInput;
|
||||
use syn::{parse_quote, Ident, LitStr, Path};
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(supports(struct_unit), attributes(bar))]
|
||||
pub struct Bar {
|
||||
pub ident: Ident,
|
||||
pub st: Path,
|
||||
pub file: LitStr,
|
||||
}
|
||||
|
||||
/// Per [#96](https://github.com/TedDriggs/darling/issues/96), make sure that an
|
||||
/// attribute which isn't a valid meta gets an error.
|
||||
/// Properties can be split across multiple attributes; this test ensures that one
|
||||
/// non-meta attribute does not interfere with the parsing of other, well-formed attributes.
|
||||
#[test]
|
||||
fn non_meta_attribute_does_not_block_others() {
|
||||
let di = parse_quote! {
|
||||
#[derive(Bar)]
|
||||
#[bar(st = RocketEngine: Debug)]
|
||||
#[bar(file = "motors/example_6.csv")]
|
||||
pub struct EstesC6;
|
||||
};
|
||||
|
||||
let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten();
|
||||
// The number of errors here is 2:
|
||||
// - The parsing error caused by a where-clause body where it doesn't belong
|
||||
// - The missing `st` value because the parsing failure blocked that attribute from
|
||||
// being read.
|
||||
assert_eq!(2, errors.len());
|
||||
}
|
||||
Reference in new issue
Block a user