Skip to content

Commit c5fe61a

Browse files
authored
Merge pull request #1648 from dtolnay/letelse
Enforce that let-else's expression does not end in brace
2 parents b2a7ac8 + ceeb59d commit c5fe61a

4 files changed

Lines changed: 150 additions & 12 deletions

File tree

src/classify.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
use crate::expr::Expr;
2+
#[cfg(feature = "parsing")]
3+
use crate::generics::TypeParamBound;
4+
#[cfg(feature = "parsing")]
5+
use crate::path::{Path, PathArguments};
6+
#[cfg(feature = "parsing")]
7+
use crate::punctuated::Punctuated;
8+
#[cfg(feature = "parsing")]
9+
use crate::ty::{ReturnType, Type};
10+
#[cfg(feature = "parsing")]
11+
use proc_macro2::{Delimiter, TokenStream, TokenTree};
12+
#[cfg(feature = "parsing")]
13+
use std::ops::ControlFlow;
214

315
pub(crate) fn requires_terminator(expr: &Expr) -> bool {
416
// see https://github.com/rust-lang/rust/blob/9a19e7604/compiler/rustc_ast/src/util/classify.rs#L7-L26
@@ -43,3 +55,130 @@ pub(crate) fn requires_terminator(expr: &Expr) -> bool {
4355
| Expr::Verbatim(_) => true
4456
}
4557
}
58+
59+
/// Whether the expression's last token is `}`.
60+
#[cfg(feature = "parsing")]
61+
pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool {
62+
loop {
63+
match expr {
64+
Expr::Array(_) => return false,
65+
Expr::Assign(e) => expr = &e.right,
66+
Expr::Async(_) => return true,
67+
Expr::Await(_) => return false,
68+
Expr::Binary(e) => expr = &e.right,
69+
Expr::Block(_) => return true,
70+
Expr::Break(e) => match &e.expr {
71+
Some(e) => expr = e,
72+
None => return false,
73+
},
74+
Expr::Call(_) => return false,
75+
Expr::Cast(e) => return type_trailing_brace(&e.ty),
76+
Expr::Closure(e) => expr = &e.body,
77+
Expr::Const(_) => return true,
78+
Expr::Continue(_) => return false,
79+
Expr::Field(_) => return false,
80+
Expr::ForLoop(_) => return true,
81+
Expr::Group(_) => return false,
82+
Expr::If(_) => return true,
83+
Expr::Index(_) => return false,
84+
Expr::Infer(_) => return false,
85+
Expr::Let(e) => expr = &e.expr,
86+
Expr::Lit(_) => return false,
87+
Expr::Loop(_) => return true,
88+
Expr::Macro(e) => return e.mac.delimiter.is_brace(),
89+
Expr::Match(_) => return true,
90+
Expr::MethodCall(_) => return false,
91+
Expr::Paren(_) => return false,
92+
Expr::Path(_) => return false,
93+
Expr::Range(e) => match &e.end {
94+
Some(end) => expr = end,
95+
None => return false,
96+
},
97+
Expr::Reference(e) => expr = &e.expr,
98+
Expr::Repeat(_) => return false,
99+
Expr::Return(e) => match &e.expr {
100+
Some(e) => expr = e,
101+
None => return false,
102+
},
103+
Expr::Struct(_) => return true,
104+
Expr::Try(_) => return false,
105+
Expr::TryBlock(_) => return true,
106+
Expr::Tuple(_) => return false,
107+
Expr::Unary(e) => expr = &e.expr,
108+
Expr::Unsafe(_) => return true,
109+
Expr::Verbatim(e) => return tokens_trailing_brace(e),
110+
Expr::While(_) => return true,
111+
Expr::Yield(e) => match &e.expr {
112+
Some(e) => expr = e,
113+
None => return false,
114+
},
115+
}
116+
}
117+
}
118+
119+
#[cfg(feature = "parsing")]
120+
fn type_trailing_brace(mut ty: &Type) -> bool {
121+
fn last_type_in_path(path: &Path) -> Option<&Type> {
122+
match &path.segments.last().unwrap().arguments {
123+
PathArguments::None | PathArguments::AngleBracketed(_) => None,
124+
PathArguments::Parenthesized(arg) => match &arg.output {
125+
ReturnType::Default => None,
126+
ReturnType::Type(_, ret) => Some(ret),
127+
},
128+
}
129+
}
130+
131+
fn last_type_in_bounds(
132+
bounds: &Punctuated<TypeParamBound, Token![+]>,
133+
) -> ControlFlow<bool, &Type> {
134+
match bounds.last().unwrap() {
135+
TypeParamBound::Trait(t) => match last_type_in_path(&t.path) {
136+
Some(t) => ControlFlow::Continue(t),
137+
None => ControlFlow::Break(false),
138+
},
139+
TypeParamBound::Lifetime(_) => ControlFlow::Break(false),
140+
TypeParamBound::Verbatim(t) => ControlFlow::Break(tokens_trailing_brace(t)),
141+
}
142+
}
143+
144+
loop {
145+
match ty {
146+
Type::Array(_) => return false,
147+
Type::BareFn(t) => match &t.output {
148+
ReturnType::Default => return false,
149+
ReturnType::Type(_, ret) => ty = ret,
150+
},
151+
Type::Group(_) => return false,
152+
Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) {
153+
ControlFlow::Break(trailing_brace) => return trailing_brace,
154+
ControlFlow::Continue(t) => ty = t,
155+
},
156+
Type::Infer(_) => return false,
157+
Type::Macro(t) => return t.mac.delimiter.is_brace(),
158+
Type::Never(_) => return false,
159+
Type::Paren(_) => return false,
160+
Type::Path(t) => match last_type_in_path(&t.path) {
161+
Some(t) => ty = t,
162+
None => return false,
163+
},
164+
Type::Ptr(t) => ty = &t.elem,
165+
Type::Reference(t) => ty = &t.elem,
166+
Type::Slice(_) => return false,
167+
Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) {
168+
ControlFlow::Break(trailing_brace) => return trailing_brace,
169+
ControlFlow::Continue(t) => ty = t,
170+
},
171+
Type::Tuple(_) => return false,
172+
Type::Verbatim(t) => return tokens_trailing_brace(t),
173+
}
174+
}
175+
}
176+
177+
#[cfg(feature = "parsing")]
178+
fn tokens_trailing_brace(tokens: &TokenStream) -> bool {
179+
if let Some(TokenTree::Group(last)) = tokens.clone().into_iter().last() {
180+
last.delimiter() == Delimiter::Brace
181+
} else {
182+
false
183+
}
184+
}

