Skip to content

Commit

Permalink
Merge #181
Browse files Browse the repository at this point in the history
181: Allow `#[project]` to be used on `if let` expressions r=taiki-e a=Aaron1011

This PR would work correctly, except for the fact that
rust-lang/rust#68618 causes a compilation error to be emitted before we
even have a chance to run. That issue is independent of the
implementation of this PR, so this PR should start working automatically
once the issue is resolved.

Co-authored-by: Aaron Hill <aa1ronham@gmail.com>
  • Loading branch information
bors[bot] and Aaron1011 authored Mar 20, 2020
2 parents e15cd8c + 9910909 commit 7081486
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 10 deletions.
46 changes: 36 additions & 10 deletions pin-project-internal/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,33 @@ pub(crate) fn attribute(args: &TokenStream, input: Stmt, mutability: Mutability)
.unwrap_or_else(|e| e.to_compile_error())
}

fn parse(mut stmt: Stmt, mutability: Mutability) -> Result<TokenStream> {
match &mut stmt {
fn replace_stmt(stmt: &mut Stmt, mutability: Mutability) -> Result<()> {
match stmt {
Stmt::Expr(Expr::Match(expr)) | Stmt::Semi(Expr::Match(expr), _) => {
Context::new(mutability).replace_expr_match(expr)
Context::new(mutability).replace_expr_match(expr);
}
Stmt::Expr(Expr::If(expr_if)) => {
let mut expr_if = expr_if;
while let Expr::Let(ref mut expr) = &mut *expr_if.cond {
Context::new(mutability).replace_expr_let(expr);
if let Some((_, ref mut expr)) = expr_if.else_branch {
if let Expr::If(new_expr_if) = &mut **expr {
expr_if = new_expr_if;
continue;
}
}
break;
}
}
Stmt::Local(local) => Context::new(mutability).replace_local(local)?,
_ => {}
}
Ok(())
}

fn parse(mut stmt: Stmt, mutability: Mutability) -> Result<TokenStream> {
replace_stmt(&mut stmt, mutability)?;
match &mut stmt {
Stmt::Item(Item::Fn(item)) => replace_item_fn(item, mutability)?,
Stmt::Item(Item::Impl(item)) => replace_item_impl(item, mutability),
Stmt::Item(Item::Use(item)) => replace_item_use(item, mutability)?,
Expand Down Expand Up @@ -73,6 +94,10 @@ impl Context {
Ok(())
}

fn replace_expr_let(&mut self, expr: &mut ExprLet) {
self.replace_pat(&mut expr.pat, true)
}

fn replace_expr_match(&mut self, expr: &mut ExprMatch) {
expr.arms.iter_mut().for_each(|Arm { pat, .. }| self.replace_pat(pat, true))
}
Expand Down Expand Up @@ -195,17 +220,18 @@ impl FnVisitor {
expr.attrs.find_remove(self.name())?
}
Stmt::Local(local) => local.attrs.find_remove(self.name())?,
Stmt::Expr(Expr::If(expr_if)) => {
if let Expr::Let(_) = &*expr_if.cond {
expr_if.attrs.find_remove(self.name())?
} else {
None
}
}
_ => return Ok(()),
};
if let Some(attr) = attr {
parse_as_empty(&attr.tokens)?;
match node {
Stmt::Expr(Expr::Match(expr)) | Stmt::Semi(Expr::Match(expr), _) => {
Context::new(self.mutability).replace_expr_match(expr)
}
Stmt::Local(local) => Context::new(self.mutability).replace_local(local)?,
_ => unreachable!(),
}
replace_stmt(node, self.mutability)?;
}
Ok(())
}
Expand Down
10 changes: 10 additions & 0 deletions tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
#![warn(rust_2018_idioms, single_use_lifetimes)]
#![allow(dead_code)]

// This hack is needed until https://github.com/rust-lang/rust/pull/69201
// makes it way into stable.
// Ceurrently, `#[attr] if true {}` doesn't even *parse* on stable,
// which means that it will error even behind a `#[rustversion::nightly]`
//
// This trick makes sure that we don't even attempt to parse
// the `#[project] if let _` test on stable.
#[rustversion::nightly]
include!("project_if_attr.rs.in");

use pin_project::{pin_project, project};
use std::pin::Pin;

Expand Down
29 changes: 29 additions & 0 deletions tests/project_if_attr.rs.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// FIXME: Once https://github.com/rust-lang/rust/pull/69201 makes its
// way into stable, move this back into `project.rs

#[test]
#[project]
fn project_if_let() {
#[pin_project]
enum Foo<A, B> {
Variant1(#[pin] A),
Variant2(u8),
Variant3 {
#[pin] field: B
}
}

let mut foo: Foo<bool, f32> = Foo::Variant1(true);
let foo = Pin::new(&mut foo).project();

#[project]
if let Foo::Variant1(a) = foo {
let a: Pin<&mut bool> = a;
assert_eq!(*a, true);
} else if let Foo::Variant2(_) = foo {
unreachable!();
} else if let Foo::Variant3 { .. } = foo {
unreachable!();
}
}

0 comments on commit 7081486

Please sign in to comment.