Skip to content

Commit

Permalink
Support unnamed extension trait
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Mar 4, 2020
1 parent d8b9a94 commit 42a501b
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 7 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ impl<T, E> ResultExt<T, E> for Result<T, E> {
}
```

You can elide the trait name. Note that in this case, `#[ext]` assigns a random name, so you cannot import/export the generated trait.

```rust
use easy_ext::ext;

#[ext]
impl<T, E> Result<T, E> {
fn err_into<U>(self) -> Result<T, U>
where
E: Into<U>,
{
self.map_err(Into::into)
}
}
```

### Supported items

* [Methods](https://doc.rust-lang.org/book/ch05-03-method-syntax.html)
Expand Down
78 changes: 73 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
attr(deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code))
))]
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, single_use_lifetimes, unreachable_pub)]
#![warn(rust_2018_idioms, unreachable_pub)]
// It cannot be included in the published code because these lints have false positives in the minimum required version.
#![cfg_attr(test, warn(single_use_lifetimes))]
#![warn(clippy::all)]
// mem::take requires Rust 1.40
#![allow(clippy::mem_replace_with_default)]
Expand All @@ -56,10 +58,14 @@
#[allow(unused_extern_crates)]
extern crate proc_macro;

use std::mem;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
mem,
};

use proc_macro::TokenStream;
use quote::ToTokens;
use proc_macro::{Delimiter, Spacing, TokenStream, TokenTree};
use quote::{format_ident, ToTokens};
use syn::{punctuated::Punctuated, *};

macro_rules! error {
Expand Down Expand Up @@ -121,8 +127,12 @@ macro_rules! error {
/// * The visibility of all the items in the original `impl` must be identical.
#[proc_macro_attribute]
pub fn ext(args: TokenStream, input: TokenStream) -> TokenStream {
let ext_ident = match syn::parse_macro_input!(args) {
None => format_ident!("__ExtTrait{}", hash(&input)),
Some(ext_ident) => ext_ident,
};

let mut item: ItemImpl = syn::parse_macro_input!(input);
let ext_ident: Ident = syn::parse_macro_input!(args);

trait_from_item(&mut item, ext_ident)
.map(ToTokens::into_token_stream)
Expand Down Expand Up @@ -233,3 +243,61 @@ fn from_method(impl_method: &ImplItemMethod) -> TraitItemMethod {
fn find_remove(attrs: &mut Vec<Attribute>, ident: &str) -> Option<Attribute> {
attrs.iter().position(|attr| attr.path.is_ident(ident)).map(|i| attrs.remove(i))
}

// =================================================================================================
// Hash

/// Returns the hash value of the input AST.
fn hash(tokens: &TokenStream) -> u64 {
let tokens = TokenStreamHelper(tokens);
let mut hasher = DefaultHasher::new();
tokens.hash(&mut hasher);
hasher.finish()
}

// Based on https://github.com/dtolnay/syn/blob/1.0.5/src/tt.rs

struct TokenTreeHelper<'a>(&'a TokenTree);

impl Hash for TokenTreeHelper<'_> {
fn hash<H: Hasher>(&self, h: &mut H) {
match self.0 {
TokenTree::Group(g) => {
0_u8.hash(h);
match g.delimiter() {
Delimiter::Parenthesis => 0_u8.hash(h),
Delimiter::Brace => 1_u8.hash(h),
Delimiter::Bracket => 2_u8.hash(h),
Delimiter::None => 3_u8.hash(h),
}

for tt in g.stream() {
TokenTreeHelper(&tt).hash(h);
}
0xff_u8.hash(h); // terminator w/ a variant we don't normally hash
}
TokenTree::Punct(op) => {
1_u8.hash(h);
op.as_char().hash(h);
match op.spacing() {
Spacing::Alone => 0_u8.hash(h),
Spacing::Joint => 1_u8.hash(h),
}
}
TokenTree::Literal(lit) => (2_u8, lit.to_string()).hash(h),
TokenTree::Ident(word) => (3_u8, word.to_string()).hash(h),
}
}
}

struct TokenStreamHelper<'a>(&'a TokenStream);

impl Hash for TokenStreamHelper<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
let tokens = self.0.clone().into_iter().collect::<Vec<_>>();
tokens.len().hash(state);
for tt in tokens {
TokenTreeHelper(&tt).hash(state);
}
}
}
4 changes: 2 additions & 2 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use easy_ext::ext;

#[test]
fn test_simple() {
#[ext(StrExt)]
#[ext]
impl str {
fn foo(&self, pat: &str) -> String {
self.replace(pat, "_")
Expand All @@ -17,7 +17,7 @@ fn test_simple() {

#[test]
fn test_params() {
#[ext(ResultExt)]
#[ext]
impl<T, E> Result<T, E> {
fn err_into<U>(self) -> Result<T, U>
where
Expand Down
1 change: 1 addition & 0 deletions tests/ui/visibility-3.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ note: the trait `StrExt` is defined here
|
4 | #[ext(StrExt)]
| ^^^^^^^^^^^^^^
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

0 comments on commit 42a501b

Please sign in to comment.