aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaurice Lam <yukl@google.com>2023-02-17 21:10:05 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-02-17 21:10:05 +0000
commit0770e2aea7e9bd3820d4a01c7ea7f7e66aa2f6e3 (patch)
tree7f2b4d4f1c885a03cc7db11512190dcd5860cec1
parent7057e6448eeb4eb1623cc8b83f9cfe453c1e303a (diff)
parent544353c622b9d81ddf1b7537d052a7545b6c1308 (diff)
downloadderive-getters-0770e2aea7e9bd3820d4a01c7ea7f7e66aa2f6e3.tar.gz
Import derive-getters crate am: 9c88cffbc6 am: 544353c622
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/derive-getters/+/2427899 Change-Id: I96ffd3167449cb5af35dd09f7684291ca172e57b Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml8
-rw-r--r--Cargo.toml50
-rw-r--r--Cargo.toml.orig30
-rw-r--r--LICENSE21
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS1
-rw-r--r--README.md102
-rw-r--r--src/dissolve.rs172
-rw-r--r--src/extract.rs27
-rw-r--r--src/faultmsg.rs68
-rw-r--r--src/getters.rs201
-rw-r--r--src/lib.rs190
-rw-r--r--tests/01-legacy.rs100
-rw-r--r--tests/02-simple-single-generic.rs62
-rw-r--r--tests/03-simple-multi-generic.rs87
-rw-r--r--tests/04-simple-lifetime-annot.rs49
-rw-r--r--tests/05-skip-rename-attributes.rs122
-rw-r--r--tests/06-plays-with-others.rs33
-rw-r--r--tests/07-dissolve-basic.rs72
-rw-r--r--tests/08-dissolve-generic-and-ref.rs62
-rw-r--r--tests/lib.rs22
24 files changed, 1505 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..bd3e580
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "c3591c3aba2cc3d906c964bf3c2f48525c504f44"
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a9d37c5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+target
+Cargo.lock
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8c91a74
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: rust
+rust:
+ - stable
+ - beta
+ - nightly
+matrix:
+ allow_failures:
+ - rust: nightly
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..e20c2e4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,50 @@
+# 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 believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "derive-getters"
+version = "0.2.0"
+authors = ["Stephan Luther <kvsari@gmail.com>"]
+autotests = false
+description = "Simple boilerplate getters generator."
+readme = "README.md"
+keywords = ["getter", "getters", "derive", "macro"]
+categories = ["development-tools::procedural-macro-helpers"]
+license = "MIT"
+repository = "https://git.sr.ht/~kvsari/derive-getters"
+
+[lib]
+name = "derive_getters"
+proc-macro = true
+
+[[test]]
+name = "tests"
+path = "tests/lib.rs"
+[dependencies.proc-macro2]
+version = "1.0"
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.syn]
+version = "1.0"
+features = ["extra-traits"]
+[dev-dependencies.serde]
+version = "1.0"
+features = ["derive"]
+
+[dev-dependencies.trybuild]
+version = "1.0"
+
+[dev-dependencies.version-sync]
+version = "0.9"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..98819dc
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,30 @@
+[package]
+name = "derive-getters"
+version = "0.2.0"
+authors = ["Stephan Luther <kvsari@gmail.com>"]
+license = "MIT"
+description = "Simple boilerplate getters generator."
+repository = "https://git.sr.ht/~kvsari/derive-getters"
+keywords = ["getter", "getters", "derive", "macro"]
+categories = ["development-tools::procedural-macro-helpers"]
+edition = "2018"
+autotests = false
+readme = "README.md"
+
+[lib]
+name = "derive_getters"
+proc-macro = true
+
+[[test]]
+name = "tests"
+path = "tests/lib.rs"
+
+[dependencies]
+quote = "1.0"
+syn = { version = "1.0", features = ["extra-traits"] }
+proc-macro2 = "1.0"
+
+[dev-dependencies]
+trybuild = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+version-sync = "0.9"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..dbddca4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Stephan Luther
+
+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..dca2088
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "derive-getters"
+description: "Simple boilerplate getters generator."
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/derive-getters"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/derive-getters/derive-getters-0.2.0.crate"
+ }
+ version: "0.2.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 2
+ day: 9
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..87a6952
--- /dev/null
+++ b/README.md
@@ -0,0 +1,102 @@
+# Derive Getters
+
+Simple `Getters` derive macro for generating field getter methods on a named struct. Included is an additional derive, `Dissolve`, that consumes the named struct returning a tuple of all fields in the order they were declared.
+
+The need for the `Getters` macro came about when I was making various data structures for JSON to deserialize into. These data structures had many fields in them to access and they weren't going to change once created. One could use `pub` everywhere but that would enable mutating the fields which is what this derive aims to avoid.
+
+Getters will be generated according to [convention](https://github.com/rust-lang/rfcs/blob/master/text/0344-conventions-galore.md#gettersetter-apis). This means that the generated methods will reside within the struct namespace.
+
+With regards to `Dissolve`, sometimes during conversion a structure must be consumed. One easy way to do this is to return a tuple of all the structs fields. Thus `Dissolve` can be considered a 'get (move) everything' method call.
+
+## What this crate won't do
+There are no mutable getters and it's not planned. There are no setters either nor will there ever be.
+
+## Rust Docs
+[Documentation is here.](https://docs.rs/derive-getters/0.2.0)
+
+## Installation
+
+Add to your `Cargo.toml`:
+```toml
+[dependencies]
+derive-getters = "0.2.0"
+```
+
+Then import the `Getters` or `Dissolve` macro in whichever module it's needed (assuming 2018 edition).
+```rust
+use derive_getters::{Getters, Dissolve};
+
+```
+Otherwise just import at crate root.
+```rust
+#[macro_use]
+extern crate derive_getters;
+```
+
+## Usage
+
+When you have a struct you want to automatically derive getters for... Just add the derive at the top like so;
+```rust
+#[derive(Getters)]
+pub struct MyCheesyStruct {
+ x: i64,
+ y: i64,
+}
+```
+
+A new impl will be produced for `MyCheesyStruct`.
+```rust
+impl MyCheesyStruct {
+ pub fn x(&self) -> &i64 {
+ &self.x
+ }
+
+ pub fn y(&self) -> &i64 {
+ &self.y
+ }
+}
+```
+
+This crate can also handle structs with simple generic parameters and lifetime annotations. Check [docs](https://docs.rs/derive-getters/0.2.0) for further details.
+```rust
+#[derive(Getters)]
+pub struct StructWithGeneric<'a, T> {
+ concrete: f64,
+ generic: T,
+ text: &'a str,
+}
+```
+
+With `Dissolve`, use it like so;
+```rust
+#[derive(Dissolve)]
+pub struct Solid {
+ a: u64,
+ b: f64,
+ c: i64,
+}
+```
+
+An impl will be produced for `Solid` like so;
+```rust
+impl Solid {
+ pub fn dissolve(self) -> (u64, f64, i64) {
+ (self.a, self.b, self.c)
+ }
+}
+```
+
+### Attributes
+This macro comes with two optional field attributes for `Getters`.
+* `#[getter(skip)]` to skip generating getters for a field.
+* `#[getter(rename = "name")]` to change the getter name to "name".
+
+And one optional struct attribute for `Dissolve`.
+* `#[dissolve(rename = "name")]` to change the name of the dissolve function to "name".
+
+## Caveats
+1. Will not work on unit structs, tuples or enums. Derive `Getters` or `Dissolve` over them and the macro will chuck a wobbly.
+2. All getter methods return an immutable reference, `&`, to their field. This means for some types it can get awkward.
+
+## Alternatives
+[getset](https://github.com/Hoverbear/getset).
diff --git a/src/dissolve.rs b/src/dissolve.rs
new file mode 100644
index 0000000..69feb0f
--- /dev/null
+++ b/src/dissolve.rs
@@ -0,0 +1,172 @@
+//! Dissolve internals
+use std::{
+ iter::Extend,
+ convert::TryFrom,
+};
+
+use proc_macro2::{TokenStream, Span};
+use quote::quote;
+use syn::{
+ DeriveInput,
+ FieldsNamed,
+ Type,
+ Ident,
+ Result,
+ Error,
+ TypeTuple,
+ AttrStyle,
+ LitStr,
+ Attribute,
+ token::Paren,
+ punctuated::Punctuated,
+ parse::{Parse, ParseStream},
+};
+
+use crate::{
+ extract::{named_fields, named_struct},
+ faultmsg::Problem,
+};
+
+pub struct Field {
+ ty: Type,
+ name: Ident,
+}
+
+impl Field {
+ fn from_field(field: &syn::Field) -> Result<Self> {
+ let name: Ident = field.ident
+ .clone()
+ .ok_or(Error::new(Span::call_site(), Problem::UnnamedField))?;
+
+ Ok(Field {
+ ty: field.ty.clone(),
+ name: name,
+ })
+ }
+
+ fn from_fields_named(fields_named: &FieldsNamed) -> Result<Vec<Self>> {
+ fields_named.named
+ .iter()
+ .map(|field| Field::from_field(field))
+ .collect()
+ }
+}
+
+struct Rename {
+ name: Ident,
+}
+
+impl Parse for Rename {
+ fn parse(input: ParseStream) -> Result<Self> {
+ syn::custom_keyword!(rename);
+
+ if input.peek(rename) {
+ let _ = input.parse::<rename>()?;
+ let _ = input.parse::<syn::Token![=]>()?;
+ let name = input.parse::<LitStr>()?;
+ if !input.is_empty() {
+ Err(Error::new(Span::call_site(), Problem::TokensFollowNewName))
+ } else {
+ let name = Ident::new(name.value().as_str(), Span::call_site());
+ Ok(Rename { name } )
+ }
+ } else {
+ Err(Error::new(Span::call_site(), Problem::InvalidAttribute))
+ }
+ }
+}
+
+fn dissolve_rename_from(attributes: &[Attribute]) -> Result<Option<Ident>> {
+ let mut current: Option<Ident> = None;
+
+ for attr in attributes {
+ if attr.style != AttrStyle::Outer { continue; }
+
+ if attr.path.is_ident("dissolve") {
+ let rename = attr.parse_args::<Rename>()?;
+ current = Some(rename.name);
+ }
+ }
+
+ Ok(current)
+}
+
+pub struct NamedStruct<'a> {
+ original: &'a DeriveInput,
+ name: Ident,
+ fields: Vec<Field>,
+ dissolve_rename: Option<Ident>,
+}
+
+impl<'a> NamedStruct<'a> {
+ pub fn emit(&self) -> TokenStream {
+ let (impl_generics, struct_generics, where_clause) = self.original.generics
+ .split_for_impl();
+ let struct_name = &self.name;
+
+ let types: Punctuated<Type, syn::Token![,]> = self.fields
+ .iter()
+ .fold(Punctuated::new(), |mut p, field| {
+ p.push(field.ty.clone());
+ p
+ });
+
+ let type_tuple = TypeTuple {
+ paren_token: Paren { span: Span::call_site() },
+ elems: types,
+ };
+
+ let fields: TokenStream = self.fields
+ .iter()
+ .enumerate()
+ .fold(TokenStream::new(), |mut ts, (count, field)| {
+ if count > 0 {
+ ts.extend(quote!(,))
+ }
+
+ let field_name = &field.name;
+ let field_expr = quote!(
+ self.#field_name
+ );
+
+ ts.extend(field_expr);
+
+ ts
+ });
+
+ let dissolve = Ident::new("dissolve", Span::call_site());
+ let fn_name = self.dissolve_rename
+ .as_ref()
+ .unwrap_or(&dissolve);
+
+ quote!(
+ impl #impl_generics #struct_name #struct_generics
+ #where_clause
+ {
+ pub fn #fn_name(self) -> #type_tuple {
+ (
+ #fields
+ )
+ }
+ }
+ )
+ }
+}
+
+impl<'a> TryFrom<&'a DeriveInput> for NamedStruct<'a> {
+ type Error = Error;
+
+ fn try_from(node: &'a DeriveInput) -> Result<Self> {
+ let struct_data = named_struct(node)?;
+ let named_fields = named_fields(struct_data)?;
+ let fields = Field::from_fields_named(named_fields)?;
+ let rename = dissolve_rename_from(node.attrs.as_slice())?;
+
+ Ok(NamedStruct {
+ original: node,
+ name: node.ident.clone(),
+ fields,
+ dissolve_rename: rename,
+ })
+ }
+}
diff --git a/src/extract.rs b/src/extract.rs
new file mode 100644
index 0000000..5b38821
--- /dev/null
+++ b/src/extract.rs
@@ -0,0 +1,27 @@
+//! Common functions
+
+use proc_macro2::Span;
+use syn::{FieldsNamed, DataStruct, DeriveInput, Data, Fields, Error, Result};
+
+use crate::faultmsg::{StructIs, Problem};
+
+pub fn named_fields<'a>(structure: &'a DataStruct) -> Result<&'a FieldsNamed> {
+ match structure.fields {
+ Fields::Named(ref fields) => Ok(fields),
+ Fields::Unnamed(_) | Fields::Unit => Err(
+ Error::new(Span::call_site(), Problem::UnnamedField)
+ ),
+ }
+}
+
+pub fn named_struct<'a>(node: &'a DeriveInput) -> Result<&'a DataStruct> {
+ match node.data {
+ Data::Struct(ref structure) => Ok(structure),
+ Data::Enum(_) => Err(
+ Error::new_spanned(node, Problem::NotNamedStruct(StructIs::Enum))
+ ),
+ Data::Union(_) => Err(
+ Error::new_spanned(node, Problem::NotNamedStruct(StructIs::Union))
+ ),
+ }
+}
diff --git a/src/faultmsg.rs b/src/faultmsg.rs
new file mode 100644
index 0000000..daaaf04
--- /dev/null
+++ b/src/faultmsg.rs
@@ -0,0 +1,68 @@
+//! Error type.
+use std::fmt;
+
+#[derive(Debug)]
+pub enum StructIs {
+ Unnamed,
+ Enum,
+ Union,
+ Unit,
+}
+
+impl fmt::Display for StructIs {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Unnamed => write!(f, "an unnamed struct"),
+ Self::Enum => write!(f, "an enum"),
+ Self::Union => write!(f, "a union"),
+ Self::Unit => write!(f, "a unit struct"),
+ }
+ }
+}
+
+// Almost an error type! But `syn` already has an error type so this just fills the
+// `T: Display` part to avoid strings littering the source.
+#[derive(Debug)]
+pub enum Problem {
+ NotNamedStruct(StructIs),
+ UnnamedField,
+ InnerAttribute,
+ EmptyAttribute,
+ NoGrouping,
+ NonParensGrouping,
+ EmptyGrouping,
+ TokensFollowSkip,
+ TokensFollowNewName,
+ InvalidAttribute,
+}
+
+impl fmt::Display for Problem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::NotNamedStruct(is) => {
+ write!(f, "type must be a named struct, not {}", is)
+ },
+ Self::UnnamedField => write!(f, "struct fields must be named"),
+ Self::InnerAttribute => {
+ write!(f, "attribute is an outer not inner attribute")
+ },
+ Self::EmptyAttribute => write!(f, "attribute has no tokens"),
+ Self::NoGrouping => write!(f, "attribute tokens must be grouped"),
+ Self::NonParensGrouping => {
+ write!(f, "attribute tokens must be within parenthesis")
+ },
+ Self::EmptyGrouping => {
+ write!(f, "no attribute tokens within parenthesis grouping")
+ },
+ Self::TokensFollowSkip => {
+ write!(f, "tokens are not meant to follow skip attribute")
+ },
+ Self::TokensFollowNewName => {
+ write!(f, "no further tokens must follow new name")
+ },
+ Self::InvalidAttribute => {
+ write!(f, "invalid attribute")
+ },
+ }
+ }
+}
diff --git a/src/getters.rs b/src/getters.rs
new file mode 100644
index 0000000..211b646
--- /dev/null
+++ b/src/getters.rs
@@ -0,0 +1,201 @@
+//! Getters internals
+use std::convert::TryFrom;
+
+use proc_macro2::{TokenStream, Span};
+use quote::quote;
+use syn::{
+ DeriveInput,
+ FieldsNamed,
+ Type,
+ AttrStyle,
+ Ident,
+ LitStr,
+ Result,
+ Error,
+ Attribute,
+ parse::{Parse, ParseStream},
+};
+
+use crate::{
+ extract::{named_fields, named_struct},
+ faultmsg::Problem,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum Action {
+ Skip,
+ Rename(Ident),
+}
+
+impl Parse for Action {
+ fn parse(input: ParseStream) -> Result<Self> {
+ syn::custom_keyword!(skip);
+ syn::custom_keyword!(rename);
+
+ if input.peek(skip) {
+ let _ = input.parse::<skip>()?;
+ if !input.is_empty() {
+ Err(Error::new(Span::call_site(), Problem::TokensFollowSkip))
+ } else {
+ Ok(Action::Skip)
+ }
+ } else if input.peek(rename) {
+ let _ = input.parse::<rename>()?;
+ let _ = input.parse::<syn::Token![=]>()?;
+ let name = input.parse::<LitStr>()?;
+ if !input.is_empty() {
+ Err(Error::new(Span::call_site(), Problem::TokensFollowNewName))
+ } else {
+ Ok(Action::Rename(Ident::new(name.value().as_str(), Span::call_site())))
+ }
+ } else {
+ Err(Error::new(Span::call_site(), Problem::InvalidAttribute))
+ }
+ }
+}
+
+fn get_action_from(attributes: &[Attribute]) -> Result<Option<Action>> {
+ let mut current: Option<Action> = None;
+
+ for attr in attributes {
+ if attr.style != AttrStyle::Outer { continue; }
+
+ if attr.path.is_ident("getter") {
+ current = Some(attr.parse_args::<Action>()?);
+ }
+ }
+
+ Ok(current)
+}
+
+pub struct Field {
+ ty: Type,
+ name: Ident,
+ getter: Ident,
+}
+
+impl Field {
+ fn from_field(field: &syn::Field) -> Result<Option<Self>> {
+ let name: Ident = field.ident
+ .clone()
+ .ok_or(Error::new(Span::call_site(), Problem::UnnamedField))?;
+
+ match get_action_from(field.attrs.as_slice())? {
+ Some(Action::Skip) => return Ok(None),
+ Some(Action::Rename(ident)) => Ok(Some(Field {
+ ty: field.ty.clone(),
+ name: name,
+ getter: ident,
+ })),
+ None => Ok(Some(Field {
+ ty: field.ty.clone(),
+ name: name.clone(),
+ getter: name,
+ })),
+ }
+ }
+
+ fn from_fields_named(fields_named: &FieldsNamed) -> Result<Vec<Self>> {
+ fields_named.named
+ .iter()
+ .try_fold(Vec::new(), |mut fields, field| {
+ if let Some(field) = Field::from_field(field)? {
+ fields.push(field);
+ }
+
+ Ok(fields)
+ })
+ }
+
+ fn emit(&self) -> TokenStream {
+ let returns = &self.ty;
+ let field_name = &self.name;
+ let getter_name = &self.getter;
+
+ match &self.ty {
+ Type::Reference(tr) => {
+ let lifetime = tr.lifetime.as_ref();
+ quote!(
+ pub fn #getter_name(&#lifetime self) -> #returns {
+ self.#field_name
+ }
+ )
+ },
+ _ => {
+ quote!(
+ pub fn #getter_name(&self) -> &#returns {
+ &self.#field_name
+ }
+ )
+ },
+ }
+ }
+}
+
+pub struct NamedStruct<'a> {
+ original: &'a DeriveInput,
+ name: Ident,
+ fields: Vec<Field>,
+}
+
+impl<'a> NamedStruct<'a> {
+ pub fn emit(&self) -> TokenStream {
+ let (impl_generics, struct_generics, where_clause) = self.original.generics
+ .split_for_impl();
+ let struct_name = &self.name;
+ let methods: Vec<TokenStream> = self.fields
+ .iter()
+ .map(|field| field.emit())
+ .collect();
+
+ quote!(
+ impl #impl_generics #struct_name #struct_generics
+ #where_clause
+ {
+ #(#methods)*
+ }
+ )
+ }
+}
+
+impl<'a> TryFrom<&'a DeriveInput> for NamedStruct<'a> {
+ type Error = Error;
+
+ fn try_from(node: &'a DeriveInput) -> Result<Self> {
+ let struct_data = named_struct(node)?;
+ let named_fields = named_fields(struct_data)?;
+ let fields = Field::from_fields_named(named_fields)?;
+
+ Ok(NamedStruct {
+ original: node,
+ name: node.ident.clone(),
+ fields,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn parse_action() -> Result<()> {
+ let a: Action = syn::parse_str("skip")?;
+ assert!(a == Action::Skip);
+
+ let r: Result<Action> = syn::parse_str("skip = blah");
+ assert!(r.is_err());
+
+ let a: Action = syn::parse_str("rename = \"hello\"")?;
+ let check = Action::Rename(Ident::new("hello", Span::call_site()));
+ assert!(a == check);
+
+ let r: Result<Action> = syn::parse_str("rename + \"chooga\"");
+ assert!(r.is_err());
+
+ let r: Result<Action> = syn::parse_str("rename = \"chooga\" | bongle");
+ assert!(r.is_err());
+
+ Ok(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..e4fe60e
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,190 @@
+//! This library provides two derive macros. One, `Getters` for autogenerating getters and
+//! `Dissolve` for consuming a struct returning a tuple of all fields. They can only be
+//! used on named structs.
+//!
+//! # Derives
+//!
+//! Only named structs can derive `Getters` or `Dissolve`.
+//!
+//! # `Getter` methods generated
+//!
+//! The getter methods generated shall bear the same name as the struct fields and be
+//! publicly visible. The methods return an immutable reference to the struct field of the
+//! same name. If there is already a method defined with that name there'll be a collision.
+//! In these cases one of two attributes can be set to either `skip` or `rename` the getter.
+//!
+//!
+//! # `Getters` Usage
+//!
+//! In lib.rs or main.rs;
+//!
+//! ```edition2018
+//! use derive_getters::Getters;
+//!
+//! #[derive(Getters)]
+//! struct Number {
+//! num: u64,
+//! }
+//!
+//! fn main() {
+//! let number = Number { num: 655 };
+//! assert!(number.num() == &655);
+//! }
+//! ```
+//!
+//! Here, a method called `num()` has been created for the `Number` struct which gives a
+//! reference to the `num` field.
+//!
+//! This macro can also derive on structs that have simple generic types. For example;
+//!
+//! ```edition2018
+//! # use derive_getters::Getters;
+//! #[derive(Getters)]
+//! struct Generic<T, U> {
+//! gen_t: T,
+//! gen_u: U,
+//! }
+//! #
+//! # fn main() { }
+//! ```
+//!
+//! The macro can also handle generic types with trait bounds. For example;
+//! ```edition2018
+//! # use derive_getters::Getters;
+//! #[derive(Getters)]
+//! struct Generic<T: Clone, U: Copy> {
+//! gen_t: T,
+//! gen_u: U,
+//! }
+//! #
+//! # fn main() { }
+//! ```
+//! The trait bounds can also be declared in a `where` clause.
+//!
+//! Additionaly, simple lifetimes are OK too;
+//! ```edition2018
+//! # use derive_getters::Getters;
+//! #[derive(Getters)]
+//! struct Annotated<'a, 'b, T> {
+//! stuff: &'a T,
+//! comp: &'b str,
+//! num: u64,
+//! }
+//! #
+//! # fn main() { }
+//! ```
+//!
+//! # `Getter` Attributes
+//! Getters can be further configured to either skip or rename a getter.
+//!
+//! * #[getter(skip)]
+//! Will skip generating a getter for the field being decorated.
+//!
+//! * #[getter(rename = "name")]
+//! Changes the name of the getter (default is the field name) to "name".
+//!
+//!```edition2018
+//! # use derive_getters::Getters;
+//! #[derive(Getters)]
+//! struct Attributed {
+//! keep_me: u64,
+//!
+//! #[getter(skip)]
+//! skip_me: u64,
+//!
+//! #[getter(rename = "number")]
+//! rename_me: u64,
+//! }
+//! #
+//! # fn main() { }
+//! ```
+//!
+//! # `Dissolve` method generated
+//!
+//! Deriving `Dissolve` on a named struct will generate a method `dissolve(self)` which
+//! shall return a tuple of all struct fields in the order they were defined. Calling this
+//! method consumes the struct. The name of this method can be changed with an attribute.
+//!
+//! # `Dissolve` usage
+//!
+//! ```edition2018
+//! # use derive_getters::Dissolve;
+//! #[derive(Dissolve)]
+//! struct Stuff {
+//! name: String,
+//! price: f64,
+//! count: usize,
+//! }
+//!
+//! fn main() {
+//! let stuff = Stuff {
+//! name: "Hogie".to_owned(),
+//! price: 123.4f64,
+//! count: 100,
+//! };
+//!
+//! let (n, p, c) = stuff.dissolve();
+//! assert!(n == "Hogie");
+//! assert!(p == 123.4f64);
+//! assert!(c == 100);
+//! }
+//! ```
+//!
+//! # `Dissolve` Attributes
+//! You can rename the `dissolve` function by using a struct attribute.
+//!
+//! * #[dissolve(rename = "name")]
+//!
+//! ```edition2018
+//! # use derive_getters::Dissolve;
+//! #[derive(Dissolve)]
+//! #[dissolve(rename = "shatter")]
+//! struct Numbers {
+//! a: u64,
+//! b: i64,
+//! c: f64,
+//! }
+//! #
+//! # fn main() { }
+//! ```
+//!
+//! # Panics
+//!
+//! If `Getters` or `Dissolve` are derived on unit or unnamed structs, enums or unions.
+//!
+//! # Cannot Do
+//! Const generics aren't handled by this macro nor are they tested.
+use std::convert::TryFrom;
+
+extern crate proc_macro;
+use syn::{DeriveInput, parse_macro_input};
+
+mod faultmsg;
+mod dissolve;
+mod getters;
+mod extract;
+
+/// Generate getter methods for all named struct fields in a seperate struct `impl` block.
+/// Getter methods share the name of the field they're 'getting'. Methods return an
+/// immutable reference to the field.
+#[proc_macro_derive(Getters, attributes(getter))]
+pub fn getters(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = parse_macro_input!(input as DeriveInput);
+
+ getters::NamedStruct::try_from(&ast)
+ .map(|ns| ns.emit())
+ .unwrap_or_else(|err| err.to_compile_error())
+ .into()
+}
+
+/// Produce a `dissolve` method that consumes the named struct returning a tuple of all the
+/// the struct fields.
+#[proc_macro_derive(Dissolve, attributes(dissolve))]
+pub fn dissolve(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = parse_macro_input!(input as DeriveInput);
+
+ dissolve::NamedStruct::try_from(&ast)
+ .map(|ns| ns.emit())
+ .unwrap_or_else(|err| err.to_compile_error())
+ .into()
+}
diff --git a/tests/01-legacy.rs b/tests/01-legacy.rs
new file mode 100644
index 0000000..fcfb923
--- /dev/null
+++ b/tests/01-legacy.rs
@@ -0,0 +1,100 @@
+//! Legacy tests from 0.0.7 version.
+
+use derive_getters::Getters;
+
+#[derive(Getters)]
+struct Number {
+ num: u64,
+}
+
+fn number_num() {
+ let number = Number { num: 655 };
+ assert!(number.num() == &655);
+}
+
+#[derive(Getters)]
+struct ManyNumbers {
+ integer: u64,
+ floating: f64,
+ byte: u8,
+}
+
+fn many_numbers() {
+ let numbers = ManyNumbers {
+ integer: 655,
+ floating: 45.5,
+ byte: 122,
+ };
+
+ assert!(numbers.integer() == &655);
+ assert!(numbers.floating() == &45.5);
+ assert!(numbers.byte() == &122);
+}
+
+#[derive(Getters)]
+struct Textual {
+ author: Option<String>,
+ heading: String,
+ lines: Vec<String>,
+}
+
+fn textual_struct() {
+ let article = Textual {
+ author: None,
+ heading: "abcdefg".to_string(),
+ lines: vec![
+ "hijk".to_string(),
+ "lmno".to_string(),
+ "pqrs".to_string(),
+ "tuvw".to_string(),
+ ],
+ };
+
+ assert!(article.author() == &None);
+ assert!(article.heading() == "abcdefg");
+ assert!(article.lines().len() == 4);
+ assert!(article.lines()[0] == "hijk");
+ assert!(article.lines()[1] == "lmno");
+ assert!(article.lines()[2] == "pqrs");
+ assert!(article.lines()[3] == "tuvw");
+
+ let book = Textual {
+ author: Some("name".to_string()),
+ heading: "1234".to_string(),
+ lines: vec![
+ "2345".to_string(),
+ "3456".to_string(),
+ "4567".to_string(),
+ "5678".to_string(),
+ ],
+ };
+
+ assert!(book.author() == &Some("name".to_string()));
+}
+
+/// There shouldn't be any dead code warnings on unused methods, only on unused slots which
+/// are not the libraries fault.
+#[derive(Getters)]
+struct DeadCode {
+ x: u64,
+ y: u64,
+ z: u64,
+}
+
+#[test]
+fn dead_code_struct() {
+ let dc = DeadCode {
+ x: 1,
+ y: 2,
+ z: 3,
+ };
+
+ assert!(*dc.x() == 1);
+}
+
+fn main() {
+ number_num();
+ many_numbers();
+ textual_struct();
+}
+
diff --git a/tests/02-simple-single-generic.rs b/tests/02-simple-single-generic.rs
new file mode 100644
index 0000000..a4c05fb
--- /dev/null
+++ b/tests/02-simple-single-generic.rs
@@ -0,0 +1,62 @@
+use derive_getters::Getters;
+
+#[derive(Getters)]
+struct SimpleSingleGeneric<T> {
+ concrete: u16,
+ generic: T,
+}
+
+impl<T> SimpleSingleGeneric<T> {
+ pub fn new(concrete: u16, generic: T) -> Self {
+ SimpleSingleGeneric { concrete, generic }
+ }
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+pub enum Enumeration {
+ One,
+ Two,
+ Three,
+}
+
+#[derive(Getters)]
+struct InvolvedSingleGeneric<T: Eq + Ord> {
+ concrete: u16,
+ generic: T,
+}
+
+impl <T: Eq + Ord> InvolvedSingleGeneric<T> {
+ pub fn new(concrete: u16, generic: T) -> Self {
+ InvolvedSingleGeneric { concrete, generic }
+ }
+}
+
+#[derive(Getters)]
+struct WhereClauseSingleGeneric<T>
+where T: Eq + Ord
+{
+ concrete: u16,
+ generic: T,
+}
+
+impl<T> WhereClauseSingleGeneric<T>
+ where T: Eq + Ord
+{
+ pub fn new(concrete: u16, generic: T) -> Self {
+ WhereClauseSingleGeneric { concrete, generic }
+ }
+}
+
+fn main() {
+ let ssg = SimpleSingleGeneric::new(23, "Hello".to_string());
+ assert!(*ssg.concrete() == 23);
+ assert!(ssg.generic() == "Hello");
+
+ let isg = InvolvedSingleGeneric::new(44, Enumeration::Two);
+ assert!(*isg.concrete() == 44);
+ assert!(*isg.generic() == Enumeration::Two);
+
+ let wcsg = WhereClauseSingleGeneric::new(99, Enumeration::Three);
+ assert!(*wcsg.concrete() == 99);
+ assert!(*wcsg.generic() == Enumeration::Three);
+}
diff --git a/tests/03-simple-multi-generic.rs b/tests/03-simple-multi-generic.rs
new file mode 100644
index 0000000..27eb19f
--- /dev/null
+++ b/tests/03-simple-multi-generic.rs
@@ -0,0 +1,87 @@
+use std::ops::Div;
+
+use derive_getters::Getters;
+
+#[derive(Getters)]
+struct SimpleMultiGeneric<T, U, V> {
+ concrete: u16,
+ generic_t: T,
+ generic_u: U,
+ generic_v: V,
+}
+
+impl<T, U, V> SimpleMultiGeneric<T, U, V> {
+ pub fn new(concrete: u16, generic_t: T, generic_u: U, generic_v: V) -> Self {
+ SimpleMultiGeneric { concrete, generic_t, generic_u, generic_v }
+ }
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+pub enum Enumeration {
+ One,
+ Two,
+ Three,
+}
+
+#[derive(Getters)]
+struct InvolvedMultiGeneric<T: Eq + Ord, U: Div, V: Clone> {
+ concrete: u16,
+ generic_t: T,
+ generic_u: U,
+ generic_v: V,
+}
+
+impl <T: Eq + Ord, U: Div, V: Clone> InvolvedMultiGeneric<T, U, V> {
+ pub fn new(concrete: u16, generic_t: T, generic_u: U, generic_v: V) -> Self {
+ InvolvedMultiGeneric { concrete, generic_t, generic_u, generic_v }
+ }
+}
+
+
+#[derive(Getters)]
+struct WhereClauseMultiGeneric<T, U, V>
+where T: Eq + Ord,
+ U: Div,
+ V: Clone,
+{
+ concrete: u16,
+ generic_t: T,
+ generic_u: U,
+ generic_v: V,
+}
+
+impl<T, U, V> WhereClauseMultiGeneric<T, U, V>
+where T: Eq + Ord,
+ U: Div,
+ V: Clone,
+{
+ pub fn new(concrete: u16, generic_t: T, generic_u: U, generic_v: V) -> Self {
+ WhereClauseMultiGeneric { concrete, generic_t, generic_u, generic_v }
+ }
+}
+
+fn main() {
+ let smg = SimpleMultiGeneric::new(
+ 23, "Hello".to_string(), 55f64, [1, 2, 3],
+ );
+ assert!(*smg.concrete() == 23);
+ assert!(smg.generic_t() == "Hello");
+ assert!(*smg.generic_u() == 55f64);
+ assert!(*smg.generic_v() == [1, 2, 3]);
+
+ let img = InvolvedMultiGeneric::new(
+ 44, Enumeration::Two, 1024f32, "String".to_string(),
+ );
+ assert!(*img.concrete() == 44);
+ assert!(*img.generic_t() == Enumeration::Two);
+ assert!(*img.generic_u() == 1024f32);
+ assert!(img.generic_v() == "String");
+
+ let wcmg = WhereClauseMultiGeneric::new(
+ 99, Enumeration::Three, 1222f64, "Might".to_string()
+ );
+ assert!(*wcmg.concrete() == 99);
+ assert!(*wcmg.generic_t() == Enumeration::Three);
+ assert!(*wcmg.generic_u() == 1222f64);
+ assert!(wcmg.generic_v() == "Might");
+}
diff --git a/tests/04-simple-lifetime-annot.rs b/tests/04-simple-lifetime-annot.rs
new file mode 100644
index 0000000..56e6f38
--- /dev/null
+++ b/tests/04-simple-lifetime-annot.rs
@@ -0,0 +1,49 @@
+use derive_getters::Getters;
+
+#[derive(Getters)]
+struct LifetimeAnnotated<'a> {
+ val1: u64,
+ val2: String,
+ buffer: &'a [u8],
+}
+
+impl<'a> LifetimeAnnotated<'a> {
+ pub fn new<T: Into<String>>(v1: u64, v2: T, buf: &'a [u8]) -> Self {
+ LifetimeAnnotated {
+ val1: v1,
+ val2: v2.into(),
+ buffer: buf,
+ }
+ }
+}
+
+#[derive(Getters)]
+struct MultiAnnotated<'a, 'b, 'c, T> {
+ v1: &'a str,
+ v2: &'b [u8],
+ v3: &'c T,
+}
+
+impl<'a, 'b, 'c, T> MultiAnnotated<'a, 'b, 'c, T> {
+ pub fn new(v1: &'a str, v2: &'b [u8], v3: &'c T) -> Self {
+ MultiAnnotated { v1, v2, v3 }
+ }
+}
+
+#[derive(PartialEq, Eq)]
+struct GenericType;
+
+fn main() {
+ let buffer: [u8; 12] = [88; 12];
+ let la = LifetimeAnnotated::new(44, "Annot", &buffer);
+
+ assert!(*la.val1() == 44);
+ assert!(la.val2() == "Annot");
+ assert!(la.buffer() == &buffer);
+
+ let gt = GenericType;
+ let ma = MultiAnnotated::new("Hello", &buffer, &gt);
+ assert!(ma.v1() == "Hello");
+ assert!(ma.v2() == &buffer);
+ assert!(ma.v3() == &gt);
+}
diff --git a/tests/05-skip-rename-attributes.rs b/tests/05-skip-rename-attributes.rs
new file mode 100644
index 0000000..5d811eb
--- /dev/null
+++ b/tests/05-skip-rename-attributes.rs
@@ -0,0 +1,122 @@
+use derive_getters::Getters;
+
+#[derive(Getters)]
+struct SkipAField {
+ keep: u64,
+
+ #[getter(skip)]
+ skip: String,
+}
+
+impl SkipAField {
+ pub fn new<T: Into<String>>(keep: u64, skip: T) -> Self {
+ SkipAField { keep, skip: skip.into() }
+ }
+}
+
+#[derive(Getters)]
+struct SkipMany {
+ keep1: u64,
+
+ #[getter(skip)]
+ skip1: String,
+
+ #[getter(skip)]
+ skip2: String,
+
+ #[getter(skip)]
+ skip3: String,
+
+ #[getter(skip)]
+ skip4: String,
+
+ keep2: String,
+}
+
+impl SkipMany {
+ pub fn new<T: Into<String>>(
+ keep1: u64, skip1: T, skip2: T, skip3: T, skip4: T, keep2: T
+ ) -> Self {
+ SkipMany {
+ keep1,
+ skip1: skip1.into(),
+ skip2: skip2.into(),
+ skip3: skip3.into(),
+ skip4: skip4.into(),
+ keep2: keep2.into(),
+ }
+ }
+}
+
+#[derive(Getters)]
+struct Rename {
+ #[getter(rename = "number")]
+ field: u64,
+}
+
+#[derive(Getters)]
+struct RenameMany {
+ #[getter(rename = "number1")]
+ field1: u64,
+
+ #[getter(rename = "number2")]
+ field2: u64,
+
+ field3: u64,
+
+ #[getter(rename = "number3")]
+ field4: u64,
+}
+
+#[derive(Getters)]
+struct Combination<'a, 'b, 'c, T> {
+ #[getter(rename = "skip_me")]
+ #[getter(skip)]
+ v1: &'a str,
+
+ #[getter(rename = "buffer")]
+ v2: &'b [u8],
+
+ #[getter(skip)]
+ v3: &'c T,
+
+ #[getter(skip)]
+ #[getter(rename = "keep_me")]
+ v4: u64,
+}
+
+impl<'a, 'b, 'c, T> Combination<'a, 'b, 'c, T> {
+ pub fn new(v1: &'a str, v2: &'b [u8], v3: &'c T, v4: u64) -> Self {
+ Combination { v1, v2, v3, v4 }
+ }
+}
+
+#[derive(PartialEq, Eq)]
+struct GenericType;
+
+fn main() {
+ let s1 = SkipAField::new(45, "You can't get me.");
+ assert!(*s1.keep() == 45);
+
+ let s2 = SkipMany::new(33, "Dodge", "Duck", "Dip", "Dive", "...dodge!");
+ assert!(*s2.keep1() == 33);
+ //assert!(s2.skip1() == "Dodge");
+ assert!(s2.keep2() == "...dodge!");
+
+ let s3 = Rename { field: 35 };
+ assert!(*s3.number() == 35);
+
+ let s4 = RenameMany { field1: 1, field2: 2, field3: 3, field4: 4 };
+ assert!(*s4.number1() == 1);
+ assert!(*s4.number2() == 2);
+ assert!(*s4.field3() == 3);
+ assert!(*s4.number3() == 4);
+
+ let gt = GenericType;
+ let buffer: [u8; 12] = [88; 12];
+ let s5 = Combination::new("Hello", &buffer, &gt, 64);
+ //assert!(s5.skip_me() == "Hello");
+ assert!(s5.buffer() == &buffer);
+ //assert!(s5.v3() == &gt);
+ assert!(*s5.keep_me() == 64);
+}
diff --git a/tests/06-plays-with-others.rs b/tests/06-plays-with-others.rs
new file mode 100644
index 0000000..b50fb24
--- /dev/null
+++ b/tests/06-plays-with-others.rs
@@ -0,0 +1,33 @@
+use serde::{Serialize, Deserialize};
+use derive_getters::Getters;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Getters, Serialize, Deserialize)]
+struct Plays {
+ #[getter(rename = "skip_me")]
+ #[getter(skip)]
+ v1: u64,
+
+ #[serde(skip)]
+ #[getter(rename = "buffer")]
+ v2: [u8; 12],
+
+ #[getter(skip)]
+ #[getter(rename = "keep_me")]
+ #[serde(rename = "value3")]
+ v3: u64,
+}
+
+impl Plays {
+ pub fn new(v1: u64, v2: [u8; 12], v3: u64) -> Self {
+ Plays { v1, v2, v3 }
+ }
+}
+
+fn main() {
+ let buffer: [u8; 12] = [88; 12];
+ let c = Plays::new(46, buffer, 64);
+ //assert!(c.skip_me() == "Hello");
+ assert!(c.buffer() == &buffer);
+ //assert!(c.v3() == &gt);
+ assert!(*c.keep_me() == 64);
+}
diff --git a/tests/07-dissolve-basic.rs b/tests/07-dissolve-basic.rs
new file mode 100644
index 0000000..302d1cc
--- /dev/null
+++ b/tests/07-dissolve-basic.rs
@@ -0,0 +1,72 @@
+//! First tests for new `dissolve` functionality
+use serde::{Serialize, Deserialize};
+use derive_getters::Dissolve;
+
+#[derive(Dissolve, Serialize, Deserialize)]
+struct Number {
+ num: u64,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
+struct Inner {
+ a: u64,
+ b: i64,
+}
+
+#[derive(Dissolve, Serialize, Deserialize)]
+struct ManyStuff {
+ name: String,
+ price: f64,
+ count: usize,
+ inner: Inner,
+}
+
+#[derive(Dissolve, Serialize, Deserialize)]
+#[dissolve(rename = "shatter")]
+struct LotsOfStuff {
+ name: String,
+ price: f64,
+ count: usize,
+ inner: Inner,
+}
+
+impl LotsOfStuff {
+ fn dissolve(&self) -> f64 {
+ self.inner.b as f64 * self.price
+ }
+}
+
+fn main() {
+ let n = Number { num: 64 };
+ let number = n.dissolve();
+ assert!(number == 64);
+
+ let inner = Inner { a: 22, b: -33 };
+ let stuff = ManyStuff {
+ name: "Hogie".to_owned(),
+ price: 123.4f64,
+ count: 100,
+ inner,
+ };
+ let (n, p, c, i) = stuff.dissolve();
+ assert!(n == "Hogie");
+ assert!(p == 123.4f64);
+ assert!(c == 100);
+ assert!(i == inner);
+
+ //let _ = stuff.dissolve();
+
+ let stuff = LotsOfStuff {
+ name: "Hogie".to_owned(),
+ price: 123.4f64,
+ count: 100,
+ inner,
+ };
+ let (n, p, c, i) = stuff.shatter();
+ assert!(n == "Hogie");
+ assert!(p == 123.4f64);
+ assert!(c == 100);
+ assert!(i == inner);
+
+ //let _ = stuff.shatter();
+}
diff --git a/tests/08-dissolve-generic-and-ref.rs b/tests/08-dissolve-generic-and-ref.rs
new file mode 100644
index 0000000..860837a
--- /dev/null
+++ b/tests/08-dissolve-generic-and-ref.rs
@@ -0,0 +1,62 @@
+//! Try with generics and references.
+
+use derive_getters::{Getters, Dissolve};
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+struct ConcreteType {
+ a: u64,
+ b: i64,
+}
+
+#[derive(Getters, Dissolve)]
+struct MultiAnnotated<'a, 'b, 'c, T> {
+ v1: &'a str,
+ v2: &'b [u8],
+ v3: &'c T,
+ owned: String,
+}
+
+impl<'a, 'b, 'c, T> MultiAnnotated<'a, 'b, 'c, T> {
+ pub fn new(v1: &'a str, v2: &'b [u8], v3: &'c T, owned: String) -> Self {
+ MultiAnnotated { v1, v2, v3, owned }
+ }
+}
+
+#[derive(Getters, Dissolve)]
+#[dissolve(rename = "unmake")]
+struct PolyAnnotated<'a, 'b, 'c, T> {
+ v1: &'a str,
+ v2: &'b [u8],
+ v3: &'c T,
+ owned: String,
+}
+
+impl<'a, 'b, 'c, T> PolyAnnotated<'a, 'b, 'c, T> {
+ pub fn new(v1: &'a str, v2: &'b [u8], v3: &'c T, owned: String) -> Self {
+ PolyAnnotated { v1, v2, v3, owned }
+ }
+
+ pub fn dissolve(self) -> String {
+ self.owned
+ }
+}
+
+fn main() {
+ let buffer: [u8; 12] = [88; 12];
+ let gt = ConcreteType { a: 44, b: -100 };
+ let ma = MultiAnnotated::new("Hi", &buffer, &gt, "Another".to_owned());
+
+ let (v1, v2, v3, owned) = ma.dissolve();
+ assert!(v1 == "Hi");
+ assert!(v2 == &buffer);
+ assert!(*v3 == gt);
+ assert!(owned == "Another");
+
+ let pa = PolyAnnotated::new("Hi", &buffer, &gt, "Another".to_owned());
+ let (v1, v2, v3, owned) = pa.unmake();
+ assert!(v1 == "Hi");
+ assert!(v2 == &buffer);
+ assert!(*v3 == gt);
+ assert!(owned == "Another");
+}
+
diff --git a/tests/lib.rs b/tests/lib.rs
new file mode 100644
index 0000000..7d67468
--- /dev/null
+++ b/tests/lib.rs
@@ -0,0 +1,22 @@
+#[test]
+fn tests() {
+ let t = trybuild::TestCases::new();
+ t.pass("tests/01-legacy.rs");
+ t.pass("tests/02-simple-single-generic.rs");
+ t.pass("tests/03-simple-multi-generic.rs");
+ t.pass("tests/04-simple-lifetime-annot.rs");
+ t.pass("tests/05-skip-rename-attributes.rs");
+ t.pass("tests/06-plays-with-others.rs");
+ t.pass("tests/07-dissolve-basic.rs");
+ t.pass("tests/08-dissolve-generic-and-ref.rs");
+}
+
+#[test]
+fn test_readme_deps() {
+ version_sync::assert_markdown_deps_updated!("README.md");
+}
+
+#[test]
+fn test_html_root_url() {
+ version_sync::assert_html_root_url_updated!("src/lib.rs");
+}