Rollup merge of #98574 - dingxiangfei2009:let-else-thir, r=oli-obk

Lower let-else in MIR

This MR will switch to lower let-else statements in MIR building instead.

To lower let-else in MIR, we build a mini-switch two branches. One branch leads to the matching case, and the other leads to the `else` block. This arrangement will allow temporary lifetime analysis running as-is so that the temporaries are properly extended according to the same rule applied to regular `let` statements.

cc https://github.com/rust-lang/rust/issues/87335

Fix #98672
This commit is contained in:
Dylan DPC 2022-07-13 19:32:33 +05:30 committed by GitHub
commit 0083cd2fd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 576 additions and 265 deletions

View file

@ -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
}
}

View file

@ -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)))
}

View file

@ -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

View file

@ -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);
}

View file

@ -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,6 +902,13 @@ 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()
}
@ -904,7 +916,7 @@ impl<'a> State<'a> {
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`:

View file

@ -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,
},

View file

@ -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)
}
}
}
}

View file

@ -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| {

View file

@ -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()
}
}

View file

@ -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,

View file

@ -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,6 +77,10 @@ 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)),
@ -84,7 +88,9 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> {
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);

View file

@ -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);
};
is_needs_drop_and_init(tcx, param_env, maybe_inits, move_data, f_ty, mpi)

View file

@ -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)

View file

@ -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>) {

View file

@ -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",
}
}

View file

@ -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

View file

@ -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);

View file

@ -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(_) => {}

View file

@ -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>) {

View file

@ -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)
}
}

View file

@ -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)
{

View file

@ -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
}

View file

@ -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

View file

@ -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>`

View file

@ -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

View file

@ -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}>`

View file

@ -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>>`

View file

@ -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 👆");
}

View file

@ -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;

View file

@ -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<'_>) {

View file

@ -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;

View file

@ -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

View file

@ -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) => {