Skip to content

Lower let-else in MIR #98574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 13, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 22 additions & 73 deletions compiler/rustc_ast_lowering/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{ImplTraitContext, ImplTraitPosition, LoweringContext};
use rustc_ast::{AttrVec, Block, BlockCheckMode, Expr, Local, LocalKind, Stmt, StmtKind};
use rustc_ast::{Block, BlockCheckMode, Local, LocalKind, Stmt, StmtKind};
use rustc_hir as hir;
use rustc_session::parse::feature_err;
use rustc_span::{sym, DesugaringKind};
use rustc_span::sym;

use smallvec::SmallVec;

@@ -36,21 +36,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
match s.kind {
StmtKind::Local(ref local) => {
let hir_id = self.lower_node_id(s.id);
match &local.kind {
LocalKind::InitElse(init, els) => {
let e = self.lower_let_else(hir_id, local, init, els, tail);
expr = Some(e);
// remaining statements are in let-else expression
break;
}
_ => {
let local = self.lower_local(local);
self.alias_attrs(hir_id, local.hir_id);
let kind = hir::StmtKind::Local(local);
let span = self.lower_span(s.span);
stmts.push(hir::Stmt { hir_id, kind, span });
}
}
let local = self.lower_local(local);
self.alias_attrs(hir_id, local.hir_id);
let kind = hir::StmtKind::Local(local);
let span = self.lower_span(s.span);
stmts.push(hir::Stmt { hir_id, kind, span });
}
StmtKind::Item(ref it) => {
stmts.extend(self.lower_item_ref(it).into_iter().enumerate().map(
@@ -101,10 +91,24 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let init = l.kind.init().map(|init| self.lower_expr(init));
let hir_id = self.lower_node_id(l.id);
let pat = self.lower_pat(&l.pat);
let els = if let LocalKind::InitElse(_, els) = &l.kind {
if !self.tcx.features().let_else {
feature_err(
&self.tcx.sess.parse_sess,
sym::let_else,
l.span,
"`let...else` statements are unstable",
)
.emit();
}
Some(self.lower_block(els, false))
} else {
None
};
let span = self.lower_span(l.span);
let source = hir::LocalSource::Normal;
self.lower_attrs(hir_id, &l.attrs);
self.arena.alloc(hir::Local { hir_id, ty, pat, init, span, source })
self.arena.alloc(hir::Local { hir_id, ty, pat, init, els, span, source })
}

fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {
@@ -115,59 +119,4 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
}
}
}

fn lower_let_else(
&mut self,
stmt_hir_id: hir::HirId,
local: &Local,
init: &Expr,
els: &Block,
tail: &[Stmt],
) -> &'hir hir::Expr<'hir> {
let ty = local
.ty
.as_ref()
.map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Variable)));
let span = self.lower_span(local.span);
let span = self.mark_span_with_reason(DesugaringKind::LetElse, span, None);
let init = self.lower_expr(init);
let local_hir_id = self.lower_node_id(local.id);
self.lower_attrs(local_hir_id, &local.attrs);
let let_expr = {
let lex = self.arena.alloc(hir::Let {
hir_id: local_hir_id,
pat: self.lower_pat(&local.pat),
ty,
init,
span,
});
self.arena.alloc(self.expr(span, hir::ExprKind::Let(lex), AttrVec::new()))
};
let then_expr = {
let (stmts, expr) = self.lower_stmts(tail);
let block = self.block_all(span, stmts, expr);
self.arena.alloc(self.expr_block(block, AttrVec::new()))
};
let else_expr = {
let block = self.lower_block(els, false);
self.arena.alloc(self.expr_block(block, AttrVec::new()))
};
self.alias_attrs(let_expr.hir_id, local_hir_id);
self.alias_attrs(else_expr.hir_id, local_hir_id);
let if_expr = self.arena.alloc(hir::Expr {
hir_id: stmt_hir_id,
span,
kind: hir::ExprKind::If(let_expr, then_expr, Some(else_expr)),
});
if !self.tcx.features().let_else {
feature_err(
&self.tcx.sess.parse_sess,
sym::let_else,
local.span,
"`let...else` statements are unstable",
)
.emit();
}
if_expr
}
}
10 changes: 9 additions & 1 deletion compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
@@ -2146,7 +2146,15 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
debug_assert!(!a.is_empty());
self.attrs.insert(hir_id.local_id, a);
}
let local = hir::Local { hir_id, init, pat, source, span: self.lower_span(span), ty: None };
let local = hir::Local {
hir_id,
init,
pat,
els: None,
source,
span: self.lower_span(span),
ty: None,
};
self.stmt(span, hir::StmtKind::Local(self.arena.alloc(local)))
}

2 changes: 2 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
@@ -1316,6 +1316,8 @@ pub struct Local<'hir> {
pub ty: Option<&'hir Ty<'hir>>,
/// Initializer expression to set the value, if any.
pub init: Option<&'hir Expr<'hir>>,
/// Else block for a `let...else` binding.
pub els: Option<&'hir Block<'hir>>,
pub hir_id: HirId,
pub span: Span,
/// Can be `ForLoopDesugar` if the `let` statement is part of a `for` loop
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
@@ -472,6 +472,9 @@ pub fn walk_local<'v, V: Visitor<'v>>(visitor: &mut V, local: &'v Local<'v>) {
walk_list!(visitor, visit_expr, &local.init);
visitor.visit_id(local.hir_id);
visitor.visit_pat(&local.pat);
if let Some(els) = local.els {
visitor.visit_block(els);
}
walk_list!(visitor, visit_ty, &local.ty);
}

18 changes: 15 additions & 3 deletions compiler/rustc_hir_pretty/src/lib.rs
Original file line number Diff line number Diff line change
@@ -883,7 +883,12 @@ impl<'a> State<'a> {
self.ann.post(self, AnnNode::SubItem(ii.hir_id()))
}

