Skip to content

Commit

Permalink
Add the pin_project attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed May 14, 2019
1 parent 8fbdcd3 commit 268623a
Show file tree
Hide file tree
Showing 5 changed files with 354 additions and 90 deletions.
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@

extern crate proc_macro;

#[cfg(feature = "project_attr")]
mod pin_project;
#[cfg(feature = "project_attr")]
mod project;
mod unsafe_project;
Expand Down Expand Up @@ -426,3 +428,9 @@ pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {
assert!(args.is_empty());
TokenStream::from(project::attribute(input.into()))
}

#[cfg(feature = "project_attr")]
#[proc_macro_attribute]
pub fn pin_project(args: TokenStream, input: TokenStream) -> TokenStream {
TokenStream::from(pin_project::attribute(args.into(), input.into()))
}
150 changes: 150 additions & 0 deletions src/pin_project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::convert::identity;

use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
visit_mut::VisitMut,
*,
};

use crate::{project::dummy, utils::default};

pub(super) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream {
syn::parse2(input)
.and_then(|mut item| {
syn::parse2(args).map(|args: Args| {
// TODO: Integrate into `replace_item_fn`?.
dummy(&mut item);
replace_item_fn(&args.0, &mut item);
item.into_token_stream()
})
})
.map_err(|err| err.to_compile_error())
.unwrap_or_else(identity)
}

fn replace_item_fn(args: &[Ident], ItemFn { decl, block, .. }: &mut ItemFn) {
decl.inputs.iter_mut().for_each(|input| match input {
FnArg::Captured(ArgCaptured {
pat: Pat::Ident(pat @ PatIdent { subpat: None, .. }),
..
}) if args.contains(&pat.ident) => {
let mut local = Local {
attrs: Vec::new(),
let_token: default(),
pats: default(),
ty: None,
init: None,
semi_token: default(),
};
let (local_pat, init) = if pat.ident == "self" {
ReplaceSelf.visit_block_mut(block);
let mut local_pat = pat.clone();
prepend_underscores_to_self(&mut local_pat.ident);
(local_pat, syn::parse_quote!(self.project()))
} else {
let ident = &pat.ident;
(pat.clone(), syn::parse_quote!(#ident.project()))
};
local.pats.push(Pat::Ident(local_pat));
local.init = Some((default(), init));
block.stmts.insert(0, Stmt::Local(local));

if pat.by_ref.is_none() {
pat.mutability = None;
}
pat.by_ref = None;
}
_ => {}
})
}

struct Args(Vec<Ident>);

impl Parse for Args {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut args = Vec::new();
let mut first = true;
while !input.is_empty() {
if first {
first = false;
} else {
let _: Token![,] = input.parse()?;
if input.is_empty() {
break;
}
}

let ident = if input.peek(Token![self]) {
let t: Token![self] = input.parse()?;
Ident::new("self", t.span)
} else {
input.parse()?
};
if args.contains(&ident) {
// TODO: error
} else {
args.push(ident);
}
}
Ok(Self(args))
}
}

// https://github.com/dtolnay/no-panic/blob/master/src/lib.rs

struct ReplaceSelf;

impl VisitMut for ReplaceSelf {
fn visit_expr_path_mut(&mut self, i: &mut ExprPath) {
if i.qself.is_none() && i.path.is_ident("self") {
prepend_underscores_to_self(&mut i.path.segments[0].ident);
}
}

fn visit_macro_mut(&mut self, i: &mut Macro) {
// We can't tell in general whether `self` inside a macro invocation
// refers to the self in the argument list or a different self
// introduced within the macro. Heuristic: if the macro input contains
// `fn`, then `self` is more likely to refer to something other than the
// outer function's self argument.
if !contains_fn(i.tts.clone()) {
i.tts = fold_token_stream(i.tts.clone());
}
}

fn visit_item_mut(&mut self, _i: &mut Item) {
// Do nothing, as `self` now means something else.
}
}

fn contains_fn(tts: TokenStream) -> bool {
tts.into_iter().any(|tt| match tt {
TokenTree::Ident(ident) => ident == "fn",
TokenTree::Group(group) => contains_fn(group.stream()),
_ => false,
})
}

fn fold_token_stream(tts: TokenStream) -> TokenStream {
tts.into_iter()
.map(|tt| match tt {
TokenTree::Ident(mut ident) => {
prepend_underscores_to_self(&mut ident);
TokenTree::Ident(ident)
}
TokenTree::Group(group) => {
let content = fold_token_stream(group.stream());
TokenTree::Group(Group::new(group.delimiter(), content))
}
other => other,
})
.collect()
}

fn prepend_underscores_to_self(ident: &mut Ident) {
if ident == "self" {
*ident = Ident::new("__self", ident.span());
}
}
177 changes: 87 additions & 90 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ use std::convert::identity;

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{punctuated::Punctuated, token::Or, *};
use syn::{
punctuated::Punctuated,
token::Or,
visit_mut::{self, VisitMut},
*,
};

use crate::utils::{proj_ident, Result};
use crate::utils::{proj_ident, Result, VecExt};

/// The attribute name.
const NAME: &str = "project";
Expand All @@ -18,7 +23,7 @@ fn parse(input: TokenStream) -> Result<TokenStream> {
match stmt {
Stmt::Expr(expr) => expr.replace(&mut Register::default()),
Stmt::Local(local) => local.replace(&mut Register::default()),
Stmt::Item(Item::Fn(item)) => visitor::dummy(item),
Stmt::Item(Item::Fn(item)) => dummy(item),
_ => {}
}
}
Expand Down Expand Up @@ -149,109 +154,101 @@ impl Register {
}
}

