From 73259dccd31725a05b06a24e54c5276edad75a64 Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Sun, 2 Jul 2023 18:30:26 +0800 Subject: [PATCH 1/3] first version of super let syntax support smoke tests of the new syntax move new_temp_lifetime into query restore classic scoping rules --- compiler/rustc_ast/src/ast.rs | 7 +- compiler/rustc_ast/src/mut_visit.rs | 2 +- compiler/rustc_ast_lowering/src/block.rs | 11 +- compiler/rustc_ast_lowering/src/lib.rs | 1 + compiler/rustc_ast_pretty/src/pprust/state.rs | 3 + compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_hir/src/hir.rs | 1 + compiler/rustc_hir_analysis/src/check/mod.rs | 3 + .../rustc_hir_analysis/src/check/scope_map.rs | 509 ++++++++++++++++++ .../rustc_hir_typeck/src/gather_locals.rs | 2 +- compiler/rustc_middle/src/arena.rs | 1 + compiler/rustc_middle/src/middle/region.rs | 23 + compiler/rustc_middle/src/query/mod.rs | 6 + .../rustc_mir_build/src/build/matches/mod.rs | 14 +- compiler/rustc_mir_build/src/build/mod.rs | 3 + compiler/rustc_mir_build/src/build/scope.rs | 7 + compiler/rustc_mir_build/src/thir/cx/expr.rs | 60 ++- compiler/rustc_mir_build/src/thir/cx/mod.rs | 3 + compiler/rustc_parse/src/parser/stmt.rs | 32 +- compiler/rustc_span/src/symbol.rs | 1 + .../src/loops/needless_range_loop.rs | 21 +- src/tools/clippy/clippy_lints/src/shadow.rs | 15 +- tests/ui/binding/super-let-negative.rs | 21 + tests/ui/binding/super-let-negative.stderr | 31 ++ tests/ui/binding/super-let-positive.rs | 55 ++ .../feature-gate-new-temp-lifetime.rs | 3 + .../feature-gate-new-temp-lifetime.stderr | 8 + 27 files changed, 798 insertions(+), 47 deletions(-) create mode 100644 compiler/rustc_hir_analysis/src/check/scope_map.rs create mode 100644 tests/ui/binding/super-let-negative.rs create mode 100644 tests/ui/binding/super-let-negative.stderr create mode 100644 tests/ui/binding/super-let-positive.rs create mode 100644 tests/ui/feature-gates/feature-gate-new-temp-lifetime.rs create mode 100644 tests/ui/feature-gates/feature-gate-new-temp-lifetime.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 296a570de6b33..c0778cf2a2ee4 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1067,20 +1067,23 @@ pub enum LocalKind { /// Local declaration with an initializer and an `else` clause. /// Example: `let Some(x) = y else { return };` InitElse(P, P), + /// Local declaration with an initializer living through the temporary lifetime. + /// Example: `super let x = y;` + Super(P), } impl LocalKind { pub fn init(&self) -> Option<&Expr> { match self { Self::Decl => None, - Self::Init(i) | Self::InitElse(i, _) => Some(i), + Self::Init(i) | Self::InitElse(i, _) | Self::Super(i) => Some(i), } } pub fn init_else_opt(&self) -> Option<(&Expr, Option<&Block>)> { match self { Self::Decl => None, - Self::Init(init) => Some((init, None)), + Self::Init(init) | Self::Super(init) => Some((init, None)), Self::InitElse(init, els) => Some((init, Some(els))), } } diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 90677151d2506..510dfddb6d96a 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -609,7 +609,7 @@ pub fn noop_visit_local(local: &mut P, vis: &mut T) { visit_opt(ty, |ty| vis.visit_ty(ty)); match kind { LocalKind::Decl => {} - LocalKind::Init(init) => { + LocalKind::Init(init) | LocalKind::Super(init) => { vis.visit_expr(init); } LocalKind::InitElse(init, els) => { diff --git a/compiler/rustc_ast_lowering/src/block.rs b/compiler/rustc_ast_lowering/src/block.rs index 865a56b2c1f15..13d850b0f5c2a 100644 --- a/compiler/rustc_ast_lowering/src/block.rs +++ b/compiler/rustc_ast_lowering/src/block.rs @@ -97,7 +97,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { 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, els, span, source }) + self.arena.alloc(hir::Local { + hir_id, + ty, + pat, + init, + els, + span, + source, + is_super: matches!(l.kind, LocalKind::Super(..)), + }) } fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode { diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 5f7439060b3c9..bdb71efeaaebd 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2342,6 +2342,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { source, span: self.lower_span(span), ty: None, + is_super: false, }; self.stmt(span, hir::StmtKind::Local(self.arena.alloc(local))) } diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 731232bce65cb..49e7144dd04a4 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1090,6 +1090,9 @@ impl<'a> State<'a> { self.print_outer_attributes(&loc.attrs); self.space_if_not_bol(); self.ibox(INDENT_UNIT); + if matches!(loc.kind, ast::LocalKind::Super(..)) { + self.word_nbsp("super") + } self.word_nbsp("let"); self.ibox(INDENT_UNIT); diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index efb0b1fbabbc8..e1a48713e8da3 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -537,6 +537,8 @@ declare_features! ( (unstable, never_type, "1.13.0", Some(35121)), /// Allows diverging expressions to fall back to `!` rather than `()`. (unstable, never_type_fallback, "1.41.0", Some(65992)), + /// Allows new temporary lifetime rules + (unstable, new_temp_lifetime, "1.72.0", Some(99999)), /// Allows `#![no_core]`. (unstable, no_core, "1.3.0", Some(29639)), /// Allows the use of `no_sanitize` attribute. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index abf7a695fd2d1..bfe18bd37e9a8 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1232,6 +1232,7 @@ pub struct Local<'hir> { /// Else block for a `let...else` binding. pub els: Option<&'hir Block<'hir>>, pub hir_id: HirId, + pub is_super: bool, pub span: Span, /// Can be `ForLoopDesugar` if the `let` statement is part of a `for` loop /// desugaring. Otherwise will be `Normal`. diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index b74431983113a..ad2141b90fc4c 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -70,6 +70,7 @@ mod errs; pub mod intrinsic; pub mod intrinsicck; mod region; +mod scope_map; pub mod wfcheck; pub use check::check_abi; @@ -105,6 +106,7 @@ use crate::util::common::indenter; use self::compare_impl_item::collect_return_position_impl_trait_in_trait_tys; use self::region::region_scope_tree; +use self::scope_map::body_scope_map; pub fn provide(providers: &mut Providers) { wfcheck::provide(providers); @@ -114,6 +116,7 @@ pub fn provide(providers: &mut Providers) { collect_return_position_impl_trait_in_trait_tys, compare_impl_const: compare_impl_item::compare_impl_const_raw, check_coroutine_obligations: check::check_coroutine_obligations, + body_scope_map, ..*providers }; } diff --git a/compiler/rustc_hir_analysis/src/check/scope_map.rs b/compiler/rustc_hir_analysis/src/check/scope_map.rs new file mode 100644 index 0000000000000..e41c29ee1bd32 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/check/scope_map.rs @@ -0,0 +1,509 @@ +use hir::intravisit::Visitor; +use rustc_hir as hir; +use rustc_index::Idx; +use rustc_middle::{ + middle::region::{BodyScopeMap, FirstStatementIndex, Scope, ScopeData}, + ty::TyCtxt, +}; +use rustc_span::source_map; + +#[derive(Debug, Copy, Clone)] +struct TemporaryLifetimeScopes { + /// Result scope of the enclosing block + enclosing_block_result_scope: Option, + /// The enclosing block scope + enclosing_block_scope: Option, + /// Result scope + result_scope: Option, + /// Intermediate scope + intermediate_scope: Option, + /// When visiting pattern, whether a super-let declaration context is active + is_super: bool, + /// When visiting expression, whether the expression is at result-producing location + is_result: bool, +} + +impl TemporaryLifetimeScopes { + fn new(scope: Scope) -> Self { + Self { + enclosing_block_result_scope: Some(scope), + enclosing_block_scope: Some(scope), + result_scope: Some(scope), + intermediate_scope: Some(scope), + is_super: false, + is_result: true, + } + } + + fn new_static() -> Self { + Self { + enclosing_block_result_scope: None, + enclosing_block_scope: None, + result_scope: None, + intermediate_scope: None, + is_super: false, + is_result: true, + } + } + + fn enter_terminating(&mut self, id: hir::ItemLocalId) { + debug!(?self, "enter terminating, before"); + let scope = Scope { id, data: ScopeData::Node }; + debug!("new_temp_lifetime: terminating scope {scope:?}"); + *self = Self { is_result: false, ..Self::new(scope) }; + debug!(?self, "enter terminating, before"); + } + + fn enter_remainder(&mut self, blk_id: hir::ItemLocalId, stmt_idx: usize) { + // `enclosing_block_result_scope` is fixed for now, because + // the remainder sits on the resulting location of its own enclosing block. + // This means that the remainder is itself result producing and so inherits + // the same result scope. + debug!(?self, "enter remainder, before"); + self.enclosing_block_scope = Some(Scope { + id: blk_id, + data: ScopeData::Remainder(FirstStatementIndex::new(stmt_idx)), + }); + debug!(?self, "enter remainder, after"); + } + + fn enter_if_then(&mut self, then: hir::ItemLocalId) { + debug!(?self, "enter if then, before"); + let scope = Scope { id: then, data: ScopeData::IfThen }; + if self.is_result { + self.intermediate_scope = Some(scope); + } else { + self.result_scope = self.intermediate_scope; + self.enclosing_block_result_scope = self.intermediate_scope; + } + self.enclosing_block_scope = Some(scope); + debug!(?self, "enter if then, after"); + } + + fn enter_scrutinee(&mut self, scrutinee: hir::ItemLocalId) { + debug!(?self, "enter scrutinee, before"); + self.result_scope = self.intermediate_scope; + self.is_result = true; + self.intermediate_scope = Some(Scope { id: scrutinee, data: ScopeData::Node }); + debug!(?self, "enter scrutinee, after"); + } + + fn enter_let_initializer(&mut self, classic_let_init: hir::ItemLocalId) { + debug!(?self, "enter let init, before"); + self.result_scope = self.enclosing_block_scope; + self.intermediate_scope = Some(Scope { id: classic_let_init, data: ScopeData::Node }); + self.is_result = true; + // consume the `super` flag + self.is_super = false; + debug!(?self, "enter let init, after"); + } + + fn enter_super_let_initializer(&mut self, super_let_init: hir::ItemLocalId) { + debug!(?self, "enter super let init, before"); + self.result_scope = self.enclosing_block_result_scope; + self.intermediate_scope = Some(Scope { id: super_let_init, data: ScopeData::Node }); + self.is_result = true; + // consume the `super` flag + self.is_super = false; + debug!(?self, "enter super let init, after"); + } + + fn enter_block(&mut self, block: hir::ItemLocalId) { + debug!(?self, "enter block, before"); + let scope = Scope { id: block, data: ScopeData::Node }; + if self.is_result { + self.intermediate_scope = Some(scope); + } else { + self.result_scope = self.intermediate_scope; + self.enclosing_block_result_scope = self.intermediate_scope; + } + self.enclosing_block_scope = Some(scope); + debug!(?self, "enter block, after"); + } + + fn enter_tail_expr(&mut self, expr: hir::ItemLocalId) { + debug!(?self, "enter tail expr, before"); + let scope = Scope { id: expr, data: ScopeData::Node }; + self.result_scope = Some(scope); + self.intermediate_scope = Some(scope); + debug!(?self, "enter tail expr, after"); + } + + fn resolve_temp_lifetime(&self) -> Option { + if self.is_result { self.result_scope } else { self.intermediate_scope } + } +} + +struct ScopeCollector<'tcx> { + tcx: TyCtxt<'tcx>, + terminating_scopes: hir::ItemLocalSet, + + /// Assignment of temporary lifetimes to each expression + expr_scope: hir::ItemLocalMap>, + + /// Assignment of scope to each variable declaration, including `super let`s. + new_var_scope: hir::ItemLocalMap>, + + cx: Context, +} + +#[derive(Clone, Copy)] +struct Context { + temp_lifetime: TemporaryLifetimeScopes, + + var_parent: Option, +} + +impl<'tcx> ScopeCollector<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> Self { + Self { + tcx, + terminating_scopes: Default::default(), + expr_scope: Default::default(), + new_var_scope: Default::default(), + cx: Context { temp_lifetime: TemporaryLifetimeScopes::new_static(), var_parent: None }, + } + } +} + +impl<'tcx> ScopeCollector<'tcx> { + fn resolve_local( + &mut self, + pat: Option<&'tcx hir::Pat<'tcx>>, + init: Option<&'tcx hir::Expr<'tcx>>, + is_super: bool, + ) { + let prev_temp_lifetime = self.cx.temp_lifetime; + + if let Some(expr) = init { + let init_id = expr.hir_id.local_id; + if is_super { + self.cx.temp_lifetime.enter_super_let_initializer(init_id) + } else { + self.cx.temp_lifetime.enter_let_initializer(init_id) + } + self.visit_expr(expr); + } + + if let Some(pat) = pat { + self.cx.temp_lifetime.is_super = is_super; + self.visit_pat(pat); + } + self.cx.temp_lifetime = prev_temp_lifetime; + } +} + +impl<'tcx> Visitor<'tcx> for ScopeCollector<'tcx> { + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { + let body_id = body.id(); + let owner_id = self.tcx.hir().body_owner_def_id(body_id); + let prev_cx = self.cx; + let outer_ts = core::mem::take(&mut self.terminating_scopes); + self.terminating_scopes.insert(body.value.hir_id.local_id); + let scope = Scope { id: body.value.hir_id.local_id, data: ScopeData::Arguments }; + self.cx.var_parent = Some(scope); + self.cx.temp_lifetime = TemporaryLifetimeScopes::new(scope); + // The arguments and `self` are parented to the fn. + for param in body.params { + self.visit_pat(¶m.pat); + } + if self.tcx.hir().body_owner_kind(owner_id).is_fn_or_closure() { + self.visit_expr(&body.value) + } else { + self.cx.var_parent = None; + self.cx.temp_lifetime = TemporaryLifetimeScopes::new_static(); + self.resolve_local(None, Some(&body.value), false) + } + self.cx = prev_cx; + self.terminating_scopes = outer_ts; + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + let expr_id = expr.hir_id.local_id; + if self.terminating_scopes.contains(&expr_id) { + self.cx.temp_lifetime.enter_terminating(expr_id) + } + + let expr_scope = self.cx.temp_lifetime.resolve_temp_lifetime(); + debug!(?expr.hir_id.local_id, ?expr.span, ?expr_scope, "resolve temp lifetime"); + self.expr_scope.insert(expr_id, expr_scope); + + { + let terminating_scopes = &mut self.terminating_scopes; + let mut terminating = |id: hir::ItemLocalId| { + terminating_scopes.insert(id); + }; + match expr.kind { + // Conditional or repeating scopes are always terminating + // scopes, meaning that temporaries cannot outlive them. + // This ensures fixed size stacks. + hir::ExprKind::Binary( + source_map::Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. }, + l, + r, + ) => { + // expr is a short circuiting operator (|| or &&). As its + // functionality can't be overridden by traits, it always + // processes bool sub-expressions. bools are Copy and thus we + // can drop any temporaries in evaluation (read) order + // (with the exception of potentially failing let expressions). + // We achieve this by enclosing the operands in a terminating + // scope, both the LHS and the RHS. + + // We optimize this a little in the presence of chains. + // Chains like a && b && c get lowered to AND(AND(a, b), c). + // In here, b and c are RHS, while a is the only LHS operand in + // that chain. This holds true for longer chains as well: the + // leading operand is always the only LHS operand that is not a + // binop itself. Putting a binop like AND(a, b) into a + // terminating scope is not useful, thus we only put the LHS + // into a terminating scope if it is not a binop. + + let terminate_lhs = match l.kind { + // let expressions can create temporaries that live on + hir::ExprKind::Let(_) => false, + // binops already drop their temporaries, so there is no + // need to put them into a terminating scope. + // This is purely an optimization to reduce the number of + // terminating scopes. + hir::ExprKind::Binary( + source_map::Spanned { + node: hir::BinOpKind::And | hir::BinOpKind::Or, + .. + }, + .., + ) => false, + // otherwise: mark it as terminating + _ => true, + }; + if terminate_lhs { + terminating(l.hir_id.local_id); + } + + // `Let` expressions (in a let-chain) shouldn't be terminating, as their temporaries + // should live beyond the immediate expression + if !matches!(r.kind, hir::ExprKind::Let(_)) { + terminating(r.hir_id.local_id); + } + } + hir::ExprKind::Loop(body, _, _, _) => { + terminating(body.hir_id.local_id); + } + + hir::ExprKind::DropTemps(expr) => { + // `DropTemps(expr)` does not denote a conditional scope. + // Rather, we want to achieve the same behavior as `{ let _t = expr; _t }`. + terminating(expr.hir_id.local_id); + } + _ => {} + } + } + + let prev_temp_lifetime = self.cx.temp_lifetime; + match expr.kind { + hir::ExprKind::If(cond, then, otherwise) => { + self.cx.temp_lifetime.enter_if_then(then.hir_id.local_id); + self.cx.temp_lifetime.enter_scrutinee(cond.hir_id.local_id); + self.visit_expr(cond); + self.cx.temp_lifetime = prev_temp_lifetime; + self.cx.temp_lifetime.is_result = true; + self.visit_expr(then); + if let Some(otherwise) = otherwise { + self.cx.temp_lifetime = prev_temp_lifetime; + self.cx.temp_lifetime.is_result = true; + self.visit_expr(otherwise) + } + } + + hir::ExprKind::Match(scrutinee, arms, _source) => { + self.cx.temp_lifetime.enter_scrutinee(scrutinee.hir_id.local_id); + self.visit_expr(scrutinee); + for arm in arms { + self.cx.temp_lifetime = prev_temp_lifetime; + self.visit_arm(arm) + } + } + + hir::ExprKind::Block(block, _label) => { + self.cx.temp_lifetime.enter_block(block.hir_id.local_id); + self.visit_block(block) + } + + hir::ExprKind::AddrOf(_, _, subexpr) + | hir::ExprKind::Unary(hir::UnOp::Deref, subexpr) => { + self.cx.temp_lifetime.is_result = true; + self.visit_expr(subexpr) + } + + hir::ExprKind::Struct(_path, fields, struct_base) => { + for hir::ExprField { expr, .. } in fields { + self.cx.temp_lifetime = prev_temp_lifetime; + self.cx.temp_lifetime.is_result = true; + self.visit_expr(expr); + } + if let Some(struct_base) = struct_base { + self.cx.temp_lifetime = prev_temp_lifetime; + // FIXME: does this make sense? It looks like so. + self.cx.temp_lifetime.is_result = true; + self.visit_expr(struct_base) + } + } + + hir::ExprKind::Tup(subexprs) => { + for subexpr in subexprs { + self.cx.temp_lifetime = prev_temp_lifetime; + self.cx.temp_lifetime.is_result = true; + self.visit_expr(subexpr); + } + } + + // FIXME: we propose to change enum tuple variant constructors and struct tuple constructors + // into proper ADT constructor, instead of calls. + _ => { + self.cx.temp_lifetime.is_result = false; + self.cx.temp_lifetime.result_scope = self.cx.temp_lifetime.intermediate_scope; + hir::intravisit::walk_expr(self, expr) + } + } + self.cx.temp_lifetime = prev_temp_lifetime; + } + + fn visit_block(&mut self, blk: &'tcx hir::Block<'tcx>) { + let prev_cx = self.cx; + let blk_id = blk.hir_id.local_id; + if self.terminating_scopes.contains(&blk_id) { + self.cx.temp_lifetime.enter_terminating(blk_id) + } + for (i, statement) in blk.stmts.iter().enumerate() { + match statement.kind { + hir::StmtKind::Local(hir::Local { els: Some(els), .. }) => { + // Let-else has a special lexical structure for variables. + // First we take a checkpoint of the current scope context here. + let mut prev_cx = self.cx; + + self.cx.temp_lifetime.enter_remainder(blk_id, i); + self.cx.var_parent = Some(Scope { + id: blk_id, + data: ScopeData::Remainder(FirstStatementIndex::new(i)), + }); + self.visit_stmt(statement); + // We need to back out temporarily to the last enclosing scope + // for the `else` block, so that even the temporaries receiving + // extended lifetime will be dropped inside this block. + // We are visiting the `else` block in this order so that + // the sequence of visits agrees with the order in the default + // `hir::intravisit` visitor. + core::mem::swap(&mut prev_cx, &mut self.cx); + let els_id = els.hir_id.local_id; + self.terminating_scopes.insert(els_id); + self.visit_block(els); + // From now on, we continue normally. + self.cx = prev_cx; + } + hir::StmtKind::Local(..) => { + // Each declaration introduces a subscope for bindings + // introduced by the declaration; this subscope covers a + // suffix of the block. Each subscope in a block has the + // previous subscope in the block as a parent, except for + // the first such subscope, which has the block itself as a + // parent. + self.cx.var_parent = Some(Scope { + id: blk_id, + data: ScopeData::Remainder(FirstStatementIndex::new(i)), + }); + self.cx.temp_lifetime.enter_remainder(blk_id, i); + self.visit_stmt(statement) + } + hir::StmtKind::Item(..) => { + // Don't create scopes for items, since they won't be + // lowered to THIR and MIR. + } + hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => self.visit_stmt(statement), + } + } + if let Some(expr) = blk.expr { + self.cx.temp_lifetime = prev_cx.temp_lifetime; + self.cx.temp_lifetime.enter_tail_expr(expr.hir_id.local_id); + self.visit_expr(expr) + } + self.cx = prev_cx; + } + + fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { + let stmt_id = stmt.hir_id.local_id; + debug!("resolve_stmt(stmt.id={:?})", stmt_id); + + let prev_cx = self.cx; + + match stmt.kind { + hir::StmtKind::Local(local) => self.visit_local(local), + hir::StmtKind::Item(_item) => {} + hir::StmtKind::Expr(expression) | hir::StmtKind::Semi(expression) => { + self.cx.temp_lifetime.enter_terminating(stmt_id); + self.visit_expr(expression) + } + } + + self.cx = prev_cx; + } + + fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { + self.resolve_local(Some(&l.pat), l.init, l.is_super) + } + + fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { + let prev_cx = self.cx; + + self.cx.var_parent = Some(Scope { id: arm.hir_id.local_id, data: ScopeData::Node }); + + self.terminating_scopes.insert(arm.body.hir_id.local_id); + + // First, assert that a local declaration arising from pattern matching is + // not super. + self.cx.temp_lifetime.is_super = false; + self.cx.temp_lifetime.enter_block(arm.hir_id.local_id); + let prev_temp_lifetime = self.cx.temp_lifetime; + self.visit_pat(arm.pat); + if let Some(expr) = arm.guard { + self.terminating_scopes.insert(expr.hir_id.local_id); + self.visit_expr(expr); + self.cx.temp_lifetime = prev_temp_lifetime; + } + self.cx.temp_lifetime.is_result = true; + self.visit_expr(arm.body); + + self.cx = prev_cx; + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + let pat_id = pat.hir_id.local_id; + + // If this is a binding then record the lifetime of that binding. + if let hir::PatKind::Binding(..) = pat.kind { + let scope = if self.cx.temp_lifetime.is_super { + self.cx.temp_lifetime.enclosing_block_result_scope + } else { + self.cx.temp_lifetime.enclosing_block_scope + }; + debug!("new_temp_lifetime: var {pat_id:?} @ {scope:?}"); + self.new_var_scope.insert(pat_id, scope); + } + + hir::intravisit::walk_pat(self, pat) + } +} + +pub fn body_scope_map(tcx: TyCtxt<'_>, def_id: hir::def_id::DefId) -> &BodyScopeMap { + let typeck_root_def_id = tcx.typeck_root_def_id(def_id); + if typeck_root_def_id != def_id { + return tcx.body_scope_map(typeck_root_def_id); + } + let map = if let Some(body_id) = tcx.hir().maybe_body_owned_by(def_id.expect_local()) { + let mut collector = ScopeCollector::new(tcx); + collector.visit_body(tcx.hir().body(body_id)); + BodyScopeMap { expr_scope: collector.expr_scope, new_var_scope: collector.new_var_scope } + } else { + BodyScopeMap { expr_scope: <_>::default(), new_var_scope: <_>::default() } + }; + tcx.arena.alloc(map) +} diff --git a/compiler/rustc_hir_typeck/src/gather_locals.rs b/compiler/rustc_hir_typeck/src/gather_locals.rs index f9af357f0e79e..769343c33bdef 100644 --- a/compiler/rustc_hir_typeck/src/gather_locals.rs +++ b/compiler/rustc_hir_typeck/src/gather_locals.rs @@ -43,7 +43,7 @@ pub(super) struct Declaration<'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, els, source: _ } = *local; + let hir::Local { hir_id, pat, ty, span, init, els, source: _, is_super: _ } = *local; Declaration { hir_id, pat, ty, span, init, origin: DeclOrigin::LocalDecl { els } } } } diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index c648b2bfe9b90..ac0eb47234077 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -39,6 +39,7 @@ macro_rules! arena_types { [decode] code_region: rustc_middle::mir::coverage::CodeRegion, [] const_allocs: rustc_middle::mir::interpret::Allocation, [] region_scope_tree: rustc_middle::middle::region::ScopeTree, + [] body_scope_map: rustc_middle::middle::region::BodyScopeMap, // Required for the incremental on-disk cache [] mir_keys: rustc_hir::def_id::DefIdSet, [] dropck_outlives: diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index b9914f6cb7a00..ea51325e864b7 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -303,6 +303,10 @@ pub struct ScopeTree { /// the values are still owned by their containing expressions. So /// we'll see that `&x`. pub yield_in_scope: UnordMap>, + + pub expr_scope: hir::ItemLocalMap>, + + pub new_var_scope: hir::ItemLocalMap>, } /// Identifies the reason that a given expression is an rvalue candidate @@ -395,3 +399,22 @@ impl ScopeTree { self.yield_in_scope.get(&scope).map(Deref::deref) } } + +#[derive(Clone, Debug, HashStable)] +pub struct BodyScopeMap { + /// Assignment of temporary lifetimes to each expression + pub expr_scope: hir::ItemLocalMap>, + + /// Assignment of scope to each variable declaration, including `super let`s. + pub new_var_scope: hir::ItemLocalMap>, +} + +impl BodyScopeMap { + pub fn var_scope_new(&self, var_id: hir::ItemLocalId) -> Option { + self.new_var_scope.get(&var_id).copied().flatten() + } + + pub fn temporary_scope_new(&self, expr_id: hir::ItemLocalId) -> Option { + self.expr_scope.get(&expr_id).copied().flatten() + } +} diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 938fba0ed0981..e568b6eb3a7d5 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1143,6 +1143,12 @@ rustc_queries! { desc { |tcx| "computing drop scopes for `{}`", tcx.def_path_str(def_id) } } + /// Per-body `region::BodyScopeMap`. The `DefId` should point to the owner of the body; + /// in the case of closures, this will be redirected to the enclosing function. + query body_scope_map(def_id: DefId) -> &'tcx crate::middle::region::BodyScopeMap { + desc { |tcx| "computing drop scopes and temporary lifetime for `{}`", tcx.def_path_str(def_id) } + } + /// Generates a MIR body for the shim. query mir_shims(key: ty::InstanceDef<'tcx>) -> &'tcx mir::Body<'tcx> { arena_cache diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 906b3205ca75d..e1997971a28d4 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -798,7 +798,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.push(block, Statement { source_info, kind: StatementKind::StorageLive(local_id) }); // Although there is almost always scope for given variable in corner cases // like #92893 we might get variable with no scope. - if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) + let scope = if let Some(scope_map) = self.scope_map { + scope_map.var_scope_new(var.0.local_id) + } else { + self.region_scope_tree.var_scope(var.0.local_id) + }; + if let Some(region_scope) = scope && schedule_drop { self.schedule_drop(span, region_scope, local_id, DropKind::Storage); @@ -813,7 +818,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { for_guard: ForGuard, ) { let local_id = self.var_local_id(var, for_guard); - if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) { + let scope = if let Some(scope_map) = self.scope_map { + scope_map.var_scope_new(var.0.local_id) + } else { + self.region_scope_tree.var_scope(var.0.local_id) + }; + if let Some(region_scope) = scope { self.schedule_drop(span, region_scope, local_id, DropKind::Value); } } diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 2b1a9fef360c0..2dce6a4e18a48 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -167,6 +167,7 @@ struct Builder<'a, 'tcx> { tcx: TyCtxt<'tcx>, infcx: InferCtxt<'tcx>, region_scope_tree: &'tcx region::ScopeTree, + scope_map: Option<&'tcx region::BodyScopeMap>, param_env: ty::ParamEnv<'tcx>, thir: &'a Thir<'tcx>, @@ -783,6 +784,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { tcx, infcx, region_scope_tree: tcx.region_scope_tree(def), + scope_map: (tcx.sess.at_least_rust_2024() && tcx.features().new_temp_lifetime) + .then(|| tcx.body_scope_map(def)), param_env, def_id: def, hir_id, diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 2d3669487718b..7df207117c270 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -992,6 +992,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } + debug!("dump known scopes"); + for scope in self.scopes.scopes.iter().rev() { + let region_scope_span = region_scope.span(self.tcx, &self.region_scope_tree); + debug!("in scope {:?} @ {region_scope_span:?}", scope.region_scope) + } + let region_scope_span = region_scope.span(self.tcx, &self.region_scope_tree); + debug!("problem scope {:?} @ {region_scope_span:?}", region_scope); span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, local); } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 35adcc33480ca..dd4aa1f72524e 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -219,12 +219,7 @@ impl<'tcx> Cx<'tcx> { let lhs = self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind }); let bin = ExprKind::Binary { op: BinOp::Add, lhs, rhs: offset }; - self.thir.exprs.push(Expr { - temp_lifetime, - ty: discr_ty, - span: span, - kind: bin, - }) + self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind: bin }) } None => offset, }; @@ -241,8 +236,11 @@ impl<'tcx> Cx<'tcx> { fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { let tcx = self.tcx; let expr_ty = self.typeck_results().expr_ty(expr); - let temp_lifetime = - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(expr.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) + }; let kind = match expr.kind { // Here comes the interesting stuff: @@ -722,9 +720,11 @@ impl<'tcx> Cx<'tcx> { }, hir::ExprKind::Loop(body, ..) => { let block_ty = self.typeck_results().node_type(body.hir_id); - let temp_lifetime = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, body.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(body.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, body.hir_id.local_id) + }; let block = self.mirror_block(body); let body = self.thir.exprs.push(Expr { ty: block_ty, @@ -833,8 +833,11 @@ impl<'tcx> Cx<'tcx> { span: Span, overloaded_callee: Option>, ) -> Expr<'tcx> { - let temp_lifetime = - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(expr.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) + }; let (ty, user_ty) = match overloaded_callee { Some(fn_def) => (fn_def, None), None => { @@ -920,9 +923,11 @@ impl<'tcx> Cx<'tcx> { // a constant reference (or constant raw pointer for `static mut`) in MIR Res::Def(DefKind::Static(_), id) => { let ty = self.tcx.static_ptr_ty(id); - let temp_lifetime = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(expr.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) + }; let kind = if self.tcx.is_thread_local_static(id) { ExprKind::ThreadLocalRef(id) } else { @@ -1001,8 +1006,11 @@ impl<'tcx> Cx<'tcx> { // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type - let temp_lifetime = - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(expr.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) + }; let fun = self.method_callee(expr, span, overloaded_callee); let fun = self.thir.exprs.push(fun); let fun_ty = self.thir[fun].ty; @@ -1022,9 +1030,11 @@ impl<'tcx> Cx<'tcx> { closure_expr: &'tcx hir::Expr<'tcx>, place: HirPlace<'tcx>, ) -> Expr<'tcx> { - let temp_lifetime = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(closure_expr.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id) + }; let var_ty = place.base_ty; // The result of capture analysis in `rustc_hir_analysis/check/upvar.rs`represents a captured path @@ -1079,9 +1089,11 @@ impl<'tcx> Cx<'tcx> { let upvar_capture = captured_place.info.capture_kind; let captured_place_expr = self.convert_captured_hir_place(closure_expr, captured_place.place.clone()); - let temp_lifetime = self - .rvalue_scopes - .temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id); + let temp_lifetime = if let Some(scope_map) = self.scope_map { + scope_map.temporary_scope_new(closure_expr.hir_id.local_id) + } else { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id) + }; match upvar_capture { ty::UpvarCapture::ByValue => captured_place_expr, diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index f4f591d15b933..1172464ecf190 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -58,6 +58,7 @@ struct Cx<'tcx> { param_env: ty::ParamEnv<'tcx>, region_scope_tree: &'tcx region::ScopeTree, + scope_map: Option<&'tcx region::BodyScopeMap>, typeck_results: &'tcx ty::TypeckResults<'tcx>, rvalue_scopes: &'tcx RvalueScopes, @@ -98,6 +99,8 @@ impl<'tcx> Cx<'tcx> { thir: Thir::new(body_type), param_env: tcx.param_env(def), region_scope_tree: tcx.region_scope_tree(def), + scope_map: (tcx.sess.at_least_rust_2024() && tcx.features().new_temp_lifetime) + .then(|| tcx.body_scope_map(def)), typeck_results, rvalue_scopes: &typeck_results.rvalue_scopes, body_owner: def.to_def_id(), diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 1bae5b3224035..a4e1f9fa2059a 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -73,7 +73,14 @@ impl<'a> Parser<'a> { } Ok(Some(if self.token.is_keyword(kw::Let) { - self.parse_local_mk(lo, attrs, capture_semi, force_collect)? + self.parse_local_mk(lo, attrs, capture_semi, force_collect, false)? + } else if self.sess.edition.at_least_rust_2024() + && self.token.is_keyword(kw::Super) + && self.look_ahead(1, |t| t.is_keyword(kw::Let)) + { + // Super let variable declaration + self.bump(); + self.parse_local_mk(lo, attrs, capture_semi, force_collect, true)? } else if self.is_kw_followed_by_ident(kw::Mut) && self.may_recover() { self.recover_stmt_local_after_let( lo, @@ -251,7 +258,7 @@ impl<'a> Parser<'a> { ) -> PResult<'a, Stmt> { let stmt = self.collect_tokens_trailing_token(attrs, ForceCollect::Yes, |this, attrs| { - let local = this.parse_local(attrs)?; + let local = this.parse_local(attrs, false)?; // FIXME - maybe capture semicolon in recovery? Ok(( this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Local(local)), @@ -269,10 +276,11 @@ impl<'a> Parser<'a> { attrs: AttrWrapper, capture_semi: bool, force_collect: ForceCollect, + is_super: bool, ) -> PResult<'a, Stmt> { self.collect_tokens_trailing_token(attrs, force_collect, |this, attrs| { this.expect_keyword(kw::Let)?; - let local = this.parse_local(attrs)?; + let local = this.parse_local(attrs, is_super)?; let trailing = if capture_semi && this.token.kind == token::Semi { TrailingToken::Semi } else { @@ -283,7 +291,7 @@ impl<'a> Parser<'a> { } /// Parses a local variable declaration. - fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P> { + fn parse_local(&mut self, attrs: AttrVec, is_super: bool) -> PResult<'a, P> { let lo = self.prev_token.span; if self.token.is_keyword(kw::Const) && self.look_ahead(1, |t| t.is_ident()) { @@ -361,7 +369,15 @@ impl<'a> Parser<'a> { } }; let kind = match init { - None => LocalKind::Decl, + None => { + if is_super { + return Err(self + .dcx() + .struct_span_err(self.token.span, "expect initializer for `super let`")); + } else { + LocalKind::Decl + } + } Some(init) => { if self.eat_keyword(kw::Else) { if self.token.is_keyword(kw::If) { @@ -374,6 +390,8 @@ impl<'a> Parser<'a> { self.check_let_else_init_bool_expr(&init); self.check_let_else_init_trailing_brace(&init); LocalKind::InitElse(init, els) + } else if is_super { + LocalKind::Super(init) } else { LocalKind::Init(init) } @@ -749,7 +767,9 @@ impl<'a> Parser<'a> { StmtKind::Local(local) if let Err(e) = self.expect_semi() => { // We might be at the `,` in `let x = foo;`. Try to recover. match &mut local.kind { - LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => { + LocalKind::Init(expr) + | LocalKind::InitElse(expr, _) + | LocalKind::Super(expr) => { self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?; // We found `foo`, have we fully recovered? self.expect_semi()?; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index aa912c93c08c6..ecf0c74bbba84 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1114,6 +1114,7 @@ symbols! { new_lower_hex, new_octal, new_pointer, + new_temp_lifetime, new_unchecked, new_upper_exp, new_upper_hex, diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 08b8a9e2ff072..42624c032d257 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -57,7 +57,13 @@ pub(super) fn check<'tcx>( if let Some(indexed_extent) = indexed_extent { let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id); let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap(); + let var_scope = if cx.tcx.sess.at_least_rust_2024() && cx.tcx.features().new_temp_lifetime { + let scope_map = cx.tcx.body_scope_map(parent_def_id); + scope_map.var_scope_new(pat.hir_id.local_id) + } else { + region_scope_tree.var_scope(pat.hir_id.local_id) + }; + let pat_extent = var_scope.unwrap(); if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { return; } @@ -256,12 +262,13 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { match res { Res::Local(hir_id) => { let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); - let extent = self - .cx - .tcx - .region_scope_tree(parent_def_id) - .var_scope(hir_id.local_id) - .unwrap(); + let region_scope_tree = self.cx.tcx.region_scope_tree(parent_def_id); + let extent = if self.cx.tcx.sess.at_least_rust_2024() && self.cx.tcx.features().new_temp_lifetime { + self.cx.tcx.body_scope_map(parent_def_id).var_scope_new(hir_id.local_id) + } else { + region_scope_tree.var_scope(hir_id.local_id) + } + .unwrap(); if index_used_directly { self.indexed_directly.insert( seqvar.segments[0].ident.name, diff --git a/src/tools/clippy/clippy_lints/src/shadow.rs b/src/tools/clippy/clippy_lints/src/shadow.rs index c74364d89d61b..325d7da19f452 100644 --- a/src/tools/clippy/clippy_lints/src/shadow.rs +++ b/src/tools/clippy/clippy_lints/src/shadow.rs @@ -161,9 +161,18 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { } fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second: ItemLocalId) -> bool { - let scope_tree = cx.tcx.region_scope_tree(owner.to_def_id()); - if let Some(first_scope) = scope_tree.var_scope(first) { - if let Some(second_scope) = scope_tree.var_scope(second) { + let def_id = owner.to_def_id(); + let scope_tree = cx.tcx.region_scope_tree(def_id); + let get_scope = |id| { + if cx.tcx.sess.at_least_rust_2024() && cx.tcx.features().new_temp_lifetime { + let scope_map = cx.tcx.body_scope_map(def_id); + scope_map.var_scope_new(id) + } else { + scope_tree.var_scope(id) + } + }; + if let Some(first_scope) = get_scope(first) { + if let Some(second_scope) = get_scope(second) { return scope_tree.is_subscope_of(second_scope, first_scope); } } diff --git a/tests/ui/binding/super-let-negative.rs b/tests/ui/binding/super-let-negative.rs new file mode 100644 index 0000000000000..96cac6b9812d4 --- /dev/null +++ b/tests/ui/binding/super-let-negative.rs @@ -0,0 +1,21 @@ +// compile-flags: -Zunstable-options +// edition: 2024 +#![feature(new_temp_lifetime)] + +fn foo() {} + +fn id(t: T) -> T { t } + +fn f(_: &T) {} + +fn main() { + let x = id({ + super let y = 1; + &y //~ ERROR: `y` does not live long enough + }); // ... because `y` is freed here + let x = id(&foo()); //~ ERROR: temporary value dropped while borrowed + // lifetime of y is equal to + // what lifetime of block *would* be if it were + // an `&`-rvalue expression (e.g., `&{...}`) + f(x); +} diff --git a/tests/ui/binding/super-let-negative.stderr b/tests/ui/binding/super-let-negative.stderr new file mode 100644 index 0000000000000..1f9403f2b025a --- /dev/null +++ b/tests/ui/binding/super-let-negative.stderr @@ -0,0 +1,31 @@ +error[E0597]: `y` does not live long enough + --> $DIR/super-let-negative.rs:14:9 + | +LL | let x = id({ + | - borrow later stored here +LL | super let y = 1; + | - binding `y` declared here +LL | &y + | ^^ borrowed value does not live long enough +LL | }); // ... because `y` is freed here + | - `y` dropped here while still borrowed + +error[E0716]: temporary value dropped while borrowed + --> $DIR/super-let-negative.rs:16:17 + | +LL | let x = id(&foo()); + | - ^^^^^- temporary value is freed at the end of this statement + | | | + | | creates a temporary value which is freed while still in use + | borrow later stored here + | +help: consider using a `let` binding to create a longer lived value + | +LL ~ let binding = foo(); +LL ~ let x = id(&binding); + | + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0597, E0716. +For more information about an error, try `rustc --explain E0597`. diff --git a/tests/ui/binding/super-let-positive.rs b/tests/ui/binding/super-let-positive.rs new file mode 100644 index 0000000000000..0b53603874258 --- /dev/null +++ b/tests/ui/binding/super-let-positive.rs @@ -0,0 +1,55 @@ +// run-pass +// compile-flags: -Zunstable-options +// edition: 2024 +#![feature(new_temp_lifetime)] +#![allow(dead_code)] +#![allow(unused_variables)] +use std::sync::Mutex; + +fn f(_: &T) {} + +fn example1() { + let x = { + super let y = 1; + &y + }; + f(x); +} + +fn example2() -> usize { + let x = Mutex::new(1); + *x.lock().unwrap() +} + +struct A; +impl A { + #[inline(never)] + fn call(&self) -> Option { + Some(1) + } +} + +fn main() { + let x = Mutex::new(A); + match x.lock().unwrap().call() { + Some(_) => { + *x.lock().unwrap() = A; + } + None => { + } + } + match x.lock().unwrap().call() { + Some(y) if y > 0 => { + *x.lock().unwrap() = A; + } + _ => { panic!() } + } + // equally this works, too + let x = match { + super let x = Some(1); + &x + } { + Some(y) => y, + None => &0, + }; +} diff --git a/tests/ui/feature-gates/feature-gate-new-temp-lifetime.rs b/tests/ui/feature-gates/feature-gate-new-temp-lifetime.rs new file mode 100644 index 0000000000000..cafdaef4f7ad1 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-new-temp-lifetime.rs @@ -0,0 +1,3 @@ +fn main() { + super let it_doesnt_work = 0; //~ ERROR: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found keyword `let` +} diff --git a/tests/ui/feature-gates/feature-gate-new-temp-lifetime.stderr b/tests/ui/feature-gates/feature-gate-new-temp-lifetime.stderr new file mode 100644 index 0000000000000..07e2af400241d --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-new-temp-lifetime.stderr @@ -0,0 +1,8 @@ +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found keyword `let` + --> $DIR/feature-gate-new-temp-lifetime.rs:2:11 + | +LL | super let it_doesnt_work = 0; + | ^^^ expected one of 8 possible tokens + +error: aborting due to 1 previous error + From a37cf5191950b5df07e7ef1df942f204cf76d10f Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Tue, 30 Jan 2024 20:38:18 +0800 Subject: [PATCH 2/3] put up a facade so that features are tested earlier --- .../rustc_hir_analysis/src/check/scope_map.rs | 35 ++++++++----- compiler/rustc_middle/src/arena.rs | 1 + compiler/rustc_middle/src/middle/region.rs | 34 +++++++++++-- compiler/rustc_middle/src/query/mod.rs | 2 +- .../rustc_mir_build/src/build/matches/mod.rs | 12 +---- compiler/rustc_mir_build/src/build/mod.rs | 5 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 49 ++++++------------- compiler/rustc_mir_build/src/thir/cx/mod.rs | 7 +-- .../src/loops/needless_range_loop.rs | 20 +++----- src/tools/clippy/clippy_lints/src/shadow.rs | 9 +--- tests/ui/binding/super-let-positive.rs | 15 +++++- 11 files changed, 97 insertions(+), 92 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/scope_map.rs b/compiler/rustc_hir_analysis/src/check/scope_map.rs index e41c29ee1bd32..edc6103254f75 100644 --- a/compiler/rustc_hir_analysis/src/check/scope_map.rs +++ b/compiler/rustc_hir_analysis/src/check/scope_map.rs @@ -2,7 +2,7 @@ use hir::intravisit::Visitor; use rustc_hir as hir; use rustc_index::Idx; use rustc_middle::{ - middle::region::{BodyScopeMap, FirstStatementIndex, Scope, ScopeData}, + middle::region::{BodyScopeMap, FirstStatementIndex, Scope, ScopeData, ScopeMapFacade}, ty::TyCtxt, }; use rustc_span::source_map; @@ -493,17 +493,28 @@ impl<'tcx> Visitor<'tcx> for ScopeCollector<'tcx> { } } -pub fn body_scope_map(tcx: TyCtxt<'_>, def_id: hir::def_id::DefId) -> &BodyScopeMap { +pub fn body_scope_map<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: hir::def_id::DefId, +) -> &'tcx ScopeMapFacade<'tcx> { let typeck_root_def_id = tcx.typeck_root_def_id(def_id); - if typeck_root_def_id != def_id { - return tcx.body_scope_map(typeck_root_def_id); - } - let map = if let Some(body_id) = tcx.hir().maybe_body_owned_by(def_id.expect_local()) { - let mut collector = ScopeCollector::new(tcx); - collector.visit_body(tcx.hir().body(body_id)); - BodyScopeMap { expr_scope: collector.expr_scope, new_var_scope: collector.new_var_scope } + if tcx.sess.at_least_rust_2024() && tcx.features().new_temp_lifetime { + if typeck_root_def_id != def_id { + return tcx.body_scope_map(typeck_root_def_id); + } + let map = if let Some(body_id) = tcx.hir().maybe_body_owned_by(def_id.expect_local()) { + let mut collector = ScopeCollector::new(tcx); + collector.visit_body(tcx.hir().body(body_id)); + BodyScopeMap { + expr_scope: collector.expr_scope, + new_var_scope: collector.new_var_scope, + } + } else { + BodyScopeMap { expr_scope: <_>::default(), new_var_scope: <_>::default() } + }; + let map = tcx.arena.alloc(map); + tcx.arena.alloc(ScopeMapFacade::Edition2024(map)) } else { - BodyScopeMap { expr_scope: <_>::default(), new_var_scope: <_>::default() } - }; - tcx.arena.alloc(map) + tcx.arena.alloc(ScopeMapFacade::Classical(tcx.region_scope_tree(typeck_root_def_id))) + } } diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index ac0eb47234077..724ec9225d349 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -40,6 +40,7 @@ macro_rules! arena_types { [] const_allocs: rustc_middle::mir::interpret::Allocation, [] region_scope_tree: rustc_middle::middle::region::ScopeTree, [] body_scope_map: rustc_middle::middle::region::BodyScopeMap, + [] scope_map_facade: rustc_middle::middle::region::ScopeMapFacade<'tcx>, // Required for the incremental on-disk cache [] mir_keys: rustc_hir::def_id::DefIdSet, [] dropck_outlives: diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index ea51325e864b7..7d4e10a8d8641 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -6,7 +6,7 @@ //! //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/borrow_check.html -use crate::ty::TyCtxt; +use crate::ty::{RvalueScopes, TyCtxt}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordMap; use rustc_hir as hir; @@ -410,11 +410,39 @@ pub struct BodyScopeMap { } impl BodyScopeMap { - pub fn var_scope_new(&self, var_id: hir::ItemLocalId) -> Option { + pub fn var_scope(&self, var_id: hir::ItemLocalId) -> Option { self.new_var_scope.get(&var_id).copied().flatten() } - pub fn temporary_scope_new(&self, expr_id: hir::ItemLocalId) -> Option { + pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> Option { self.expr_scope.get(&expr_id).copied().flatten() } } + +/// Facade to switch between classical, pre-2024, temporary lifetime rules +/// and Edition 2024 rules. +#[derive(Clone, Debug, HashStable)] +pub enum ScopeMapFacade<'tcx> { + Classical(&'tcx ScopeTree), + Edition2024(&'tcx BodyScopeMap), +} + +impl<'tcx> ScopeMapFacade<'tcx> { + pub fn var_scope(&self, var_id: hir::ItemLocalId) -> Option { + match self { + ScopeMapFacade::Classical(map) => map.var_scope(var_id), + ScopeMapFacade::Edition2024(map) => map.var_scope(var_id), + } + } + + pub fn temporary_scope( + &self, + rvalue_scopes: &RvalueScopes, + expr_id: hir::ItemLocalId, + ) -> Option { + match self { + ScopeMapFacade::Classical(map) => rvalue_scopes.temporary_scope(map, expr_id), + ScopeMapFacade::Edition2024(map) => map.temporary_scope(expr_id), + } + } +} diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index e568b6eb3a7d5..4b3927017e321 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1145,7 +1145,7 @@ rustc_queries! { /// Per-body `region::BodyScopeMap`. The `DefId` should point to the owner of the body; /// in the case of closures, this will be redirected to the enclosing function. - query body_scope_map(def_id: DefId) -> &'tcx crate::middle::region::BodyScopeMap { + query body_scope_map(def_id: DefId) -> &'tcx crate::middle::region::ScopeMapFacade<'tcx> { desc { |tcx| "computing drop scopes and temporary lifetime for `{}`", tcx.def_path_str(def_id) } } diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index e1997971a28d4..e32134e12c55f 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -798,11 +798,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.push(block, Statement { source_info, kind: StatementKind::StorageLive(local_id) }); // Although there is almost always scope for given variable in corner cases // like #92893 we might get variable with no scope. - let scope = if let Some(scope_map) = self.scope_map { - scope_map.var_scope_new(var.0.local_id) - } else { - self.region_scope_tree.var_scope(var.0.local_id) - }; + let scope = self.scope_map.var_scope(var.0.local_id); if let Some(region_scope) = scope && schedule_drop { @@ -818,11 +814,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { for_guard: ForGuard, ) { let local_id = self.var_local_id(var, for_guard); - let scope = if let Some(scope_map) = self.scope_map { - scope_map.var_scope_new(var.0.local_id) - } else { - self.region_scope_tree.var_scope(var.0.local_id) - }; + let scope = self.scope_map.var_scope(var.0.local_id); if let Some(region_scope) = scope { self.schedule_drop(span, region_scope, local_id, DropKind::Value); } diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 2dce6a4e18a48..ff53cb9cc591e 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -167,7 +167,7 @@ struct Builder<'a, 'tcx> { tcx: TyCtxt<'tcx>, infcx: InferCtxt<'tcx>, region_scope_tree: &'tcx region::ScopeTree, - scope_map: Option<&'tcx region::BodyScopeMap>, + scope_map: &'tcx region::ScopeMapFacade<'tcx>, param_env: ty::ParamEnv<'tcx>, thir: &'a Thir<'tcx>, @@ -784,8 +784,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { tcx, infcx, region_scope_tree: tcx.region_scope_tree(def), - scope_map: (tcx.sess.at_least_rust_2024() && tcx.features().new_temp_lifetime) - .then(|| tcx.body_scope_map(def)), + scope_map: tcx.body_scope_map(def), param_env, def_id: def, hir_id, diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index dd4aa1f72524e..93dc5d40c376e 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -236,11 +236,8 @@ impl<'tcx> Cx<'tcx> { fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { let tcx = self.tcx; let expr_ty = self.typeck_results().expr_ty(expr); - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(expr.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, expr.hir_id.local_id); let kind = match expr.kind { // Here comes the interesting stuff: @@ -720,11 +717,8 @@ impl<'tcx> Cx<'tcx> { }, hir::ExprKind::Loop(body, ..) => { let block_ty = self.typeck_results().node_type(body.hir_id); - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(body.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, body.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, body.hir_id.local_id); let block = self.mirror_block(body); let body = self.thir.exprs.push(Expr { ty: block_ty, @@ -833,11 +827,8 @@ impl<'tcx> Cx<'tcx> { span: Span, overloaded_callee: Option>, ) -> Expr<'tcx> { - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(expr.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, expr.hir_id.local_id); let (ty, user_ty) = match overloaded_callee { Some(fn_def) => (fn_def, None), None => { @@ -923,11 +914,8 @@ impl<'tcx> Cx<'tcx> { // a constant reference (or constant raw pointer for `static mut`) in MIR Res::Def(DefKind::Static(_), id) => { let ty = self.tcx.static_ptr_ty(id); - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(expr.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, expr.hir_id.local_id); let kind = if self.tcx.is_thread_local_static(id) { ExprKind::ThreadLocalRef(id) } else { @@ -1006,11 +994,8 @@ impl<'tcx> Cx<'tcx> { // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(expr.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, expr.hir_id.local_id); let fun = self.method_callee(expr, span, overloaded_callee); let fun = self.thir.exprs.push(fun); let fun_ty = self.thir[fun].ty; @@ -1030,11 +1015,8 @@ impl<'tcx> Cx<'tcx> { closure_expr: &'tcx hir::Expr<'tcx>, place: HirPlace<'tcx>, ) -> Expr<'tcx> { - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(closure_expr.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, closure_expr.hir_id.local_id); let var_ty = place.base_ty; // The result of capture analysis in `rustc_hir_analysis/check/upvar.rs`represents a captured path @@ -1089,11 +1071,8 @@ impl<'tcx> Cx<'tcx> { let upvar_capture = captured_place.info.capture_kind; let captured_place_expr = self.convert_captured_hir_place(closure_expr, captured_place.place.clone()); - let temp_lifetime = if let Some(scope_map) = self.scope_map { - scope_map.temporary_scope_new(closure_expr.hir_id.local_id) - } else { - self.rvalue_scopes.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id) - }; + let temp_lifetime = + self.scope_map.temporary_scope(self.rvalue_scopes, closure_expr.hir_id.local_id); match upvar_capture { ty::UpvarCapture::ByValue => captured_place_expr, diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index 1172464ecf190..df5779ebca1ed 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -57,8 +57,7 @@ struct Cx<'tcx> { param_env: ty::ParamEnv<'tcx>, - region_scope_tree: &'tcx region::ScopeTree, - scope_map: Option<&'tcx region::BodyScopeMap>, + scope_map: &'tcx region::ScopeMapFacade<'tcx>, typeck_results: &'tcx ty::TypeckResults<'tcx>, rvalue_scopes: &'tcx RvalueScopes, @@ -98,9 +97,7 @@ impl<'tcx> Cx<'tcx> { tcx, thir: Thir::new(body_type), param_env: tcx.param_env(def), - region_scope_tree: tcx.region_scope_tree(def), - scope_map: (tcx.sess.at_least_rust_2024() && tcx.features().new_temp_lifetime) - .then(|| tcx.body_scope_map(def)), + scope_map: tcx.body_scope_map(def), typeck_results, rvalue_scopes: &typeck_results.rvalue_scopes, body_owner: def.to_def_id(), diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 42624c032d257..330c29e475931 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -57,12 +57,7 @@ pub(super) fn check<'tcx>( if let Some(indexed_extent) = indexed_extent { let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id); let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let var_scope = if cx.tcx.sess.at_least_rust_2024() && cx.tcx.features().new_temp_lifetime { - let scope_map = cx.tcx.body_scope_map(parent_def_id); - scope_map.var_scope_new(pat.hir_id.local_id) - } else { - region_scope_tree.var_scope(pat.hir_id.local_id) - }; + let var_scope = cx.tcx.body_scope_map(parent_def_id).var_scope(pat.hir_id.local_id); let pat_extent = var_scope.unwrap(); if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { return; @@ -262,13 +257,12 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { match res { Res::Local(hir_id) => { let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); - let region_scope_tree = self.cx.tcx.region_scope_tree(parent_def_id); - let extent = if self.cx.tcx.sess.at_least_rust_2024() && self.cx.tcx.features().new_temp_lifetime { - self.cx.tcx.body_scope_map(parent_def_id).var_scope_new(hir_id.local_id) - } else { - region_scope_tree.var_scope(hir_id.local_id) - } - .unwrap(); + let extent = self + .cx + .tcx + .body_scope_map(parent_def_id) + .var_scope(hir_id.local_id) + .unwrap(); if index_used_directly { self.indexed_directly.insert( seqvar.segments[0].ident.name, diff --git a/src/tools/clippy/clippy_lints/src/shadow.rs b/src/tools/clippy/clippy_lints/src/shadow.rs index 325d7da19f452..8c3b18a15d3c0 100644 --- a/src/tools/clippy/clippy_lints/src/shadow.rs +++ b/src/tools/clippy/clippy_lints/src/shadow.rs @@ -163,14 +163,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second: ItemLocalId) -> bool { let def_id = owner.to_def_id(); let scope_tree = cx.tcx.region_scope_tree(def_id); - let get_scope = |id| { - if cx.tcx.sess.at_least_rust_2024() && cx.tcx.features().new_temp_lifetime { - let scope_map = cx.tcx.body_scope_map(def_id); - scope_map.var_scope_new(id) - } else { - scope_tree.var_scope(id) - } - }; + let get_scope = |id| cx.tcx.body_scope_map(def_id).var_scope(id); if let Some(first_scope) = get_scope(first) { if let Some(second_scope) = get_scope(second) { return scope_tree.is_subscope_of(second_scope, first_scope); diff --git a/tests/ui/binding/super-let-positive.rs b/tests/ui/binding/super-let-positive.rs index 0b53603874258..df536be702330 100644 --- a/tests/ui/binding/super-let-positive.rs +++ b/tests/ui/binding/super-let-positive.rs @@ -4,7 +4,7 @@ #![feature(new_temp_lifetime)] #![allow(dead_code)] #![allow(unused_variables)] -use std::sync::Mutex; +use std::sync::{Mutex, atomic::AtomicU8}; fn f(_: &T) {} @@ -30,7 +30,18 @@ impl A { } fn main() { - let x = Mutex::new(A); + // The following binding is valid but extraneous. + super let x = Mutex::new(A); + static MY_ATOMIC: AtomicU8 = { + super let value = 0; + AtomicU8::new(value) + }; + const MY_CONST: A = { + super let value = A; + value + }; + + let x = Mutex::new(MY_CONST); match x.lock().unwrap().call() { Some(_) => { *x.lock().unwrap() = A; From 63054485ec281b41d5fc9419609907b7fcd7d5d1 Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Mon, 12 Feb 2024 04:00:44 +0800 Subject: [PATCH 3/3] recognise data contructor calls --- .../rustc_hir_analysis/src/check/scope_map.rs | 25 ++++++++++++++++++- tests/ui/binding/super-let-positive.rs | 8 ++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir_analysis/src/check/scope_map.rs b/compiler/rustc_hir_analysis/src/check/scope_map.rs index edc6103254f75..f6fd280c63e0e 100644 --- a/compiler/rustc_hir_analysis/src/check/scope_map.rs +++ b/compiler/rustc_hir_analysis/src/check/scope_map.rs @@ -164,6 +164,21 @@ impl<'tcx> ScopeCollector<'tcx> { cx: Context { temp_lifetime: TemporaryLifetimeScopes::new_static(), var_parent: None }, } } + + fn is_data_constructor(&self, callee: &hir::Expr<'_>) -> bool { + match callee.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { + res: + hir::def::Res::Def(hir::def::DefKind::Ctor(_, _), _) + | hir::def::Res::SelfCtor(_), + .. + }, + )) => true, + _ => false, + } + } } impl<'tcx> ScopeCollector<'tcx> { @@ -336,7 +351,7 @@ impl<'tcx> Visitor<'tcx> for ScopeCollector<'tcx> { } hir::ExprKind::Struct(_path, fields, struct_base) => { - for hir::ExprField { expr, .. } in fields { + for &hir::ExprField { expr, .. } in fields { self.cx.temp_lifetime = prev_temp_lifetime; self.cx.temp_lifetime.is_result = true; self.visit_expr(expr); @@ -349,6 +364,14 @@ impl<'tcx> Visitor<'tcx> for ScopeCollector<'tcx> { } } + hir::ExprKind::Call(callee, args) if self.is_data_constructor(callee) => { + for expr in args { + self.cx.temp_lifetime = prev_temp_lifetime; + self.cx.temp_lifetime.is_result = true; + self.visit_expr(expr); + } + } + hir::ExprKind::Tup(subexprs) => { for subexpr in subexprs { self.cx.temp_lifetime = prev_temp_lifetime; diff --git a/tests/ui/binding/super-let-positive.rs b/tests/ui/binding/super-let-positive.rs index df536be702330..133393c5091c7 100644 --- a/tests/ui/binding/super-let-positive.rs +++ b/tests/ui/binding/super-let-positive.rs @@ -21,6 +21,14 @@ fn example2() -> usize { *x.lock().unwrap() } +fn example3() { + enum Enum<'a> { + A(&'a u8), + } + fn compute() -> u8 { 1 } + let x = Enum::A(&compute()); +} + struct A; impl A { #[inline(never)]