rust-lang/rust · #164076
Fix ICE when move expressions reference out-of-scope local bindings
compiler/rustc_resolve/src/diagnostics/impls.rs11 + / 0 −
@@ -1436,6 +1436,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ResolutionError::BindingInNeverPattern => { self.dcx().create_err(diagnostics::BindingInNeverPattern { span }) }+ ResolutionError::CannotUseInnerLocalInMoveExpr {+ move_expr_span,+ ident,+ scope_span,+ } => self.dcx().create_err(diagnostics::CannotUseInnerLocalInMoveExpr {+ span,+ ident,+ binding_span: ident.span,+ move_expr_span,+ scope_span,+ }), } } compiler/rustc_resolve/src/diagnostics/mod.rs14 + / 0 −
@@ -525,6 +525,20 @@ pub(crate) struct BindingInNeverPattern { pub(crate) span: Span, } +#[derive(Diagnostic)]+#[diag("cannot use `{$ident}` in this move expression")]+pub(crate) struct CannotUseInnerLocalInMoveExpr {+ #[primary_span]+ pub(crate) span: Span,+ pub(crate) ident: Ident,+ #[label("`{$ident}` is defined here")]+ pub(crate) binding_span: Span,+ #[label("this expression is evaluated outside that scope")]+ pub(crate) move_expr_span: Span,+ #[label("`{$ident}` is defined within this scope")]+ pub(crate) scope_span: Span,+}+ #[derive(Diagnostic)] #[diag("duplicate definitions with name `{$name}`:", code = E0201)] pub(crate) struct TraitImplDuplicate {compiler/rustc_resolve/src/ident.rs21 + / 0 −
@@ -1511,9 +1511,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { for rib in ribs { match rib.kind {+ // `first_rib` marks the start of the closure or coroutine scope.+ // The initializer is evaluated outside that scope, so bindings defined+ // inside it are unavailable, while outer bindings remain accessible.+ RibKind::MoveExpr { first_rib, move_expr_span, scope_span }+ if rib_index >= first_rib =>+ {+ if let Some(span) = finalize {+ self.report_error(+ span,+ CannotUseInnerLocalInMoveExpr {+ move_expr_span,+ ident: original_rib_ident_def,+ scope_span,+ },+ );+ }+ return Res::Err;+ } RibKind::Normal | RibKind::Block(..) | RibKind::FnOrCoroutine+ | RibKind::MoveExpr { .. } | RibKind::Module(..) | RibKind::MacroDefinition(..) | RibKind::ForwardGenericParamBan(_) => {@@ -1610,6 +1629,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { RibKind::Normal | RibKind::Block(..) | RibKind::FnOrCoroutine+ | RibKind::MoveExpr { .. } | RibKind::Module(..) | RibKind::MacroDefinition(..) | RibKind::InlineAsmSym@@ -1717,6 +1737,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { RibKind::Normal | RibKind::Block(..) | RibKind::FnOrCoroutine+ | RibKind::MoveExpr { .. } | RibKind::Module(..) | RibKind::MacroDefinition(..) | RibKind::InlineAsmSymcompiler/rustc_resolve/src/late.rs74 + / 13 −
@@ -219,6 +219,12 @@ pub(crate) enum RibKind<'ra> { /// We passed through a function, closure or coroutine signature. Disallow labels. FnOrCoroutine, + MoveExpr {+ first_rib: usize,+ move_expr_span: Span,+ scope_span: Span,+ },+ /// We passed through an item scope. Disallow upvars. Item(HasGenericParams, DefKind), @@ -264,6 +270,7 @@ impl RibKind<'_> { RibKind::Normal | RibKind::Block(..) | RibKind::FnOrCoroutine+ | RibKind::MoveExpr { .. } | RibKind::ConstantItem(..) | RibKind::Module(_) | RibKind::MacroDefinition(_)@@ -840,6 +847,8 @@ struct LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { /// The current set of local scopes for types and values. ribs: PerNS<Vec<Rib<'ra>>>, + move_expr_scopes: Vec<Option<(usize, Span)>>,+ /// Previous popped `rib`, only used for diagnostic. last_block_rib: Option<Rib<'ra>>, @@ -901,7 +910,9 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc // Always report errors in items we just entered. let old_ignore = replace(&mut self.in_func_body, false); with_owner(self, item.id, |this| {- this.with_lifetime_rib(LifetimeRibKind::Item, |this| this.resolve_item(item))+ this.with_move_expr_scope(None, |this| {+ this.with_lifetime_rib(LifetimeRibKind::Item, |this| this.resolve_item(item))+ }) }); self.in_func_body = old_ignore; self.diag_metadata.current_item = prev;@@ -1617,6 +1628,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { type_ns: vec![Rib::new(start_rib_kind)], macro_ns: vec![Rib::new(start_rib_kind)], },+ move_expr_scopes: Vec::new(), last_block_rib: None, label_ribs: Vec::new(), lifetime_ribs: Vec::new(),@@ -1728,6 +1740,39 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } + fn with_move_expr_scope<T>(+ &mut self,+ scope_span: Option<Span>,+ work: impl FnOnce(&mut Self) -> T,+ ) -> T {+ if !self.r.features.move_expr() {+ return work(self);+ }+ let scope = scope_span.map(|span| (self.ribs[ValueNS].len(), span));+ self.move_expr_scopes.push(scope);+ let result = work(self);+ self.move_expr_scopes.pop();+ result+ }++ fn with_move_expr_initializer<T>(+ &mut self,+ move_expr_span: Span,+ work: impl FnOnce(&mut Self) -> T,+ ) -> T {+ let Some(Some((first_rib, scope_span))) = self.move_expr_scopes.last().copied() else {+ return work(self);+ };+ self.move_expr_scopes.pop();+ let result = self.with_rib(+ ValueNS,+ RibKind::MoveExpr { first_rib, move_expr_span, scope_span },+ work,+ );+ self.move_expr_scopes.push(Some((first_rib, scope_span)));+ result+ }+ fn visit_generic_params(&mut self, params: &'ast [GenericParam], add_self_upper: bool) { // For type parameter defaults, we have to ban access // to following type parameters, as the GenericArgs can only@@ -5566,30 +5611,46 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ExprKind::Type(ref _type_expr, ref _ty) => { visit::walk_expr(self, expr); }+ ExprKind::Move(..) => {+ self.with_move_expr_initializer(expr.span, |this| visit::walk_expr(this, expr));+ } // For closures, RibKind::FnOrCoroutine is added in visit_fn ExprKind::Closure(ast::Closure { binder: ClosureBinder::For { ref generic_params, span },+ fn_decl_span, .. }) => {- self.with_generic_param_rib(- generic_params,- RibKind::Normal,- expr.id,- LifetimeBinderKind::Closure,- span,- |this| visit::walk_expr(this, expr),- );+ self.with_move_expr_scope(Some(fn_decl_span), |this| {+ this.with_generic_param_rib(+ generic_params,+ RibKind::Normal,+ expr.id,+ LifetimeBinderKind::Closure,+ span,+ |this| visit::walk_expr(this, expr),+ );+ });+ }+ ExprKind::Closure(ref closure) => {+ self.with_move_expr_scope(Some(closure.fn_decl_span), |this| {+ visit::walk_expr(this, expr)+ }); }- ExprKind::Closure(..) => visit::walk_expr(self, expr),- ExprKind::Gen(..) => {- self.with_label_rib(RibKind::FnOrCoroutine, |this| visit::walk_expr(this, expr));+ ExprKind::Gen(_, _, _, decl_span) => {+ self.with_move_expr_scope(Some(decl_span), |this| {+ this.with_label_rib(RibKind::FnOrCoroutine, |this| {+ visit::walk_expr(this, expr)+ });+ }); } ExprKind::Repeat(ref elem, ref ct) => { self.visit_expr(elem); self.resolve_anon_const(ct, AnonConstKind::ConstArg(IsRepeatExpr::Yes)); } ExprKind::ConstBlock(ref ct) => {- self.resolve_anon_const(ct, AnonConstKind::InlineConst);+ self.with_move_expr_scope(None, |this| {+ this.resolve_anon_const(ct, AnonConstKind::InlineConst);+ }); } ExprKind::Index(ref elem, ref idx, _) => { self.resolve_expr(elem, Some(expr));compiler/rustc_resolve/src/lib.rs24 + / 5 −
@@ -262,7 +262,10 @@ enum ResolutionError<'ra> { /// Error E0416: identifier is bound more than once in the same pattern. IdentifierBoundMoreThanOnceInSamePattern(Ident), /// Error E0426: use of undeclared label.- UndeclaredLabel { name: Symbol, suggestion: Option<LabelSuggestion> },+ UndeclaredLabel {+ name: Symbol,+ suggestion: Option<LabelSuggestion>,+ }, /// Error E0433: failed to resolve. FailedToResolve { segment: Symbol,@@ -273,7 +276,9 @@ enum ResolutionError<'ra> { message: String, }, /// Error E0434: can't capture dynamic environment in a fn item.- CannotCaptureDynamicEnvironmentInFnItem { suggest_closure: bool },+ CannotCaptureDynamicEnvironmentInFnItem {+ suggest_closure: bool,+ }, /// Error E0435: attempt to use a non-constant value in a constant. AttemptToUseNonConstantValueInConstant { ident: Ident,@@ -296,7 +301,9 @@ enum ResolutionError<'ra> { // FIXME(generic_const_parameter_types): This should give custom output specifying it's only // problematic to use *forward declared* parameters when the feature is enabled. /// ERROR E0770: the type of const parameters must not depend on other generic parameters.- ParamInTyOfConstParam { name: Symbol },+ ParamInTyOfConstParam {+ name: Symbol,+ }, /// cannot use self in const param SelfInConstParam, /// generic parameters must not be used inside const evaluations.@@ -310,11 +317,18 @@ enum ResolutionError<'ra> { /// generic parameters must not be used inside enum discriminants. /// /// This error is emitted even with `generic_const_exprs`.- ParamInEnumDiscriminant { name: Symbol, param_kind: ParamKindInEnumDiscriminant },+ ParamInEnumDiscriminant {+ name: Symbol,+ param_kind: ParamKindInEnumDiscriminant,+ }, /// Error E0735: generic parameters with a default cannot use `Self` ForwardDeclaredSelf(ForwardGenericParamBanReason), /// Error E0767: use of unreachable label- UnreachableLabel { name: Symbol, definition_span: Span, suggestion: Option<LabelSuggestion> },+ UnreachableLabel {+ name: Symbol,+ definition_span: Span,+ suggestion: Option<LabelSuggestion>,+ }, /// Error E0323, E0324, E0325: mismatch between trait item and impl item. TraitImplMismatch { name: Ident,@@ -339,6 +353,11 @@ enum ResolutionError<'ra> { LowercaseSelf, /// A never pattern has a binding. BindingInNeverPattern,+ CannotUseInnerLocalInMoveExpr {+ move_expr_span: Span,+ ident: Ident,+ scope_span: Span,+ }, } #[derive(Debug)]tests/ui/move-expr/closure-local-bindings.rsadded77 + / 0 −
@@ -0,0 +1,77 @@+//@ check-fail+//@ edition: 2024++#![allow(incomplete_features)]+#![feature(gen_blocks, move_expr)]++macro_rules! move_value {+ ($value:expr) => { move($value) };+}++fn main() {+ let _ = |x: i32| move(x);+ //~^ ERROR cannot use `x` in this move expression++ let _ = |(x, _): (i32, i32)| move(x);+ //~^ ERROR cannot use `x` in this move expression++ let x = 1;+ let _ = || {+ let x = 2;+ move(x);+ //~^ ERROR cannot use `x` in this move expression+ };++ let _ = || {+ for x in [1] {+ move(x);+ //~^ ERROR cannot use `x` in this move expression+ }+ };++ let _ = || {+ if let Some(x) = Some(1) {+ move(x);+ //~^ ERROR cannot use `x` in this move expression+ }+ };++ let _ = || {+ let x = 1;+ || move(move(x))+ //~^ ERROR cannot use `x` in this move expression+ };++ let _ = || || move({+ let x = 1;+ move(x)+ //~^ ERROR cannot use `x` in this move expression+ });++ let _ = || {+ let x = 1;+ move_value!(x);+ //~^ ERROR cannot use `x` in this move expression+ };++ let _ = async |x: i32| move(x);+ //~^ ERROR cannot use `x` in this move expression++ let _ = async {+ let x = 1;+ move(x);+ //~^ ERROR cannot use `x` in this move expression+ };++ let _ = gen {+ let x = 1;+ yield move(x);+ //~^ ERROR cannot use `x` in this move expression+ };++ let _ = || {+ let x = 1;+ move(|| x)+ //~^ ERROR cannot use `x` in this move expression+ };+}tests/ui/move-expr/closure-local-bindings.stderradded125 + / 0 −
@@ -0,0 +1,125 @@+error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:12:27+ |+LL | let _ = |x: i32| move(x);+ | -------- -----^-+ | || |+ | || this expression is evaluated outside that scope+ | |`x` is defined here+ | `x` is defined within this scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:15:39+ |+LL | let _ = |(x, _): (i32, i32)| move(x);+ | -------------------- -----^-+ | | | |+ | | | this expression is evaluated outside that scope+ | | `x` is defined here+ | `x` is defined within this scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:21:14+ |+LL | let _ = || {+ | -- `x` is defined within this scope+LL | let x = 2;+ | - `x` is defined here+LL | move(x);+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:27:18+ |+LL | let _ = || {+ | -- `x` is defined within this scope+LL | for x in [1] {+ | - `x` is defined here+LL | move(x);+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:34:18+ |+LL | let _ = || {+ | -- `x` is defined within this scope+LL | if let Some(x) = Some(1) {+ | - `x` is defined here+LL | move(x);+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:41:22+ |+LL | let _ = || {+ | -- `x` is defined within this scope+LL | let x = 1;+ | - `x` is defined here+LL | || move(move(x))+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:47:14+ |+LL | let _ = || || move({+ | -- `x` is defined within this scope+LL | let x = 1;+ | - `x` is defined here+LL | move(x)+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:53:21+ |+LL | ($value:expr) => { move($value) };+ | ------------ this expression is evaluated outside that scope+...+LL | let _ = || {+ | -- `x` is defined within this scope+LL | let x = 1;+ | - `x` is defined here+LL | move_value!(x);+ | ^++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:57:33+ |+LL | let _ = async |x: i32| move(x);+ | -------------- -----^-+ | | | |+ | | | this expression is evaluated outside that scope+ | | `x` is defined here+ | `x` is defined within this scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:62:14+ |+LL | let _ = async {+ | ----- `x` is defined within this scope+LL | let x = 1;+ | - `x` is defined here+LL | move(x);+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:68:20+ |+LL | let _ = gen {+ | --- `x` is defined within this scope+LL | let x = 1;+ | - `x` is defined here+LL | yield move(x);+ | -----^- this expression is evaluated outside that scope++error: cannot use `x` in this move expression+ --> $DIR/closure-local-bindings.rs:74:17+ |+LL | let _ = || {+ | -- `x` is defined within this scope+LL | let x = 1;+ | - `x` is defined here+LL | move(|| x)+ | --------^- this expression is evaluated outside that scope++error: aborting due to 12 previous errors+tests/ui/move-expr/closure-local-uninitialized.rsadded13 + / 0 −
@@ -0,0 +1,13 @@+//@ check-fail+// Regression test for https://github.com/rust-lang/rust/issues/156509++#![allow(incomplete_features)]+#![feature(move_expr)]++fn main() {+ let _c = || {+ let x;+ move(x);+ //~^ ERROR cannot use `x` in this move expression+ };+}tests/ui/move-expr/closure-local-uninitialized.stderradded12 + / 0 −
@@ -0,0 +1,12 @@+error: cannot use `x` in this move expression+ --> $DIR/closure-local-uninitialized.rs:10:14+ |+LL | let _c = || {+ | -- `x` is defined within this scope+LL | let x;+ | - `x` is defined here+LL | move(x);+ | -----^- this expression is evaluated outside that scope++error: aborting due to 1 previous error+tests/ui/move-expr/move-initializer-local-bindings.rsadded66 + / 0 −
@@ -0,0 +1,66 @@+//@ run-pass++#![allow(incomplete_features, unused_variables)]+#![feature(move_expr)]++fn main() {+ let c = || move({+ let x = 3;+ x+ });+ assert_eq!(c(), 3);++ let c = || move({+ let x = 4;+ (|| x)()+ });+ assert_eq!(c(), 4);++ let outer = || {+ let x = 5;+ || move(x)+ };+ assert_eq!(outer()(), 5);++ let outer = || {+ let x = 6;+ || move({+ let y = 7;+ || move(y + x)+ })+ };+ assert_eq!(outer()()(), 13);++ let c = || move({+ fn identity(x: i32) -> i32 {+ x+ }+ identity(8)+ });+ assert_eq!(c(), 8);++ let c = || move(const { 9 });+ assert_eq!(c(), 9);++ let x = 10;+ let c = || move(x);+ let d = || move(x + 1);+ assert_eq!(c(), 10);+ assert_eq!(d(), 11);++ let c = || {+ let x = 12;+ move({+ let x = 13;+ x+ })+ };+ assert_eq!(c(), 13);++ let c = |x: i32| move({+ let x;+ x = 14;+ x+ });+ assert_eq!(c(0), 14);+}tests/ui/move-expr/name-resolution.rs2 + / 4 −
@@ -1,13 +1,11 @@-//@ check-pass-//@ ignore-test (#155050): currently ICEs instead of reporting a name-resolution error-// FIXME(TaKO8Ki): Remove this ignore once closure-local names in `move(expr)` produce a real-// diagnostic instead of hitting the current `Res::Err` ICE path.+//@ check-fail #![allow(incomplete_features)] #![feature(move_expr)] fn main() { let _c = || { let x = 3; move(x);+ //~^ ERROR cannot use `x` in this move expression }; }tests/ui/move-expr/name-resolution.stderradded12 + / 0 −
@@ -0,0 +1,12 @@+error: cannot use `x` in this move expression+ --> $DIR/name-resolution.rs:8:14+ |+LL | let _c = || {+ | -- `x` is defined within this scope+LL | let x = 3;+ | - `x` is defined here+LL | move(x);+ | -----^- this expression is evaluated outside that scope++error: aborting due to 1 previous error+