pub fn print_local(&mut self, init: Option<&hir::Expr<'_>>, decl: impl Fn(&mut Self)) {
pub fn print_local(
&mut self,
init: Option<&hir::Expr<'_>>,
els: Option<&hir::Block<'_>>,
decl: impl Fn(&mut Self),
) {
self.space_if_not_bol();
self.ibox(INDENT_UNIT);
self.word_nbsp("let");
@@ -897,14 +902,21 @@ impl<'a> State<'a> {
self.word_space("=");
self.print_expr(init);
}

if let Some(els) = els {
self.nbsp();
self.word_space("else");
self.print_block(els);
}

self.end()
}

pub fn print_stmt(&mut self, st: &hir::Stmt<'_>) {
self.maybe_print_comment(st.span.lo());
match st.kind {
hir::StmtKind::Local(loc) => {
self.print_local(loc.init, |this| this.print_local_decl(loc));
self.print_local(loc.init, loc.els, |this| this.print_local_decl(loc));
}
hir::StmtKind::Item(item) => self.ann.nested(self, Nested::Item(item)),
hir::StmtKind::Expr(expr) => {
@@ -1404,7 +1416,7 @@ impl<'a> State<'a> {

// Print `let _t = $init;`:
let temp = Ident::from_str("_t");
self.print_local(Some(init), |this| this.print_ident(temp));
self.print_local(Some(init), None, |this| this.print_ident(temp));
self.word(";");

// Print `_t`:
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/thir.rs
Original file line number Diff line number Diff line change
@@ -182,6 +182,9 @@ pub enum StmtKind<'tcx> {
/// `let pat: ty = <INIT>`
initializer: Option<ExprId>,

/// `let pat: ty = <INIT> else { <ELSE> }
else_block: Option<Block>,

/// The lint level for this `let` statement.
lint_level: LintLevel,
},
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/thir/visit.rs
Original file line number Diff line number Diff line change
@@ -167,11 +167,15 @@ pub fn walk_stmt<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, stmt: &Stm
init_scope: _,
ref pattern,
lint_level: _,
else_block,
} => {
if let Some(init) = initializer {
visitor.visit_expr(&visitor.thir()[*init]);
}
visitor.visit_pat(pattern);
if let Some(block) = else_block {
visitor.visit_block(block)
}
}
}
}
33 changes: 23 additions & 10 deletions compiler/rustc_mir_build/src/build/block.rs
Original file line number Diff line number Diff line change
@@ -99,6 +99,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
ref pattern,
initializer,
lint_level,
else_block,
} => {
let ignores_expr_result = matches!(*pattern.kind, PatKind::Wild);
this.block_context.push(BlockFrame::Statement { ignores_expr_result });
@@ -124,18 +125,30 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|this| {
let scope = (*init_scope, source_info);
this.in_scope(scope, *lint_level, |this| {
this.declare_bindings(
visibility_scope,
remainder_span,
pattern,
ArmHasGuard(false),
Some((None, initializer_span)),
);
this.expr_into_pattern(block, pattern.clone(), init)
if let Some(else_block) = else_block {
this.ast_let_else(
block,
init,
initializer_span,
else_block,
visibility_scope,
remainder_span,
pattern,
)
} else {
this.declare_bindings(
visibility_scope,
remainder_span,
pattern,
ArmHasGuard(false),
Some((None, initializer_span)),
);
this.expr_into_pattern(block, pattern.clone(), init) // irrefutable pattern
}
})
}
},
)
);
)
} else {
let scope = (*init_scope, source_info);
unpack!(this.in_scope(scope, *lint_level, |this| {
77 changes: 74 additions & 3 deletions compiler/rustc_mir_build/src/build/matches/mod.rs
Original file line number Diff line number Diff line change
@@ -1615,7 +1615,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// those N possible outcomes, create a (initially empty)
// vector of candidates. Those are the candidates that still
// apply if the test has that particular outcome.
debug!("match_candidates: test={:?} match_pair={:?}", test, match_pair);
debug!("test_candidates: test={:?} match_pair={:?}", test, match_pair);
let mut target_candidates: Vec<Vec<&mut Candidate<'pat, 'tcx>>> = vec![];
target_candidates.resize_with(test.targets(), Default::default);

@@ -1635,8 +1635,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
// at least the first candidate ought to be tested
assert!(total_candidate_count > candidates.len());
debug!("tested_candidates: {}", total_candidate_count - candidates.len());
debug!("untested_candidates: {}", candidates.len());
debug!("test_candidates: tested_candidates: {}", total_candidate_count - candidates.len());
debug!("test_candidates: untested_candidates: {}", candidates.len());

// HACK(matthewjasper) This is a closure so that we can let the test
// create its blocks before the rest of the match. This currently
@@ -2274,4 +2274,75 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
debug!("declare_binding: vars={:?}", locals);
self.var_indices.insert(var_id, locals);
}

pub(crate) fn ast_let_else(
&mut self,
mut block: BasicBlock,
init: &Expr<'tcx>,
initializer_span: Span,
else_block: &Block,
visibility_scope: Option<SourceScope>,
remainder_span: Span,
pattern: &Pat<'tcx>,
) -> BlockAnd<()> {
let scrutinee = unpack!(block = self.lower_scrutinee(block, init, initializer_span));
let pat = Pat { ty: init.ty, span: else_block.span, kind: Box::new(PatKind::Wild) };
let mut wildcard = Candidate::new(scrutinee.clone(), &pat, false);
self.declare_bindings(
visibility_scope,
remainder_span,
pattern,
ArmHasGuard(false),
Some((None, initializer_span)),
);
let mut candidate = Candidate::new(scrutinee.clone(), pattern, false);
let fake_borrow_temps = self.lower_match_tree(
block,
initializer_span,
pattern.span,
false,
&mut [&mut candidate, &mut wildcard],
);
// This block is for the matching case
let matching = self.bind_pattern(
self.source_info(pattern.span),
candidate,
None,
&fake_borrow_temps,
initializer_span,
None,
None,
None,
);
// This block is for the failure case
let failure = self.bind_pattern(
self.source_info(else_block.span),
wildcard,
None,
&fake_borrow_temps,
initializer_span,
None,
None,
None,
);
// This place is not really used because this destination place
// should never be used to take values at the end of the failure
// block.
let dummy_place = Place { local: RETURN_PLACE, projection: ty::List::empty() };
let failure_block;
unpack!(
failure_block = self.ast_block(
dummy_place,
failure,
else_block,
self.source_info(else_block.span),
)
);
self.cfg.terminate(
failure_block,
self.source_info(else_block.span),
TerminatorKind::Unreachable,
);
matching.unit()
}
}
3 changes: 3 additions & 0 deletions compiler/rustc_mir_build/src/thir/cx/block.rs
Original file line number Diff line number Diff line change
@@ -74,6 +74,8 @@ impl<'tcx> Cx<'tcx> {
)),
};

