From 198b1407c68b8ba0b77c42b932413c37eff04fb3 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Tue, 28 Jan 2020 16:50:17 -0500 Subject: [PATCH 1/6] [WIP] Allow `#[project]` to be used on `if let` expressions 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. --- pin-project-internal/src/project.rs | 47 +++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index 8bb07d54..81cd3527 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -13,17 +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 { - 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); + return Ok(true) + } + Stmt::Expr(Expr::If(expr_if)) => { + if let Expr::Let(ref mut expr) = &mut *expr_if.cond { + Context::new(mutability).replace_expr_let(expr); + return Ok(true); + } } Stmt::Local(local) => Context::new(mutability).replace_local(local)?, - 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)?, _ => {} } + Ok(false) +} + +fn parse(mut stmt: Stmt, mutability: Mutability) -> Result { + if !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)?, + _ => {} + } + } Ok(stmt.into_token_stream()) } @@ -73,6 +89,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)) } @@ -195,17 +215,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(()) } From f626aa8ec6d681754c967c2d376ce9b255064e8b Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Tue, 28 Jan 2020 18:07:39 -0500 Subject: [PATCH 2/6] Run 'cargo fmt' --- pin-project-internal/src/project.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index 81cd3527..4ce61bc3 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -17,7 +17,7 @@ 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); - return Ok(true) + return Ok(true); } Stmt::Expr(Expr::If(expr_if)) => { if let Expr::Let(ref mut expr) = &mut *expr_if.cond { From 93e6c76d006df064d7edf544b1000b45fb71c30c Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 9 Mar 2020 12:16:48 -0400 Subject: [PATCH 3/6] Also project `else if let` --- pin-project-internal/src/project.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index 4ce61bc3..689a90bc 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -13,32 +13,37 @@ pub(crate) fn attribute(args: &TokenStream, input: Stmt, mutability: Mutability) .unwrap_or_else(|e| e.to_compile_error()) } -fn replace_stmt(stmt: &mut Stmt, mutability: Mutability) -> Result { +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); - return Ok(true); } Stmt::Expr(Expr::If(expr_if)) => { - if let Expr::Let(ref mut expr) = &mut *expr_if.cond { + let mut expr_if = expr_if; + while let Expr::Let(ref mut expr) = &mut *expr_if.cond { Context::new(mutability).replace_expr_let(expr); - return Ok(true); + 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(false) + Ok(()) } fn parse(mut stmt: Stmt, mutability: Mutability) -> Result { - if !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)?, - _ => {} - } + 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)?, + _ => {} } Ok(stmt.into_token_stream()) From eb80d67fdd40efbd7fa1752049a82e4f074b2c8a Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 9 Mar 2020 12:17:06 -0400 Subject: [PATCH 4/6] Add a test --- tests/project.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/project.rs b/tests/project.rs index 84858d8a..3c182b7f 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -86,6 +86,33 @@ fn project_stmt_expr() { assert_eq!(val, true); } +#[rustversion::nightly] +#[test] +#[project] +fn project_if_let() { + #[pin_project] + enum Foo { + Variant1(#[pin] A), + Variant2(u8), + Variant3 { + #[pin] field: B + } + } + + let mut foo: Foo = 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!(); + } +} + #[test] fn project_impl() { #[pin_project] From 1d5231e72f880d987cece302d44e10f5ef435a57 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 9 Mar 2020 12:55:05 -0400 Subject: [PATCH 5/6] Make tests pass on stable --- tests/project.rs | 37 ++++++++++--------------------------- tests/project_if_attr.rs.in | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 tests/project_if_attr.rs.in diff --git a/tests/project.rs b/tests/project.rs index 3c182b7f..6463ab57 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -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; @@ -86,33 +96,6 @@ fn project_stmt_expr() { assert_eq!(val, true); } -#[rustversion::nightly] -#[test] -#[project] -fn project_if_let() { - #[pin_project] - enum Foo { - Variant1(#[pin] A), - Variant2(u8), - Variant3 { - #[pin] field: B - } - } - - let mut foo: Foo = 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!(); - } -} - #[test] fn project_impl() { #[pin_project] diff --git a/tests/project_if_attr.rs.in b/tests/project_if_attr.rs.in new file mode 100644 index 00000000..2d1eb267 --- /dev/null +++ b/tests/project_if_attr.rs.in @@ -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 { + Variant1(#[pin] A), + Variant2(u8), + Variant3 { + #[pin] field: B + } + } + + let mut foo: Foo = 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!(); + } +} + From 9910909e7ceec27d6b05511c577863ad74be7efa Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 9 Mar 2020 12:55:27 -0400 Subject: [PATCH 6/6] Run `cargo fmt` --- pin-project-internal/src/project.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pin-project-internal/src/project.rs b/pin-project-internal/src/project.rs index 689a90bc..92627d43 100644 --- a/pin-project-internal/src/project.rs +++ b/pin-project-internal/src/project.rs @@ -25,7 +25,7 @@ fn replace_stmt(stmt: &mut Stmt, mutability: Mutability) -> Result<()> { 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 + continue; } } break;