diff options
author | Martin Geisler <mgeisler@google.com> | 2024-03-21 15:42:53 +0100 |
---|---|---|
committer | Martin Geisler <mgeisler@google.com> | 2024-03-26 17:46:47 +0100 |
commit | 8ddb0abb828ad2993c3a3cd23e0f24aa6f9f736f (patch) | |
tree | def60f9ddc8691c7d7e8d772082afa058d6272bf | |
parent | f9a69249116bbd1ab777f11e807c812e294ce83d (diff) | |
download | darling-8ddb0abb828ad2993c3a3cd23e0f24aa6f9f736f.tar.gz |
Import 'darling' crateupstream
Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
For Build Team: go/ab-third-party-imports
Bug: http://b/328420253
Test: m libdarling
Change-Id: I759dddf9d1306135cef49357590d7853589418ba
69 files changed, 4197 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..df21d32 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "25ce6ecc079c58a18cbecbdcc98bf8d647410edd" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d83adf0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/target +**/target +**/*.rs.bk +Cargo.lock +.vscode +.rls.toml +.DS_STORE +[._]*.sw? +[._]sw? diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..0c9efe3 --- /dev/null +++ b/Android.bp @@ -0,0 +1,609 @@ +// This file is generated by cargo_embargo. +// Do not modify this file as changes will be overridden on upgrade. + +// TODO: Add license. +rust_test_host { + name: "darling_test_tests_accrue_errors", + crate_name: "accrue_errors", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/accrue_errors.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_attrs_with", + crate_name: "attrs_with", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/attrs_with.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_computed_bound", + crate_name: "computed_bound", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/computed_bound.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_custom_bound", + crate_name: "custom_bound", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/custom_bound.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_defaults", + crate_name: "defaults", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/defaults.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_enums_default", + crate_name: "enums_default", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/enums_default.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_enums_newtype", + crate_name: "enums_newtype", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/enums_newtype.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_enums_struct", + crate_name: "enums_struct", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/enums_struct.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_enums_unit", + crate_name: "enums_unit", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/enums_unit.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_error", + crate_name: "error", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/error.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_flatten", + crate_name: "flatten", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/flatten.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_flatten_error_accumulation", + crate_name: "flatten_error_accumulation", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/flatten_error_accumulation.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_flatten_from_field", + crate_name: "flatten_from_field", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/flatten_from_field.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_from_generics", + crate_name: "from_generics", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/from_generics.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_from_meta", + crate_name: "from_meta", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/from_meta.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_from_type_param", + crate_name: "from_type_param", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/from_type_param.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_from_type_param_default", + crate_name: "from_type_param_default", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/from_type_param_default.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_from_variant", + crate_name: "from_variant", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/from_variant.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_generics", + crate_name: "generics", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/generics.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_happy_path", + crate_name: "happy_path", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/happy_path.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_hash_map", + crate_name: "hash_map", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/hash_map.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_multiple", + crate_name: "multiple", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/multiple.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_newtype", + crate_name: "newtype", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/newtype.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_skip", + crate_name: "skip", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/skip.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_split_declaration", + crate_name: "split_declaration", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/split_declaration.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_supports", + crate_name: "supports", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/supports.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_test_host { + name: "darling_test_tests_unsupported_attributes", + crate_name: "unsupported_attributes", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["tests/unsupported_attributes.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libdarling", + "libdarling_core", + "libproc_macro2", + "libquote", + "libsyn", + ], + proc_macros: ["libdarling_macro"], +} + +rust_library_host { + name: "libdarling", + crate_name: "darling", + cargo_env_compat: true, + cargo_pkg_version: "0.20.8", + srcs: ["src/lib.rs"], + edition: "2018", + rustlibs: ["libdarling_core"], + proc_macros: ["libdarling_macro"], + compile_multilib: "first", +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..931a8c0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,303 @@ +# Changelog + +## v0.20.8 (February 23, 2024) + +- Add `#[darling(with = ...)]` support to `attrs` magic field to allow using custom receiver types for `attrs` [#273](https://github.com/TedDriggs/darling/issues/273) + +## v0.20.7 (February 22, 2024) + +- Add `#[darling(flatten)]` to allow forwarding unknown fields to another struct [#146](https://github.com/TedDriggs/darling/issues/146) +- Don't suggest names of skipped fields in derived impls [#268](https://github.com/TedDriggs/darling/issues/268) + +## v0.20.6 (February 14, 2024) + +- Fix some missing syn invisible group handling in `FromMeta` impls [#263](https://github.com/TedDriggs/darling/pull/263) +- Fix misleading error message on `Error::unexpected_type` so it no longer implies the type was a literal [#264](https://github.com/TedDriggs/darling/pull/264) +- Impl `FromMeta` `Vec` of literals, e.g. `LitStr` [#265](https://github.com/TedDriggs/pull/265) + +## v0.20.5 (January 30, 2024) + +- Add `Flag::span` inherent method, as `Flag` can no longer impl `syn::spanned::Spanned` [#242](https://github.com/TedDriggs/darling/issues/242) + +## v0.20.4 (January 29, 2024) + +- Accept bare paths in `#[darling(default = ...)]` [#258](https://github.com/TedDriggs/darling/pull/258) +- Add `FromMeta` impl for `PathBuf` [#259](https://github.com/TedDriggs/darling/pull/259) +- Improve `FromMeta` implementation for enums [#260](https://github.com/TedDriggs/darling/pull/260) + - Properly implement unit arms in `FromMeta::from_list` to provide a consistent API for heterogeneous enums that include a mix of unit, newtype and struct variants + - Add `#[darling(word)]` attribute for unit enum variants (See [#63](https://github.com/TedDriggs/darling/issues/63) for details) + +## v0.20.3 (July 12, 2023) + +- Add `FromMeta` impl for `u128` and `i128` [#243](https://github.com/TedDriggs/darling/pull/243) + +## v0.20.2 (May 25, 2023) + +- Allow darling users to omit quotation marks for paths and idents [#236](https://github.com/TedDriggs/darling/pull/236) +- Add new util functions for controlling how quotation marks are handled when reading into `Expr` fields [#235](https://github.com/TedDriggs/darling/pull/235) + +## v0.20.1 (May 2, 2023) + +- Add `Clone` impl for `NestedMeta` [#230](https://github.com/TedDriggs/darling/pull/230) + +## v0.20.0 (April 27, 2023) + +- Bump syn to version 2, courtesy of @jonasbb [#227](https://github.com/TedDriggs/darling/issues/227) + +### Breaking Changes + +- Replace all occurrences of syn::NestedMeta with darling::ast::NestedMeta. + +- Replacement for the deprecated AttributeArgs: + +```rust +// Before + +parse_macro_input!(args as AttributeArgs); + +// After + +match NestedMeta::parse_meta_list(args) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(Error::from(e).write_errors()); + } +}; +``` + +- In GenericParamExt, `LifetimeDef` is now `LifetimeParam`. +- In GenericParamExt, `as_lifetime_def` is renamed to `as_lifetime_param`. +- Flag and SpannedValue no longer implement `syn::spanned::Spanned`. +- The MSRV (minimum supported Rust version) is now 1.56, because of syn. + +### Deprecation Warnings + +In previous versions of `darling`, arbitrary expressions were passed in attributes by wrapping them in quotation marks. +v0.20.0 preserves this behavior for `syn::Expr`, but as a result a field expecting a `syn::Expr` cannot accept a string literal - it will incorrectly attempt to parse the contents. If this is an issue for you, please add a comment to [#229](https://github.com/TedDriggs/darling/issues/229). + +## v0.14.4 (March 9, 2023) + +- Add support for child diagnostics when `diagnostics` feature enabled [#224](https://github.com/TedDriggs/darling/issues/224) + +## v0.14.3 (February 3, 2023) + +- Re-export `syn` from `darling` to avoid requiring that consuming crates have a `syn` dependency. +- Change `<SpannedValue<T> as FromMeta>` impl to more precisely capture the _value_ span, as opposed to the span of the entire item. +- Add `darling::util::{AsShape, Shape, ShapeSet}` to improve "shape" validation for structs and variants. [#222](https://github.com/TedDriggs/issues/222) + +## v0.14.2 (October 26, 2022) + +- Derived impls of `FromMeta` will now error on literals, rather than silently ignoring them. [#193](https://github.com/TedDriggs/darling/pull/193) +- Don't include property paths in compile errors when spans are available. [#203](https://github.com/TedDriggs/darling/pull/203) + +## v0.14.1 (April 28, 2022) + +- Fix a bug where using a trait that accepts `#[darling(attributes(...))]` without specifying any attributes would emit code that did not compile. [#183](https://github.com/TedDriggs/darling/issues/183) +- Impl `Clone` for `darling::Error` [#184](https://github.com/TedDriggs/darling/pull/184) +- Impl `From<darling::Error> for syn::Error` [#184](https://github.com/TedDriggs/darling/pull/184) +- Add `Error::span` and `Error::explicit_span` methods [#184](https://github.com/TedDriggs/darling/pull/184) + +## v0.14.0 (April 13, 2022) + +- **BREAKING CHANGE:** Remove many trait impls from `util::Flag`. + This type had a number of deref and operator impls that made it usable as sort-of-a-boolean. + Real-world usage showed this type is more useful if it's able to carry a span for good errors, + and that most of those impls were unnecessary. [#179](https://github.com/TedDriggs/darling/pull/179) +- Remove need for `#[darling(default)]` on `Option<T>` and `Flag` fields [#161](https://github.com/TedDriggs/darling/issues/161) +- Improve validation of enum shapes [#178](https://github.com/TedDriggs/darling/pull/178) +- Bump `proc-macro2` dependency to 1.0.37 [#180](https://github.com/TedDriggs/darling/pull/180) +- Bump `quote` dependency to 1.0.18 [#180](https://github.com/TedDriggs/darling/pull/180) +- Bump `syn` dependency to 1.0.91 [#180](https://github.com/TedDriggs/darling/pull/180) + +## v0.13.4 (April 6, 2022) + +- Impl `FromMeta` for `syn::Visibility` [#173](https://github.com/TedDriggs/darling/pull/173) + +## v0.13.3 (April 5, 2022) + +- Add `error::Accumulator` for dealing with multiple errors [#164](https://github.com/TedDriggs/darling/pull/164) +- Impl `FromMeta` for `syn::Type` and its variants [#172](https://github.com/TedDriggs/darling/pulls/172) + +## v0.13.2 (March 30, 2022) + +- Impl `FromMeta` for `syn::ExprPath` [#169](https://github.com/TedDriggs/darling/issues/169) + +## v0.13.1 (December 7, 2021) + +- Add `FromAttributes` trait and macro [#151](https://github.com/TedDriggs/darling/issues/151) + +## v0.13.0 (May 20, 2021) + +- Update darling to 2018 edition [#129](https://github.com/TedDriggs/darling/pull/129) +- Error on duplicate fields in `#[darling(...)]` attributes [#130](https://github.com/TedDriggs/darling/pull/130) +- Impl `Copy` for `SpannedValue<T: Copy>` +- Add `SpannedValue::map_ref` + +## v0.13.0-beta (April 20, 2021) + +- Update darling to 2018 edition [#129](https://github.com/TedDriggs/darling/pull/129) +- Error on duplicate fields in `#[darling(...)]` attributes [#130](https://github.com/TedDriggs/darling/pull/130) + +## v0.12.4 (April 20, 2021) + +- Add `and_then` to derive macros for `darling` + +## v0.12.3 (April 8, 2021) + +- Fix `FromMeta` impl for `char` not to panic [#126](https://github.com/TedDriggs/darling/pull/126) + +## v0.12.2 (February 23, 2021) + +- Impl `FromMeta` for `HashMap<Ident, V>` and `HashMap<Path, V>` + +## v0.12.1 (February 22, 2021) + +- Impl `FromMeta` for `syn::ExprArray` [#122](https://github.com/TedDriggs/darling/pull/122) +- Remove use of `unreachable` from `darling::ast::Data` [#123](https://github.com/TedDriggs/darling/pull/123) +- Add `darling::ast::Data::try_empty_from` to avoid panics when trying to read a union body [#123](https://github.com/TedDriggs/darling/pull/123) + +## v0.12.0 (January 5, 2021) + +- POSSIBLY BREAKING: Derived impls of `FromDeriveInput`, `FromField`, `FromVariant`, and `FromTypeParam` will now error when encountering an attribute `darling` has been asked to parse that isn't a supported shape. + Any crates using `darling` that relied on those attributes being silently ignored could see new errors reported in their dependent crates. [#113](https://github.com/TedDriggs/darling/pull/113) +- Impl `syn::spanned::Spanned` for `darling::util::SpannedValue` [#113](https://github.com/TedDriggs/darling/pull/113) +- Add `darling::util::parse_attribute_to_meta_list` to provide useful errors during attribute parsing [#113](https://github.com/TedDriggs/darling/pull/113) +- Add `impl From<syn::Error> for Error` to losslessly propagate `syn` errors [#116](https://github.com/TedDriggs/darling/pull/116) + +## v0.11.0 (December 14, 2020) + +- Bump minor version due to unexpected breaking change [#107](https://github.com/TedDriggs/darling/issues/107) + +## v0.10.3 (December 10, 2020) + +- Add `discriminant` magic field when deriving `FromVariant` [#105](https://github.com/TedDriggs/darling/pull/105) + +## v0.10.2 (October 30, 2019) + +- Bump syn dependency to 1.0.1 [#83](https://github.com/TedDriggs/darling/pull/83) + +## v0.10.1 (September 25, 2019) + +- Fix test compilation errors [#81](https://github.com/TedDriggs/darling/pull/81) + +## v0.10.0 (August 15, 2019) + +- Bump syn and quote to 1.0 [#79](https://github.com/TedDriggs/darling/pull/79) +- Increase rust version to 1.31 + +## v0.9.0 (March 20, 2019) + +- Enable "did you mean" suggestions by default +- Make `darling_core::{codegen, options}` private [#58](https://github.com/TedDriggs/darling/issues/58) +- Fix `Override::as_mut`: [#66](https://github.com/TedDriggs/darling/issues/66) + +## v0.8.6 (March 18, 2019) + +- Added "did you mean" suggestions for unknown fields behind the `suggestions` flag [#60](https://github.com/TedDriggs/issues/60) +- Added `Error::unknown_field_with_alts` to support the suggestion use-case. +- Added `ast::Fields::len` and `ast::Fields::is_empty` methods. + +## v0.8.5 (February 4, 2019) + +- Accept unquoted positive numeric literals [#52](https://github.com/TedDriggs/issues/52) +- Add `FromMeta` to the `syn::Lit` enum and its variants +- Improve error message for unexpected literal formats to not say "other" + +## v0.8.4 (February 4, 2019) + +- Use `syn::Error` to provide precise errors before `proc_macro::Diagnostic` is available +- Add `diagnostics` feature flag to toggle between stable and unstable error backends +- Attach error information in more contexts +- Add `allow_unknown_fields` to support parsing the same attribute multiple times for different macros [#51](https://github.com/darling/issues/51) +- Proc-macro authors will now see better errors in `darling` attributes + +## v0.8.3 (January 21, 2019) + +- Attach spans to errors in generated trait impls [#37](https://github.com/darling/issues/37) +- Attach spans to errors for types with provided bespoke implementations +- Deprecate `set_span` from 0.8.2, as spans should never be broadened after being initially set + +## v0.8.2 (January 17, 2019) + +- Add spans to errors to make quality warnings and errors easy in darling. This is blocked on diagnostics stabilizing. +- Add `darling::util::SpannedValue` so proc-macro authors can remember position information alongside parsed values. + +## v0.8.0 + +- Update dependency on `syn` to 0.15 [#44](https://github.com/darling/pull/44). Thanks to @hcpl + +## v0.7.0 (July 24, 2018) + +- Update dependencies on `syn` and `proc-macro2` +- Add `util::IdentString`, which acts as an Ident or its string equivalent + +## v0.6.3 (May 22, 2018) + +- Add support for `Uses*` traits in where predicates + +## v0.6.2 (May 22, 2018) + +- Add `usage` module for tracking type param and lifetime usage in generic declarations + - Add `UsesTypeParams` and `CollectsTypeParams` traits [#37](https://github.com/darling/issues/37) + - Add `UsesLifetimes` and `CollectLifetimes` traits [#41](https://github.com/darling/pull/41) +- Don't add `FromMeta` bounds to type parameters only used by skipped fields [#40](https://github.com/darling/pull/40) + +## v0.6.1 (May 17, 2018) + +- Fix an issue where the `syn` update broke shape validation [#36](https://github.com/TedDriggs/darling/issues/36) + +## v0.6.0 (May 15, 2018) + +### Breaking Changes + +- Renamed `FromMetaItem` to `FromMeta`, and renamed `from_meta_item` method to `from_meta` +- Added dedicated `derive(FromMetaItem)` which panics and redirects users to `FromMeta` + +## v0.5.0 (May 10, 2018) + +- Add `ast::Generics` and `ast::GenericParam` to work with generics in a manner similar to `ast::Data` +- Add `ast::GenericParamExt` to support alternate representations of generic parameters +- Add `util::WithOriginal` to get a parsed representation and syn's own struct for a syntax block +- Add `FromGenerics` and `FromGenericParam` traits (without derive support) +- Change generated code for `generics` magic field to invoke `FromGenerics` trait during parsing +- Add `FromTypeParam` trait [#30](https://github.com/TedDriggs/darling/pull/30). Thanks to @upsuper + +## v0.4.0 (April 5, 2018) + +- Update dependencies on `proc-macro`, `quote`, and `syn` [#26](https://github.com/TedDriggs/darling/pull/26). Thanks to @hcpl + +## v0.3.3 (April 2, 2018) + +**YANKED** + +## v0.3.2 (March 13, 2018) + +- Derive `Default` on `darling::Ignored` (fixes [#25](https://github.com/TedDriggs/darling/issues/25)). + +## v0.3.1 (March 7, 2018) + +- Support proc-macro2/nightly [#24](https://github.com/TedDriggs/darling/pull/24). Thanks to @kdy1 + +## v0.3.0 (January 26, 2018) + +### Breaking Changes + +- Update `syn` to 0.12 [#20](https://github.com/TedDriggs/darling/pull/20). Thanks to @Eijebong +- Update `quote` to 0.4 [#20](https://github.com/TedDriggs/darling/pull/20). Thanks to @Eijebong +- Rename magic field `body` in derived `FromDeriveInput` structs to `data` to stay in sync with `syn` +- Rename magic field `data` in derived `FromVariant` structs to `fields` to stay in sync with `syn` + +## v0.2.2 (December 5, 2017) + +- Update `lazy_static` to 1.0 [#15](https://github.com/TedDriggs/darling/pull/16). Thanks to @Eijebong + +## v0.2.1 (November 28, 2017) + +- Add `impl FromMetaItem` for integer types [#15](https://github.com/TedDriggs/darling/pull/15) + +## v0.2.0 (June 18, 2017) + +- Added support for returning multiple errors from parsing [#5](https://github.com/TedDriggs/darling/pull/5) +- Derived impls no longer return on first error [#5](https://github.com/TedDriggs/darling/pull/5) +- Removed default types for `V` and `F` from `ast::Body` +- Enum variants are automatically converted to snake_case [#12](https://github.com/TedDriggs/darling/pull/12) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..61a0c60 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,58 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "darling" +version = "0.20.8" +authors = ["Ted Driggs <ted.driggs@outlook.com>"] +exclude = [ + "/.travis.yml", + "/publish.sh", + "/.github/**", +] +description = """ +A proc-macro library for reading attributes into structs when +implementing custom derives. +""" +documentation = "https://docs.rs/darling/0.20.8" +readme = "README.md" +license = "MIT" +repository = "https://github.com/TedDriggs/darling" + +[dependencies.darling_core] +version = "=0.20.8" + +[dependencies.darling_macro] +version = "=0.20.8" + +[dev-dependencies.proc-macro2] +version = "1.0.37" + +[dev-dependencies.quote] +version = "1.0.18" + +[dev-dependencies.syn] +version = "2.0.15" + +[features] +default = ["suggestions"] +diagnostics = ["darling_core/diagnostics"] +suggestions = ["darling_core/suggestions"] + +[target."cfg(compiletests)".dev-dependencies.rustversion] +version = "1.0.9" + +[target."cfg(compiletests)".dev-dependencies.trybuild] +version = "1.0.38" + +[badges.maintenance] +status = "actively-developed" @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Ted Driggs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..74f306c --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "darling" +description: "()" +third_party { + identifier { + type: "crates.io" + value: "darling" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/darling/darling-0.20.8.crate" + primary_source: true + } + version: "0.20.8" + license_type: NOTICE + last_upgrade_date { + year: 2024 + month: 3 + day: 6 + } +} diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MIT @@ -0,0 +1,2 @@ +# Bug component: 688011 +include platform/prebuilts/rust:main:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..023e8ad --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# Darling + +[![Build Status](https://github.com/TedDriggs/darling/workflows/CI/badge.svg)](https://github.com/TedDriggs/darling/actions) +[![Latest Version](https://img.shields.io/crates/v/darling.svg)](https://crates.io/crates/darling) +[![Rustc Version 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)] + +`darling` is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by `serde` both in its internals and in its API. + +# Benefits + +- Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment. +- Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields. + +# Usage + +`darling` provides a set of traits which can be derived or manually implemented. + +1. `FromMeta` is used to extract values from a meta-item in an attribute. Implementations are likely reusable for many libraries, much like `FromStr` or `serde::Deserialize`. Trait implementations are provided for primitives, some std types, and some `syn` types. +2. `FromDeriveInput` is implemented or derived by each proc-macro crate which depends on `darling`. This is the root for input parsing; it gets access to the identity, generics, and visibility of the target type, and can specify which attribute names should be parsed or forwarded from the input AST. +3. `FromField` is implemented or derived by each proc-macro crate which depends on `darling`. Structs deriving this trait will get access to the identity (if it exists), type, and visibility of the field. +4. `FromVariant` is implemented or derived by each proc-macro crate which depends on `darling`. Structs deriving this trait will get access to the identity and contents of the variant, which can be transformed the same as any other `darling` input. +5. `FromAttributes` is a lower-level version of the more-specific `FromDeriveInput`, `FromField`, and `FromVariant` traits. Structs deriving this trait get a meta-item extractor and error collection which works for any syntax element, including traits, trait items, and functions. This is useful for non-derive proc macros. + +## Additional Modules + +- `darling::ast` provides generic types for representing the AST. +- `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum. +- `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`. + +# Example + +```rust,ignore +#[macro_use] +extern crate darling; +extern crate syn; + +#[derive(Default, FromMeta)] +#[darling(default)] +pub struct Lorem { + #[darling(rename = "sit")] + ipsum: bool, + dolor: Option<String>, +} + +#[derive(FromDeriveInput)] +#[darling(attributes(my_crate), forward_attrs(allow, doc, cfg))] +pub struct MyTraitOpts { + ident: syn::Ident, + attrs: Vec<syn::Attribute>, + lorem: Lorem, +} +``` + +The above code will then be able to parse this input: + +```rust,ignore +/// A doc comment which will be available in `MyTraitOpts::attrs`. +#[derive(MyTrait)] +#[my_crate(lorem(dolor = "Hello", sit))] +pub struct ConsumingType; +``` + +# Attribute Macros + +Non-derive attribute macros are supported. +To parse arguments for attribute macros, derive `FromMeta` on the argument receiver type, then pass `&syn::AttributeArgs` to the `from_list` method. +This will produce a normal `darling::Result<T>` that can be used the same as a result from parsing a `DeriveInput`. + +## Macro Code + +```rust,ignore +use darling::{Error, FromMeta}; +use darling::ast::NestedMeta; +use syn::ItemFn; +use proc_macro::TokenStream; + +#[derive(Debug, FromMeta)] +struct MacroArgs { + #[darling(default)] + timeout_ms: Option<u16>, + path: String, +} + +#[proc_macro_attribute] +pub fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream { + let attr_args = match NestedMeta::parse_meta_list(args.into()) { + Ok(v) => v, + Err(e) => { return TokenStream::from(Error::from(e).write_errors()); } + }; + let _input = syn::parse_macro_input!(input as ItemFn); + + let _args = match MacroArgs::from_list(&attr_args) { + Ok(v) => v, + Err(e) => { return TokenStream::from(e.write_errors()); } + }; + + // do things with `args` + unimplemented!() +} +``` + +## Consuming Code + +```rust,ignore +use your_crate::your_attr; + +#[your_attr(path = "hello", timeout_ms = 15)] +fn do_stuff() { + println!("Hello"); +} +``` + +# Features + +Darling's features are built to work well for real-world projects. + +- **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`. + Additionally, `Option<T>` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those. +- **Field Renaming**: Fields can have different names in usage vs. the backing code. +- **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`. + - **Transformation of forwarded attributes**: You can add `#[darling(with=path)]` to the `attrs` field to use a custom function to transform the forwarded attributes before they're provided to your struct. The function signature is `fn(Vec<Attribute>) -> darling::Result<T>`, where `T` is the type you declared for the `attrs` field. Returning an error from this function will propagate with all other parsing errors. +- **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct. +- **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items. +- **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`. +- **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases. +- **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields. +- **Struct flattening**: Use `#[darling(flatten)]` to remove one level of structure when presenting your meta item to users. Fields that are not known to the parent struct will be forwarded to the `flatten` field. + +## Shape Validation + +Some proc-macros only work on structs, while others need enums whose variants are either unit or newtype variants. +Darling makes this sort of validation extremely simple. +On the receiver that derives `FromDeriveInput`, add `#[darling(supports(...))]` and then list the shapes that your macro should accept. + +| Name | Description | +| ---------------- | ------------------------------------------------------------------------- | +| `any` | Accept anything | +| `struct_any` | Accept any struct | +| `struct_named` | Accept structs with named fields, e.g. `struct Example { field: String }` | +| `struct_newtype` | Accept newtype structs, e.g. `struct Example(String)` | +| `struct_tuple` | Accept tuple structs, e.g. `struct Example(String, String)` | +| `struct_unit` | Accept unit structs, e.g. `struct Example;` | +| `enum_any` | Accept any enum | +| `enum_named` | Accept enum variants with named fields | +| `enum_newtype` | Accept newtype enum variants | +| `enum_tuple` | Accept tuple enum variants | +| `enum_unit` | Accept unit enum variants | + +Each one is additive, so listing `#[darling(supports(struct_any, enum_newtype))]` would accept all structs and any enum where every variant is a newtype variant. + +This can also be used when deriving `FromVariant`, without the `enum_` prefix. diff --git a/cargo_embargo.json b/cargo_embargo.json new file mode 100644 index 0000000..ef024dc --- /dev/null +++ b/cargo_embargo.json @@ -0,0 +1,10 @@ +{ + "tests": true, + "features": [], + "package": { + "darling": { + "device_supported": false, + "host_first_multilib": true + } + } +} diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..e221e79 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +msrv = "1.56.0" +disallowed-names = [] # we want to be able to use placeholder names in tests
\ No newline at end of file diff --git a/compiletests.sh b/compiletests.sh new file mode 100755 index 0000000..a8ce042 --- /dev/null +++ b/compiletests.sh @@ -0,0 +1 @@ +RUSTFLAGS="--cfg=compiletests" cargo +1.65.0 test --test compiletests
\ No newline at end of file diff --git a/examples/automatic_bounds.rs b/examples/automatic_bounds.rs new file mode 100644 index 0000000..8312afe --- /dev/null +++ b/examples/automatic_bounds.rs @@ -0,0 +1,73 @@ +use darling::{FromDeriveInput, FromMeta}; + +#[derive(FromMeta, PartialEq, Eq, Debug)] +enum Volume { + Whisper, + Talk, + Shout, +} + +/// A more complex example showing the ability to skip at a field or struct +/// level while still tracking which type parameters need to be bounded. +/// This can be seen by expanding this example using `cargo expand`. +#[derive(FromMeta)] +#[allow(dead_code)] +enum Emphasis<T> { + Constant(Volume), + Variable(darling::util::PathList), + #[darling(skip)] + PerPhoneme(Option<T>), + Strided { + #[darling(skip)] + step: Vec<T>, + #[darling(multiple)] + volume: Vec<Volume>, + }, +} + +#[derive(FromDeriveInput)] +#[darling(attributes(speak))] +struct SpeakingOptions<T, U> { + max_volume: U, + #[darling(skip, default)] + additional_data: Vec<T>, +} + +#[derive(Default)] +struct Phoneme { + #[allow(dead_code)] + first: String, +} + +// This is probably the holy grail for `darling`'s own internal use-case: +// Auto-apply `Default` bound to skipped *field* types in `where` clause. +impl<T, U> Default for SpeakingOptions<T, U> +where + Vec<T>: Default, + U: Default, +{ + fn default() -> Self { + Self { + max_volume: Default::default(), + additional_data: Default::default(), + } + } +} + +fn main() { + let derive_input = syn::parse_str( + r#" + #[derive(Speak)] + #[speak(max_volume = "shout")] + enum HtmlElement { + Div(String) + } + "#, + ) + .unwrap(); + + let parsed: SpeakingOptions<Phoneme, Volume> = + FromDeriveInput::from_derive_input(&derive_input).unwrap(); + assert_eq!(parsed.max_volume, Volume::Shout); + assert_eq!(parsed.additional_data.len(), 0); +} diff --git a/examples/consume_fields.rs b/examples/consume_fields.rs new file mode 100644 index 0000000..f5cd435 --- /dev/null +++ b/examples/consume_fields.rs @@ -0,0 +1,175 @@ +// 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)] + +//! This example shows how to do struct and field parsing using darling. + +use darling::{ast, FromDeriveInput, FromField, FromMeta}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse_str; + +/// A speaking volume. Deriving `FromMeta` will cause this to be usable +/// as a string value for a meta-item key. +#[derive(Debug, Clone, Copy, FromMeta)] +#[darling(default)] +enum Volume { + Normal, + Whisper, + Shout, +} + +impl Default for Volume { + fn default() -> Self { + Volume::Normal + } +} + +/// Support parsing from a full derive input. Unlike FromMeta, this isn't +/// composable; each darling-dependent crate should have its own struct to handle +/// when its trait is derived. +#[derive(Debug, FromDeriveInput)] +// This line says that we want to process all attributes declared with `my_trait`, +// and that darling should panic if this receiver is given an enum. +#[darling(attributes(my_trait), supports(struct_any))] +struct MyInputReceiver { + /// The struct ident. + ident: syn::Ident, + + /// The type's generics. You'll need these any time your trait is expected + /// to work with types that declare generics. + generics: syn::Generics, + + /// Receives the body of the struct or enum. We don't care about + /// struct fields because we previously told darling we only accept structs. + data: ast::Data<(), MyFieldReceiver>, + + /// The Input Receiver demands a volume, so use `Volume::Normal` if the + /// caller doesn't provide one. + #[darling(default)] + volume: Volume, +} + +impl ToTokens for MyInputReceiver { + fn to_tokens(&self, tokens: &mut TokenStream) { + let MyInputReceiver { + ref ident, + ref generics, + ref data, + volume, + } = *self; + + let (imp, ty, wher) = generics.split_for_impl(); + let fields = data + .as_ref() + .take_struct() + .expect("Should never be enum") + .fields; + + // Generate the format string which shows each field and its name + let fmt_string = fields + .iter() + .enumerate() + .map(|(i, f)| { + // We have to preformat the ident in this case so we can fall back + // to the field index for unnamed fields. It's not easy to read, + // unfortunately. + format!( + "{} = {{}}", + f.ident + .as_ref() + .map(|v| format!("{}", v)) + .unwrap_or_else(|| format!("{}", i)) + ) + }) + .collect::<Vec<_>>() + .join(", "); + + // Generate the actual values to fill the format string. + let field_list = fields + .into_iter() + .enumerate() + .map(|(i, f)| { + let field_volume = f.volume.unwrap_or(volume); + + // This works with named or indexed fields, so we'll fall back to the index so we can + // write the output as a key-value pair. + let field_ident = f.ident + .as_ref() + .map(|v| quote!(#v)) + .unwrap_or_else(|| { + let i = syn::Index::from(i); + quote!(#i) + }); + + match field_volume { + Volume::Normal => quote!(self.#field_ident), + Volume::Shout => { + quote!(::std::string::ToString::to_string(&self.#field_ident).to_uppercase()) + } + Volume::Whisper => { + quote!(::std::string::ToString::to_string(&self.#field_ident).to_lowercase()) + } + } + }) + .collect::<Vec<_>>(); + + tokens.extend(quote! { + impl #imp Speak for #ident #ty #wher { + fn speak(&self, writer: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(writer, #fmt_string, #(#field_list),*) + } + } + }); + } +} + +#[derive(Debug, FromField)] +#[darling(attributes(my_trait))] +struct MyFieldReceiver { + /// Get the ident of the field. For fields in tuple or newtype structs or + /// enum bodies, this can be `None`. + ident: Option<syn::Ident>, + + /// This magic field name pulls the type from the input. + ty: syn::Type, + + /// We declare this as an `Option` so that during tokenization we can write + /// `field.volume.unwrap_or(derive_input.volume)` to facilitate field-level + /// overrides of struct-level settings. + /// + /// Because this field is an `Option`, we don't need to include `#[darling(default)]` + volume: Option<Volume>, +} + +fn main() { + let input = r#"#[derive(MyTrait)] +#[my_trait(volume = "shout")] +pub struct Foo { + #[my_trait(volume = "whisper")] + bar: bool, + + baz: i64, +}"#; + + let parsed = parse_str(input).unwrap(); + let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap(); + let tokens = quote!(#receiver); + + println!( + r#" +INPUT: + +{} + +PARSED AS: + +{:?} + +EMITS: + +{} + "#, + input, receiver, tokens + ); +} diff --git a/examples/expr_with.rs b/examples/expr_with.rs new file mode 100644 index 0000000..037ce25 --- /dev/null +++ b/examples/expr_with.rs @@ -0,0 +1,19 @@ +use darling::{util::parse_expr, FromDeriveInput}; +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>, +} + +fn main() { + let input = Receiver::from_derive_input(&parse_quote! { + #[demo(example1 = test::path)] + struct Example; + }) + .unwrap(); + + assert_eq!(input.example1, Some(parse_quote!(test::path))); +} diff --git a/examples/fallible_read.rs b/examples/fallible_read.rs new file mode 100644 index 0000000..848c3d2 --- /dev/null +++ b/examples/fallible_read.rs @@ -0,0 +1,85 @@ +//! This example demonstrates techniques for performing custom error handling +//! in a derive-input receiver. +//! +//! 1. Using `darling::Result` as a carrier to preserve the error for later display +//! 1. Using `Result<T, syn::Meta>` to attempt a recovery in imperative code +//! 1. Using the `map` darling meta-item to post-process a field before returning +//! 1. Using the `and_then` darling meta-item to post-process the receiver before returning + +use darling::{FromDeriveInput, FromMeta}; +use syn::parse_str; + +#[derive(Debug, FromDeriveInput)] +#[darling(attributes(my_trait), and_then = MyInputReceiver::autocorrect)] +pub struct MyInputReceiver { + /// This field must be present and a string or else parsing will panic. + #[darling(map = MyInputReceiver::make_string_shouty)] + name: String, + + /// If this field fails to parse, the struct can still be built; the field + /// will contain the error. The consuming struct can then decide if this + /// blocks code generation. If so, panic or fail in `and_then`. + frequency: darling::Result<i64>, + + /// If this field fails to parse, the struct can still be built; the field + /// will contain an `Err` with the original `syn::Meta`. This can be used + /// for alternate parsing attempts before panicking. + amplitude: Result<u64, syn::Meta>, +} + +impl MyInputReceiver { + /// This function will be called by `darling` _after_ it's finished parsing the + /// `name` field but before initializing `name` with the resulting value. It's + /// a good place for transforms that are easiest to express on already-built + /// types. + fn make_string_shouty(s: String) -> String { + s.to_uppercase() + } + + /// This function will be called by `darling` _after_ it's finished parsing the + /// input but before returning to the caller. This is a good place to initialize + /// skipped fields or to perform corrections that don't lend themselves to being + /// done elsewhere. + fn autocorrect(self) -> darling::Result<Self> { + let Self { + name, + frequency, + amplitude, + } = self; + + // Amplitude doesn't have a sign, so if we received a negative number then + // we'll go ahead and make it positive. + let amplitude = match amplitude { + Ok(amp) => amp, + Err(mi) => (i64::from_meta(&mi)?).unsigned_abs(), + }; + + Ok(Self { + name, + frequency, + amplitude: Ok(amplitude), + }) + } +} + +fn main() { + let input = r#"#[derive(MyTrait)] +#[my_trait(name="Jon", amplitude = "-1", frequency = 1)] +pub struct Foo;"#; + + let parsed = parse_str(input).unwrap(); + let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap(); + + println!( + r#" +INPUT: + +{} + +PARSED AS: + +{:?} + "#, + input, receiver + ); +} diff --git a/examples/heterogeneous_enum_and_word.rs b/examples/heterogeneous_enum_and_word.rs new file mode 100644 index 0000000..029549f --- /dev/null +++ b/examples/heterogeneous_enum_and_word.rs @@ -0,0 +1,79 @@ +//! This example demonstrates: +//! +//! - The behavior of a derived `FromMeta` implementation for heterogeneous enums +//! (i.e. enums that include a mix of unit, newtype and struct variants). +//! - Using `#[darling(word)]` to specify a unit variant to use when a receiver field +//! is specified without a value (i.e. a unit variant to use for deriving the +//! `FromMeta::from_word` method). +//! - Using `#[darling(default)]` on a receiver field to fall back to `Default::default()` +//! for the enum's value when the receiver field is not specified by the caller. + +use darling::{Error, FromDeriveInput, FromMeta}; +use syn::parse_quote; + +/// A playback volume. +#[derive(Debug, FromMeta, PartialEq, Eq)] +enum Volume { + Normal, + #[darling(word)] + Low, + High, + #[darling(rename = "dB")] + Decibels(u8), +} + +impl Default for Volume { + fn default() -> Self { + Volume::Normal + } +} + +#[derive(Debug, FromDeriveInput)] +#[darling(attributes(play))] +struct PlayReceiver { + #[darling(default)] + volume: Volume, +} + +fn main() { + // `Default::default()` is used when `volume` is not specified. + let missing_volume = PlayReceiver::from_derive_input(&parse_quote! { + #[play] + struct Player; + }) + .unwrap(); + assert_eq!(Volume::Normal, missing_volume.volume); + + // `#[darling(word)]` unit variant is used when `volume` is specified as a word with no value. + let empty_volume = PlayReceiver::from_derive_input(&parse_quote! { + #[play(volume)] + struct Player; + }) + .unwrap(); + assert_eq!(Volume::Low, empty_volume.volume); + + // Specified `volume` value is used when provided. + let unit_variant_volume = PlayReceiver::from_derive_input(&parse_quote! { + #[play(volume(high))] + struct Player; + }) + .unwrap(); + assert_eq!(Volume::High, unit_variant_volume.volume); + let newtype_volume = PlayReceiver::from_derive_input(&parse_quote! { + #[play(volume(dB = 100))] + struct Player; + }) + .unwrap(); + assert_eq!(Volume::Decibels(100), newtype_volume.volume); + + // Multiple `volume` values result in an error. + let err = PlayReceiver::from_derive_input(&parse_quote! { + #[play(volume(low, dB = 20))] + struct Player; + }) + .unwrap_err(); + assert_eq!( + err.to_string(), + Error::too_many_items(1).at("volume").to_string() + ); +} diff --git a/examples/shorthand_or_long_field.rs b/examples/shorthand_or_long_field.rs new file mode 100644 index 0000000..750d83e --- /dev/null +++ b/examples/shorthand_or_long_field.rs @@ -0,0 +1,79 @@ +//! Example showing potentially-nested meta item parsing with `darling::util::Override`. +//! +//! Based on https://stackoverflow.com/q/68046070/86381 by https://github.com/peterjoel + +// 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 std::borrow::Cow; + +use darling::{util::Override, FromDeriveInput, FromMeta}; +use syn::{Ident, Path}; + +#[derive(Debug, FromDeriveInput)] +#[darling(attributes(myderive))] +struct MyDeriveInput { + ident: Ident, + /// We can infer the right "table" behavior for this derive, but we want the caller to be + /// explicit that they're expecting the inference behavior to avoid cluttering some hypothetical + /// database. Therefore this field is required, but can take word form or key-value form. + /// + /// To make this field optional, we could add `#[darling(default)]`, or we could + /// wrap it in `Option` if the presence or absence of the word makes a difference. + table: Override<Table>, +} + +impl MyDeriveInput { + fn table(&self) -> Cow<'_, Table> { + match &self.table { + Override::Explicit(value) => Cow::Borrowed(value), + Override::Inherit => Cow::Owned(Table { + name: self.ident.to_string(), + value: None, + }), + } + } +} + +#[derive(Debug, Clone, FromMeta)] +struct Table { + name: String, + value: Option<Path>, +} + +fn from_str(s: &str) -> darling::Result<MyDeriveInput> { + FromDeriveInput::from_derive_input(&syn::parse_str(s)?) +} + +fn main() { + let missing = from_str( + r#" + #[derive(MyTrait)] + struct Foo(u64); + "#, + ) + .unwrap_err(); + + let short_form = from_str( + r#" + #[derive(MyTrait)] + #[myderive(table)] + struct Foo(u64); + "#, + ) + .unwrap(); + + let long_form = from_str( + r#" + #[derive(MyTrait)] + #[myderive(table(name = "Custom"))] + struct Foo(u64); + "#, + ) + .unwrap(); + + println!("Error when missing: {}", missing); + println!("Short form: {:?}", short_form.table()); + println!("Long form: {:?}", long_form.table()); +} diff --git a/examples/supports_struct.rs b/examples/supports_struct.rs new file mode 100644 index 0000000..97a5bb9 --- /dev/null +++ b/examples/supports_struct.rs @@ -0,0 +1,61 @@ +// 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::{ast, util, FromDeriveInput, FromField}; +use syn::{Ident, Type}; + +#[derive(Debug, FromField)] +#[darling(attributes(lorem))] +pub struct LoremField { + ident: Option<Ident>, + ty: Type, + #[darling(default)] + skip: bool, +} + +#[derive(Debug, FromDeriveInput)] +#[darling(attributes(lorem), supports(struct_named))] +pub struct Lorem { + ident: Ident, + data: ast::Data<util::Ignored, LoremField>, +} + +fn main() { + let good_input = r#"#[derive(Lorem)] +pub struct Foo { + #[lorem(skip)] + bar: bool, + + baz: i64, +}"#; + + let bad_input = r#"#[derive(Lorem)] + pub struct BadFoo(String, u32);"#; + + let parsed = syn::parse_str(good_input).unwrap(); + let receiver = Lorem::from_derive_input(&parsed).unwrap(); + let wrong_shape_parsed = syn::parse_str(bad_input).unwrap(); + let wrong_shape = Lorem::from_derive_input(&wrong_shape_parsed).expect_err("Shape was wrong"); + + println!( + r#" +INPUT: + +{} + +PARSED AS: + +{:?} + +BAD INPUT: + +{} + +PRODUCED ERROR: + +{} + "#, + good_input, receiver, bad_input, wrong_shape + ); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..657a6ff --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,110 @@ +//! # Darling +//! Darling is a tool for declarative attribute parsing in proc macro implementations. +//! +//! +//! ## Design +//! Darling takes considerable design inspiration from [`serde`](https://serde.rs). A data structure that can be +//! read from any attribute implements `FromMeta` (or has an implementation automatically +//! generated using `derive`). Any crate can provide `FromMeta` implementations, even one not +//! specifically geared towards proc-macro authors. +//! +//! Proc-macro crates should provide their own structs which implement or derive `FromDeriveInput`, +//! `FromField`, `FromVariant`, `FromGenerics`, _et alia_ to gather settings relevant to their operation. +//! +//! ## Attributes +//! There are a number of attributes that `darling` exposes to enable finer-grained control over the code +//! it generates. +//! +//! * **Field renaming**: You can use `#[darling(rename="new_name")]` on a field to change the name Darling looks for. +//! You can also use `#[darling(rename_all="...")]` at the struct or enum level to apply a casing rule to all fields or variants. +//! * **Map function**: You can use `#[darling(map="path::to::function")]` to run code on a field before its stored in the struct. +//! * **Default values**: You can use `#[darling(default)]` at the type or field level to use that type's default value to fill +//! in values not specified by the caller. +//! * **Skipped fields**: You can skip a variant or field using `#[darling(skip)]`. Fields marked with this will fall back to +//! `Default::default()` for their value, but you can override that with an explicit default or a value from the type-level default. +//! +//! ## Forwarded Fields +//! All derivable traits except `FromMeta` support forwarding some fields from the input AST to the derived struct. +//! These fields are matched up by identifier **before** `rename` attribute values are considered, +//! allowing you to use their names for your own properties. +//! The deriving struct is responsible for making sure the types of fields it chooses to declare are compatible with this table. +//! +//! A deriving struct is free to include or exclude any of the fields below. +//! +//! ### `FromDeriveInput` +//! |Field name|Type|Meaning| +//! |---|---|---| +//! |`ident`|`syn::Ident`|The identifier of the passed-in type| +//! |`vis`|`syn::Visibility`|The visibility of the passed-in type| +//! |`generics`|`T: darling::FromGenerics`|The generics of the passed-in type. This can be `syn::Generics`, `darling::ast::Generics`, or any compatible type.| +//! |`data`|`darling::ast::Data`|The body of the passed-in type| +//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in type. These are controlled using the `forward_attrs` attribute.| +//! +//! ### `FromField` +//! |Field name|Type|Meaning| +//! |---|---|---| +//! |`ident`|`Option<syn::Ident>`|The identifier of the passed-in field, or `None` for tuple fields| +//! |`vis`|`syn::Visibility`|The visibility of the passed-in field| +//! |`ty`|`syn::Type`|The type of the passed-in field| +//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in field. These are controlled using the `forward_attrs` attribute.| +//! +//! ### `FromTypeParam` +//! |Field name|Type|Meaning| +//! |---|---|---| +//! |`ident`|`syn::Ident`|The identifier of the passed-in type param| +//! |`bounds`|`Vec<syn::TypeParamBound>`|The bounds applied to the type param| +//! |`default`|`Option<syn::Type>`|The default type of the parameter, if one exists| +//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in type param. These are controlled using the `forward_attrs` attribute.| +//! +//! ### `FromVariant` +//! |Field name|Type|Meaning| +//! |---|---|---| +//! |`ident`|`syn::Ident`|The identifier of the passed-in variant| +//! |`discriminant`|`Option<syn::Expr>`|For a variant such as `Example = 2`, the `2`| +//! |`fields`|`darling::ast::Fields<T> where T: FromField`|The fields associated with the variant| +//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in variant. These are controlled using the `forward_attrs` attribute.| + +extern crate core; + +#[allow(unused_imports)] +#[macro_use] +extern crate darling_macro; + +#[doc(hidden)] +pub use darling_macro::*; + +#[doc(inline)] +pub use darling_core::{ + FromAttributes, FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta, + FromTypeParam, FromVariant, +}; + +#[doc(inline)] +pub use darling_core::{Error, Result}; + +#[doc(inline)] +pub use darling_core::{ast, error, usage, util}; + +// XXX exported so that `ExtractAttribute::extractor` can convert a path into tokens. +// This is likely to change in the future, so only generated code should depend on this export. +#[doc(hidden)] +pub use darling_core::ToTokens; + +/// Core/std trait re-exports. This should help produce generated code which doesn't +/// depend on `std` unnecessarily, and avoids problems caused by aliasing `std` or any +/// of the referenced types. +#[doc(hidden)] +pub mod export { + pub use core::convert::From; + pub use core::default::Default; + pub use core::option::Option::{self, None, Some}; + pub use core::result::Result::{self, Err, Ok}; + pub use darling_core::syn; + pub use std::string::ToString; + pub use std::vec::Vec; + + pub use crate::ast::NestedMeta; +} + +#[macro_use] +mod macros_public; diff --git a/src/macros_public.rs b/src/macros_public.rs new file mode 100644 index 0000000..c264fcc --- /dev/null +++ b/src/macros_public.rs @@ -0,0 +1,96 @@ +//! Macros that should be exported from both `darling_core` and `darling`. +//! Note that these are **sym-linked** into the main code, and so cannot declare on items that are exported differently +//! in `darling_core` vs. `darling`. + +/// Generator for `UsesTypeParam` impls that unions the used type parameters of the selected fields. +/// +/// # Usage +/// The macro takes the type implementing the trait as the first argument, then a comma-separated list of +/// fields for the rest of its arguments. +/// +/// The type of each passed-in field must implement `UsesTypeParams`, or the resulting code won't compile. +/// +/// ```rust +/// # extern crate syn; +/// # use darling_core::uses_type_params; +/// # +/// struct MyField { +/// ty: syn::Type, +/// } +/// +/// uses_type_params!(MyField, ty); +/// +/// fn main() { +/// // no test run +/// } +/// ``` +/// +/// `darling` cannot derive this trait automatically, as it doesn't know which information extracted from +/// proc-macro input is meant to constitute "using" the type parameter, but crate consumers should +/// implement it by hand or using the macro. +#[macro_export] +macro_rules! uses_type_params { + ($impl_type:ty, $accessor:ident) => { + impl $crate::usage::UsesTypeParams for $impl_type { + fn uses_type_params<'gen>( + &self, + options: &$crate::usage::Options, + type_set: &'gen $crate::usage::IdentSet + ) -> $crate::usage::IdentRefSet<'gen> { + self.$accessor.uses_type_params(options, type_set) + } + } + }; + ($impl_type:ty, $first:ident, $($field:ident),+) => { + impl $crate::usage::UsesTypeParams for $impl_type { + fn uses_type_params<'gen>( + &self, + options: &$crate::usage::Options, + type_set: &'gen $crate::usage::IdentSet + ) -> $crate::usage::IdentRefSet<'gen> { + let mut hits = self.$first.uses_type_params(options, type_set); + $( + hits.extend(self.$field.uses_type_params(options, type_set)); + )* + hits + } + } + }; +} + +/// Generator for `UsesLifetimes` impls that unions the used lifetimes of the selected fields. +/// +/// # Usage +/// The macro takes the type implementing the trait as the first argument, then a comma-separated list of +/// fields for the rest of its arguments. +/// +/// The type of each passed-in field must implement `UsesLifetimes`, or the resulting code won't compile. +#[macro_export] +macro_rules! uses_lifetimes { + ($impl_type:ty, $accessor:ident) => { + impl $crate::usage::UsesLifetimes for $impl_type { + fn uses_lifetimes<'gen>( + &self, + options: &$crate::usage::Options, + type_set: &'gen $crate::usage::LifetimeSet + ) -> $crate::usage::LifetimeRefSet<'gen> { + self.$accessor.uses_lifetimes(options, type_set) + } + } + }; + ($impl_type:ty, $first:ident, $($field:ident),+) => { + impl $crate::usage::UsesLifetimes for $impl_type { + fn uses_lifetimes<'gen>( + &self, + options: &$crate::usage::Options, + type_set: &'gen $crate::usage::LifetimeSet + ) -> $crate::usage::LifetimeRefSet<'gen> { + let mut hits = self.$first.uses_lifetimes(options, type_set); + $( + hits.extend(self.$field.uses_lifetimes(options, type_set)); + )* + hits + } + } + }; +} diff --git a/tests/accrue_errors.rs b/tests/accrue_errors.rs new file mode 100644 index 0000000..e8799c4 --- /dev/null +++ b/tests/accrue_errors.rs @@ -0,0 +1,102 @@ +#![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); + assert_eq!(3, 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()); +} diff --git a/tests/attrs_with.rs b/tests/attrs_with.rs new file mode 100644 index 0000000..198322d --- /dev/null +++ b/tests/attrs_with.rs @@ -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`")); +} diff --git a/tests/compile-fail/attrs_with_bad_fn.rs b/tests/compile-fail/attrs_with_bad_fn.rs new file mode 100644 index 0000000..6063f64 --- /dev/null +++ b/tests/compile-fail/attrs_with_bad_fn.rs @@ -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() {} diff --git a/tests/compile-fail/attrs_with_bad_fn.stderr b/tests/compile-fail/attrs_with_bad_fn.stderr new file mode 100644 index 0000000..efdc201 --- /dev/null +++ b/tests/compile-fail/attrs_with_bad_fn.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> tests/compile-fail/attrs_with_bad_fn.rs:11:22 + | +11 | #[darling(with = bad_converter)] + | ^^^^^^^^^^^^^ + | | + | expected enum `Result`, found struct `Vec` + | arguments to this function are incorrect + | + = note: expected enum `Result<_, darling::Error>` + found struct `Vec<Attribute>` +note: associated function 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))] + | +++ + diff --git a/tests/compile-fail/default_expr_wrong_type.rs b/tests/compile-fail/default_expr_wrong_type.rs new file mode 100644 index 0000000..cd9a0fd --- /dev/null +++ b/tests/compile-fail/default_expr_wrong_type.rs @@ -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() {} diff --git a/tests/compile-fail/default_expr_wrong_type.stderr b/tests/compile-fail/default_expr_wrong_type.stderr new file mode 100644 index 0000000..a8b7c50 --- /dev/null +++ b/tests/compile-fail/default_expr_wrong_type.stderr @@ -0,0 +1,21 @@ +error[E0308]: mismatched types + --> tests/compile-fail/default_expr_wrong_type.rs:5:25 + | +5 | #[darling(default = "usize::default")] + | ^^^^^^^^^^^^^^^^ expected struct `String`, found `usize` + | +help: try using a conversion method + | +5 | #[darling(default = "usize::default".to_string())] + | ++++++++++++ +5 | #[darling(default = "usize::default".to_string())] + | ++++++++++++ + +error[E0308]: mismatched types + --> tests/compile-fail/default_expr_wrong_type.rs:8:35 + | +8 | #[darling(multiple, default = "usize::default")] + | ^^^^^^^^^^^^^^^^ expected struct `Vec`, found `usize` + | + = note: expected struct `Vec<String>` + found type `usize` diff --git a/tests/compile-fail/duplicate_word_across_variants.rs b/tests/compile-fail/duplicate_word_across_variants.rs new file mode 100644 index 0000000..3c8a8f8 --- /dev/null +++ b/tests/compile-fail/duplicate_word_across_variants.rs @@ -0,0 +1,12 @@ +use darling::FromMeta; + +#[derive(FromMeta)] +enum Choice { + #[darling(word)] + A, + #[darling(word)] + B, + C, +} + +fn main() {} diff --git a/tests/compile-fail/duplicate_word_across_variants.stderr b/tests/compile-fail/duplicate_word_across_variants.stderr new file mode 100644 index 0000000..6f8c4bb --- /dev/null +++ b/tests/compile-fail/duplicate_word_across_variants.stderr @@ -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)] + | ^^^^ diff --git a/tests/compile-fail/duplicate_word_on_variant.rs b/tests/compile-fail/duplicate_word_on_variant.rs new file mode 100644 index 0000000..e294292 --- /dev/null +++ b/tests/compile-fail/duplicate_word_on_variant.rs @@ -0,0 +1,10 @@ +use darling::FromMeta; + +#[derive(FromMeta)] +enum Choice { + #[darling(word, word)] + A, + B, +} + +fn main() {} diff --git a/tests/compile-fail/duplicate_word_on_variant.stderr b/tests/compile-fail/duplicate_word_on_variant.stderr new file mode 100644 index 0000000..bc489d4 --- /dev/null +++ b/tests/compile-fail/duplicate_word_on_variant.stderr @@ -0,0 +1,5 @@ +error: Duplicate field `word` + --> tests/compile-fail/duplicate_word_on_variant.rs:5:21 + | +5 | #[darling(word, word)] + | ^^^^ diff --git a/tests/compile-fail/flatten_meta_conflicts.rs b/tests/compile-fail/flatten_meta_conflicts.rs new file mode 100644 index 0000000..62543e7 --- /dev/null +++ b/tests/compile-fail/flatten_meta_conflicts.rs @@ -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() {} diff --git a/tests/compile-fail/flatten_meta_conflicts.stderr b/tests/compile-fail/flatten_meta_conflicts.stderr new file mode 100644 index 0000000..6fa4c28 --- /dev/null +++ b/tests/compile-fail/flatten_meta_conflicts.stderr @@ -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)] + | ^^^^ diff --git a/tests/compile-fail/flatten_multiple_fields.rs b/tests/compile-fail/flatten_multiple_fields.rs new file mode 100644 index 0000000..a093054 --- /dev/null +++ b/tests/compile-fail/flatten_multiple_fields.rs @@ -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() {} diff --git a/tests/compile-fail/flatten_multiple_fields.stderr b/tests/compile-fail/flatten_multiple_fields.stderr new file mode 100644 index 0000000..7750bfb --- /dev/null +++ b/tests/compile-fail/flatten_multiple_fields.stderr @@ -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)] + | ^^^^^^^ diff --git a/tests/compile-fail/not_impl_from_meta.rs b/tests/compile-fail/not_impl_from_meta.rs new file mode 100644 index 0000000..f27d716 --- /dev/null +++ b/tests/compile-fail/not_impl_from_meta.rs @@ -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() {} diff --git a/tests/compile-fail/not_impl_from_meta.stderr b/tests/compile-fail/not_impl_from_meta.stderr new file mode 100644 index 0000000..a166a6f --- /dev/null +++ b/tests/compile-fail/not_impl_from_meta.stderr @@ -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`: + () + Arc<T> + AtomicBool + ExprArray + ExprPath + Flag + HashMap<String, V, S> + HashMap<proc_macro2::Ident, V, S> + 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`: + () + Arc<T> + AtomicBool + ExprArray + ExprPath + Flag + HashMap<String, V, S> + HashMap<proc_macro2::Ident, V, S> + and $N others diff --git a/tests/compile-fail/skip_field_not_impl_default.rs b/tests/compile-fail/skip_field_not_impl_default.rs new file mode 100644 index 0000000..f0d44c7 --- /dev/null +++ b/tests/compile-fail/skip_field_not_impl_default.rs @@ -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() {} diff --git a/tests/compile-fail/skip_field_not_impl_default.stderr b/tests/compile-fail/skip_field_not_impl_default.stderr new file mode 100644 index 0000000..de46982 --- /dev/null +++ b/tests/compile-fail/skip_field_not_impl_default.stderr @@ -0,0 +1,21 @@ +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)] + | + +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)] + | diff --git a/tests/compile-fail/word_on_wrong_variant_type.rs b/tests/compile-fail/word_on_wrong_variant_type.rs new file mode 100644 index 0000000..4bf99ca --- /dev/null +++ b/tests/compile-fail/word_on_wrong_variant_type.rs @@ -0,0 +1,10 @@ +use darling::FromMeta; + +#[derive(FromMeta)] +enum Meta { + Unit, + #[darling(word)] + NotUnit(String) +} + +fn main() {} diff --git a/tests/compile-fail/word_on_wrong_variant_type.stderr b/tests/compile-fail/word_on_wrong_variant_type.stderr new file mode 100644 index 0000000..4193727 --- /dev/null +++ b/tests/compile-fail/word_on_wrong_variant_type.stderr @@ -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)] + | ^^^^ diff --git a/tests/compiletests.rs b/tests/compiletests.rs new file mode 100644 index 0000000..00a5b32 --- /dev/null +++ b/tests/compiletests.rs @@ -0,0 +1,16 @@ +#![cfg(compiletests)] + +#[rustversion::stable(1.65)] +#[test] +fn compile_test() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile-fail/*.rs"); +} + +#[rustversion::not(stable(1.65))] +#[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" + ); +} diff --git a/tests/computed_bound.rs b/tests/computed_bound.rs new file mode 100644 index 0000000..abdb102 --- /dev/null +++ b/tests/computed_bound.rs @@ -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); +} diff --git a/tests/custom_bound.rs b/tests/custom_bound.rs new file mode 100644 index 0000000..312f147 --- /dev/null +++ b/tests/custom_bound.rs @@ -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() {} diff --git a/tests/defaults.rs b/tests/defaults.rs new file mode 100644 index 0000000..b74c745 --- /dev/null +++ b/tests/defaults.rs @@ -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()); + } +} diff --git a/tests/enums_default.rs b/tests/enums_default.rs new file mode 100644 index 0000000..fa6829e --- /dev/null +++ b/tests/enums_default.rs @@ -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); +} diff --git a/tests/enums_newtype.rs b/tests/enums_newtype.rs new file mode 100644 index 0000000..c2c4ec9 --- /dev/null +++ b/tests/enums_newtype.rs @@ -0,0 +1,90 @@ +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), + 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 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(); +} diff --git a/tests/enums_struct.rs b/tests/enums_struct.rs new file mode 100644 index 0000000..cae4cd5 --- /dev/null +++ b/tests/enums_struct.rs @@ -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() {} diff --git a/tests/enums_unit.rs b/tests/enums_unit.rs new file mode 100644 index 0000000..34e0135 --- /dev/null +++ b/tests/enums_unit.rs @@ -0,0 +1,14 @@ +//! Test expansion of enum variants which have no associated data. + +use darling::FromMeta; + +#[derive(Debug, FromMeta)] +#[darling(rename_all = "snake_case")] +enum Pattern { + Owned, + Immutable, + Mutable, +} + +#[test] +fn expansion() {} diff --git a/tests/error.rs b/tests/error.rs new file mode 100644 index 0000000..7274e40 --- /dev/null +++ b/tests/error.rs @@ -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()); +} diff --git a/tests/flatten.rs b/tests/flatten.rs new file mode 100644 index 0000000..d49373a --- /dev/null +++ b/tests/flatten.rs @@ -0,0 +1,181 @@ +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 + ); +} + +#[test] +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() + .to_string(); + assert!( + errors.contains("`blast`"), + "Should contain `blast` as did-you-mean suggestion: {}", + errors + ); +} diff --git a/tests/flatten_error_accumulation.rs b/tests/flatten_error_accumulation.rs new file mode 100644 index 0000000..5cd6280 --- /dev/null +++ b/tests/flatten_error_accumulation.rs @@ -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); +} diff --git a/tests/flatten_from_field.rs b/tests/flatten_from_field.rs new file mode 100644 index 0000000..3338f04 --- /dev/null +++ b/tests/flatten_from_field.rs @@ -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); +} diff --git a/tests/from_generics.rs b/tests/from_generics.rs new file mode 100644 index 0000000..e2cc99e --- /dev/null +++ b/tests/from_generics.rs @@ -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); +} diff --git a/tests/from_meta.rs b/tests/from_meta.rs new file mode 100644 index 0000000..886465c --- /dev/null +++ b/tests/from_meta.rs @@ -0,0 +1,109 @@ +use darling::{Error, FromMeta}; +use syn::parse_quote; + +#[derive(Debug, FromMeta)] +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() + ); +} + +/// 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()); + } +} diff --git a/tests/from_type_param.rs b/tests/from_type_param.rs new file mode 100644 index 0000000..50ec306 --- /dev/null +++ b/tests/from_type_param.rs @@ -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); + } +} diff --git a/tests/from_type_param_default.rs b/tests/from_type_param_default.rs new file mode 100644 index 0000000..9d65665 --- /dev/null +++ b/tests/from_type_param_default.rs @@ -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()); + } +} diff --git a/tests/from_variant.rs b/tests/from_variant.rs new file mode 100644 index 0000000..e89b8ff --- /dev/null +++ b/tests/from_variant.rs @@ -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"); + } +} diff --git a/tests/generics.rs b/tests/generics.rs new file mode 100644 index 0000000..ba65781 --- /dev/null +++ b/tests/generics.rs @@ -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(); +} diff --git a/tests/happy_path.rs b/tests/happy_path.rs new file mode 100644 index 0000000..a56aee7 --- /dev/null +++ b/tests/happy_path.rs @@ -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()), + } + } + ); +} diff --git a/tests/hash_map.rs b/tests/hash_map.rs new file mode 100644 index 0000000..881cd1c --- /dev/null +++ b/tests/hash_map.rs @@ -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); +} diff --git a/tests/multiple.rs b/tests/multiple.rs new file mode 100644 index 0000000..b2243e6 --- /dev/null +++ b/tests/multiple.rs @@ -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()] + ); +} diff --git a/tests/newtype.rs b/tests/newtype.rs new file mode 100644 index 0000000..10d0238 --- /dev/null +++ b/tests/newtype.rs @@ -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)); +} diff --git a/tests/skip.rs b/tests/skip.rs new file mode 100644 index 0000000..f930ca5 --- /dev/null +++ b/tests/skip.rs @@ -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) } + ) +} diff --git a/tests/split_declaration.rs b/tests/split_declaration.rs new file mode 100644 index 0000000..8db11d0 --- /dev/null +++ b/tests/split_declaration.rs @@ -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()); +} diff --git a/tests/suggestions.rs b/tests/suggestions.rs new file mode 100644 index 0000000..862084f --- /dev/null +++ b/tests/suggestions.rs @@ -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")); +} diff --git a/tests/supports.rs b/tests/supports.rs new file mode 100644 index 0000000..d6c7556 --- /dev/null +++ b/tests/supports.rs @@ -0,0 +1,90 @@ +use darling::{ast, FromDeriveInput, 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, ()>, +} + +#[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<(), syn::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(); +} diff --git a/tests/unsupported_attributes.rs b/tests/unsupported_attributes.rs new file mode 100644 index 0000000..c0143ce --- /dev/null +++ b/tests/unsupported_attributes.rs @@ -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()); +} |