let else_block = local.els.map(|els| self.mirror_block(els));

let mut pattern = self.pattern_from_hir(local.pat);
debug!(?pattern);

@@ -110,6 +112,7 @@ impl<'tcx> Cx<'tcx> {
},
pattern,
initializer: local.init.map(|init| self.mirror_expr(init)),
else_block,
lint_level: LintLevel::Explicit(local.hir_id),
},
opt_destruction_scope: opt_dxn_ext,
21 changes: 13 additions & 8 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ use rustc_session::lint::builtin::{
};
use rustc_session::Session;
use rustc_span::source_map::Spanned;
use rustc_span::{BytePos, DesugaringKind, ExpnKind, Span};
use rustc_span::{BytePos, Span};

pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: DefId) {
let body_id = match def_id.as_local() {
@@ -77,14 +77,20 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {

fn visit_local(&mut self, loc: &'tcx hir::Local<'tcx>) {
intravisit::walk_local(self, loc);
let els = loc.els;
if let Some(init) = loc.init && els.is_some() {
self.check_let(&loc.pat, init, loc.span);
}

let (msg, sp) = match loc.source {
hir::LocalSource::Normal => ("local binding", Some(loc.span)),
hir::LocalSource::AsyncFn => ("async fn binding", None),
hir::LocalSource::AwaitDesugar => ("`await` future binding", None),
hir::LocalSource::AssignDesugar(_) => ("destructuring assignment binding", None),
};
self.check_irrefutable(&loc.pat, msg, sp);
if els.is_none() {
self.check_irrefutable(&loc.pat, msg, sp);
}
}

fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
@@ -1125,17 +1131,16 @@ fn let_source_parent(tcx: TyCtxt<'_>, parent: HirId, pat_id: Option<HirId>) -> L
}) if Some(*hir_id) == pat_id => {
return LetSource::IfLetGuard;
}
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Let(..), span, .. }) => {
let expn_data = span.ctxt().outer_expn_data();
if let ExpnKind::Desugaring(DesugaringKind::LetElse) = expn_data.kind {
return LetSource::LetElse(expn_data.call_site);
}
}
_ => {}
}

let parent_parent = hir.get_parent_node(parent);
let parent_parent_node = hir.get(parent_parent);
if let hir::Node::Stmt(hir::Stmt { kind: hir::StmtKind::Local(_), span, .. }) =
parent_parent_node
{
return LetSource::LetElse(*span);
}

let parent_parent_parent = hir.get_parent_node(parent_parent);
let parent_parent_parent_parent = hir.get_parent_node(parent_parent_parent);
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/remove_uninit_drops.rs
Original file line number Diff line number Diff line change
@@ -102,7 +102,7 @@ fn is_needs_drop_and_init<'tcx>(
let field_needs_drop_and_init = |(f, f_ty, mpi)| {
let child = move_path_children_matching(move_data, mpi, |x| x.is_field_to(f));
let Some(mpi) = child else {
return f_ty.needs_drop(tcx, param_env);
return Ty::needs_drop(f_ty, tcx, param_env);
Copy link
Contributor Author

@dingxiangfei2009 dingxiangfei2009 Jul 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this this code does not compile because the type of f_ty is not resolved to Ty.

Here is a reason why this compiled with lowering in AST but not anymore with lowering in MIR. With AST lowering, this let...else was transformed to:

if let Some(mpi) = child {
    is_needs_drop_and_init(tcx, param_env, maybe_inits, move_data, f_ty, mpi)
} else {
    return f_ty.needs_drop(tcx, param_env);
}

Since the is_needs_drop_and_init function call was visted first, typeck resolved the type of f_ty to Ty before visiting the return statement.

We no longer visit the expressions in this order with lowering MIR. This time, the return statement is visited first. We need to resolve the method call but f_ty has no type obligation at this point yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change should not be necessary anymore since the order in typeck has been adjusted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the adjustment in order, even after moving the check on the else block into check_decl at the tail position, this is unfortunately still necessary as type annotation is needed is still emitted with the unchanged call for the reason possibly presented in the previous comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh 🤦 now I see it. Thanks!

I guess I'd prefer to annotate the f_ty argument instead of changing this call site.

};

is_needs_drop_and_init(tcx, param_env, maybe_inits, move_data, f_ty, mpi)
45 changes: 40 additions & 5 deletions compiler/rustc_passes/src/liveness.rs
Original file line number Diff line number Diff line change
@@ -278,7 +278,7 @@ impl<'tcx> IrMaps<'tcx> {
pats.extend(inner_pat.iter());
}
Struct(_, fields, _) => {
let (short, not_short): (Vec<&_>, Vec<&_>) =
let (short, not_short): (Vec<_>, _) =
fields.iter().partition(|f| f.is_shorthand);
shorthand_field_ids.extend(short.iter().map(|f| f.pat.hir_id));
pats.extend(not_short.iter().map(|f| f.pat));
@@ -298,7 +298,7 @@ impl<'tcx> IrMaps<'tcx> {
}
}

return shorthand_field_ids;
shorthand_field_ids
}

fn add_from_pat(&mut self, pat: &hir::Pat<'tcx>) {
@@ -368,6 +368,9 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {

fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
self.add_from_pat(&local.pat);
if local.els.is_some() {
self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id));
}
intravisit::walk_local(self, local);
}

@@ -800,8 +803,40 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
// initialization, which is mildly more complex than checking
// once at the func header but otherwise equivalent.

