Skip to content

Commit

Permalink
Enforce that let-else's expression does not end in brace
Browse files Browse the repository at this point in the history
  • Loading branch information
dtolnay committed May 12, 2024
1 parent b2a7ac8 commit 30c62e1
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 12 deletions.
133 changes: 133 additions & 0 deletions src/classify.rs
@@ -1,4 +1,10 @@
use crate::expr::Expr;
use crate::generics::TypeParamBound;
use crate::path::{Path, PathArguments};
use crate::punctuated::Punctuated;
use crate::ty::{ReturnType, Type};
use proc_macro2::{Delimiter, TokenStream, TokenTree};
use std::ops::ControlFlow;

pub(crate) fn requires_terminator(expr: &Expr) -> bool {
// see https://github.com/rust-lang/rust/blob/9a19e7604/compiler/rustc_ast/src/util/classify.rs#L7-L26
Expand Down Expand Up @@ -43,3 +49,130 @@ pub(crate) fn requires_terminator(expr: &Expr) -> bool {
| Expr::Verbatim(_) => true
}
}

/// Whether the expression's last token is `}`.
#[cfg(feature = "parsing")]
pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool {
loop {
match expr {
Expr::Array(_) => return false,
Expr::Assign(e) => expr = &e.right,
Expr::Async(_) => return true,
Expr::Await(_) => return false,
Expr::Binary(e) => expr = &e.right,
Expr::Block(_) => return true,
Expr::Break(e) => match &e.expr {
Some(e) => expr = e,
None => return false,
},
Expr::Call(_) => return false,
Expr::Cast(e) => return type_trailing_brace(&e.ty),
Expr::Closure(e) => expr = &e.body,
Expr::Const(_) => return true,
Expr::Continue(_) => return false,
Expr::Field(_) => return false,
Expr::ForLoop(_) => return true,
Expr::Group(_) => return false,
Expr::If(_) => return true,
Expr::Index(_) => return false,
Expr::Infer(_) => return false,
Expr::Let(e) => expr = &e.expr,
Expr::Lit(_) => return false,
Expr::Loop(_) => return true,
Expr::Macro(e) => return e.mac.delimiter.is_brace(),
Expr::Match(_) => return true,
Expr::MethodCall(_) => return false,
Expr::Paren(_) => return false,
Expr::Path(_) => return false,
Expr::Range(e) => match &e.end {
Some(end) => expr = end,
None => return false,
},
Expr::Reference(e) => expr = &e.expr,
Expr::Repeat(_) => return false,
Expr::Return(e) => match &e.expr {
Some(e) => expr = e,
None => return false,
},
Expr::Struct(_) => return true,
Expr::Try(_) => return false,
Expr::TryBlock(_) => return true,
Expr::Tuple(_) => return false,
Expr::Unary(e) => expr = &e.expr,
Expr::Unsafe(_) => return true,
Expr::Verbatim(e) => return tokens_trailing_brace(e),
Expr::While(_) => return true,
Expr::Yield(e) => match &e.expr {
Some(e) => expr = e,
None => return false,
},
}
}
}

#[cfg(feature = "parsing")]
fn type_trailing_brace(mut ty: &Type) -> bool {
fn last_type_in_path(path: &Path) -> Option<&Type> {
match &path.segments.last().unwrap().arguments {
PathArguments::None | PathArguments::AngleBracketed(_) => None,
PathArguments::Parenthesized(arg) => match &arg.output {
ReturnType::Default => None,
ReturnType::Type(_, ret) => Some(ret),
},
}
}

fn last_type_in_bounds(
bounds: &Punctuated<TypeParamBound, Token![+]>,
) -> ControlFlow<bool, &Type> {
match bounds.last().unwrap() {
TypeParamBound::Trait(t) => match last_type_in_path(&t.path) {
Some(t) => ControlFlow::Continue(t),
None => ControlFlow::Break(false),
},
TypeParamBound::Lifetime(_) => ControlFlow::Break(false),
TypeParamBound::Verbatim(t) => ControlFlow::Break(tokens_trailing_brace(t)),
}
}

loop {
match ty {
Type::Array(_) => return false,
Type::BareFn(t) => match &t.output {
ReturnType::Default => return false,
ReturnType::Type(_, ret) => ty = ret,
},
Type::Group(_) => return false,
Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) {
ControlFlow::Break(trailing_brace) => return trailing_brace,
ControlFlow::Continue(t) => ty = t,
},
Type::Infer(_) => return false,
Type::Macro(t) => return t.mac.delimiter.is_brace(),
Type::Never(_) => return false,
Type::Paren(_) => return false,
Type::Path(t) => match last_type_in_path(&t.path) {
Some(t) => ty = t,
None => return false,
},
Type::Ptr(t) => ty = &t.elem,
Type::Reference(t) => ty = &t.elem,
Type::Slice(_) => return false,
Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) {
ControlFlow::Break(trailing_brace) => return trailing_brace,
ControlFlow::Continue(t) => ty = t,
},
Type::Tuple(_) => return false,
Type::Verbatim(t) => return tokens_trailing_brace(t),
}
}
}

#[cfg(feature = "parsing")]
fn tokens_trailing_brace(tokens: &TokenStream) -> bool {
if let Some(TokenTree::Group(last)) = tokens.clone().into_iter().last() {
last.delimiter() == Delimiter::Brace
} else {
false
}
}
11 changes: 1 addition & 10 deletions src/item.rs
Expand Up @@ -921,7 +921,7 @@ pub(crate) mod parsing {
};
use crate::lifetime::Lifetime;
use crate::lit::LitStr;
use crate::mac::{self, Macro, MacroDelimiter};
use crate::mac::{self, Macro};
use crate::parse::discouraged::Speculative as _;
use crate::parse::{Parse, ParseBuffer, ParseStream};
use crate::pat::{Pat, PatType, PatWild};
Expand Down Expand Up @@ -2872,15 +2872,6 @@ pub(crate) mod parsing {
}
}

impl MacroDelimiter {
pub(crate) fn is_brace(&self) -> bool {
match self {
MacroDelimiter::Brace(_) => true,
MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => false,
}
}
}

#[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
impl Parse for StaticMutability {
fn parse(input: ParseStream) -> Result<Self> {
Expand Down
8 changes: 8 additions & 0 deletions src/mac.rs
Expand Up @@ -40,6 +40,14 @@ impl MacroDelimiter {
MacroDelimiter::Bracket(token) => &token.span,
}
}

#[cfg(all(feature = "full", feature = "parsing"))]
pub(crate) fn is_brace(&self) -> bool {
match self {
MacroDelimiter::Brace(_) => true,
MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => false,
}
}
}

impl Macro {
Expand Down
4 changes: 2 additions & 2 deletions src/stmt.rs
Expand Up @@ -298,8 +298,8 @@ pub(crate) mod parsing {
let eq_token: Token![=] = eq_token;
let expr: Expr = input.parse()?;

let diverge = if let Some(else_token) = input.parse()? {
let else_token: Token![else] = else_token;
let diverge = if !classify::expr_trailing_brace(&expr) && input.peek(Token![else]) {
let else_token: Token![else] = input.parse()?;
let diverge = ExprBlock {
attrs: Vec::new(),
label: None,
Expand Down

0 comments on commit 30c62e1

Please sign in to comment.