Skip to content

0.6: support Deref<Target = T>; new-type pattern #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.0] — 2022-11-17

- Add `ImplTrait::support_path_args`, `ImplArgs::path_args` (#26)
- Path args: support `Deref<Target = Foo>` (#26)

## [0.5.2] — 2022-10-06

- Add `singleton!` macro (#25)
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "impl-tools"
version = "0.5.2"
version = "0.6.0"
authors = ["Diggory Hardy <[email protected]>"]
edition = "2021"
license = "MIT/Apache-2.0"
Expand All @@ -21,7 +21,7 @@ proc-macro-error = "1.0"
version = "1.0.14"

[dependencies.impl-tools-lib]
version = "0.5.2"
version = "0.6.0"
path = "lib"

[dev-dependencies]
Expand Down
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,16 @@ Unlike [alternatives](#alternatives), `#[autoimpl]` has minimal and intuitive sy
use impl_tools::autoimpl;
use std::fmt::Debug;

#[autoimpl(for<'a, T: trait + ?Sized> Box<T>)]
// Generates: impl<'a, T: Animal + ?Sized> Animal for Box<T> { .. }
// Impl Animal for Box<T> where T: Animal + ?Sized
#[autoimpl(for<T: trait + ?Sized> Box<T>)]
trait Animal {
fn number_of_legs(&self) -> u32;
}

// Impl Debug for Named<T, A: Animal> omitting field animal from output
#[autoimpl(Debug ignore self.animal where T: Debug)]
// Generates: impl<T, A: Animal> std::fmt::Debug for Named<A> where T: Debug { .. }
// Impl Deref and DerefMut to field animal for Named<T, A: Animal>
#[autoimpl(Deref, DerefMut using self.animal)]
// Generates: impl<T, A: Animal> std::ops::Deref for Named<A> { .. }
// Generates: impl<T, A: Animal> std::ops::DerefMut for Named<A> { .. }
struct Named<T, A: Animal> {
name: T,
animal: A,
Expand All @@ -66,6 +65,47 @@ fn main() {
}
```

#### New-type wrappers

A combination of `Deref` on the new-type and trait-reimplementation on the
trait allows succinct new-type patterns:

```rust
use impl_tools::autoimpl;
use std::sync::Arc;

// Impl Foo for &T, &mut T and Arc<T>
#[autoimpl(for<T: trait + ?Sized> &T, &mut T, Arc<T>)]
// Optional: impl Foo for NewFoo (requires NewFoo: Deref<Target = T>)
#[autoimpl(for<T: trait> NewFoo<T>)]
pub trait Foo {
fn success(&self) -> bool;
}

// Impl Deref and DerefMut to a Target which itself supports Foo
#[autoimpl(Deref<Target = T>, DerefMut using self.0)]
pub struct NewFoo<T: Foo>(T);

// Impl Deref and DerefMut to a Target which itself supports Foo
#[autoimpl(Deref<Target = dyn Foo>, DerefMut using self.0)]
pub struct ArcDynFoo(Arc<dyn Foo>);

#[test]
fn test_foo_newtypes() {
struct Success;
impl Foo for Success {
fn success(&self) -> bool { true }
}

// We can now directly call Foo's methods on the wrapper:
assert!(NewFoo(Success).success());
assert!(ArcDynFoo(Arc::new(Success)).success());
}
```

See [`tests/newtype.rs`](https://github.com/kas-gui/impl-tools/blob/master/tests/newtype.rs) for more variants of this pattern.


### Impl Default

`#[impl_default]` implements `std::default::Default`:
Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "impl-tools-lib"
version = "0.5.2"
version = "0.6.0"
authors = ["Diggory Hardy <[email protected]>"]
edition = "2021"
license = "MIT/Apache-2.0"
Expand Down
62 changes: 49 additions & 13 deletions lib/src/autoimpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use proc_macro_error::emit_error;
use quote::{quote, TokenStreamExt};
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{parse2, Field, Fields, Ident, Index, Item, ItemStruct, Member, Path, Token};
use syn::{
parse2, Field, Fields, Ident, Index, Item, ItemStruct, Member, Path, PathArguments, Token,
};

mod impl_misc;
mod impl_using;
Expand Down Expand Up @@ -46,6 +48,11 @@ pub trait ImplTrait {
/// This path is matched against trait names in `#[autoimpl]` parameters.
fn path(&self) -> SimplePath;

/// True if this target supports path arguments
fn support_path_arguments(&self) -> bool {
false
}

/// True if this target supports ignoring fields
///
/// Default implementation: `false`
Expand Down Expand Up @@ -133,6 +140,8 @@ pub enum Error {
CallSite(&'static str),
/// Emit an error with the given `span` and `message`
WithSpan(Span, &'static str),
/// Emit an error regarding path arguments
PathArguments(&'static str),
}

/// Result type
Expand Down Expand Up @@ -211,6 +220,7 @@ mod parsing {
}

let args = ImplArgs {
path_arguments: PathArguments::None,
ignores,
using,
clause,
Expand All @@ -226,10 +236,15 @@ impl ImplTraits {
/// This attribute does not modify the item.
/// The caller should append the result to `item` tokens.
pub fn expand(
mut self,
self,
item: Toks,
find_impl: impl Fn(&Path) -> Option<&'static dyn ImplTrait>,
) -> Toks {
let ImplTraits {
mut targets,
mut args,
} = self;

let item = match parse2::<Item>(item) {
Ok(Item::Struct(item)) => item,
Ok(item) => {
Expand All @@ -245,8 +260,14 @@ impl ImplTraits {
let mut not_supporting_ignore = vec![];
let mut not_supporting_using = vec![];

let mut impl_targets: Vec<(Span, _)> = Vec::with_capacity(self.targets.len());
for target in self.targets.drain(..) {
let mut impl_targets: Vec<(Span, _, _)> = Vec::with_capacity(targets.len());
for mut target in targets.drain(..) {
let target_span = target.span();
let path_args = target
.segments
.last_mut()
.map(|seg| std::mem::take(&mut seg.arguments))
.unwrap_or(PathArguments::None);
let target_impl = match find_impl(&target) {
Some(impl_) => impl_,
None => {
Expand All @@ -262,26 +283,33 @@ impl ImplTraits {
if !target_impl.support_using() {
not_supporting_using.push(target.clone());
}
if !(path_args.is_empty() || target_impl.support_path_arguments()) {
emit_error!(
target_span,
"target {} does not support path arguments",
target_impl.path()
);
}

impl_targets.push((target.span(), target_impl));
impl_targets.push((target.span(), target_impl, path_args));
}

if !self.args.ignores.is_empty() {
if !args.ignores.is_empty() {
for (target, except_with) in not_supporting_ignore.into_iter() {
if let Some(path) = except_with {
if impl_targets
.iter()
.any(|(_span, target_impl)| path == target_impl.path())
.any(|(_, target_impl, _)| path == target_impl.path())
{
continue;
}
}
emit_error!(target, "target does not support `ignore`",);
}
}
if self.args.using.is_some() {
if args.using.is_some() {
for target in not_supporting_using.into_iter() {
emit_error!(target, "`target does not support `using`",);
emit_error!(target, "target does not support `using`",);
}
}

Expand All @@ -307,22 +335,25 @@ impl ImplTraits {
}

let mut toks = Toks::new();
for mem in &self.args.ignores {
for mem in &args.ignores {
check_is_field(mem, &item.fields);
}
if let Some(mem) = self.args.using_member() {
if let Some(mem) = args.using_member() {
check_is_field(mem, &item.fields);
}

for (span, target) in impl_targets.drain(..) {
match target.struct_impl(&item, &self.args) {
for (span, target, path_args) in impl_targets.drain(..) {
let path_args_span = path_args.span();
args.path_arguments = path_args;
match target.struct_impl(&item, &args) {
Ok(items) => toks.append_all(items),
Err(error) => match error {
Error::RequireUsing => {
emit_error!(span, "target requires argument `using self.FIELD`")
}
Error::CallSite(msg) => emit_error!(span, msg),
Error::WithSpan(span, msg) => emit_error!(span, msg),
Error::PathArguments(msg) => emit_error!(path_args_span, msg),
},
}
}
Expand All @@ -332,6 +363,11 @@ impl ImplTraits {

/// Arguments passed to [`ImplTrait`] implementation methods
pub struct ImplArgs {
/// Path arguments to trait
///
/// Example: if the target is `Deref<Target = T>`, this is `<Target = T>`.
/// This is always empty unless [`ImplTrait::support_path_args`] returns true.
pub path_arguments: PathArguments,
/// Fields ignored in attribute
pub ignores: Vec<Member>,
/// Field specified to 'use' in attribute
Expand Down
33 changes: 30 additions & 3 deletions lib/src/autoimpl/impl_using.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use super::{Error, ImplArgs, ImplTrait, Result};
use crate::SimplePath;
use proc_macro2::TokenStream as Toks;
use quote::quote;
use syn::ItemStruct;
use syn::{ItemStruct, PathArguments};

/// Implement [`core::borrow::Borrow`]
pub struct ImplBorrow;
Expand Down Expand Up @@ -126,16 +126,43 @@ impl ImplTrait for ImplDeref {
SimplePath::new(&["", "core", "ops", "Deref"])
}

fn support_path_arguments(&self) -> bool {
true
}

fn support_using(&self) -> bool {
true
}

fn struct_items(&self, item: &ItemStruct, args: &ImplArgs) -> Result<(Toks, Toks)> {
if let Some(field) = args.using_field(&item.fields) {
let ty = field.ty.clone();
let target = match args.path_arguments {
PathArguments::None => field.ty.clone(),
PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
ref args,
..
}) => {
let mut result = None;
for arg in args {
if let syn::GenericArgument::Binding(b) = arg {
if b.ident == "Target" && result.is_none() {
result = Some(b.ty.clone());
continue;
}
}
return Err(Error::PathArguments("expected `<Target = ..>`"));
}
match result {
Some(r) => r,
None => return Err(Error::PathArguments("expected `<Target = ..>`")),
}
}
PathArguments::Parenthesized(_) => return Err(Error::PathArguments("unexpected")),
};

let member = args.using_member().unwrap();
let method = quote! {
type Target = #ty;
type Target = #target;
fn deref(&self) -> &Self::Target {
&self.#member
}
Expand Down
13 changes: 13 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ pub use singleton::{Singleton, SingletonField, SingletonScope};
#[derive(PartialEq, Eq)]
pub struct SimplePath(&'static [&'static str]);

impl std::fmt::Display for SimplePath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
if !self.0.is_empty() {
write!(f, "{}", self.0[0])?;
for component in &self.0[1..] {
write!(f, "::{}", component)?;
}
}

Ok(())
}
}

impl SimplePath {
/// Construct, verifying validity
///
Expand Down
Loading