src/item.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ pub(crate) mod parsing {
921921
};
922922
use crate::lifetime::Lifetime;
923923
use crate::lit::LitStr;
924-
use crate::mac::{self, Macro, MacroDelimiter};
924+
use crate::mac::{self, Macro};
925925
use crate::parse::discouraged::Speculative as _;
926926
use crate::parse::{Parse, ParseBuffer, ParseStream};
927927
use crate::pat::{Pat, PatType, PatWild};
@@ -2872,15 +2872,6 @@ pub(crate) mod parsing {
28722872
}
28732873
}
28742874

2875-
impl MacroDelimiter {
2876-
pub(crate) fn is_brace(&self) -> bool {
2877-
match self {
2878-
MacroDelimiter::Brace(_) => true,
2879-
MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => false,
2880-
}
2881-
}
2882-
}
2883-
28842875
#[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
28852876
impl Parse for StaticMutability {
28862877
fn parse(input: ParseStream) -> Result<Self> {

src/mac.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ impl MacroDelimiter {
4040
MacroDelimiter::Bracket(token) => &token.span,
4141
}
4242
}
43+
44+
#[cfg(all(feature = "full", feature = "parsing"))]
45+
pub(crate) fn is_brace(&self) -> bool {
46+
match self {
47+
MacroDelimiter::Brace(_) => true,
48+
MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => false,
49+
}
50+
}
4351
}
4452

4553
impl Macro {

src/stmt.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,8 @@ pub(crate) mod parsing {
298298
let eq_token: Token![=] = eq_token;
299299
let expr: Expr = input.parse()?;
300300

301-
let diverge = if let Some(else_token) = input.parse()? {
302-
let else_token: Token![else] = else_token;
301+
let diverge = if !classify::expr_trailing_brace(&expr) && input.peek(Token![else]) {
302+
let else_token: Token![else] = input.parse()?;
303303
let diverge = ExprBlock {
304304
attrs: Vec::new(),
305305
label: None,

0 commit comments

Comments
 (0)