Skip to content

Commit

Permalink
Derive AsRef and AsMut (JelteF#88)
Browse files Browse the repository at this point in the history
* add stubs

* document new feature

* expand as_ref impl

* expand as_mut impl

* address ci issues
  • Loading branch information
reynoldsbd authored and JelteF committed Sep 15, 2019
1 parent 6d1dbe3 commit d7a03c9
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 2 deletions.
22 changes: 22 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ appveyor = { repository = "JelteF/derive_more" }
nightly = []
add_assign_like = []
add_like = []
as_mut = []
as_ref = []
constructor = []
deref = []
deref_mut = []
Expand All @@ -65,6 +67,8 @@ generate-parsing-rs = ["peg"]
default = [
"add_assign_like",
"add_like",
"as_mut",
"as_ref",
"constructor",
"deref",
"deref_mut",
Expand Down Expand Up @@ -95,6 +99,16 @@ name = "add"
path = "tests/add.rs"
required-features = ["add_like"]

[[test]]
name = "as_mut"
path = "tests/as_mut.rs"
required-features = ["as_mut"]

[[test]]
name = "as_ref"
path = "tests/as_ref.rs"
required-features = ["as_ref"]

[[test]]
name = "boats_display_derive"
path = "tests/boats_display_derive.rs"
Expand Down Expand Up @@ -191,6 +205,8 @@ path = "tests/no_std.rs"
required-features = [
"add_assign_like",
"add_like",
"as_mut",
"as_ref",
"constructor",
"deref",
"deref_mut",
Expand All @@ -213,6 +229,8 @@ path = "tests/generics.rs"
required-features = [
"add_assign_like",
"add_like",
"as_mut",
"as_ref",
"constructor",
"deref",
"deref_mut",
Expand All @@ -234,6 +252,8 @@ path = "tests/lib.rs"
required-features = [
"add_assign_like",
"add_like",
"as_mut",
"as_ref",
"constructor",
"deref",
"deref_mut",
Expand All @@ -255,6 +275,8 @@ path = "examples/deny_missing_docs.rs"
required-features = [
"add_assign_like",
"add_like",
"as_mut",
"as_ref",
"constructor",
"deref",
"deref_mut",
Expand Down
76 changes: 76 additions & 0 deletions doc/as_mut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
% What #[derive(AsMut)] generates

Deriving `AsMut` generates one or more implementations of `AsMut`, each corresponding to one of the
fields of the decorated type. This allows types which contain some `T` to be passed anywhere that an
`AsMut<T>` is accepted.

# Newtypes and Structs with One Field

When `AsMut` is derived for a newtype or struct with one field, a single implementation is generated
to expose the underlying field.

```rust
#[derive(AsMut)]
struct MyWrapper(String);



// Generates:

impl AsMut<String> for MyWrapper {
fn as_mut(&mut self) -> &mut String {
&mut self.0
}
}
```

# Structs with Multiple Fields

When `AsMut` is derived for a struct with more than one field (including tuple structs), you must
also mark one or more fields with the `#[as_mut]` attribute. An implementation will be generated for
each indicated field.

```rust
#[derive(AsMut)]
struct MyWrapper {
#[as_mut]
name: String,
#[as_mut]
path: Path,
valid: bool,
}



// Generates:

impl AsMut<String> for MyWrapper {
fn as_mut(&mut self) -> &mut String {
&mut self.name
}
}

impl AsMut<Path> for MyWrapper {
fn as_mut(&mut self) -> &mut Path {
&mut self.path
}
}
```

Note that `AsMut<T>` may only be implemented once for any given type `T`. This means any attempt to
mark more than one field of the same type with `#[as_mut]` will result in a compilation error.

```rust
// Error! Conflicting implementations of AsMut<String>
#[derive(AsMut)]
struct MyWrapper {
#[as_mut]
str1: String,
#[as_mut]
str2: String,
}
```

# Enums

Deriving `AsMut` for enums is not supported.
76 changes: 76 additions & 0 deletions doc/as_ref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
% What #[derive(AsRef)] generates

Deriving `AsRef` generates one or more implementations of `AsRef`, each corresponding to one of the
fields of the decorated type. This allows types which contain some `T` to be passed anywhere that an
`AsRef<T>` is accepted.

# Newtypes and Structs with One Field

When `AsRef` is derived for a newtype or struct with one field, a single implementation is generated
to expose the underlying field.

```rust
#[derive(AsRef)]
struct MyWrapper(String);



// Generates:

impl AsRef<String> for MyWrapper {
fn as_ref(&self) -> &String {
&self.0
}
}
```

# Structs with Multiple Fields

When `AsRef` is derived for a struct with more than one field (including tuple structs), you must
also mark one or more fields with the `#[as_ref]` attribute. An implementation will be generated for
each indicated field.

```rust
#[derive(AsRef)]
struct MyWrapper {
#[as_ref]
name: String,
#[as_ref]
path: Path,
valid: bool,
}



// Generates:

impl AsRef<String> for MyWrapper {
fn as_ref(&self) -> &String {
&self.name
}
}

impl AsRef<Path> for MyWrapper {
fn as_ref(&self) -> &Path {
&self.path
}
}
```

Note that `AsRef<T>` may only be implemented once for any given type `T`. This means any attempt to
mark more than one field of the same type with `#[as_ref]` will result in a compilation error.

```rust
// Error! Conflicting implementations of AsRef<String>
#[derive(AsRef)]
struct MyWrapper {
#[as_ref]
str1: String,
#[as_ref]
str2: String,
}
```

# Enums

Deriving `AsRef` for enums is not supported.
23 changes: 23 additions & 0 deletions src/as_mut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
use crate::utils;


pub fn expand(input: &DeriveInput, _: &str) -> TokenStream {

let input_type = &input.ident;
let (impl_generics, input_generics, where_clause) = input.generics.split_for_impl();
let (field_type, field_ident) = utils::extract_field_info(&input.data, "as_mut");

quote! {#(
impl#impl_generics ::core::convert::AsMut<#field_type> for #input_type#input_generics
#where_clause
{
#[inline]
fn as_mut(&mut self) -> &mut #field_type {
&mut self.#field_ident
}
}
)*}
}
23 changes: 23 additions & 0 deletions src/as_ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
use crate::utils;


pub fn expand(input: &DeriveInput, _: &str) -> TokenStream {

let input_type = &input.ident;
let (impl_generics, input_generics, where_clause) = input.generics.split_for_impl();
let (field_type, field_ident) = utils::extract_field_info(&input.data, "as_ref");

quote! {#(
impl#impl_generics ::core::convert::AsRef<#field_type> for #input_type#input_generics
#where_clause
{
#[inline]
fn as_ref(&self) -> &#field_type {
&self.#field_ident
}
}
)*}
}
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ mod add_assign_like;
mod add_helpers;
#[cfg(feature = "add_like")]
mod add_like;
#[cfg(feature = "as_mut")]
mod as_mut;
#[cfg(feature = "as_ref")]
mod as_ref;
#[cfg(feature = "constructor")]
mod constructor;
#[cfg(feature = "deref")]
Expand Down Expand Up @@ -435,3 +439,6 @@ create_derive!(
DerefMutToInner,
deref_mut_to_inner_derive
);

create_derive!("as_ref", as_ref, AsRef, as_ref_derive, as_ref);
create_derive!("as_mut", as_mut, AsMut, as_mut_derive, as_mut);
64 changes: 62 additions & 2 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse_str, Field, FieldsNamed, FieldsUnnamed, GenericParam, Generics, Ident, Index, Type,
TypeParamBound, WhereClause,
parse_str, Data, Field, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics, Ident,
Index, Type, TypeParamBound, WhereClause,
};

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -196,3 +196,63 @@ pub fn unnamed_to_vec(fields: &FieldsUnnamed) -> Vec<&Field> {
pub fn named_to_vec(fields: &FieldsNamed) -> Vec<&Field> {
fields.named.iter().collect()
}


/// Checks whether `field` is decorated with the specifed simple attribute (e.g. `#[as_ref]`)
fn has_simple_attr(field: &Field, attr: &str) -> bool {
field.attrs.iter().any(|a| {
a.parse_meta()
.map(|m| {
m.path()
.segments
.first()
.map(|p| p.ident == attr)
.unwrap_or(false)
})
.unwrap_or(false)
})
}


/// Extracts types and identifiers from fields in the given struct
///
/// If `data` contains more than one field, only fields decorated with `attr` are considered.
pub fn extract_field_info<'a>(data: &'a Data, attr: &str) -> (Vec<&'a Type>, Vec<TokenStream>) {

// Get iter over fields and check named/unnamed
let named;
let fields = match data {
Data::Struct(data) => match data.fields {
Fields::Named(_) => {
named = true;
data.fields.iter()
},
Fields::Unnamed(_) => {
named = false;
data.fields.iter()
},
Fields::Unit => panic!("struct must have one or more fields"),
},
_ => panic!("only structs may derive this trait"),
};

// If necessary, filter out undecorated fields
let len = fields.len();
let fields = fields.filter(|f| len == 1 || has_simple_attr(f, attr));

// Extract info needed to generate impls
if named {
fields.map(|f| {
let ident = f.ident.as_ref().unwrap();
(&f.ty, quote!(#ident))
})
.unzip()
} else {
fields.enumerate()
.map(|(i, f)| {
let index = Index::from(i);
(&f.ty, quote!(#index))
})
.unzip()
}
}
Loading

0 comments on commit d7a03c9

Please sign in to comment.