let succ = self.propagate_through_opt_expr(local.init, succ);
self.define_bindings_in_pat(&local.pat, succ)
if let Some(els) = local.els {
// Eventually, `let pat: ty = init else { els };` is mostly equivalent to
// `let (bindings, ...) = match init { pat => (bindings, ...), _ => els };`
// except that extended lifetime applies at the `init` location.
//
// (e)
// |
// v
// (expr)
// / \
// | |
// v v
// bindings els
// |
// v
// ( succ )
//
if let Some(init) = local.init {
let else_ln = self.propagate_through_block(els, succ);
let ln = self.live_node(local.hir_id, local.span);
self.init_from_succ(ln, succ);
self.merge_from_succ(ln, else_ln);
let succ = self.propagate_through_expr(init, ln);
self.define_bindings_in_pat(&local.pat, succ)
} else {
span_bug!(
stmt.span,
"variable is uninitialized but an unexpected else branch is found"
)
}
} else {
let succ = self.propagate_through_opt_expr(local.init, succ);
self.define_bindings_in_pat(&local.pat, succ)
}
}
hir::StmtKind::Item(..) => succ,
hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => {
@@ -1121,7 +1156,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
// (rvalue) || (rvalue)
// | || |
// v || v
// (write of place) || (place components)
// (write of place) || (place components)
// | || |
// v || v
// (succ) || (succ)
12 changes: 3 additions & 9 deletions compiler/rustc_save_analysis/src/dump_visitor.rs
Original file line number Diff line number Diff line change
@@ -82,14 +82,7 @@ impl<'tcx> DumpVisitor<'tcx> {
pub fn new(save_ctxt: SaveContext<'tcx>) -> DumpVisitor<'tcx> {
let span_utils = SpanUtils::new(&save_ctxt.tcx.sess);
let dumper = Dumper::new(save_ctxt.config.clone());
DumpVisitor {
tcx: save_ctxt.tcx,
save_ctxt,
dumper,
span: span_utils,
// mac_defs: FxHashSet::default(),
// macro_calls: FxHashSet::default(),
}
DumpVisitor { tcx: save_ctxt.tcx, save_ctxt, dumper, span: span_utils }
}

pub fn analysis(&self) -> &rls_data::Analysis {
@@ -1425,9 +1418,10 @@ impl<'tcx> Visitor<'tcx> for DumpVisitor<'tcx> {
self.process_macro_use(l.span);
self.process_var_decl(&l.pat);

// Just walk the initializer and type (don't want to walk the pattern again).
// Just walk the initializer, the else branch and type (don't want to walk the pattern again).
walk_list!(self, visit_ty, &l.ty);
walk_list!(self, visit_expr, &l.init);
walk_list!(self, visit_block, l.els);
}

fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'tcx>) {
2 changes: 0 additions & 2 deletions compiler/rustc_span/src/hygiene.rs
Original file line number Diff line number Diff line change
@@ -1141,7 +1141,6 @@ pub enum DesugaringKind {
Async,
Await,
ForLoop,
LetElse,
WhileLoop,
}

@@ -1157,7 +1156,6 @@ impl DesugaringKind {
DesugaringKind::YeetExpr => "`do yeet` expression",
DesugaringKind::OpaqueTy => "`impl Trait`",
DesugaringKind::ForLoop => "`for` loop",
DesugaringKind::LetElse => "`let...else`",
DesugaringKind::WhileLoop => "`while` loop",
}
}
Original file line number Diff line number Diff line change
@@ -1311,7 +1311,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
visitor.visit_body(&body);

let typeck_results = self.in_progress_typeck_results.map(|t| t.borrow()).unwrap();
let Some(liberated_sig) = typeck_results.liberated_fn_sigs().get(fn_hir_id) else { return false; };
let Some(liberated_sig) = typeck_results.liberated_fn_sigs().get(fn_hir_id).copied() else { return false; };

let ret_types = visitor
.returns
21 changes: 1 addition & 20 deletions compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
@@ -997,26 +997,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
coerce.coerce(self, &self.misc(sp), then_expr, then_ty);

if let Some(else_expr) = opt_else_expr {
let else_ty = if sp.desugaring_kind() == Some(DesugaringKind::LetElse) {
// todo introduce `check_expr_with_expectation(.., Expectation::LetElse)`
// for errors that point to the offending expression rather than the entire block.
// We could use `check_expr_eq_type(.., tcx.types.never)`, but then there is no
// way to detect that the expected type originated from let-else and provide
// a customized error.
let else_ty = self.check_expr(else_expr);
let cause = self.cause(else_expr.span, ObligationCauseCode::LetElse);

if let Some(mut err) =
self.demand_eqtype_with_origin(&cause, self.tcx.types.never, else_ty)
{
err.emit();
self.tcx.ty_error()
} else {
else_ty
}
} else {
self.check_expr_with_expectation(else_expr, expected)
};
let else_ty = self.check_expr_with_expectation(else_expr, expected);
let else_diverges = self.diverges.get();

let opt_suggest_box_span = self.opt_suggest_box_span(else_ty, orig_expected);
16 changes: 14 additions & 2 deletions compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
@@ -1215,6 +1215,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_pat_top(&decl.pat, decl_ty, ty_span, origin_expr);
let pat_ty = self.node_ty(decl.pat.hir_id);
self.overwrite_local_ty_if_err(decl.hir_id, decl.pat, decl_ty, pat_ty);

if let Some(blk) = decl.els {
let previous_diverges = self.diverges.get();
let else_ty = self.check_block_with_expected(blk, NoExpectation);
let cause = self.cause(blk.span, ObligationCauseCode::LetElse);
if let Some(mut err) =
self.demand_eqtype_with_origin(&cause, self.tcx.types.never, else_ty)
{
err.emit();
}
self.diverges.set(previous_diverges);
}
}

/// Type check a `let` statement.
@@ -1236,8 +1248,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let old_has_errors = self.has_errors.replace(false);

match stmt.kind {
hir::StmtKind::Local(ref l) => {
self.check_decl_local(&l);
hir::StmtKind::Local(l) => {
self.check_decl_local(l);
}
// Ignore for now.
hir::StmtKind::Item(_) => {}
9 changes: 5 additions & 4 deletions compiler/rustc_typeck/src/check/gather_locals.rs
Original file line number Diff line number Diff line change
@@ -16,19 +16,20 @@ pub(super) struct Declaration<'a> {
pub ty: Option<&'a hir::Ty<'a>>,
pub span: Span,
pub init: Option<&'a hir::Expr<'a>>,
pub els: Option<&'a hir::Block<'a>>,
}

impl<'a> From<&'a hir::Local<'a>> for Declaration<'a> {
fn from(local: &'a hir::Local<'a>) -> Self {
let hir::Local { hir_id, pat, ty, span, init, .. } = *local;
Declaration { hir_id, pat, ty, span, init }
let hir::Local { hir_id, pat, ty, span, init, els, source: _ } = *local;
Declaration { hir_id, pat, ty, span, init, els }
}
}

impl<'a> From<&'a hir::Let<'a>> for Declaration<'a> {
fn from(let_expr: &'a hir::Let<'a>) -> Self {
let hir::Let { hir_id, pat, ty, span, init } = *let_expr;
Declaration { hir_id, pat, ty, span, init: Some(init) }
Declaration { hir_id, pat, ty, span, init: Some(init), els: None }
}
}

@@ -101,7 +102,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
// Add explicitly-declared locals.
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
self.declare(local.into());
intravisit::walk_local(self, local);
intravisit::walk_local(self, local)
}

fn visit_let_expr(&mut self, let_expr: &'tcx hir::Let<'tcx>) {
12 changes: 9 additions & 3 deletions compiler/rustc_typeck/src/check/region.rs
Original file line number Diff line number Diff line change
@@ -460,6 +460,7 @@ fn resolve_local<'tcx>(
visitor: &mut RegionResolutionVisitor<'tcx>,
pat: Option<&'tcx hir::Pat<'tcx>>,
init: Option<&'tcx hir::Expr<'tcx>>,
els: Option<&'tcx hir::Block<'tcx>>,
) {
debug!("resolve_local(pat={:?}, init={:?})", pat, init);

@@ -537,13 +538,18 @@ fn resolve_local<'tcx>(
}
}

// Make sure we visit the initializer first, so expr_and_pat_count remains correct
// Make sure we visit the initializer first, so expr_and_pat_count remains correct.
// The correct order, as shared between generator_interior, drop_ranges and intravisitor,
// is to walk initializer, followed by pattern bindings, finally followed by the `else` block.
if let Some(expr) = init {
visitor.visit_expr(expr);
}
if let Some(pat) = pat {
visitor.visit_pat(pat);
}
if let Some(els) = els {
visitor.visit_block(els);
}

/// Returns `true` if `pat` match the `P&` non-terminal.
///
@@ -764,7 +770,7 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
// (i.e., `'static`), which means that after `g` returns, it drops,
// and all the associated destruction scope rules apply.
self.cx.var_parent = None;
resolve_local(self, None, Some(&body.value));
resolve_local(self, None, Some(&body.value), None);
}