mod visitor {
use syn::visit_mut::{self, VisitMut};

use crate::utils::VecExt;
pub(crate) fn dummy(item: &mut ItemFn) {
Dummy.visit_item_fn_mut(item)
}

use super::*;
struct Dummy;

pub(super) fn dummy(item: &mut ItemFn) {
Dummy.visit_item_fn_mut(item)
impl VisitMut for Dummy {
fn visit_stmt_mut(&mut self, stmt: &mut Stmt) {
visit_mut::visit_stmt_mut(self, stmt);
visit_stmt_mut(stmt);
}

struct Dummy;
// Stop at item bounds
fn visit_item_mut(&mut self, _item: &mut Item) {}
}

impl VisitMut for Dummy {
fn visit_stmt_mut(&mut self, stmt: &mut Stmt) {
visit_mut::visit_stmt_mut(self, stmt);
visit_stmt_mut(stmt);
fn visit_stmt_mut(stmt: &mut Stmt) {
fn parse_attr<A: AttrsMut + Replace>(attrs: &mut A) {
if attrs.find_remove() {
attrs.replace(&mut Register::default());
}

// Stop at item bounds
fn visit_item_mut(&mut self, _item: &mut Item) {}
}

fn visit_stmt_mut(stmt: &mut Stmt) {
fn parse_attr<A: AttrsMut + Replace>(attrs: &mut A) {
if attrs.find_remove() {
attrs.replace(&mut Register::default());
}
}

match stmt {
Stmt::Expr(expr) => parse_attr(expr),
Stmt::Local(local) => parse_attr(local),
_ => {}
}
match stmt {
Stmt::Expr(expr) => parse_attr(expr),
Stmt::Local(local) => parse_attr(local),
_ => {}
}
}

trait AttrsMut {
fn attrs_mut<T, F: FnOnce(&mut Vec<Attribute>) -> T>(&mut self, f: F) -> T;
trait AttrsMut {
fn attrs_mut<T, F: FnOnce(&mut Vec<Attribute>) -> T>(&mut self, f: F) -> T;

fn find_remove(&mut self) -> bool {
self.attrs_mut(|attrs| attrs.find_remove(NAME))
}
fn find_remove(&mut self) -> bool {
self.attrs_mut(|attrs| attrs.find_remove(NAME))
}
}

impl AttrsMut for Local {
fn attrs_mut<T, F: FnOnce(&mut Vec<Attribute>) -> T>(&mut self, f: F) -> T {
f(&mut self.attrs)
}
impl AttrsMut for Local {
fn attrs_mut<T, F: FnOnce(&mut Vec<Attribute>) -> T>(&mut self, f: F) -> T {
f(&mut self.attrs)
}
}

macro_rules! attrs_impl {
($($Expr:ident),*) => {
impl AttrsMut for Expr {
fn attrs_mut<T, F: FnOnce(&mut Vec<Attribute>) -> T>(&mut self, f: F) -> T {
match self {
$(Expr::$Expr(expr) => f(&mut expr.attrs),)*
Expr::Verbatim(_) => f(&mut Vec::with_capacity(0)),
}
macro_rules! attrs_impl {
($($Expr:ident),*) => {
impl AttrsMut for Expr {
fn attrs_mut<T, F: FnOnce(&mut Vec<Attribute>) -> T>(&mut self, f: F) -> T {
match self {
$(Expr::$Expr(expr) => f(&mut expr.attrs),)*
Expr::Verbatim(_) => f(&mut Vec::with_capacity(0)),
}
}
};
}
}
};
}

attrs_impl! {
Box,
InPlace,
Array,
Call,
MethodCall,
Tuple,
Binary,
Unary,
Lit,
Cast,
Type,
Let,
If,
While,
ForLoop,
Loop,
Match,
Closure,
Unsafe,
Block,
Assign,
AssignOp,
Field,
Index,
Range,
Path,
Reference,
Break,
Continue,
Return,
Macro,
Struct,
Repeat,
Paren,
Group,
Try,
Async,
TryBlock,
Yield
}
attrs_impl! {
Box,
InPlace,
Array,
Call,
MethodCall,
Tuple,
Binary,
Unary,
Lit,
Cast,
Type,
Let,
If,
While,
ForLoop,
Loop,
Match,
Closure,
Unsafe,
Block,
Assign,
AssignOp,
Field,
Index,
Range,
Path,
Reference,
Break,
Continue,
Return,
Macro,
Struct,
Repeat,
Paren,
Group,
Try,
Async,
TryBlock,
Yield
}
5 changes: 5 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ pub(crate) fn failed<T>(name: &str, msg: &str) -> Result<T> {
Err(compile_err(&format!("`{}` {}", name, msg)))
}

#[cfg(feature = "project_attr")]
pub(crate) fn default<T: Default>() -> T {
T::default()
}

pub(crate) trait VecExt {
fn find_remove(&mut self, ident: &str) -> bool;
}
Expand Down
Loading

0 comments on commit 268623a

Please sign in to comment.