1
Fork 0

Rollup merge of #139112 - m-ou-se:super-let, r=lcnr

Implement `super let`

Tracking issue: https://github.com/rust-lang/rust/issues/139076

This implements `super let` as proposed in #139080, based on the following two equivalence rules.

1. For all expressions `$expr` in any context, these are equivalent:
  - `& $expr`
  - `{ super let a = & $expr; a }`

2. And, additionally, these are equivalent in any context when `$expr` is a temporary (aka rvalue):
  - `& $expr`
  - `{ super let a = $expr; & a }`

So far, this experiment has a few interesting results:

## Interesting result 1

In this snippet:

```rust
super let a = f(&temp());
```

I originally expected temporary `temp()` would be dropped at the end of the statement (`;`), just like in a regular `let`, because `temp()` is not subject to temporary lifetime extension.

However, it turns out that that would break the fundamental equivalence rules.

For example, in

```rust
g(&f(&temp()));
```

the temporary `temp()` will be dropped at the `;`.

The first equivalence rule tells us this must be equivalent:

```rust
g({ super let a = &f(&temp()); a });
```

But that means that `temp()` must live until the last `;` (after `g()`), not just the first `;` (after `f()`).

While this was somewhat surprising to me at first, it does match the exact behavior we need for `pin!()`: The following _should work_. (See also https://github.com/rust-lang/rust/issues/138718)

```rust
g(pin!(f(&mut temp())));
```

Here, `temp()` lives until the end of the statement. This makes sense from the perspective of the user, as no other `;` or `{}` are visible. Whether `pin!()` uses a `{}` block internally or not should be irrelevant.

This means that _nothing_ in a `super let` statement will be dropped at the end of that super let statement. It does not even need its own scope.

This raises questions that are useful for later on:

- Will this make temporaries live _too long_ in cases where `super let` is used not in a hidden block in a macro, but as a visible statement in code like the following?

    ```rust
    let writer = {
        super let file = File::create(&format!("/home/{user}/test"));
        Writer::new(&file)
    };
    ```

- Is a `let` statement in a block still the right syntax for this? Considering it has _no_ scope of its own, maybe neither a block nor a statement should be involved

This leads me to think that instead of `{ super let $pat = $init; $expr }`, we might want to consider something like `let $pat = $init in $expr` or `$expr where $pat = $init`. Although there are also issues with these, as it isn't obvious anymore if `$init` should be subject to temporary lifetime extension. (Do we want both `let _ = _ in ..` and `super let _ = _ in ..`?)

## Interesting result 2

What about `super let x;` without initializer?

```rust
let a = {
    super let x;
    x = temp();
    &x
};
```

This works fine with the implementation in this PR: `x` is extended to live as long as `a`.

While it matches my expectations, a somewhat interesting thing to realize is that these are _not_ equivalent:

- `super let x = $expr;`
- `super let x; x = $expr;`

In the first case, all temporaries in $expr will live at least as long as (the result of) the surrounding block.
In the second case, temporaries will be dropped at the end of the assignment statement. (Because the assignment statement itself "is not `super`".)

This difference in behavior might be confusing, but it _might_ be useful.
One might want to extend the lifetime of a variable without extending all the temporaries in the initializer expression.

On the other hand, that can also be expressed as:

- `let x = $expr; super let x = x;` (w/o temporary lifetime extension), or
- `super let x = { $expr };` (w/ temporary lifetime extension)

So, this raises these questions:

- Do we want to accept `super let x;` without initializer at all?

- Does it make sense for statements other than let statements to be "super"? An expression statement also drops temporaries at its `;`, so now that we discovered that `super let` basically disables that `;` (see interesting result 1), is there a use to having other statements without their own scope? (I don't think that's ever useful?)

## Interesting result 3

This works now:

```rust
super let Some(x) = a.get(i) else { return };
```

I didn't put in any special cases for `super let else`. This is just the behavior that 'naturally' falls out when implementing `super let` without thinking of the `let else` case.

- Should `super let else` work?

## Interesting result 4

This 'works':

```rust
fn main() {
    super let a = 123;
}
```

I didn't put in any special cases for `super let` at function scope. I had expected the code to cause an ICE or other weird failure when used at function body scope, because there's no way to let the variable live as long as the result of the function.

This raises the question:

- Does this mean that this behavior is the natural/expected behavior when `super let` is used at function scope? Or is this just a quirk and should we explicitly disallow `super let` in a function body? (Probably the latter.)

---

The questions above do not need an answer to land this PR. These questions should be considered when redesigning/rfc'ing/stabilizing the feature.
This commit is contained in:
Stuart Cook 2025-04-07 22:29:18 +10:00 committed by GitHub
commit 27c6e40755
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 615 additions and 50 deletions

View file

@ -1175,6 +1175,7 @@ pub enum MacStmtStyle {
#[derive(Clone, Encodable, Decodable, Debug)]
pub struct Local {
pub id: NodeId,
pub super_: Option<Span>,
pub pat: P<Pat>,
pub ty: Option<P<Ty>>,
pub kind: LocalKind,
@ -3932,7 +3933,7 @@ mod size_asserts {
static_assert_size!(Item, 144);
static_assert_size!(ItemKind, 80);
static_assert_size!(LitKind, 24);
static_assert_size!(Local, 80);
static_assert_size!(Local, 96);
static_assert_size!(MetaItemLit, 40);
static_assert_size!(Param, 40);
static_assert_size!(Pat, 72);

View file

@ -704,7 +704,8 @@ fn walk_parenthesized_parameter_data<T: MutVisitor>(vis: &mut T, args: &mut Pare
}
fn walk_local<T: MutVisitor>(vis: &mut T, local: &mut P<Local>) {
let Local { id, pat, ty, kind, span, colon_sp, attrs, tokens } = local.deref_mut();
let Local { id, super_, pat, ty, kind, span, colon_sp, attrs, tokens } = local.deref_mut();
visit_opt(super_, |sp| vis.visit_span(sp));
vis.visit_id(id);
visit_attrs(vis, attrs);
vis.visit_pat(pat);

View file

@ -323,7 +323,7 @@ pub fn walk_crate<'a, V: Visitor<'a>>(visitor: &mut V, krate: &'a Crate) -> V::R
}
pub fn walk_local<'a, V: Visitor<'a>>(visitor: &mut V, local: &'a Local) -> V::Result {
let Local { id: _, pat, ty, kind, span: _, colon_sp: _, attrs, tokens: _ } = local;
let Local { id: _, super_: _, pat, ty, kind, span: _, colon_sp: _, attrs, tokens: _ } = local;
walk_list!(visitor, visit_attribute, attrs);
try_visit!(visitor.visit_pat(pat));
visit_opt!(visitor, visit_ty, ty);

View file

@ -95,6 +95,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fn lower_local(&mut self, l: &Local) -> &'hir hir::LetStmt<'hir> {
// Let statements are allowed to have impl trait in bindings.
let super_ = l.super_;
let ty = l.ty.as_ref().map(|t| {
self.lower_ty(t, self.impl_trait_in_bindings_ctxt(ImplTraitPosition::Variable))
});
@ -109,7 +110,7 @@ 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, l.span);
self.arena.alloc(hir::LetStmt { hir_id, ty, pat, init, els, span, source })
self.arena.alloc(hir::LetStmt { hir_id, super_, ty, pat, init, els, span, source })
}
fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {

View file

@ -2218,6 +2218,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
self.attrs.insert(hir_id.local_id, a);
}
let local = hir::LetStmt {
super_: None,
hir_id,
init,
pat,

View file

@ -1336,6 +1336,9 @@ impl<'a> State<'a> {
self.print_outer_attributes(&loc.attrs);
self.space_if_not_bol();
self.ibox(INDENT_UNIT);
if loc.super_.is_some() {
self.word_nbsp("super");
}
self.word_nbsp("let");
self.ibox(INDENT_UNIT);

View file

@ -230,6 +230,7 @@ impl<'a> ExtCtxt<'a> {
self.pat_ident(sp, ident)
};
let local = P(ast::Local {
super_: None,
pat,
ty,
id: ast::DUMMY_NODE_ID,
@ -245,6 +246,7 @@ impl<'a> ExtCtxt<'a> {
/// Generates `let _: Type;`, which is usually used for type assertions.
pub fn stmt_let_type_only(&self, span: Span, ty: P<ast::Ty>) -> ast::Stmt {
let local = P(ast::Local {
super_: None,
pat: self.pat_wild(span),
ty: Some(ty),
id: ast::DUMMY_NODE_ID,

View file

@ -630,7 +630,7 @@ declare_features! (
/// Allows string patterns to dereference values to match them.
(unstable, string_deref_patterns, "1.67.0", Some(87121)),
/// Allows `super let` statements.
(incomplete, super_let, "CURRENT_RUSTC_VERSION", Some(139076)),
(unstable, super_let, "CURRENT_RUSTC_VERSION", Some(139076)),
/// Allows subtrait items to shadow supertrait items.
(unstable, supertrait_item_shadowing, "1.86.0", Some(89151)),
/// Allows using `#[thread_local]` on `static` items.

View file

@ -1821,6 +1821,8 @@ pub enum StmtKind<'hir> {
/// Represents a `let` statement (i.e., `let <pat>:<ty> = <init>;`).
#[derive(Debug, Clone, Copy, HashStable_Generic)]
pub struct LetStmt<'hir> {
/// Span of `super` in `super let`.
pub super_: Option<Span>,
pub pat: &'hir Pat<'hir>,
/// Type annotation, if any (otherwise the type will be inferred).
pub ty: Option<&'hir Ty<'hir>>,
@ -4854,7 +4856,7 @@ mod size_asserts {
static_assert_size!(ImplItemKind<'_>, 40);
static_assert_size!(Item<'_>, 88);
static_assert_size!(ItemKind<'_>, 64);
static_assert_size!(LetStmt<'_>, 64);
static_assert_size!(LetStmt<'_>, 72);
static_assert_size!(Param<'_>, 32);
static_assert_size!(Pat<'_>, 72);
static_assert_size!(Path<'_>, 40);

View file

@ -8,6 +8,7 @@
use std::mem;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{self, Visitor};
@ -44,6 +45,8 @@ struct ScopeResolutionVisitor<'tcx> {
scope_tree: ScopeTree,
cx: Context,
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
}
/// Records the lifetime of a local variable as `cx.var_parent`
@ -214,18 +217,29 @@ fn resolve_stmt<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, stmt: &'tcx hi
let stmt_id = stmt.hir_id.local_id;
debug!("resolve_stmt(stmt.id={:?})", stmt_id);
// Every statement will clean up the temporaries created during
// execution of that statement. Therefore each statement has an
// associated destruction scope that represents the scope of the
// statement plus its destructors, and thus the scope for which
// regions referenced by the destructors need to survive.
if let hir::StmtKind::Let(LetStmt { super_: Some(_), .. }) = stmt.kind {
// `super let` statement does not start a new scope, such that
//
// { super let x = identity(&temp()); &x }.method();
//
// behaves exactly as
//
// (&identity(&temp()).method();
intravisit::walk_stmt(visitor, stmt);
} else {
// Every statement will clean up the temporaries created during
// execution of that statement. Therefore each statement has an
// associated destruction scope that represents the scope of the
// statement plus its destructors, and thus the scope for which
// regions referenced by the destructors need to survive.
let prev_parent = visitor.cx.parent;
visitor.enter_node_scope_with_dtor(stmt_id, true);
let prev_parent = visitor.cx.parent;
visitor.enter_node_scope_with_dtor(stmt_id, true);
intravisit::walk_stmt(visitor, stmt);
intravisit::walk_stmt(visitor, stmt);
visitor.cx.parent = prev_parent;
visitor.cx.parent = prev_parent;
}
}
fn resolve_expr<'tcx>(
@ -478,14 +492,19 @@ fn resolve_expr<'tcx>(
visitor.cx = prev_cx;
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum LetKind {
Regular,
Super,
}
fn resolve_local<'tcx>(
visitor: &mut ScopeResolutionVisitor<'tcx>,
pat: Option<&'tcx hir::Pat<'tcx>>,
init: Option<&'tcx hir::Expr<'tcx>>,
let_kind: LetKind,
) {
debug!("resolve_local(pat={:?}, init={:?})", pat, init);
let blk_scope = visitor.cx.var_parent;
debug!("resolve_local(pat={:?}, init={:?}, let_kind={:?})", pat, init, let_kind);
// As an exception to the normal rules governing temporary
// lifetimes, initializers in a let have a temporary lifetime
@ -543,14 +562,50 @@ fn resolve_local<'tcx>(
// A, but the inner rvalues `a()` and `b()` have an extended lifetime
// due to rule C.
if let_kind == LetKind::Super {
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
// This expression was lifetime-extended by a parent let binding. E.g.
//
// let a = {
// super let b = temp();
// &b
// };
//
// (Which needs to behave exactly as: let a = &temp();)
//
// Processing of `let a` will have already decided to extend the lifetime of this
// `super let` to its own var_scope. We use that scope.
visitor.cx.var_parent = scope;
} else {
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
//
// identity({ super let x = temp(); &x }).method();
//
// (Which needs to behave exactly as: identity(&temp()).method();)
//
// Iterate up to the enclosing destruction scope to find the same scope that will also
// be used for the result of the block itself.
while let Some(s) = visitor.cx.var_parent {
let parent = visitor.scope_tree.parent_map.get(&s).cloned();
if let Some(Scope { data: ScopeData::Destruction, .. }) = parent {
break;
}
visitor.cx.var_parent = parent;
}
}
}
if let Some(expr) = init {
record_rvalue_scope_if_borrow_expr(visitor, expr, blk_scope);
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);
if let Some(pat) = pat {
if is_binding_pat(pat) {
visitor.scope_tree.record_rvalue_candidate(
expr.hir_id,
RvalueCandidate { target: expr.hir_id.local_id, lifetime: blk_scope },
RvalueCandidate {
target: expr.hir_id.local_id,
lifetime: visitor.cx.var_parent,
},
);
}
}
@ -562,6 +617,7 @@ fn resolve_local<'tcx>(
if let Some(expr) = init {
visitor.visit_expr(expr);
}
if let Some(pat) = pat {
visitor.visit_pat(pat);
}
@ -640,6 +696,7 @@ fn resolve_local<'tcx>(
/// | [ ..., E&, ... ]
/// | ( ..., E&, ... )
/// | {...; E&}
/// | { super let ... = E&; ... }
/// | if _ { ...; E& } else { ...; E& }
/// | match _ { ..., _ => E&, ... }
/// | box E&
@ -676,6 +733,13 @@ fn resolve_local<'tcx>(
if let Some(subexpr) = block.expr {
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
}
for stmt in block.stmts {
if let hir::StmtKind::Let(local) = stmt.kind
&& let Some(_) = local.super_
{
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id);
}
}
}
hir::ExprKind::If(_, then_block, else_block) => {
record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id);
@ -801,7 +865,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
local_id: body.value.hir_id.local_id,
data: ScopeData::Destruction,
});
resolve_local(this, None, Some(body.value));
resolve_local(this, None, Some(body.value), LetKind::Regular);
}
})
}
@ -819,7 +883,11 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
resolve_expr(self, ex, false);
}
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) {
resolve_local(self, Some(l.pat), l.init)
let let_kind = match l.super_ {
Some(_) => LetKind::Super,
None => LetKind::Regular,
};
resolve_local(self, Some(l.pat), l.init, let_kind);
}
fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
let body = self.tcx.hir_body(c.body);
@ -848,6 +916,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
cx: Context { parent: None, var_parent: None },
pessimistic_yield: false,
fixup_scopes: vec![],
extended_super_lets: Default::default(),
};
visitor.scope_tree.root_body = Some(body.value.hir_id);

View file

@ -960,12 +960,16 @@ impl<'a> State<'a> {
fn print_local(
&mut self,
super_: bool,
init: Option<&hir::Expr<'_>>,
els: Option<&hir::Block<'_>>,
decl: impl Fn(&mut Self),
) {
self.space_if_not_bol();
self.ibox(INDENT_UNIT);
if super_ {
self.word_nbsp("super");
}
self.word_nbsp("let");
self.ibox(INDENT_UNIT);
@ -995,7 +999,9 @@ impl<'a> State<'a> {
self.maybe_print_comment(st.span.lo());
match st.kind {
hir::StmtKind::Let(loc) => {
self.print_local(loc.init, loc.els, |this| this.print_local_decl(loc));
self.print_local(loc.super_.is_some(), 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) => {
@ -1488,7 +1494,7 @@ impl<'a> State<'a> {
// Print `let _t = $init;`:
let temp = Ident::from_str("_t");
self.print_local(Some(init), None, |this| this.print_ident(temp));
self.print_local(false, Some(init), None, |this| this.print_ident(temp));
self.word(";");
// Print `_t`:

View file

@ -43,7 +43,7 @@ pub(super) struct Declaration<'a> {
impl<'a> From<&'a hir::LetStmt<'a>> for Declaration<'a> {
fn from(local: &'a hir::LetStmt<'a>) -> Self {
let hir::LetStmt { hir_id, pat, ty, span, init, els, source: _ } = *local;
let hir::LetStmt { hir_id, super_: _, pat, ty, span, init, els, source: _ } = *local;
Declaration { hir_id, pat, ty, span, init, origin: DeclOrigin::LocalDecl { els } }
}
}

View file

@ -75,10 +75,11 @@ impl<'a> Parser<'a> {
let stmt = if self.token.is_keyword(kw::Super) && self.is_keyword_ahead(1, &[kw::Let]) {
self.collect_tokens(None, attrs, force_collect, |this, attrs| {
let super_span = this.token.span;
this.expect_keyword(exp!(Super))?;
this.psess.gated_spans.gate(sym::super_let, this.prev_token.span);
this.expect_keyword(exp!(Let))?;
let local = this.parse_local(attrs)?; // FIXME(mara): implement super let
this.psess.gated_spans.gate(sym::super_let, super_span);
let local = this.parse_local(Some(super_span), attrs)?;
let trailing = Trailing::from(capture_semi && this.token == token::Semi);
Ok((
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
@ -89,7 +90,7 @@ impl<'a> Parser<'a> {
} else if self.token.is_keyword(kw::Let) {
self.collect_tokens(None, attrs, force_collect, |this, attrs| {
this.expect_keyword(exp!(Let))?;
let local = this.parse_local(attrs)?;
let local = this.parse_local(None, attrs)?;
let trailing = Trailing::from(capture_semi && this.token == token::Semi);
Ok((
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
@ -294,7 +295,7 @@ impl<'a> Parser<'a> {
force_collect: ForceCollect,
) -> PResult<'a, Stmt> {
let stmt = self.collect_tokens(None, attrs, force_collect, |this, attrs| {
let local = this.parse_local(attrs)?;
let local = this.parse_local(None, attrs)?;
// FIXME - maybe capture semicolon in recovery?
Ok((
this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)),
@ -308,8 +309,8 @@ impl<'a> Parser<'a> {
}
/// Parses a local variable declaration.
fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
let lo = self.prev_token.span;
fn parse_local(&mut self, super_: Option<Span>, attrs: AttrVec) -> PResult<'a, P<Local>> {
let lo = super_.unwrap_or(self.prev_token.span);
if self.token.is_keyword(kw::Const) && self.look_ahead(1, |t| t.is_ident()) {
self.dcx().emit_err(errors::ConstLetMutuallyExclusive { span: lo.to(self.token.span) });
@ -411,6 +412,7 @@ impl<'a> Parser<'a> {
};
let hi = if self.token == token::Semi { self.token.span } else { self.prev_token.span };
Ok(P(ast::Local {
super_,
ty,
pat,
kind,

View file

@ -2,3 +2,10 @@ fn main() {
super let a = 1;
//~^ ERROR `super let` is experimental
}
// Check that it also isn't accepted in cfg'd out code.
#[cfg(any())]
fn a() {
super let a = 1;
//~^ ERROR `super let` is experimental
}

View file

@ -8,6 +8,16 @@ LL | super let a = 1;
= help: add `#![feature(super_let)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 1 previous error
error[E0658]: `super let` is experimental
--> $DIR/feature-gate-super-let.rs:9:5
|
LL | super let a = 1;
| ^^^^^
|
= note: see issue #139076 <https://github.com/rust-lang/rust/issues/139076> for more information
= help: add `#![feature(super_let)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.

View file

@ -5,25 +5,25 @@ ast-stats-1 Crate 40 ( 0.6%) 1 40
ast-stats-1 GenericArgs 40 ( 0.6%) 1 40
ast-stats-1 - AngleBracketed 40 ( 0.6%) 1
ast-stats-1 ExprField 48 ( 0.7%) 1 48
ast-stats-1 Attribute 64 ( 1.0%) 2 32
ast-stats-1 Attribute 64 ( 0.9%) 2 32
ast-stats-1 - DocComment 32 ( 0.5%) 1
ast-stats-1 - Normal 32 ( 0.5%) 1
ast-stats-1 WherePredicate 72 ( 1.1%) 1 72
ast-stats-1 - BoundPredicate 72 ( 1.1%) 1
ast-stats-1 ForeignItem 80 ( 1.2%) 1 80
ast-stats-1 - Fn 80 ( 1.2%) 1
ast-stats-1 Local 80 ( 1.2%) 1 80
ast-stats-1 Arm 96 ( 1.4%) 2 48
ast-stats-1 Local 96 ( 1.4%) 1 96
ast-stats-1 FnDecl 120 ( 1.8%) 5 24
ast-stats-1 Param 160 ( 2.4%) 4 40
ast-stats-1 Stmt 160 ( 2.4%) 5 32
ast-stats-1 - Let 32 ( 0.5%) 1
ast-stats-1 - MacCall 32 ( 0.5%) 1
ast-stats-1 - Expr 96 ( 1.4%) 3
ast-stats-1 Block 192 ( 2.9%) 6 32
ast-stats-1 Block 192 ( 2.8%) 6 32
ast-stats-1 FieldDef 208 ( 3.1%) 2 104
ast-stats-1 Variant 208 ( 3.1%) 2 104
ast-stats-1 AssocItem 320 ( 4.8%) 4 80
ast-stats-1 AssocItem 320 ( 4.7%) 4 80
ast-stats-1 - Fn 160 ( 2.4%) 2
ast-stats-1 - Type 160 ( 2.4%) 2
ast-stats-1 GenericBound 352 ( 5.2%) 4 88
@ -33,7 +33,7 @@ ast-stats-1 Pat 504 ( 7.5%) 7 72
ast-stats-1 - Struct 72 ( 1.1%) 1
ast-stats-1 - Wild 72 ( 1.1%) 1
ast-stats-1 - Ident 360 ( 5.3%) 5
ast-stats-1 Expr 576 ( 8.6%) 8 72
ast-stats-1 Expr 576 ( 8.5%) 8 72
ast-stats-1 - Match 72 ( 1.1%) 1
ast-stats-1 - Path 72 ( 1.1%) 1
ast-stats-1 - Struct 72 ( 1.1%) 1
@ -41,8 +41,8 @@ ast-stats-1 - Lit 144 ( 2.1%) 2
ast-stats-1 - Block 216 ( 3.2%) 3
ast-stats-1 PathSegment 744 (11.0%) 31 24
ast-stats-1 Ty 896 (13.3%) 14 64
ast-stats-1 - Ptr 64 ( 1.0%) 1
ast-stats-1 - Ref 64 ( 1.0%) 1
ast-stats-1 - Ptr 64 ( 0.9%) 1
ast-stats-1 - Ref 64 ( 0.9%) 1
ast-stats-1 - ImplicitSelf 128 ( 1.9%) 2
ast-stats-1 - Path 640 ( 9.5%) 10
ast-stats-1 Item 1_296 (19.2%) 9 144
@ -53,7 +53,7 @@ ast-stats-1 - Trait 144 ( 2.1%) 1
ast-stats-1 - Fn 288 ( 4.3%) 2
ast-stats-1 - Use 432 ( 6.4%) 3
ast-stats-1 ----------------------------------------------------------------
ast-stats-1 Total 6_736 116
ast-stats-1 Total 6_752 116
ast-stats-1
ast-stats-2 POST EXPANSION AST STATS
ast-stats-2 Name Accumulated Size Count Item Size
@ -66,8 +66,8 @@ ast-stats-2 WherePredicate 72 ( 1.0%) 1 72
ast-stats-2 - BoundPredicate 72 ( 1.0%) 1
ast-stats-2 ForeignItem 80 ( 1.1%) 1 80
ast-stats-2 - Fn 80 ( 1.1%) 1
ast-stats-2 Local 80 ( 1.1%) 1 80
ast-stats-2 Arm 96 ( 1.3%) 2 48
ast-stats-2 Local 96 ( 1.3%) 1 96
ast-stats-2 FnDecl 120 ( 1.6%) 5 24
ast-stats-2 InlineAsm 120 ( 1.6%) 1 120
ast-stats-2 Attribute 128 ( 1.7%) 4 32
@ -84,14 +84,14 @@ ast-stats-2 Variant 208 ( 2.8%) 2 104
ast-stats-2 AssocItem 320 ( 4.3%) 4 80
ast-stats-2 - Fn 160 ( 2.2%) 2
ast-stats-2 - Type 160 ( 2.2%) 2
ast-stats-2 GenericBound 352 ( 4.8%) 4 88
ast-stats-2 - Trait 352 ( 4.8%) 4
ast-stats-2 GenericBound 352 ( 4.7%) 4 88
ast-stats-2 - Trait 352 ( 4.7%) 4
ast-stats-2 GenericParam 480 ( 6.5%) 5 96
ast-stats-2 Pat 504 ( 6.8%) 7 72
ast-stats-2 - Struct 72 ( 1.0%) 1
ast-stats-2 - Wild 72 ( 1.0%) 1
ast-stats-2 - Ident 360 ( 4.9%) 5
ast-stats-2 Expr 648 ( 8.8%) 9 72
ast-stats-2 Expr 648 ( 8.7%) 9 72
ast-stats-2 - InlineAsm 72 ( 1.0%) 1
ast-stats-2 - Match 72 ( 1.0%) 1
ast-stats-2 - Path 72 ( 1.0%) 1
@ -113,7 +113,7 @@ ast-stats-2 - Trait 144 ( 1.9%) 1
ast-stats-2 - Fn 288 ( 3.9%) 2
ast-stats-2 - Use 576 ( 7.8%) 4
ast-stats-2 ----------------------------------------------------------------
ast-stats-2 Total 7_400 127
ast-stats-2 Total 7_416 127
ast-stats-2
hir-stats HIR STATS
hir-stats Name Accumulated Size Count Item Size
@ -126,11 +126,11 @@ hir-stats TraitItemRef 56 ( 0.6%) 2 28
hir-stats GenericArg 64 ( 0.7%) 4 16
hir-stats - Type 16 ( 0.2%) 1
hir-stats - Lifetime 48 ( 0.5%) 3
hir-stats Local 64 ( 0.7%) 1 64
hir-stats Param 64 ( 0.7%) 2 32
hir-stats Body 72 ( 0.8%) 3 24
hir-stats ImplItemRef 72 ( 0.8%) 2 36
hir-stats InlineAsm 72 ( 0.8%) 1 72
hir-stats Local 72 ( 0.8%) 1 72
hir-stats WherePredicate 72 ( 0.8%) 3 24
hir-stats - BoundPredicate 72 ( 0.8%) 3
hir-stats Arm 80 ( 0.9%) 2 40
@ -143,8 +143,8 @@ hir-stats Attribute 128 ( 1.4%) 4 32
hir-stats FieldDef 128 ( 1.4%) 2 64
hir-stats GenericArgs 144 ( 1.6%) 3 48
hir-stats Variant 144 ( 1.6%) 2 72
hir-stats GenericBound 256 ( 2.9%) 4 64
hir-stats - Trait 256 ( 2.9%) 4
hir-stats GenericBound 256 ( 2.8%) 4 64
hir-stats - Trait 256 ( 2.8%) 4
hir-stats Block 288 ( 3.2%) 6 48
hir-stats Pat 360 ( 4.0%) 5 72
hir-stats - Struct 72 ( 0.8%) 1
@ -156,7 +156,7 @@ hir-stats Ty 720 ( 8.0%) 15 48
hir-stats - Ptr 48 ( 0.5%) 1
hir-stats - Ref 48 ( 0.5%) 1
hir-stats - Path 624 ( 6.9%) 13
hir-stats Expr 768 ( 8.6%) 12 64
hir-stats Expr 768 ( 8.5%) 12 64
hir-stats - InlineAsm 64 ( 0.7%) 1
hir-stats - Match 64 ( 0.7%) 1
hir-stats - Path 64 ( 0.7%) 1
@ -174,5 +174,5 @@ hir-stats - Use 352 ( 3.9%) 4
hir-stats Path 1_240 (13.8%) 31 40
hir-stats PathSegment 1_920 (21.4%) 40 48
hir-stats ----------------------------------------------------------------
hir-stats Total 8_980 180
hir-stats Total 8_988 180
hir-stats

View file

@ -0,0 +1,174 @@
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:30:28
|
LL | super let b = DropMe(&mut x);
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - borrow might be used here, when `b` is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:46:28
|
LL | super let b = &DropMe(&mut x);
| --------------
| | |
| | `x` is borrowed here
| a temporary with access to the borrow is created here ...
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:64:32
|
LL | super let b = identity(&DropMe(&mut x));
| --------------
| | |
| | `x` is borrowed here
| a temporary with access to the borrow is created here ...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | };
| - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:87:36
|
LL | super let b = identity(&DropMe(&mut x));
| --------------
| | |
| | `x` is borrowed here
| a temporary with access to the borrow is created here ...
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | ));
| - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:107:28
|
LL | super let b = DropMe(&mut x);
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - borrow might be used here, when `b` is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:125:28
|
LL | super let b = DropMe(&mut x);
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - borrow might be used here, when `b` is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:143:28
|
LL | super let b = DropMe(&mut x);
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - borrow might be used here, when `b` is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:159:28
|
LL | b = DropMe(&mut x);
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
LL | drop(a);
| - borrow later used here
error[E0716]: temporary value dropped while borrowed
--> $DIR/super-let.rs:172:33
|
LL | #[cfg(borrowck)] { a = &String::from("asdf"); };
| ^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
...
LL | let _ = a;
| - borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:206:28
|
LL | super let d = &DropMe(&mut x);
| --------------
| | |
| | `x` is borrowed here
| a temporary with access to the borrow is created here ...
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:227:32
|
LL | super let d = identity(&DropMe(&mut x));
| --------------
| | |
| | `x` is borrowed here
| a temporary with access to the borrow is created here ...
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | };
| - ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:246:28
|
LL | super let b = DropMe(&mut x);
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - borrow might be used here, when `b` is dropped and runs the `Drop` code for type `DropMe`
error[E0506]: cannot assign to `x` because it is borrowed
--> $DIR/super-let.rs:263:28
|
LL | let dropme = Some(DropMe(&mut x));
| ------ `x` is borrowed here
...
LL | #[cfg(borrowck)] { x = true; }
| ^^^^^^^^ `x` is assigned to here but it was already borrowed
...
LL | }
| - borrow might be used here, when `x` is dropped and runs the `Drop` code for type `DropMe`
error: aborting due to 13 previous errors
Some errors have detailed explanations: E0506, E0716.
For more information about an error, try `rustc --explain E0506`.

286
tests/ui/super-let.rs Normal file
View file

@ -0,0 +1,286 @@
// Check in two ways:
// - borrowck: Check with borrow checking errors when things are alive and dead.
// - runtime: Check with a mutable bool if things are dropped on time.
//
//@ revisions: runtime borrowck
//@ [runtime] run-pass
//@ [borrowck] check-fail
#![allow(dropping_references)]
#![feature(super_let, stmt_expr_attributes)]
use std::convert::identity;
struct DropMe<'a>(&'a mut bool);
impl Drop for DropMe<'_> {
fn drop(&mut self) {
*self.0 = true;
}
}
// Check that a super let variable lives as long as the result of a block.
fn extended_variable() {
let mut x = false;
{
let a = {
super let b = DropMe(&mut x);
&b
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true) // ok
}
// Check that the init expression of a super let is subject to (temporary) lifetime extension.
fn extended_temporary() {
let mut x = false;
{
let a = {
super let b = &DropMe(&mut x);
b
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true); // ok
}
// Check that even non-extended temporaries live until the end of the block,
// but (unlike extended temporaries) not beyond that.
//
// This is necessary for things like select(pin!(identity(&temp()))) to work.
fn non_extended() {
let mut x = false;
{
let _a = {
// Use identity() to supress temporary lifetime extension.
super let b = identity(&DropMe(&mut x));
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
b
// DropMe is still alive here...
};
// ... but not here.
assert_eq!(x, true); // ok
}
}
// Check that even non-extended temporaries live until the end of the block,
// but (unlike extended temporaries) not beyond that.
//
// This is necessary for things like select(pin!(identity(&temp()))) to work.
fn non_extended_in_expression() {
let mut x = false;
{
identity((
{
// Use identity() to supress temporary lifetime extension.
super let b = identity(&DropMe(&mut x));
b
},
{
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
// DropMe is still alive here...
}
));
// ... but not here.
assert_eq!(x, true); // ok
}
}
// Check `super let` in a match arm.
fn match_arm() {
let mut x = false;
{
let a = match Some(123) {
Some(_) => {
super let b = DropMe(&mut x);
&b
}
None => unreachable!(),
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true); // ok
}
// Check `super let` in an if body.
fn if_body() {
let mut x = false;
{
let a = if true {
super let b = DropMe(&mut x);
&b
} else {
unreachable!()
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true); // ok
}
// Check `super let` in an else body.
fn else_body() {
let mut x = false;
{
let a = if false {
unreachable!()
} else {
super let b = DropMe(&mut x);
&b
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true); // ok
}
fn without_initializer() {
let mut x = false;
{
let a = {
super let b;
b = DropMe(&mut x);
b
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true);
}
// Assignment isn't special, even when assigning to a `super let` variable.
fn assignment() {
let mut x = false;
{
super let a;
#[cfg(borrowck)] { a = &String::from("asdf"); }; //[borrowck]~ ERROR dropped while borrowed
#[cfg(runtime)] { a = drop(&DropMe(&mut x)); } // Temporary dropped at the `;` as usual.
assert_eq!(x, true);
let _ = a;
}
}
// `super let mut` should work just fine.
fn mutable() {
let mut x = false;
{
let a = {
super let mut b = None;
&mut b
};
*a = Some(DropMe(&mut x));
}
assert_eq!(x, true);
}
// Temporary lifetime extension should recurse through `super let`s.
fn multiple_levels() {
let mut x = false;
{
let a = {
super let b = {
super let c = {
super let d = &DropMe(&mut x);
d
};
c
};
b
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
drop(a);
// DropMe is still alive here...
}
// ... but not here.
assert_eq!(x, true);
}
// Non-extended temporaries should be dropped at the
// end of the first parent statement that isn't `super`.
fn multiple_levels_but_no_extension() {
let mut x = false;
{
let _a = {
super let b = {
super let c = {
super let d = identity(&DropMe(&mut x));
d
};
c
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
b
// DropMe is still alive here...
};
// ... but not here.
assert_eq!(x, true);
}
}
// Check for potential weird interactions with `let else`.
fn super_let_and_let_else() {
let mut x = false;
{
let a = 'a: {
let Some(_) = Some(123) else { unreachable!() };
super let b = DropMe(&mut x);
let None = Some(123) else { break 'a &b };
unreachable!()
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
// DropMe is still alive here...
drop(a);
}
// ... but not here.
assert_eq!(x, true);
}
// Check if `super let .. else ..;` works.
fn super_let_else() {
let mut x = false;
{
let a = {
let dropme = Some(DropMe(&mut x));
super let Some(x) = dropme else { unreachable!() };
&x
};
#[cfg(borrowck)] { x = true; } //[borrowck]~ ERROR borrowed
// DropMe is still alive here...
drop(a);
}
// ... but not here.
assert_eq!(x, true);
}
fn main() {
extended_variable();
extended_temporary();
non_extended();
non_extended_in_expression();
match_arm();
if_body();
else_body();
without_initializer();
assignment();
mutable();
multiple_levels();
multiple_levels_but_no_extension();
super_let_and_let_else();
super_let_else();
}