if body.generator_kind.is_some() {
@@ -791,7 +797,7 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
resolve_expr(self, ex);
}
fn visit_local(&mut self, l: &'tcx Local<'tcx>) {
resolve_local(self, Some(&l.pat), l.init);
resolve_local(self, Some(&l.pat), l.init, l.els)
}
}

206 changes: 115 additions & 91 deletions compiler/rustc_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
@@ -2,7 +2,10 @@
//! normal visitor, which just walks the entire body in one shot, the
//! `ExprUseVisitor` determines how expressions are being used.
use std::slice::from_ref;

use hir::def::DefKind;
use hir::Expr;
// Export these here so that Clippy can use them.
pub use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, Projection};

@@ -252,96 +255,16 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
}

hir::ExprKind::Let(hir::Let { pat, init, .. }) => {
self.walk_local(init, pat, |t| t.borrow_expr(init, ty::ImmBorrow));
self.walk_local(init, pat, None, |t| t.borrow_expr(init, ty::ImmBorrow))
}

hir::ExprKind::Match(ref discr, arms, _) => {
let discr_place = return_if_err!(self.mc.cat_expr(discr));

// Matching should not always be considered a use of the place, hence
// discr does not necessarily need to be borrowed.
// We only want to borrow discr if the pattern contain something other
// than wildcards.
let ExprUseVisitor { ref mc, body_owner: _, delegate: _ } = *self;
let mut needs_to_be_read = false;
for arm in arms.iter() {
return_if_err!(mc.cat_pattern(discr_place.clone(), arm.pat, |place, pat| {
match &pat.kind {
PatKind::Binding(.., opt_sub_pat) => {
// If the opt_sub_pat is None, than the binding does not count as
// a wildcard for the purpose of borrowing discr.
if opt_sub_pat.is_none() {
needs_to_be_read = true;
}
}
PatKind::Path(qpath) => {
// A `Path` pattern is just a name like `Foo`. This is either a
// named constant or else it refers to an ADT variant

let res = self.mc.typeck_results.qpath_res(qpath, pat.hir_id);
match res {
Res::Def(DefKind::Const, _)
| Res::Def(DefKind::AssocConst, _) => {
// Named constants have to be equated with the value
// being matched, so that's a read of the value being matched.
//
// FIXME: We don't actually reads for ZSTs.
needs_to_be_read = true;
}
_ => {
// Otherwise, this is a struct/enum variant, and so it's
// only a read if we need to read the discriminant.
needs_to_be_read |= is_multivariant_adt(place.place.ty());
}
}
}
PatKind::TupleStruct(..) | PatKind::Struct(..) | PatKind::Tuple(..) => {
// For `Foo(..)`, `Foo { ... }` and `(...)` patterns, check if we are matching
// against a multivariant enum or struct. In that case, we have to read
// the discriminant. Otherwise this kind of pattern doesn't actually
// read anything (we'll get invoked for the `...`, which may indeed
// perform some reads).

let place_ty = place.place.ty();
needs_to_be_read |= is_multivariant_adt(place_ty);
}
PatKind::Lit(_) | PatKind::Range(..) => {
// If the PatKind is a Lit or a Range then we want
// to borrow discr.
needs_to_be_read = true;
}
PatKind::Or(_)
| PatKind::Box(_)
| PatKind::Slice(..)
| PatKind::Ref(..)
| PatKind::Wild => {
// If the PatKind is Or, Box, Slice or Ref, the decision is made later
// as these patterns contains subpatterns
// If the PatKind is Wild, the decision is made based on the other patterns being
// examined
}
}
}));
}

if needs_to_be_read {
self.borrow_expr(discr, ty::ImmBorrow);
} else {
let closure_def_id = match discr_place.place.base {
PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id.to_def_id()),
_ => None,
};

self.delegate.fake_read(
&discr_place,
FakeReadCause::ForMatchedPlace(closure_def_id),
discr_place.hir_id,
);

// We always want to walk the discriminant. We want to make sure, for instance,
// that the discriminant has been initialized.
self.walk_expr(discr);
}
self.maybe_read_scrutinee(
discr,
discr_place.clone(),
arms.iter().map(|arm| arm.pat),
);

// treatment of the discriminant is handled while walking the arms.
for arm in arms {
@@ -453,8 +376,8 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {

fn walk_stmt(&mut self, stmt: &hir::Stmt<'_>) {
match stmt.kind {
hir::StmtKind::Local(hir::Local { pat, init: Some(expr), .. }) => {
self.walk_local(expr, pat, |_| {});
hir::StmtKind::Local(hir::Local { pat, init: Some(expr), els, .. }) => {
self.walk_local(expr, pat, *els, |_| {})
}

hir::StmtKind::Local(_) => {}
@@ -470,13 +393,114 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
}
}

fn walk_local<F>(&mut self, expr: &hir::Expr<'_>, pat: &hir::Pat<'_>, mut f: F)
where
fn maybe_read_scrutinee<'t>(
&mut self,
discr: &Expr<'_>,
discr_place: PlaceWithHirId<'tcx>,
pats: impl Iterator<Item = &'t hir::Pat<'t>>,
) {
// Matching should not always be considered a use of the place, hence
// discr does not necessarily need to be borrowed.
// We only want to borrow discr if the pattern contain something other
// than wildcards.
let ExprUseVisitor { ref mc, body_owner: _, delegate: _ } = *self;
let mut needs_to_be_read = false;
for pat in pats {
return_if_err!(mc.cat_pattern(discr_place.clone(), pat, |place, pat| {
match &pat.kind {
PatKind::Binding(.., opt_sub_pat) => {
// If the opt_sub_pat is None, than the binding does not count as
// a wildcard for the purpose of borrowing discr.
if opt_sub_pat.is_none() {
needs_to_be_read = true;
}
}
PatKind::Path(qpath) => {
// A `Path` pattern is just a name like `Foo`. This is either a
// named constant or else it refers to an ADT variant

let res = self.mc.typeck_results.qpath_res(qpath, pat.hir_id);
match res {
Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => {
// Named constants have to be equated with the value
// being matched, so that's a read of the value being matched.
//
// FIXME: We don't actually reads for ZSTs.
needs_to_be_read = true;
}
_ => {
// Otherwise, this is a struct/enum variant, and so it's
// only a read if we need to read the discriminant.
needs_to_be_read |= is_multivariant_adt(place.place.ty());
}
}
}
PatKind::TupleStruct(..) | PatKind::Struct(..) | PatKind::Tuple(..) => {
// For `Foo(..)`, `Foo { ... }` and `(...)` patterns, check if we are matching
// against a multivariant enum or struct. In that case, we have to read
// the discriminant. Otherwise this kind of pattern doesn't actually
// read anything (we'll get invoked for the `...`, which may indeed
// perform some reads).

let place_ty = place.place.ty();
needs_to_be_read |= is_multivariant_adt(place_ty);
}
PatKind::Lit(_) | PatKind::Range(..) => {
// If the PatKind is a Lit or a Range then we want
// to borrow discr.
needs_to_be_read = true;
}
PatKind::Or(_)
| PatKind::Box(_)
| PatKind::Slice(..)
| PatKind::Ref(..)
| PatKind::Wild => {
// If the PatKind is Or, Box, Slice or Ref, the decision is made later
// as these patterns contains subpatterns
// If the PatKind is Wild, the decision is made based on the other patterns being
// examined
}
}
}));
}

if needs_to_be_read {
self.borrow_expr(discr, ty::ImmBorrow);
} else {
let closure_def_id = match discr_place.place.base {
PlaceBase::Upvar(upvar_id) => Some(upvar_id.closure_expr_id.to_def_id()),
_ => None,
};

self.delegate.fake_read(
&discr_place,
FakeReadCause::ForMatchedPlace(closure_def_id),
discr_place.hir_id,
);

// We always want to walk the discriminant. We want to make sure, for instance,
// that the discriminant has been initialized.
self.walk_expr(discr);
}
}

fn walk_local<F>(
&mut self,
expr: &hir::Expr<'_>,
pat: &hir::Pat<'_>,
els: Option<&hir::Block<'_>>,
mut f: F,
) where
F: FnMut(&mut Self),
{
self.walk_expr(expr);
let expr_place = return_if_err!(self.mc.cat_expr(expr));
f(self);
if let Some(els) = els {
// borrowing because we need to test the descriminant
self.maybe_read_scrutinee(expr, expr_place.clone(), from_ref(pat).iter());
self.walk_block(els)
}
self.walk_irrefutable_pat(&expr_place, &pat);
}

@@ -667,7 +691,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
let ExprUseVisitor { ref mc, body_owner: _, ref mut delegate } = *self;
return_if_err!(mc.cat_pattern(discr_place.clone(), pat, |place, pat| {
if let PatKind::Binding(_, canonical_id, ..) = pat.kind {
debug!("walk_pat: binding place={:?} pat={:?}", place, pat,);
debug!("walk_pat: binding place={:?} pat={:?}", place, pat);
if let Some(bm) =
mc.typeck_results.extract_binding_mode(tcx.sess, pat.hir_id, pat.span)
{
53 changes: 53 additions & 0 deletions src/test/ui/async-await/async-await-let-else.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// edition:2021
#![feature(let_else)]
use std::rc::Rc;

async fn foo(x: Option<bool>) {
let Some(_) = x else {
let r = Rc::new(());
bar().await
};
}

async fn bar() -> ! {
panic!()
}

fn is_send<T: Send>(_: T) {}

async fn foo2(x: Option<bool>) {
let Some(_) = x else {
bar2(Rc::new(())).await
};
}

async fn bar2<T>(_: T) -> ! {
panic!()
}

async fn foo3(x: Option<bool>) {
let Some(_) = x else {
(Rc::new(()), bar().await);
return;
};
}

async fn foo4(x: Option<bool>) {
let Some(_) = x else {
let r = Rc::new(());
bar().await;
println!("{:?}", r);
return;
};
}

fn main() {
is_send(foo(Some(true)));
//~^ ERROR future cannot be sent between threads safely
is_send(foo2(Some(true)));
//~^ ERROR future cannot be sent between threads safely
is_send(foo3(Some(true)));
//~^ ERROR future cannot be sent between threads safely
is_send(foo4(Some(true)));
//~^ ERROR future cannot be sent between threads safely
}
94 changes: 94 additions & 0 deletions src/test/ui/async-await/async-await-let-else.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
error: future cannot be sent between threads safely
--> $DIR/async-await-let-else.rs:45:13
|
LL | is_send(foo(Some(true)));
| ^^^^^^^^^^^^^^^ future returned by `foo` is not `Send`
|
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
--> $DIR/async-await-let-else.rs:8:14
|
LL | let r = Rc::new(());
| - has type `Rc<()>` which is not `Send`
LL | bar().await
| ^^^^^^ await occurs here, with `r` maybe used later
LL | };
| - `r` is later dropped here
note: required by a bound in `is_send`
--> $DIR/async-await-let-else.rs:16:15
|
LL | fn is_send<T: Send>(_: T) {}
| ^^^^ required by this bound in `is_send`

error: future cannot be sent between threads safely
--> $DIR/async-await-let-else.rs:47:13
|
LL | is_send(foo2(Some(true)));
| ^^^^^^^^^^^^^^^^ future returned by `foo2` is not `Send`
|
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
--> $DIR/async-await-let-else.rs:20:26
|
LL | bar2(Rc::new(())).await
| ----------- ^^^^^^ await occurs here, with `Rc::new(())` maybe used later
| |
| has type `Rc<()>` which is not `Send`
LL | };
| - `Rc::new(())` is later dropped here
note: required by a bound in `is_send`
--> $DIR/async-await-let-else.rs:16:15
|
LL | fn is_send<T: Send>(_: T) {}
| ^^^^ required by this bound in `is_send`

error: future cannot be sent between threads safely
--> $DIR/async-await-let-else.rs:49:13
|
LL | is_send(foo3(Some(true)));
| ^^^^^^^^^^^^^^^^ future returned by `foo3` is not `Send`
|
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
--> $DIR/async-await-let-else.rs:30:28
|
LL | (Rc::new(()), bar().await);
| ----------- ^^^^^^ await occurs here, with `Rc::new(())` maybe used later
| |
| has type `Rc<()>` which is not `Send`
note: `Rc::new(())` is later dropped here
--> $DIR/async-await-let-else.rs:30:35
|
LL | (Rc::new(()), bar().await);
| ^
note: required by a bound in `is_send`
--> $DIR/async-await-let-else.rs:16:15
|
LL | fn is_send<T: Send>(_: T) {}
| ^^^^ required by this bound in `is_send`

error: future cannot be sent between threads safely
--> $DIR/async-await-let-else.rs:51:13
|
LL | is_send(foo4(Some(true)));
| ^^^^^^^^^^^^^^^^ future returned by `foo4` is not `Send`
|
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
note: future is not `Send` as this value is used across an await
--> $DIR/async-await-let-else.rs:38:14
|
LL | let r = Rc::new(());
| - has type `Rc<()>` which is not `Send`
LL | bar().await;
| ^^^^^^ await occurs here, with `r` maybe used later
...
LL | };
| - `r` is later dropped here
note: required by a bound in `is_send`
--> $DIR/async-await-let-else.rs:16:15
|
LL | fn is_send<T: Send>(_: T) {}
| ^^^^ required by this bound in `is_send`

error: aborting due to 4 previous errors

Original file line number Diff line number Diff line change
@@ -2,7 +2,9 @@ error[E0308]: mismatched types
--> $DIR/let-else-binding-explicit-mut-annotated.rs:9:37
|
LL | let Some(n): &mut Option<i32> = &&Some(5i32) else { return };
| ^^^^^^^^^^^^ types differ in mutability
| ---------------- ^^^^^^^^^^^^ types differ in mutability
| |
| expected due to this
|
= note: expected mutable reference `&mut Option<i32>`
found reference `&&Option<i32>`
@@ -11,7 +13,9 @@ error[E0308]: mismatched types
--> $DIR/let-else-binding-explicit-mut-annotated.rs:13:37
|
LL | let Some(n): &mut Option<i32> = &&mut Some(5i32) else { return };
| ^^^^^^^^^^^^^^^^ types differ in mutability
| ---------------- ^^^^^^^^^^^^^^^^ types differ in mutability
| |
| expected due to this
|
= note: expected mutable reference `&mut Option<i32>`
found reference `&&mut Option<i32>`
12 changes: 6 additions & 6 deletions src/test/ui/let-else/let-else-check.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: unused variable: `x`
--> $DIR/let-else-check.rs:18:9
--> $DIR/let-else-check.rs:14:13
|
LL | let x = 1;
| ^ help: if this is intentional, prefix it with an underscore: `_x`
LL | let x = 1;
| ^ help: if this is intentional, prefix it with an underscore: `_x`
|
note: the lint level is defined here
--> $DIR/let-else-check.rs:3:9
@@ -11,10 +11,10 @@ LL | #![deny(unused_variables)]
| ^^^^^^^^^^^^^^^^

error: unused variable: `x`
--> $DIR/let-else-check.rs:14:13
--> $DIR/let-else-check.rs:18:9
|
LL | let x = 1;
| ^ help: if this is intentional, prefix it with an underscore: `_x`
LL | let x = 1;
| ^ help: if this is intentional, prefix it with an underscore: `_x`

error: aborting due to 2 previous errors

18 changes: 9 additions & 9 deletions src/test/ui/let-else/let-else-non-diverging.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
error[E0308]: `else` clause of `let...else` does not diverge
--> $DIR/let-else-non-diverging.rs:12:32
--> $DIR/let-else-non-diverging.rs:4:32
|
LL | let Some(x) = Some(1) else { Some(2) };
| ^^^^^^^^^^^ expected `!`, found enum `Option`
LL | let Some(x) = Some(1) else {
| ________________________________^
LL | | Some(2)
LL | | };
| |_____^ expected `!`, found enum `Option`
|
= note: expected type `!`
found enum `Option<{integer}>`
@@ -26,13 +29,10 @@ LL | | };
= help: ...or use `match` instead of `let...else`

error[E0308]: `else` clause of `let...else` does not diverge
--> $DIR/let-else-non-diverging.rs:4:32
--> $DIR/let-else-non-diverging.rs:12:32
|
LL | let Some(x) = Some(1) else {
| ________________________________^
LL | | Some(2)
LL | | };
| |_____^ expected `!`, found enum `Option`
LL | let Some(x) = Some(1) else { Some(2) };
| ^^^^^^^^^^^ expected `!`, found enum `Option`
|
= note: expected type `!`
found enum `Option<{integer}>`
16 changes: 12 additions & 4 deletions src/test/ui/let-else/let-else-ref-bindings.stderr
Original file line number Diff line number Diff line change
@@ -20,7 +20,9 @@ error[E0308]: mismatched types
--> $DIR/let-else-ref-bindings.rs:24:34
|
LL | let Some(a): Option<&[u8]> = some else { return };
| ^^^^ expected `&[u8]`, found struct `Vec`
| ------------- ^^^^ expected `&[u8]`, found struct `Vec`
| |
| expected due to this
|
= note: expected enum `Option<&[u8]>`
found enum `Option<Vec<u8>>`
@@ -29,7 +31,9 @@ error[E0308]: mismatched types
--> $DIR/let-else-ref-bindings.rs:27:34
|
LL | let Some(a): Option<&[u8]> = &some else { return };
| ^^^^^ expected enum `Option`, found `&Option<Vec<u8>>`
| ------------- ^^^^^ expected enum `Option`, found `&Option<Vec<u8>>`
| |
| expected due to this
|
= note: expected enum `Option<&[u8]>`
found reference `&Option<Vec<u8>>`
@@ -56,7 +60,9 @@ error[E0308]: mismatched types
--> $DIR/let-else-ref-bindings.rs:52:38
|
LL | let Some(a): Option<&mut [u8]> = some else { return };
| ^^^^ expected `&mut [u8]`, found struct `Vec`
| ----------------- ^^^^ expected `&mut [u8]`, found struct `Vec`
| |
| expected due to this
|
= note: expected enum `Option<&mut [u8]>`
found enum `Option<Vec<u8>>`
@@ -65,7 +71,9 @@ error[E0308]: mismatched types
--> $DIR/let-else-ref-bindings.rs:55:38
|
LL | let Some(a): Option<&mut [u8]> = &mut some else { return };
| ^^^^^^^^^ expected enum `Option`, found mutable reference
| ----------------- ^^^^^^^^^ expected enum `Option`, found mutable reference
| |
| expected due to this
|
= note: expected enum `Option<&mut [u8]>`
found mutable reference `&mut Option<Vec<u8>>`
25 changes: 25 additions & 0 deletions src/test/ui/let-else/let-else-temporary-lifetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// run-pass
#![feature(let_else)]

use std::sync::atomic::{AtomicU8, Ordering};

static TRACKER: AtomicU8 = AtomicU8::new(0);

#[derive(Default)]
struct Droppy {
inner: u32,
}

impl Drop for Droppy {
fn drop(&mut self) {
TRACKER.store(1, Ordering::Release);
println!("I've been dropped");
}
}

fn main() {
assert_eq!(TRACKER.load(Ordering::Acquire), 0);
let 0 = Droppy::default().inner else { return };
assert_eq!(TRACKER.load(Ordering::Acquire), 1);
println!("Should have dropped 👆");
}
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ use rustc_lint::LateContext;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) {
let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) {
([stmt, stmts @ ..], expr) => {
if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
if let StmtKind::Local(&Local { init: Some(e), els: None, .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
(e, !stmts.is_empty() || expr.is_some())
} else {
return;
3 changes: 2 additions & 1 deletion src/tools/clippy/clippy_lints/src/matches/mod.rs
Original file line number Diff line number Diff line change
@@ -1041,7 +1041,8 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}

fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local);
self.infallible_destructuring_match_linted |=
local.els.is_none() && infallible_destructuring_match::check(cx, local);
}

fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
1 change: 1 addition & 0 deletions src/tools/clippy/clippy_lints/src/no_effect.rs
Original file line number Diff line number Diff line change
@@ -92,6 +92,7 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
if_chain! {
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id);
if let Some(init) = local.init;
if local.els.is_none();
if !local.pat.span.from_expansion();
if has_no_effect(cx, init);
if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
5 changes: 1 addition & 4 deletions src/tools/clippy/clippy_lints/src/returns.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::Span;
use rustc_span::sym;

@@ -203,9 +202,7 @@ fn check_final_expr<'tcx>(
check_block_return(cx, ifblock);
}
if let Some(else_clause) = else_clause_opt {
if expr.span.desugaring_kind() != Some(DesugaringKind::LetElse) {
check_final_expr(cx, else_clause, None, RetReplacement::Empty);
}
check_final_expr(cx, else_clause, None, RetReplacement::Empty);
}
},
// a match expr, check all arms
8 changes: 6 additions & 2 deletions src/tools/clippy/clippy_utils/src/hir_utils.rs
Original file line number Diff line number Diff line change
@@ -102,7 +102,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> {
impl HirEqInterExpr<'_, '_, '_> {
pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
match (&left.kind, &right.kind) {
(&StmtKind::Local(l), &StmtKind::Local(r)) => {
(&StmtKind::Local(l, ), &StmtKind::Local(r, )) => {
// This additional check ensures that the type of the locals are equivalent even if the init
// expression or type have some inferred parts.
if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
@@ -117,6 +117,7 @@ impl HirEqInterExpr<'_, '_, '_> {
// these only get added if the init and type is equal.
both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
&& both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
&& both(&l.els, &r.els, |l, r| self.eq_block(l, r))
&& self.eq_pat(l.pat, r.pat)
},
(&StmtKind::Expr(l), &StmtKind::Expr(r)) | (&StmtKind::Semi(l), &StmtKind::Semi(r)) => self.eq_expr(l, r),
@@ -921,11 +922,14 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
std::mem::discriminant(&b.kind).hash(&mut self.s);

match &b.kind {
StmtKind::Local(local) => {
StmtKind::Local(local, ) => {
self.hash_pat(local.pat);
if let Some(init) = local.init {
self.hash_expr(init);
}
if let Some(els) = local.els {
self.hash_block(els);
}
},
StmtKind::Item(..) => {},
StmtKind::Expr(expr) | StmtKind::Semi(expr) => {