From 31bd2c954a5ffca6a48eb8e886fd2430ae42d264 Mon Sep 17 00:00:00 2001 From: AMMAAR-IC Date: Sun, 12 Apr 2026 01:33:03 +0530 Subject: [PATCH 01/36] jsondoclint: simplify code using idiomatic Rust - Use matches!() macro instead of match in item_kind.rs - Remove unnecessary derefs and simplify Option mapping in validator.rs - Replace clone() on Copy type with *id - Fix doc comment indentation - Remove redundant tuple parens in tests.rs - Remove needless borrow and closure in main.rs --- src/tools/jsondoclint/src/item_kind.rs | 6 +---- src/tools/jsondoclint/src/main.rs | 4 +-- src/tools/jsondoclint/src/validator.rs | 28 +++++++++----------- src/tools/jsondoclint/src/validator/tests.rs | 2 +- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/tools/jsondoclint/src/item_kind.rs b/src/tools/jsondoclint/src/item_kind.rs index e2738636a1458..d2e08cd9e5853 100644 --- a/src/tools/jsondoclint/src/item_kind.rs +++ b/src/tools/jsondoclint/src/item_kind.rs @@ -77,11 +77,7 @@ impl Kind { } pub fn can_appear_in_glob_import(self) -> bool { - match self { - Kind::Module => true, - Kind::Enum => true, - _ => false, - } + matches!(self, Kind::Module | Kind::Enum) } pub fn can_appear_in_trait(self) -> bool { diff --git a/src/tools/jsondoclint/src/main.rs b/src/tools/jsondoclint/src/main.rs index 5cbf346086062..f4278658200f0 100644 --- a/src/tools/jsondoclint/src/main.rs +++ b/src/tools/jsondoclint/src/main.rs @@ -81,7 +81,7 @@ fn main() -> Result<()> { [sel] => eprintln!( "{} not in index or paths, but referred to at '{}'", err.id.0, - json_find::to_jsonpath(&sel) + json_find::to_jsonpath(sel) ), [sel, ..] => { if verbose { @@ -99,7 +99,7 @@ fn main() -> Result<()> { eprintln!( "{} not in index or paths, but referred to at '{}' and {} more", err.id.0, - json_find::to_jsonpath(&sel), + json_find::to_jsonpath(sel), sels.len() - 1, ) } diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index 0a4051fcbe8cd..01deafe20b354 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -20,10 +20,10 @@ const LOCAL_CRATE_ID: u32 = 0; /// It is made of several parts. /// /// - `check_*`: These take a type from [`rustdoc_json_types`], and check that -/// it is well formed. This involves calling `check_*` functions on -/// fields of that item, and `add_*` functions on [`Id`]s. +/// it is well formed. This involves calling `check_*` functions on +/// fields of that item, and `add_*` functions on [`Id`]s. /// - `add_*`: These add an [`Id`] to the worklist, after validating it to check if -/// the `Id` is a kind expected in this situation. +/// the `Id` is a kind expected in this situation. #[derive(Debug)] pub struct Validator<'a> { pub(crate) errs: Vec, @@ -262,17 +262,17 @@ impl<'a> Validator<'a> { Type::Generic(_) => {} Type::Primitive(_) => {} Type::Pat { type_, __pat_unstable_do_not_use: _ } => self.check_type(type_), - Type::FunctionPointer(fp) => self.check_function_pointer(&**fp), + Type::FunctionPointer(fp) => self.check_function_pointer(fp), Type::Tuple(tys) => tys.iter().for_each(|ty| self.check_type(ty)), - Type::Slice(inner) => self.check_type(&**inner), - Type::Array { type_, len: _ } => self.check_type(&**type_), + Type::Slice(inner) => self.check_type(inner), + Type::Array { type_, len: _ } => self.check_type(type_), Type::ImplTrait(bounds) => bounds.iter().for_each(|b| self.check_generic_bound(b)), Type::Infer => {} - Type::RawPointer { is_mutable: _, type_ } => self.check_type(&**type_), - Type::BorrowedRef { lifetime: _, is_mutable: _, type_ } => self.check_type(&**type_), + Type::RawPointer { is_mutable: _, type_ } => self.check_type(type_), + Type::BorrowedRef { lifetime: _, is_mutable: _, type_ } => self.check_type(type_), Type::QualifiedPath { name: _, args, self_type, trait_ } => { - self.check_opt_generic_args(&args); - self.check_type(&**self_type); + self.check_opt_generic_args(args); + self.check_type(self_type); if let Some(trait_) = trait_ { self.check_path(trait_, PathKind::Trait); } @@ -404,7 +404,7 @@ impl<'a> Validator<'a> { // which encodes rustc implementation details. if item_info.crate_id == LOCAL_CRATE_ID && !self.krate.index.contains_key(id) { self.errs.push(Error { - id: id.clone(), + id: *id, kind: ErrorKind::Custom( "Id for local item in `paths` but not in `index`".to_owned(), ), @@ -483,16 +483,14 @@ impl<'a> Validator<'a> { } fn fail(&mut self, id: &Id, kind: ErrorKind) { - self.errs.push(Error { id: id.clone(), kind }); + self.errs.push(Error { id: *id, kind }); } fn kind_of(&mut self, id: &Id) -> Option { if let Some(item) = self.krate.index.get(id) { Some(Kind::from_item(item)) - } else if let Some(summary) = self.krate.paths.get(id) { - Some(Kind::from_summary(summary)) } else { - None + self.krate.paths.get(id).map(Kind::from_summary) } } } diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs index dd0b4ac5601a7..74bf5b0595ad3 100644 --- a/src/tools/jsondoclint/src/validator/tests.rs +++ b/src/tools/jsondoclint/src/validator/tests.rs @@ -78,7 +78,7 @@ fn errors_on_local_in_paths_and_not_index() { span: None, visibility: Visibility::Public, docs: None, - links: FxHashMap::from_iter([(("prim@i32".to_owned(), Id(2)))]), + links: FxHashMap::from_iter([("prim@i32".to_owned(), Id(2))]), attrs: Vec::new(), deprecation: None, inner: ItemEnum::Module(Module { From a469a4ae638200c264c553e72204335e20d661a1 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Wed, 29 Apr 2026 15:27:09 +0800 Subject: [PATCH 02/36] rustdoc: correctly display unresolved item for "extern prelude" paths - Add a check while trying to resolve parents, so that for links like [`::unresolved::path`], rustdoc will say: "no item named `unresolved` in scope" instead of: "no item named `` in scope" - Update corresponding test --- src/librustdoc/passes/collect_intra_doc_links.rs | 8 +++++++- tests/rustdoc-ui/intra-doc/global-path.rs | 3 +++ tests/rustdoc-ui/intra-doc/global-path.stderr | 10 ++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 45061a02e7525..099c998c8ab69 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2048,13 +2048,13 @@ fn resolution_failure( 'outer: loop { // FIXME(jynelson): this might conflict with my `Self` fix in #76467 let Some((start, end)) = name.rsplit_once("::") else { + // `name` is now the first path segment, which didn't resolve. // avoid bug that marked [Quux::Z] as missing Z, not Quux if partial_res.is_none() { *unresolved = name.into(); } break; }; - name = start; for ns in [TypeNS, ValueNS, MacroNS] { if let Ok(v_res) = collector.resolve(start, ns, None, item_id, module_id) @@ -2068,6 +2068,12 @@ fn resolution_failure( } } *unresolved = end.into(); + if start.is_empty() && partial_res.is_none() { + // `start` being empty means `path_str` was written like "::path::to::item". + // In this case, `end` is the first path segment that we should report. + break; + } + name = start; } let last_found_module = match *partial_res { diff --git a/tests/rustdoc-ui/intra-doc/global-path.rs b/tests/rustdoc-ui/intra-doc/global-path.rs index 4064475355f32..43380b07ee0ef 100644 --- a/tests/rustdoc-ui/intra-doc/global-path.rs +++ b/tests/rustdoc-ui/intra-doc/global-path.rs @@ -5,4 +5,7 @@ /// [::Unresolved] //~^ WARN unresolved link to `::Unresolved` +/// +/// [::std::unresolved] +//~^ WARN unresolved link to `::std::unresolved` pub struct Item; diff --git a/tests/rustdoc-ui/intra-doc/global-path.stderr b/tests/rustdoc-ui/intra-doc/global-path.stderr index 02379cd6cdf69..aed93f1131239 100644 --- a/tests/rustdoc-ui/intra-doc/global-path.stderr +++ b/tests/rustdoc-ui/intra-doc/global-path.stderr @@ -2,9 +2,15 @@ warning: unresolved link to `::Unresolved` --> $DIR/global-path.rs:6:6 | LL | /// [::Unresolved] - | ^^^^^^^^^^^^ no item named `` in scope + | ^^^^^^^^^^^^ no item named `Unresolved` in scope | = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default -warning: 1 warning emitted +warning: unresolved link to `::std::unresolved` + --> $DIR/global-path.rs:9:6 + | +LL | /// [::std::unresolved] + | ^^^^^^^^^^^^^^^^^ no item named `unresolved` in module `std` + +warning: 2 warnings emitted From f62d5368a250d3379093964caf5c4938267b4e4e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 11:40:18 +0900 Subject: [PATCH 03/36] add move_expr feature flag --- compiler/rustc_feature/src/unstable.rs | 2 ++ compiler/rustc_span/src/symbol.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 5af522486b1f6..93fc3e0478b6a 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -646,6 +646,8 @@ declare_features! ( (unstable, must_not_suspend, "1.57.0", Some(83310)), /// Allows `mut ref` and `mut ref mut` identifier patterns. (incomplete, mut_ref, "1.79.0", Some(123076)), + /// Allows `move(expr)` in closures. + (incomplete, move_expr, "CURRENT_RUSTC_VERSION", None), /// Allows using `#[naked]` on `extern "Rust"` functions. (unstable, naked_functions_rustic_abi, "1.88.0", Some(138997)), /// Allows using `#[target_feature(enable = "...")]` on `#[naked]` on functions. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ea2880d8d7dff..e7cc43fdf8ebc 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1323,6 +1323,7 @@ symbols! { more_qualified_paths, more_struct_aliases, movbe_target_feature, + move_expr, move_ref_pattern, move_size_limit, movrs_target_feature, From 98348de63a5047b3ee7d8182efa8486e6eaac79b Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 21:14:27 +0900 Subject: [PATCH 04/36] add move(expr) syntax --- compiler/rustc_ast/src/ast.rs | 3 +++ compiler/rustc_ast/src/util/classify.rs | 2 ++ compiler/rustc_ast/src/visit.rs | 4 +++- compiler/rustc_ast_lowering/src/errors.rs | 7 +++++++ compiler/rustc_ast_lowering/src/expr.rs | 16 ++++++++++++++-- compiler/rustc_ast_passes/src/feature_gate.rs | 3 +++ .../rustc_ast_pretty/src/pprust/state/expr.rs | 5 +++++ .../rustc_builtin_macros/src/assert/context.rs | 3 +++ compiler/rustc_parse/src/parser/expr.rs | 17 +++++++++++++++++ compiler/rustc_passes/src/input_stats.rs | 2 +- .../ui/feature-gates/feature-gate-move_expr.rs | 4 ++++ .../feature-gates/feature-gate-move_expr.stderr | 12 ++++++++++++ tests/ui/macros/stringify.rs | 3 +++ 13 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-move_expr.rs create mode 100644 tests/ui/feature-gates/feature-gate-move_expr.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 0be00f4d00be7..a6e63214b3bf3 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1594,6 +1594,7 @@ impl Expr { // need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b` // but we need to print `(let _ = a) < b` as-is with parens. | ExprKind::Let(..) + | ExprKind::Move(..) | ExprKind::Unary(..) => ExprPrecedence::Prefix, // Need parens if and only if there are prefix attributes. @@ -1763,6 +1764,8 @@ pub enum ExprKind { Binary(BinOp, Box, Box), /// A unary operation (e.g., `!x`, `*x`). Unary(UnOp, Box), + /// A `move(expr)` expression. + Move(Box, Span), /// A literal (e.g., `1`, `"foo"`). Lit(token::Lit), /// A cast (e.g., `foo as f64`). diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 43ef6897b79cf..0c98b0e5e7b4f 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -108,6 +108,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { Assign(e, _, _) | AssignOp(_, e, _) | Await(e, _) + | Move(e, _) | Use(e, _) | Binary(_, e, _) | Call(e, _) @@ -183,6 +184,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { | Ret(Some(e)) | Unary(_, e) | Yeet(Some(e)) + | Move(e, _) | Become(e) => { expr = e; } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index cbd7cb3ee8c6f..ed8c3787bfec4 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -1024,7 +1024,9 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, block, opt_label), ExprKind::Gen(capt, body, kind, decl_span) => visit_visitable!($($mut)? vis, capt, body, kind, decl_span), - ExprKind::Await(expr, span) | ExprKind::Use(expr, span) => + ExprKind::Await(expr, span) + | ExprKind::Move(expr, span) + | ExprKind::Use(expr, span) => visit_visitable!($($mut)? vis, expr, span), ExprKind::Assign(lhs, rhs, span) => visit_visitable!($($mut)? vis, lhs, rhs, span), diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 95b8bb48c6a9c..a1c1d1e11d694 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -136,6 +136,13 @@ pub(crate) struct ClosureCannotBeStatic { pub fn_decl_span: Span, } +#[derive(Diagnostic)] +#[diag("`move(expr)` is only supported in plain closures")] +pub(crate) struct MoveExprOnlyInPlainClosures { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("functional record updates are not allowed in destructuring assignments")] pub(crate) struct FunctionalRecordUpdateDestructuringAssignment { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index eaa22e071af4b..8a86c66d6e9cb 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -19,8 +19,8 @@ use visit::{Visitor, walk_expr}; use super::errors::{ AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, ClosureCannotBeStatic, CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment, - InclusiveRangeWithNoEnd, MatchArmWithNoBody, NeverPatternWithBody, NeverPatternWithGuard, - UnderscoreExprLhsAssign, + InclusiveRangeWithNoEnd, MatchArmWithNoBody, MoveExprOnlyInPlainClosures, NeverPatternWithBody, + NeverPatternWithGuard, UnderscoreExprLhsAssign, }; use super::{ GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode, ResolverAstLoweringExt, @@ -211,6 +211,18 @@ impl<'hir> LoweringContext<'_, 'hir> { }, ), ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr), + ExprKind::Move(_, move_kw_span) => { + if !self.tcx.features().move_expr() { + return self.expr_err( + *move_kw_span, + self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), + ); + } + self.dcx().emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span }); + hir::ExprKind::Err( + self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), + ) + } ExprKind::Use(expr, use_kw_span) => self.lower_expr_use(*use_kw_span, expr), ExprKind::Closure(Closure { binder, diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index e38716cbb7fb7..b0679487b8ebb 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -352,6 +352,9 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } _ => (), }, + ast::ExprKind::Move(_, move_kw_span) => { + gate!(&self, move_expr, move_kw_span, "`move(expr)` syntax is experimental"); + } _ => {} } visit::walk_expr(self, e) diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index a20ef210da973..78aedbd4f7f4b 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -630,6 +630,11 @@ impl<'a> State<'a> { ); self.word(".await"); } + ast::ExprKind::Move(expr, _) => { + self.word("move("); + self.print_expr(expr, FixupContext::default()); + self.word(")"); + } ast::ExprKind::Use(expr, _) => { self.print_expr_cond_paren( expr, diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 6ad9c61840fae..f15acc154baf3 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -248,6 +248,9 @@ impl<'cx, 'a> Context<'cx, 'a> { self.manage_cond_expr(arg); } } + ExprKind::Move(local_expr, _) => { + self.manage_cond_expr(local_expr); + } ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => { let path_ident = path_segment.ident; self.manage_initial_capture(expr, path_ident); diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 35c271cb70204..e5de0d972df6a 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -564,6 +564,12 @@ impl<'a> Parser<'a> { token::Ident(..) if this.token.is_keyword(kw::Box) => { make_it!(this, attrs, |this, _| this.parse_expr_box(lo)) } + token::Ident(..) + if this.token.is_keyword(kw::Move) + && this.look_ahead(1, |t| *t == token::OpenParen) => + { + make_it!(this, attrs, |this, _| this.parse_expr_move(lo)) + } token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => { make_it!(this, attrs, |this, _| this.recover_not_expr(lo)) } @@ -607,6 +613,16 @@ impl<'a> Parser<'a> { Ok((span, ExprKind::Err(guar))) } + fn parse_expr_move(&mut self, move_kw: Span) -> PResult<'a, (Span, ExprKind)> { + self.bump(); + self.psess.gated_spans.gate(sym::move_expr, move_kw); + self.expect(exp!(OpenParen))?; + let expr = self.parse_expr()?; + self.expect(exp!(CloseParen))?; + let span = move_kw.to(self.prev_token.span); + Ok((span, ExprKind::Move(expr, move_kw))) + } + fn is_mistaken_not_ident_negation(&self) -> bool { let token_cannot_continue_expr = |t: &Token| match t.uninterpolate().kind { // These tokens can start an expression after `!`, but @@ -4387,6 +4403,7 @@ impl MutVisitor for CondChecker<'_> { } ExprKind::Unary(_, _) | ExprKind::Await(_, _) + | ExprKind::Move(_, _) | ExprKind::Use(_, _) | ExprKind::AssignOp(_, _, _) | ExprKind::Range(_, _, _) diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index e424cc09fb607..9127e4936803d 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -657,7 +657,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { (self, e, e.kind, None, ast, Expr, ExprKind), [ Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, - If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign, + If, While, ForLoop, Loop, Match, Closure, Block, Await, Move, Use, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy diff --git a/tests/ui/feature-gates/feature-gate-move_expr.rs b/tests/ui/feature-gates/feature-gate-move_expr.rs new file mode 100644 index 0000000000000..a2ab1bb8b1d00 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-move_expr.rs @@ -0,0 +1,4 @@ +fn main() { + let _ = || move(2); + //~^ ERROR `move(expr)` syntax is experimental +} diff --git a/tests/ui/feature-gates/feature-gate-move_expr.stderr b/tests/ui/feature-gates/feature-gate-move_expr.stderr new file mode 100644 index 0000000000000..28ab95ababc16 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-move_expr.stderr @@ -0,0 +1,12 @@ +error[E0658]: `move(expr)` syntax is experimental + --> $DIR/feature-gate-move_expr.rs:2:16 + | +LL | let _ = || move(2); + | ^^^^ + | + = help: add `#![feature(move_expr)]` 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 + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/macros/stringify.rs b/tests/ui/macros/stringify.rs index b48b037b22949..fec991ec95b7b 100644 --- a/tests/ui/macros/stringify.rs +++ b/tests/ui/macros/stringify.rs @@ -3,6 +3,7 @@ //@ compile-flags: --test #![allow(incomplete_features)] +#![allow(unused_features)] #![feature(auto_traits)] #![feature(box_patterns)] #![feature(const_block_items)] @@ -11,6 +12,7 @@ #![feature(decl_macro)] #![feature(macro_guard_matcher)] #![feature(more_qualified_paths)] +#![feature(move_expr)] #![feature(never_patterns)] #![feature(specialization)] #![feature(trait_alias)] @@ -110,6 +112,7 @@ fn test_expr() { c1!(expr, [ *expr ], "*expr"); c1!(expr, [ !expr ], "!expr"); c1!(expr, [ -expr ], "-expr"); + c1!(expr, [ move(expr) ], "move(expr)"); // ExprKind::Lit c1!(expr, [ 'x' ], "'x'"); From 9abe21c24bf958413ce30a4c9583bef8f6f361fb Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 21:18:59 +0900 Subject: [PATCH 05/36] lower move(expr) in plain closures --- compiler/rustc_ast_lowering/src/expr.rs | 241 ++++++++++++++---- compiler/rustc_ast_lowering/src/lib.rs | 2 + compiler/rustc_hir/src/hir.rs | 7 + compiler/rustc_hir/src/intravisit.rs | 1 + compiler/rustc_hir_pretty/src/lib.rs | 1 + tests/ui/move-expr/outside-plain-closure.rs | 7 + .../ui/move-expr/outside-plain-closure.stderr | 8 + tests/ui/move-expr/plain-closure.rs | 12 + 8 files changed, 236 insertions(+), 43 deletions(-) create mode 100644 tests/ui/move-expr/outside-plain-closure.rs create mode 100644 tests/ui/move-expr/outside-plain-closure.stderr create mode 100644 tests/ui/move-expr/plain-closure.rs diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 8a86c66d6e9cb..a5d07f02197a9 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -2,6 +2,7 @@ use std::mem; use std::ops::ControlFlow; use std::sync::Arc; +use rustc_ast::node_id::NodeMap; use rustc_ast::*; use rustc_ast_pretty::pprust::expr_to_string; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -30,6 +31,41 @@ use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScop pub(super) struct WillCreateDefIdsVisitor; +struct MoveExprOccurrence<'a> { + id: NodeId, + move_kw_span: Span, + expr: &'a Expr, +} + +struct MoveExprCollector<'a> { + occurrences: Vec>, +} + +impl<'a> MoveExprCollector<'a> { + fn collect(expr: &'a Expr) -> Vec> { + let mut this = Self { occurrences: Vec::new() }; + this.visit_expr(expr); + this.occurrences + } +} + +impl<'a> Visitor<'a> for MoveExprCollector<'a> { + fn visit_expr(&mut self, expr: &'a Expr) { + match &expr.kind { + ExprKind::Move(inner, move_kw_span) => { + self.visit_expr(inner); + self.occurrences.push(MoveExprOccurrence { + id: expr.id, + move_kw_span: *move_kw_span, + expr: inner, + }); + } + ExprKind::Closure(..) | ExprKind::Gen(..) | ExprKind::ConstBlock(..) => {} + _ => walk_expr(self, expr), + } + } +} + impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor { type Result = ControlFlow; @@ -94,11 +130,12 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::ForLoop { pat, iter, body, label, kind } => { return self.lower_expr_for(e, pat, iter, body, *label, *kind); } + ExprKind::Closure(box closure) => return self.lower_expr_closure_expr(e, closure), _ => (), } let expr_hir_id = self.lower_node_id(e.id); - let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); + self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); let kind = match &e.kind { ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)), @@ -218,48 +255,34 @@ impl<'hir> LoweringContext<'_, 'hir> { self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), ); } - self.dcx().emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span }); - hir::ExprKind::Err( - self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), - ) + if let Some((ident, binding)) = self + .move_expr_bindings + .last() + .and_then(|bindings| bindings.get(&e.id).copied()) + { + hir::ExprKind::Path(hir::QPath::Resolved( + None, + self.arena.alloc(hir::Path { + span: self.lower_span(e.span), + res: Res::Local(binding), + segments: arena_vec![ + self; + hir::PathSegment::new( + self.lower_ident(ident), + self.next_id(), + Res::Local(binding), + ) + ], + }), + )) + } else { + self.dcx().emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span }); + hir::ExprKind::Err( + self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), + ) + } } ExprKind::Use(expr, use_kw_span) => self.lower_expr_use(*use_kw_span, expr), - ExprKind::Closure(Closure { - binder, - capture_clause, - constness, - coroutine_kind, - movability, - fn_decl, - body, - fn_decl_span, - fn_arg_span, - }) => match coroutine_kind { - Some(coroutine_kind) => self.lower_expr_coroutine_closure( - binder, - *capture_clause, - e.id, - expr_hir_id, - *coroutine_kind, - *constness, - fn_decl, - body, - *fn_decl_span, - *fn_arg_span, - ), - None => self.lower_expr_closure( - attrs, - binder, - *capture_clause, - e.id, - *constness, - *movability, - fn_decl, - body, - *fn_decl_span, - *fn_arg_span, - ), - }, ExprKind::Gen(capture_clause, block, genblock_kind, decl_span) => { let desugaring_kind = match genblock_kind { GenBlockKind::Async => hir::CoroutineDesugaring::Async, @@ -394,7 +417,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr), - ExprKind::Paren(_) | ExprKind::ForLoop { .. } => { + ExprKind::Paren(_) | ExprKind::ForLoop { .. } | ExprKind::Closure(..) => { unreachable!("already handled") } @@ -795,6 +818,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span: None, kind: hir::ClosureKind::Coroutine(coroutine_kind), constness: hir::Constness::NotConst, + explicit_captures: &[], })) } @@ -1058,6 +1082,134 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span)) } + fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> { + let expr_hir_id = self.lower_node_id(e.id); + let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); + + match closure.coroutine_kind { + Some(coroutine_kind) => hir::Expr { + hir_id: expr_hir_id, + kind: self.lower_expr_coroutine_closure( + &closure.binder, + closure.capture_clause, + e.id, + expr_hir_id, + coroutine_kind, + closure.constness, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + ), + span: self.lower_span(e.span), + }, + None => self.lower_expr_plain_closure_with_move_exprs( + expr_hir_id, + attrs, + &closure.binder, + closure.capture_clause, + e.id, + closure.constness, + closure.movability, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + e.span, + ), + } + } + + fn lower_expr_plain_closure_with_move_exprs( + &mut self, + expr_hir_id: HirId, + attrs: &[rustc_hir::Attribute], + binder: &ClosureBinder, + capture_clause: CaptureBy, + closure_id: NodeId, + constness: Const, + movability: Movability, + decl: &FnDecl, + body: &Expr, + fn_decl_span: Span, + fn_arg_span: Span, + whole_span: Span, + ) -> hir::Expr<'hir> { + let occurrences = MoveExprCollector::collect(body); + if occurrences.is_empty() { + return hir::Expr { + hir_id: expr_hir_id, + kind: self.lower_expr_closure( + attrs, + binder, + capture_clause, + closure_id, + constness, + movability, + decl, + body, + fn_decl_span, + fn_arg_span, + &[], + ), + span: self.lower_span(whole_span), + }; + } + + let mut bindings = NodeMap::default(); + let mut lowered_occurrences = Vec::with_capacity(occurrences.len()); + for (index, occurrence) in occurrences.iter().enumerate() { + let ident = + Ident::from_str_and_span(&format!("__move_expr_{index}"), occurrence.move_kw_span); + let (pat, binding) = self.pat_ident(occurrence.expr.span, ident); + bindings.insert(occurrence.id, (ident, binding)); + lowered_occurrences.push((occurrence, pat, binding)); + } + + self.move_expr_bindings.push(bindings); + let mut stmts = Vec::with_capacity(lowered_occurrences.len()); + for (occurrence, pat, _) in &lowered_occurrences { + let init = self.lower_expr(occurrence.expr); + stmts.push(self.stmt_let_pat( + None, + occurrence.expr.span, + Some(init), + *pat, + hir::LocalSource::Normal, + )); + } + + let explicit_captures = self.arena.alloc_from_iter(lowered_occurrences.iter().map( + |(occurrence, _, binding)| hir::ExplicitCapture { + var_hir_id: *binding, + origin_span: self.lower_span(occurrence.move_kw_span), + }, + )); + + let closure_expr = self.arena.alloc(hir::Expr { + hir_id: expr_hir_id, + kind: self.lower_expr_closure( + attrs, + binder, + capture_clause, + closure_id, + constness, + movability, + decl, + body, + fn_decl_span, + fn_arg_span, + explicit_captures, + ), + span: self.lower_span(whole_span), + }); + self.move_expr_bindings.pop(); + + let stmts = self.arena.alloc_from_iter(stmts); + let block = self.block_all(whole_span, stmts, Some(closure_expr)); + self.expr(whole_span, hir::ExprKind::Block(block, None)) + } + fn lower_expr_closure( &mut self, attrs: &[rustc_hir::Attribute], @@ -1070,6 +1222,7 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Expr, fn_decl_span: Span, fn_arg_span: Span, + explicit_captures: &'hir [hir::ExplicitCapture], ) -> hir::ExprKind<'hir> { let closure_def_id = self.local_def_id(closure_id); let (binder_clause, generic_params) = self.lower_closure_binder(binder); @@ -1105,6 +1258,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span: Some(self.lower_span(fn_arg_span)), kind: closure_kind, constness: self.lower_constness(constness), + explicit_captures, }); hir::ExprKind::Closure(c) @@ -1226,7 +1380,8 @@ impl<'hir> LoweringContext<'_, 'hir> { // knows that a `FnDecl` output type like `-> &str` actually means // "coroutine that returns &str", rather than directly returning a `&str`. kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring), - constness: self.lower_constness(constness), + constness: hir::Constness::NotConst, + explicit_captures: &[], }); hir::ExprKind::Closure(c) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 1ce4478c09e8b..3af3e1b0e544a 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -158,6 +158,7 @@ struct LoweringContext<'a, 'hir> { allow_async_fn_traits: Arc<[Symbol]>, delayed_lints: Vec, + move_expr_bindings: Vec>, attribute_parser: AttributeParser<'hir>, } @@ -216,6 +217,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // interact with `gen`/`async gen` blocks allow_async_iterator: [sym::gen_future, sym::async_iterator].into(), + move_expr_bindings: Vec::new(), attribute_parser: AttributeParser::new( tcx.sess, tcx.features(), diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 2f18b09cf1ae8..668367e066a40 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1682,6 +1682,13 @@ pub struct Closure<'hir> { /// The span of the argument block `|...|` pub fn_arg_span: Option, pub kind: ClosureKind, + pub explicit_captures: &'hir [ExplicitCapture], +} + +#[derive(Debug, Clone, Copy, HashStable_Generic)] +pub struct ExplicitCapture { + pub var_hir_id: HirId, + pub origin_span: Span, } #[derive(Clone, PartialEq, Eq, Debug, Copy, Hash, StableHash, Encodable, Decodable)] diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 68d8c12ec099c..85f0e97a0a9d6 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -893,6 +893,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) fn_arg_span: _, kind: _, constness: _, + explicit_captures: _, }) => { walk_list!(visitor, visit_generic_param, bound_generic_params); try_visit!(visitor.visit_fn(FnKind::Closure, fn_decl, body, *span, def_id)); diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 1a401af1d328d..637ae115131a1 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1645,6 +1645,7 @@ impl<'a> State<'a> { fn_arg_span: _, kind: _, def_id: _, + explicit_captures: _, }) => { self.print_closure_binder(binder, bound_generic_params); self.print_constness(constness); diff --git a/tests/ui/move-expr/outside-plain-closure.rs b/tests/ui/move-expr/outside-plain-closure.rs new file mode 100644 index 0000000000000..c4aa6551119fe --- /dev/null +++ b/tests/ui/move-expr/outside-plain-closure.rs @@ -0,0 +1,7 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let _ = move(String::from("nope")); + //~^ ERROR `move(expr)` is only supported in plain closures +} diff --git a/tests/ui/move-expr/outside-plain-closure.stderr b/tests/ui/move-expr/outside-plain-closure.stderr new file mode 100644 index 0000000000000..68c4223641304 --- /dev/null +++ b/tests/ui/move-expr/outside-plain-closure.stderr @@ -0,0 +1,8 @@ +error: `move(expr)` is only supported in plain closures + --> $DIR/outside-plain-closure.rs:5:13 + | +LL | let _ = move(String::from("nope")); + | ^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/move-expr/plain-closure.rs b/tests/ui/move-expr/plain-closure.rs new file mode 100644 index 0000000000000..1047425b2d003 --- /dev/null +++ b/tests/ui/move-expr/plain-closure.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = String::from("hello"); + let c = || { + let t = move(s); + println!("{}", t.len()); + }; + c(); +} From 0349562e92595f888ad12f2fcdfc39acf8bd2b66 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 21:20:20 +0900 Subject: [PATCH 06/36] force move(expr) captures to ByValue --- compiler/rustc_hir_typeck/src/upvar.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index dfdc7c3d8df0a..1d57a71b104d0 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -208,6 +208,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let _ = euv::ExprUseVisitor::new(&closure_fcx, &mut delegate).consume_body(body); + let explicit_captures = match self.tcx.hir_node(closure_hir_id).expect_expr().kind { + hir::ExprKind::Closure(closure) => closure.explicit_captures, + _ => bug!("expected closure expr for {:?}", closure_hir_id), + }; + for capture in explicit_captures { + let place = closure_fcx.place_for_root_variable(closure_def_id, capture.var_hir_id); + delegate.capture_information.push(( + place, + ty::CaptureInfo { + capture_kind_expr_id: Some(capture.var_hir_id), + path_expr_id: Some(capture.var_hir_id), + capture_kind: UpvarCapture::ByValue, + }, + )); + } + // There are several curious situations with coroutine-closures where // analysis is too aggressive with borrows when the coroutine-closure is // marked `move`. Specifically: From 7fb00e119175841af9ad0363e67aba3a07fa6dab Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 22:26:35 +0900 Subject: [PATCH 07/36] support `ast::ExprKind::Move` in clippy --- src/tools/clippy/clippy_utils/src/sugg.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 92f08b604ca58..f194103ae2e88 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -231,6 +231,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Loop(..) | ast::ExprKind::MacCall(..) | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Move(..) | ast::ExprKind::Paren(..) | ast::ExprKind::Underscore | ast::ExprKind::Path(..) From fa706711f741a05c3214b538fef12712b800d6e1 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 9 Apr 2026 23:12:40 +0900 Subject: [PATCH 08/36] support `ast::ExprKind::Move` in rustfmt --- src/tools/rustfmt/src/expr.rs | 4 ++++ src/tools/rustfmt/src/utils.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index d34706a2ba5cd..daffc215c621c 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -125,6 +125,10 @@ pub(crate) fn format_expr( let callee_str = callee.rewrite_result(context, shape)?; rewrite_call(context, &callee_str, args, inner_span, shape) } + ast::ExprKind::Move(ref subexpr, move_kw_span) => { + let inner_span = mk_sp(move_kw_span.hi(), expr.span.hi()); + rewrite_call(context, "move", std::slice::from_ref(subexpr), inner_span, shape) + } ast::ExprKind::Paren(ref subexpr) => rewrite_paren(context, subexpr, shape, expr.span), ast::ExprKind::Binary(op, ref lhs, ref rhs) => { // FIXME: format comments between operands and operator diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index b052e74d8bf20..de72c9ce14bc3 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -553,6 +553,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr | ast::ExprKind::Field(..) | ast::ExprKind::IncludedBytes(..) | ast::ExprKind::InlineAsm(..) + | ast::ExprKind::Move(..) | ast::ExprKind::OffsetOf(..) | ast::ExprKind::UnsafeBinderCast(..) | ast::ExprKind::Let(..) From 1c2edccdbfc0600019ae5d6728cff37155fd33a1 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 16 Apr 2026 21:24:46 +0900 Subject: [PATCH 09/36] fix tidy errors replace TODO with FIXME --- compiler/rustc_ast_lowering/src/expr.rs | 29 +++++++++---------- compiler/rustc_feature/src/unstable.rs | 4 +-- compiler/rustc_hir/src/hir.rs | 1 - compiler/rustc_hir_typeck/src/upvar.rs | 4 +-- src/tools/rustfmt/src/expr.rs | 8 ++++- tests/ui/README.md | 4 +++ .../feature-gate-move_expr.stderr | 1 + 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index a5d07f02197a9..9d29a1037b60a 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -250,10 +250,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr), ExprKind::Move(_, move_kw_span) => { if !self.tcx.features().move_expr() { - return self.expr_err( - *move_kw_span, - self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), - ); + return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap()); } if let Some((ident, binding)) = self .move_expr_bindings @@ -276,10 +273,10 @@ impl<'hir> LoweringContext<'_, 'hir> { }), )) } else { - self.dcx().emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span }); - hir::ExprKind::Err( - self.dcx().span_delayed_bug(*move_kw_span, "invalid move(expr)"), - ) + let guar = self + .dcx() + .emit_err(MoveExprOnlyInPlainClosures { span: *move_kw_span }); + hir::ExprKind::Err(guar) } } ExprKind::Use(expr, use_kw_span) => self.lower_expr_use(*use_kw_span, expr), @@ -1087,6 +1084,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); match closure.coroutine_kind { + // FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too. + // For the first step, we only support plain closures. Some(coroutine_kind) => hir::Expr { hir_id: expr_hir_id, kind: self.lower_expr_coroutine_closure( @@ -1179,12 +1178,11 @@ impl<'hir> LoweringContext<'_, 'hir> { )); } - let explicit_captures = self.arena.alloc_from_iter(lowered_occurrences.iter().map( - |(occurrence, _, binding)| hir::ExplicitCapture { - var_hir_id: *binding, - origin_span: self.lower_span(occurrence.move_kw_span), - }, - )); + let explicit_captures = self.arena.alloc_from_iter( + lowered_occurrences + .iter() + .map(|(_, _, binding)| hir::ExplicitCapture { var_hir_id: *binding }), + ); let closure_expr = self.arena.alloc(hir::Expr { hir_id: expr_hir_id, @@ -1380,9 +1378,10 @@ impl<'hir> LoweringContext<'_, 'hir> { // knows that a `FnDecl` output type like `-> &str` actually means // "coroutine that returns &str", rather than directly returning a `&str`. kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring), - constness: hir::Constness::NotConst, + constness: self.lower_constness(constness), explicit_captures: &[], }); + hir::ExprKind::Closure(c) } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 93fc3e0478b6a..17f84154b9fff 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -638,6 +638,8 @@ declare_features! ( (unstable, mips_target_feature, "1.27.0", Some(150253)), /// Allows qualified paths in struct expressions, struct patterns and tuple struct patterns. (unstable, more_qualified_paths, "1.54.0", Some(86935)), + /// Allows `move(expr)` in closures. + (incomplete, move_expr, "CURRENT_RUSTC_VERSION", Some(155050)), /// The `movrs` target feature on x86. (unstable, movrs_target_feature, "1.88.0", Some(137976)), /// Allows the `multiple_supertrait_upcastable` lint. @@ -646,8 +648,6 @@ declare_features! ( (unstable, must_not_suspend, "1.57.0", Some(83310)), /// Allows `mut ref` and `mut ref mut` identifier patterns. (incomplete, mut_ref, "1.79.0", Some(123076)), - /// Allows `move(expr)` in closures. - (incomplete, move_expr, "CURRENT_RUSTC_VERSION", None), /// Allows using `#[naked]` on `extern "Rust"` functions. (unstable, naked_functions_rustic_abi, "1.88.0", Some(138997)), /// Allows using `#[target_feature(enable = "...")]` on `#[naked]` on functions. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 668367e066a40..1c1a268462832 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1688,7 +1688,6 @@ pub struct Closure<'hir> { #[derive(Debug, Clone, Copy, HashStable_Generic)] pub struct ExplicitCapture { pub var_hir_id: HirId, - pub origin_span: Span, } #[derive(Clone, PartialEq, Eq, Debug, Copy, Hash, StableHash, Encodable, Decodable)] diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 1d57a71b104d0..15af428d8ec49 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -217,8 +217,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { delegate.capture_information.push(( place, ty::CaptureInfo { - capture_kind_expr_id: Some(capture.var_hir_id), - path_expr_id: Some(capture.var_hir_id), + capture_kind_expr_id: Some(closure_hir_id), + path_expr_id: Some(closure_hir_id), capture_kind: UpvarCapture::ByValue, }, )); diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index daffc215c621c..8a3674bff1ca6 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -127,7 +127,13 @@ pub(crate) fn format_expr( } ast::ExprKind::Move(ref subexpr, move_kw_span) => { let inner_span = mk_sp(move_kw_span.hi(), expr.span.hi()); - rewrite_call(context, "move", std::slice::from_ref(subexpr), inner_span, shape) + rewrite_call( + context, + "move", + std::slice::from_ref(subexpr), + inner_span, + shape, + ) } ast::ExprKind::Paren(ref subexpr) => rewrite_paren(context, subexpr, shape, expr.span), ast::ExprKind::Binary(op, ref lhs, ref rhs) => { diff --git a/tests/ui/README.md b/tests/ui/README.md index 2fe1657e7ecf2..1ab22a65ce507 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -927,6 +927,10 @@ Tests on the module system. **FIXME**: `tests/ui/imports/` should probably be merged with this. +## `tests/ui/move-expr/` + +Tests for `#![feature(move_expr)]`. + ## `tests/ui/moves` Tests on moves (destructive moves). diff --git a/tests/ui/feature-gates/feature-gate-move_expr.stderr b/tests/ui/feature-gates/feature-gate-move_expr.stderr index 28ab95ababc16..8b1da2c06893d 100644 --- a/tests/ui/feature-gates/feature-gate-move_expr.stderr +++ b/tests/ui/feature-gates/feature-gate-move_expr.stderr @@ -4,6 +4,7 @@ error[E0658]: `move(expr)` syntax is experimental LL | let _ = || move(2); | ^^^^ | + = note: see issue #155050 for more information = help: add `#![feature(move_expr)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date From 5ea6b0fe4620970e827ac98434004db00413d6b3 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 16 Apr 2026 21:25:35 +0900 Subject: [PATCH 10/36] add ui tests for move expr --- tests/ui/move-expr/borrow-only.rs | 13 +++++++++++ tests/ui/move-expr/capture-reference.rs | 15 +++++++++++++ tests/ui/move-expr/capture-reference.stderr | 17 ++++++++++++++ tests/ui/move-expr/copy-type.rs | 15 +++++++++++++ tests/ui/move-expr/double-move.rs | 13 +++++++++++ tests/ui/move-expr/double-move.stderr | 23 +++++++++++++++++++ tests/ui/move-expr/move-fnonce.rs | 14 ++++++++++++ tests/ui/move-expr/move-fnonce.stderr | 22 ++++++++++++++++++ tests/ui/move-expr/name-resolution.rs | 13 +++++++++++ tests/ui/move-expr/nested-closures.rs | 15 +++++++++++++ tests/ui/move-expr/use-after-move.rs | 12 ++++++++++ tests/ui/move-expr/use-after-move.stderr | 25 +++++++++++++++++++++ 12 files changed, 197 insertions(+) create mode 100644 tests/ui/move-expr/borrow-only.rs create mode 100644 tests/ui/move-expr/capture-reference.rs create mode 100644 tests/ui/move-expr/capture-reference.stderr create mode 100644 tests/ui/move-expr/copy-type.rs create mode 100644 tests/ui/move-expr/double-move.rs create mode 100644 tests/ui/move-expr/double-move.stderr create mode 100644 tests/ui/move-expr/move-fnonce.rs create mode 100644 tests/ui/move-expr/move-fnonce.stderr create mode 100644 tests/ui/move-expr/name-resolution.rs create mode 100644 tests/ui/move-expr/nested-closures.rs create mode 100644 tests/ui/move-expr/use-after-move.rs create mode 100644 tests/ui/move-expr/use-after-move.stderr diff --git a/tests/ui/move-expr/borrow-only.rs b/tests/ui/move-expr/borrow-only.rs new file mode 100644 index 0000000000000..f5c6ba228cfb7 --- /dev/null +++ b/tests/ui/move-expr/borrow-only.rs @@ -0,0 +1,13 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = vec![1, 2, 3]; + let c = || { + let t = &move(s); + println!("{t:?}"); + }; + + c(); +} diff --git a/tests/ui/move-expr/capture-reference.rs b/tests/ui/move-expr/capture-reference.rs new file mode 100644 index 0000000000000..a4c2caee57052 --- /dev/null +++ b/tests/ui/move-expr/capture-reference.rs @@ -0,0 +1,15 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let c = { + let x = 22; + || { + let y = move(&x); + //~^ ERROR `x` does not live long enough + println!("{y:?}"); + } + }; + + c(); +} diff --git a/tests/ui/move-expr/capture-reference.stderr b/tests/ui/move-expr/capture-reference.stderr new file mode 100644 index 0000000000000..8b970e98d6bdb --- /dev/null +++ b/tests/ui/move-expr/capture-reference.stderr @@ -0,0 +1,17 @@ +error[E0597]: `x` does not live long enough + --> $DIR/capture-reference.rs:8:26 + | +LL | let c = { + | - borrow later captured here by closure +LL | let x = 22; + | - binding `x` declared here +LL | || { +LL | let y = move(&x); + | ^^ borrowed value does not live long enough +... +LL | }; + | - `x` dropped here while still borrowed + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/move-expr/copy-type.rs b/tests/ui/move-expr/copy-type.rs new file mode 100644 index 0000000000000..4cc790f7103ff --- /dev/null +++ b/tests/ui/move-expr/copy-type.rs @@ -0,0 +1,15 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = 22; + let c = || { + let y = move(x); + let z = x; + assert_eq!(y + z, 44); + }; + + c(); + c(); +} diff --git a/tests/ui/move-expr/double-move.rs b/tests/ui/move-expr/double-move.rs new file mode 100644 index 0000000000000..2d4beb2246e1e --- /dev/null +++ b/tests/ui/move-expr/double-move.rs @@ -0,0 +1,13 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = vec![1, 2, 3]; + let _c = || { + let y = move(x); + let z = move(x); + //~^ ERROR use of moved value: `x` + drop(y); + drop(z); + }; +} diff --git a/tests/ui/move-expr/double-move.stderr b/tests/ui/move-expr/double-move.stderr new file mode 100644 index 0000000000000..ddb01814f7260 --- /dev/null +++ b/tests/ui/move-expr/double-move.stderr @@ -0,0 +1,23 @@ +error[E0382]: use of moved value: `x` + --> $DIR/double-move.rs:8:22 + | +LL | let x = vec![1, 2, 3]; + | - move occurs because `x` has type `Vec`, which does not implement the `Copy` trait +LL | let _c = || { +LL | let y = move(x); + | - value moved here +LL | let z = move(x); + | ^ value used here after move + | +help: consider cloning the value if the performance cost is acceptable + | +LL | let y = move(x.clone()); + | ++++++++ +help: borrow this binding in the pattern to avoid moving the value + | +LL | let y = move(ref x); + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/move-expr/move-fnonce.rs b/tests/ui/move-expr/move-fnonce.rs new file mode 100644 index 0000000000000..a9d44cad19502 --- /dev/null +++ b/tests/ui/move-expr/move-fnonce.rs @@ -0,0 +1,14 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = vec![1, 2, 3]; + let mut c = || { + let t = move(s); + println!("{t:?}"); + }; + + c(); + c(); + //~^ ERROR use of moved value: `c` +} diff --git a/tests/ui/move-expr/move-fnonce.stderr b/tests/ui/move-expr/move-fnonce.stderr new file mode 100644 index 0000000000000..635b3cd6a4fa2 --- /dev/null +++ b/tests/ui/move-expr/move-fnonce.stderr @@ -0,0 +1,22 @@ +error[E0382]: use of moved value: `c` + --> $DIR/move-fnonce.rs:12:5 + | +LL | c(); + | --- `c` moved due to this call +LL | c(); + | ^ value used here after move + | +note: closure cannot be invoked more than once because it moves the variable `__move_expr_0` out of its environment + --> $DIR/move-fnonce.rs:7:17 + | +LL | let t = move(s); + | ^^^^^^^ +note: this value implements `FnOnce`, which causes it to be moved when called + --> $DIR/move-fnonce.rs:11:5 + | +LL | c(); + | ^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/move-expr/name-resolution.rs b/tests/ui/move-expr/name-resolution.rs new file mode 100644 index 0000000000000..7c0886183eb36 --- /dev/null +++ b/tests/ui/move-expr/name-resolution.rs @@ -0,0 +1,13 @@ +//@ 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. +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let _c = || { + let x = 3; + move(x); + }; +} diff --git a/tests/ui/move-expr/nested-closures.rs b/tests/ui/move-expr/nested-closures.rs new file mode 100644 index 0000000000000..b472dca4c0024 --- /dev/null +++ b/tests/ui/move-expr/nested-closures.rs @@ -0,0 +1,15 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = String::from("hello"); + let outer = || { + let inner = || move(x.clone()); + let y = inner(); + assert_eq!(y, "hello"); + assert_eq!(x, "hello"); + }; + + outer(); +} diff --git a/tests/ui/move-expr/use-after-move.rs b/tests/ui/move-expr/use-after-move.rs new file mode 100644 index 0000000000000..69f2bf35125df --- /dev/null +++ b/tests/ui/move-expr/use-after-move.rs @@ -0,0 +1,12 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x = vec![1, 2, 3]; + let _c = || { + //~^ ERROR borrow of moved value: `x` + let y = move(x); + println!("{x:?}"); + drop(y); + }; +} diff --git a/tests/ui/move-expr/use-after-move.stderr b/tests/ui/move-expr/use-after-move.stderr new file mode 100644 index 0000000000000..4b97f812815c0 --- /dev/null +++ b/tests/ui/move-expr/use-after-move.stderr @@ -0,0 +1,25 @@ +error[E0382]: borrow of moved value: `x` + --> $DIR/use-after-move.rs:6:14 + | +LL | let x = vec![1, 2, 3]; + | - move occurs because `x` has type `Vec`, which does not implement the `Copy` trait +LL | let _c = || { + | ^^ value borrowed here after move +LL | +LL | let y = move(x); + | - value moved here +LL | println!("{x:?}"); + | - borrow occurs due to use in closure + | +help: consider cloning the value if the performance cost is acceptable + | +LL | let y = move(x.clone()); + | ++++++++ +help: borrow this binding in the pattern to avoid moving the value + | +LL | let y = move(ref x); + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. From ee5a270c81449f1c010dac79bfeffce391e1564f Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 16 Apr 2026 21:49:10 +0900 Subject: [PATCH 11/36] use pre-expansion feature gate, `gate_all!` instead --- compiler/rustc_ast_passes/src/feature_gate.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index b0679487b8ebb..b43dbad611a0e 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -352,9 +352,6 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } _ => (), }, - ast::ExprKind::Move(_, move_kw_span) => { - gate!(&self, move_expr, move_kw_span, "`move(expr)` syntax is experimental"); - } _ => {} } visit::walk_expr(self, e) @@ -506,6 +503,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(impl_restriction, "`impl` restrictions are experimental"); gate_all!(min_generic_const_args, "unbraced const blocks as const args are experimental"); gate_all!(more_qualified_paths, "usage of qualified paths in this context is experimental"); + gate_all!(move_expr, "`move(expr)` syntax is experimental"); gate_all!(mut_ref, "mutable by-reference bindings are experimental"); gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(postfix_match, "postfix match is experimental"); From f848f32a9fd49eb100071dfb13efa120d6ea21b5 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 30 Apr 2026 15:22:46 +0900 Subject: [PATCH 12/36] use ExprUseVisitor delegate for explicit move captures --- compiler/rustc_hir_typeck/src/upvar.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 15af428d8ec49..5911f1c9e26ef 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -54,6 +54,7 @@ use tracing::{debug, instrument}; use super::FnCtxt; use crate::expr_use_visitor as euv; +use crate::expr_use_visitor::Delegate as _; /// Describe the relationship between the paths of two places /// eg: @@ -214,14 +215,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; for capture in explicit_captures { let place = closure_fcx.place_for_root_variable(closure_def_id, capture.var_hir_id); - delegate.capture_information.push(( - place, - ty::CaptureInfo { - capture_kind_expr_id: Some(closure_hir_id), - path_expr_id: Some(closure_hir_id), - capture_kind: UpvarCapture::ByValue, - }, - )); + delegate.consume(&PlaceWithHirId { hir_id: capture.var_hir_id, place }, closure_hir_id); } // There are several curious situations with coroutine-closures where From cf937b8da2a273199b22f4a45e65e73a320f9128 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 30 Apr 2026 15:58:58 +0900 Subject: [PATCH 13/36] test multiple move expressions in one closure --- tests/ui/move-expr/plain-closure.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/ui/move-expr/plain-closure.rs b/tests/ui/move-expr/plain-closure.rs index 1047425b2d003..2ec01e35569b4 100644 --- a/tests/ui/move-expr/plain-closure.rs +++ b/tests/ui/move-expr/plain-closure.rs @@ -9,4 +9,13 @@ fn main() { println!("{}", t.len()); }; c(); + + let a = String::from("hello"); + let b = String::from("world"); + let c = || { + let x = move(a); + let y = move(b); + println!("{} {}", x, y); + }; + c(); } From aac8bd23bb51624dc845f060fd2117e3d75af86b Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 30 Apr 2026 15:59:38 +0900 Subject: [PATCH 14/36] add move expression parser ambiguity tests --- tests/ui/move-expr/parse-ambiguity-errors.rs | 14 ++++++++++++++ tests/ui/move-expr/parse-ambiguity-errors.stderr | 14 ++++++++++++++ tests/ui/move-expr/parse-ambiguity.rs | 13 +++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 tests/ui/move-expr/parse-ambiguity-errors.rs create mode 100644 tests/ui/move-expr/parse-ambiguity-errors.stderr create mode 100644 tests/ui/move-expr/parse-ambiguity.rs diff --git a/tests/ui/move-expr/parse-ambiguity-errors.rs b/tests/ui/move-expr/parse-ambiguity-errors.rs new file mode 100644 index 0000000000000..c2927373cb8a7 --- /dev/null +++ b/tests/ui/move-expr/parse-ambiguity-errors.rs @@ -0,0 +1,14 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x: bool = true; + let y: bool = true; + let _ = move(x) || y; + //~^ ERROR `move(expr)` is only supported in plain closures + + let x: bool = true; + let y: bool = true; + let _ = move[x] || y; + //~^ ERROR expected one of +} diff --git a/tests/ui/move-expr/parse-ambiguity-errors.stderr b/tests/ui/move-expr/parse-ambiguity-errors.stderr new file mode 100644 index 0000000000000..c4dc929eac36c --- /dev/null +++ b/tests/ui/move-expr/parse-ambiguity-errors.stderr @@ -0,0 +1,14 @@ +error: expected one of `async`, `|`, or `||`, found `[` + --> $DIR/parse-ambiguity-errors.rs:12:17 + | +LL | let _ = move[x] || y; + | ^ expected one of `async`, `|`, or `||` + +error: `move(expr)` is only supported in plain closures + --> $DIR/parse-ambiguity-errors.rs:7:13 + | +LL | let _ = move(x) || y; + | ^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/move-expr/parse-ambiguity.rs b/tests/ui/move-expr/parse-ambiguity.rs new file mode 100644 index 0000000000000..bfe4b82b2b0ff --- /dev/null +++ b/tests/ui/move-expr/parse-ambiguity.rs @@ -0,0 +1,13 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let x: bool = true; + let y: bool = true; + let _ = || move(x) || y; + + let x: bool = true; + let y: bool = true; + let _ = move || move(x) || y; +} From fc6f09e19b9d6792f02f745edda0e591f8ae03df Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 30 Apr 2026 16:25:40 +0900 Subject: [PATCH 15/36] document move expression lowering bindings --- compiler/rustc_ast_lowering/src/expr.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 9d29a1037b60a..fc88b70267152 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -252,6 +252,9 @@ impl<'hir> LoweringContext<'_, 'hir> { if !self.tcx.features().move_expr() { return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap()); } + // `last()` selects the binding map for the closure body currently + // being lowered. The map is keyed by the AST `NodeId`, so `e.id` + // selects the synthetic local for this exact `move(...)` occurrence. if let Some((ident, binding)) = self .move_expr_bindings .last() @@ -1165,6 +1168,9 @@ impl<'hir> LoweringContext<'_, 'hir> { lowered_occurrences.push((occurrence, pat, binding)); } + // During body lowering, replace each `move(...)` occurrence with the + // synthetic local recorded in this closure's binding map. Nested closures + // push their own maps. self.move_expr_bindings.push(bindings); let mut stmts = Vec::with_capacity(lowered_occurrences.len()); for (occurrence, pat, _) in &lowered_occurrences { @@ -1201,6 +1207,8 @@ impl<'hir> LoweringContext<'_, 'hir> { ), span: self.lower_span(whole_span), }); + // Restore the enclosing closure's substitution map before lowering the + // block that contains the synthetic `let`s. self.move_expr_bindings.pop(); let stmts = self.arena.alloc_from_iter(stmts); From 01589557335237013d33092fddc1b350c58901e7 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 30 Apr 2026 17:44:28 +0900 Subject: [PATCH 16/36] document move expression lowering and capture flow document move expression lowering flow --- compiler/rustc_ast_lowering/src/expr.rs | 40 ++++++++++++++++++++++++- compiler/rustc_ast_lowering/src/lib.rs | 3 ++ compiler/rustc_hir/src/hir.rs | 2 ++ compiler/rustc_hir_typeck/src/upvar.rs | 7 +++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index fc88b70267152..49df2ddcbf3e1 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -31,12 +31,22 @@ use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScop pub(super) struct WillCreateDefIdsVisitor; +/// A `move(...)` expression found while scanning a plain closure body. struct MoveExprOccurrence<'a> { + /// The `NodeId` of the outer `move(...)` expression. id: NodeId, + /// Span of the `move` token, used for the generated binding name. move_kw_span: Span, + /// The expression inside `move(...)`; e.g. `foo.bar` in `move(foo.bar)`. expr: &'a Expr, } +/// Collects the `move(...)` expressions that belong to one plain closure body. +/// +/// For `|| move(foo.bar).clone()`, this records the outer `move(foo.bar)` +/// occurrence and the inner expression `foo.bar`. Nested closures, generators, +/// const blocks, and items are lowered as separate bodies, so this visitor does +/// not collect `move(...)` expressions from them. struct MoveExprCollector<'a> { occurrences: Vec>, } @@ -53,6 +63,8 @@ impl<'a> Visitor<'a> for MoveExprCollector<'a> { fn visit_expr(&mut self, expr: &'a Expr) { match &expr.kind { ExprKind::Move(inner, move_kw_span) => { + // For `move(foo.bar)`, first collect any nested `move(...)` + // expressions in `foo.bar`, then record this outer occurrence. self.visit_expr(inner); self.occurrences.push(MoveExprOccurrence { id: expr.id, @@ -64,6 +76,8 @@ impl<'a> Visitor<'a> for MoveExprCollector<'a> { _ => walk_expr(self, expr), } } + + fn visit_item(&mut self, _: &'a Item) {} } impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor { @@ -1082,6 +1096,8 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span)) } + // Lowers closure expressions, including the `move(...)` desugaring for + // plain closures. fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> { let expr_hir_id = self.lower_node_id(e.id); let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); @@ -1137,8 +1153,18 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span: Span, whole_span: Span, ) -> hir::Expr<'hir> { + // `move(...)` evaluates its inner expression when the closure is created + // and captures the result by value. For example: + // + // `|| move(foo).bar` + // + // is lowered roughly as: + // + // `let __move_expr_0 = foo; || __move_expr_0.bar` let occurrences = MoveExprCollector::collect(body); if occurrences.is_empty() { + // No `move(...)` expressions in this closure body; lower the closure + // normally, with no explicit captures. return hir::Expr { hir_id: expr_hir_id, kind: self.lower_expr_closure( @@ -1161,6 +1187,9 @@ impl<'hir> LoweringContext<'_, 'hir> { let mut bindings = NodeMap::default(); let mut lowered_occurrences = Vec::with_capacity(occurrences.len()); for (index, occurrence) in occurrences.iter().enumerate() { + // Create one synthetic local per `move(...)` expression and remember + // which AST node should be replaced by that local while lowering the + // closure body. let ident = Ident::from_str_and_span(&format!("__move_expr_{index}"), occurrence.move_kw_span); let (pat, binding) = self.pat_ident(occurrence.expr.span, ident); @@ -1174,6 +1203,10 @@ impl<'hir> LoweringContext<'_, 'hir> { self.move_expr_bindings.push(bindings); let mut stmts = Vec::with_capacity(lowered_occurrences.len()); for (occurrence, pat, _) in &lowered_occurrences { + // Evaluate the expression inside `move(...)` before creating the + // closure and store it in a synthetic local: + // `|| move(foo).bar` becomes roughly + // `let __move_expr_0 = foo; || __move_expr_0.bar`. let init = self.lower_expr(occurrence.expr); stmts.push(self.stmt_let_pat( None, @@ -1187,9 +1220,15 @@ impl<'hir> LoweringContext<'_, 'hir> { let explicit_captures = self.arena.alloc_from_iter( lowered_occurrences .iter() + // Force the generated locals to be captured by value even if + // the lowered closure body only borrows them, as in + // `move(foo).clone()`. .map(|(_, _, binding)| hir::ExplicitCapture { var_hir_id: *binding }), ); + // Lower the closure itself while `move_expr_bindings` contains this + // closure's substitutions, so each `move(...)` in the body is replaced + // with its generated local. let closure_expr = self.arena.alloc(hir::Expr { hir_id: expr_hir_id, kind: self.lower_expr_closure( @@ -1389,7 +1428,6 @@ impl<'hir> LoweringContext<'_, 'hir> { constness: self.lower_constness(constness), explicit_captures: &[], }); - hir::ExprKind::Closure(c) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 3af3e1b0e544a..43c24c6a61268 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -158,6 +158,9 @@ struct LoweringContext<'a, 'hir> { allow_async_fn_traits: Arc<[Symbol]>, delayed_lints: Vec, + /// Stack of per-closure `move(...)` substitution maps. Each map is keyed by + /// the AST `NodeId` of a `move(...)` occurrence and points to the synthetic + /// local used while lowering that closure body. move_expr_bindings: Vec>, attribute_parser: AttributeParser<'hir>, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 1c1a268462832..299b045370c9e 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1685,6 +1685,8 @@ pub struct Closure<'hir> { pub explicit_captures: &'hir [ExplicitCapture], } +/// A HIR local that must be captured by value even if ordinary closure capture +/// analysis would infer a weaker capture kind from its uses in the body. #[derive(Debug, Clone, Copy, HashStable_Generic)] pub struct ExplicitCapture { pub var_hir_id: HirId, diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 5911f1c9e26ef..25b5540457f19 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -207,8 +207,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fake_reads: Default::default(), }; + // First collect the captures implied by the operations in the closure + // body. This records how each place is actually used: borrowed, modified, + // moved, and so on. let _ = euv::ExprUseVisitor::new(&closure_fcx, &mut delegate).consume_body(body); + // `consume_body` only sees how the lowered closure body uses those + // places. For `move(foo).clone()`, the body may only borrow the + // synthetic local for `foo`, but the source `move(...)` still requires + // capturing that local by value. let explicit_captures = match self.tcx.hir_node(closure_hir_id).expect_expr().kind { hir::ExprKind::Closure(closure) => closure.explicit_captures, _ => bug!("expected closure expr for {:?}", closure_hir_id), From 7a80a36c4549db05d0ae41c8c84a4307814538df Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 30 Apr 2026 21:38:24 +0900 Subject: [PATCH 17/36] use `HashStable` instead of `HashStable_Generic` --- compiler/rustc_hir/src/hir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 299b045370c9e..0a7aecb6470a0 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1687,7 +1687,7 @@ pub struct Closure<'hir> { /// A HIR local that must be captured by value even if ordinary closure capture /// analysis would infer a weaker capture kind from its uses in the body. -#[derive(Debug, Clone, Copy, HashStable_Generic)] +#[derive(Debug, Clone, Copy, HashStable)] pub struct ExplicitCapture { pub var_hir_id: HirId, } From 16a446ec0b0e594242160f45273c2f7ec32c4870 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 7 May 2026 22:35:31 +0900 Subject: [PATCH 18/36] collect move exprs during closure lowering --- compiler/rustc_ast_lowering/src/expr.rs | 302 ++++++++++++++---------- compiler/rustc_ast_lowering/src/lib.rs | 8 +- compiler/rustc_hir/src/hir.rs | 2 +- tests/ui/move-expr/plain-closure.rs | 8 + 4 files changed, 184 insertions(+), 136 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 49df2ddcbf3e1..148814f9c259a 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -31,8 +31,8 @@ use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScop pub(super) struct WillCreateDefIdsVisitor; -/// A `move(...)` expression found while scanning a plain closure body. -struct MoveExprOccurrence<'a> { +/// A `move(...)` expression found while looking up generated initializers. +struct MoveExprInitializer<'a> { /// The `NodeId` of the outer `move(...)` expression. id: NodeId, /// Span of the `move` token, used for the generated binding name. @@ -41,32 +41,45 @@ struct MoveExprOccurrence<'a> { expr: &'a Expr, } -/// Collects the `move(...)` expressions that belong to one plain closure body. -/// -/// For `|| move(foo.bar).clone()`, this records the outer `move(foo.bar)` -/// occurrence and the inner expression `foo.bar`. Nested closures, generators, -/// const blocks, and items are lowered as separate bodies, so this visitor does -/// not collect `move(...)` expressions from them. -struct MoveExprCollector<'a> { - occurrences: Vec>, +/// State for `move(...)` expressions found while lowering one plain closure body. +pub(super) struct MoveExprState<'hir> { + pub(super) bindings: NodeMap<(Ident, HirId)>, + pub(super) occurrences: Vec>, } -impl<'a> MoveExprCollector<'a> { - fn collect(expr: &'a Expr) -> Vec> { - let mut this = Self { occurrences: Vec::new() }; +impl<'hir> Default for MoveExprState<'hir> { + fn default() -> Self { + Self { bindings: NodeMap::default(), occurrences: Vec::new() } + } +} + +pub(super) struct MoveExprOccurrence<'hir> { + id: NodeId, + ident: Ident, + pat: &'hir hir::Pat<'hir>, + binding: HirId, + explicit_capture: bool, +} + +/// Looks up the initializer expression for each `move(...)` occurrence. +struct MoveExprInitializerFinder<'a> { + initializers: Vec>, +} + +impl<'a> MoveExprInitializerFinder<'a> { + fn collect(expr: &'a Expr) -> Vec> { + let mut this = Self { initializers: Vec::new() }; this.visit_expr(expr); - this.occurrences + this.initializers } } -impl<'a> Visitor<'a> for MoveExprCollector<'a> { +impl<'a> Visitor<'a> for MoveExprInitializerFinder<'a> { fn visit_expr(&mut self, expr: &'a Expr) { match &expr.kind { ExprKind::Move(inner, move_kw_span) => { - // For `move(foo.bar)`, first collect any nested `move(...)` - // expressions in `foo.bar`, then record this outer occurrence. self.visit_expr(inner); - self.occurrences.push(MoveExprOccurrence { + self.initializers.push(MoveExprInitializer { id: expr.id, move_kw_span: *move_kw_span, expr: inner, @@ -102,6 +115,42 @@ impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor { } impl<'hir> LoweringContext<'_, 'hir> { + fn with_move_expr_bindings( + &mut self, + state: Option>, + f: impl FnOnce(&mut Self) -> T, + ) -> (T, Option>) { + self.move_expr_bindings.push(state); + let result = f(self); + let state = self.move_expr_bindings.pop().unwrap_or_else(|| { + span_bug!(DUMMY_SP, "`move_expr_bindings` stack was empty after lowering") + }); + (result, state) + } + + fn record_move_expr( + &mut self, + id: NodeId, + inner: &Expr, + move_kw_span: Span, + explicit_capture: bool, + ) -> (Ident, HirId) { + let index = self + .move_expr_bindings + .last() + .and_then(|state| state.as_ref()) + .map_or(0, |state| state.occurrences.len()); + let ident = Ident::from_str_and_span(&format!("__move_expr_{index}"), move_kw_span); + let (pat, binding) = self.pat_ident(inner.span, ident); + let Some(state) = self.move_expr_bindings.last_mut().and_then(|state| state.as_mut()) + else { + span_bug!(move_kw_span, "`move(...)` lowered without a plain closure body state"); + }; + state.bindings.insert(id, (ident, binding)); + state.occurrences.push(MoveExprOccurrence { id, ident, pat, binding, explicit_capture }); + (ident, binding) + } + fn lower_exprs(&mut self, exprs: &[Box]) -> &'hir [hir::Expr<'hir>] { self.arena.alloc_from_iter(exprs.iter().map(|x| self.lower_expr_mut(x))) } @@ -144,7 +193,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::ForLoop { pat, iter, body, label, kind } => { return self.lower_expr_for(e, pat, iter, body, *label, *kind); } - ExprKind::Closure(box closure) => return self.lower_expr_closure_expr(e, closure), + ExprKind::Closure(closure) => return self.lower_expr_closure_expr(e, closure), _ => (), } @@ -262,18 +311,23 @@ impl<'hir> LoweringContext<'_, 'hir> { }, ), ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr), - ExprKind::Move(_, move_kw_span) => { + ExprKind::Move(inner, move_kw_span) => { if !self.tcx.features().move_expr() { return self.expr_err(*move_kw_span, self.dcx().has_errors().unwrap()); } - // `last()` selects the binding map for the closure body currently - // being lowered. The map is keyed by the AST `NodeId`, so `e.id` - // selects the synthetic local for this exact `move(...)` occurrence. - if let Some((ident, binding)) = self - .move_expr_bindings - .last() - .and_then(|bindings| bindings.get(&e.id).copied()) - { + if let Some(state) = self.move_expr_bindings.last().and_then(Option::as_ref) { + let existing = state.bindings.get(&e.id).copied(); + let (ident, binding) = existing.unwrap_or_else(|| { + for nested in MoveExprInitializerFinder::collect(inner) { + self.record_move_expr( + nested.id, + nested.expr, + nested.move_kw_span, + false, + ); + } + self.record_move_expr(e.id, inner, *move_kw_span, true) + }); hir::ExprKind::Path(hir::QPath::Resolved( None, self.arena.alloc(hir::Path { @@ -311,7 +365,14 @@ impl<'hir> LoweringContext<'_, 'hir> { e.span, desugaring_kind, hir::CoroutineSource::Block, - |this| this.with_new_scopes(e.span, |this| this.lower_block_expr(block)), + |this| { + this.with_new_scopes(e.span, |this| { + let (expr, _) = this.with_move_expr_bindings(None, |this| { + this.lower_block_expr(block) + }); + expr + }) + }, ) } ExprKind::Block(blk, opt_label) => { @@ -445,11 +506,10 @@ impl<'hir> LoweringContext<'_, 'hir> { pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock { self.with_new_scopes(c.value.span, |this| { let def_id = this.local_def_id(c.id); - hir::ConstBlock { - def_id, - hir_id: this.lower_node_id(c.id), - body: this.lower_const_body(c.value.span, Some(&c.value)), - } + let (body, _) = this.with_move_expr_bindings(None, |this| { + this.lower_const_body(c.value.span, Some(&c.value)) + }); + hir::ConstBlock { def_id, hir_id: this.lower_node_id(c.id), body } }) } @@ -1153,102 +1213,69 @@ impl<'hir> LoweringContext<'_, 'hir> { fn_arg_span: Span, whole_span: Span, ) -> hir::Expr<'hir> { - // `move(...)` evaluates its inner expression when the closure is created - // and captures the result by value. For example: - // - // `|| move(foo).bar` - // - // is lowered roughly as: - // - // `let __move_expr_0 = foo; || __move_expr_0.bar` - let occurrences = MoveExprCollector::collect(body); - if occurrences.is_empty() { - // No `move(...)` expressions in this closure body; lower the closure - // normally, with no explicit captures. + let (closure_kind, move_expr_state) = self.lower_expr_closure( + attrs, + binder, + capture_clause, + closure_id, + constness, + movability, + decl, + body, + fn_decl_span, + fn_arg_span, + ); + + if move_expr_state.occurrences.is_empty() { return hir::Expr { hir_id: expr_hir_id, - kind: self.lower_expr_closure( - attrs, - binder, - capture_clause, - closure_id, - constness, - movability, - decl, - body, - fn_decl_span, - fn_arg_span, - &[], - ), + kind: closure_kind, span: self.lower_span(whole_span), }; } - let mut bindings = NodeMap::default(); - let mut lowered_occurrences = Vec::with_capacity(occurrences.len()); - for (index, occurrence) in occurrences.iter().enumerate() { - // Create one synthetic local per `move(...)` expression and remember - // which AST node should be replaced by that local while lowering the - // closure body. - let ident = - Ident::from_str_and_span(&format!("__move_expr_{index}"), occurrence.move_kw_span); - let (pat, binding) = self.pat_ident(occurrence.expr.span, ident); - bindings.insert(occurrence.id, (ident, binding)); - lowered_occurrences.push((occurrence, pat, binding)); - } - - // During body lowering, replace each `move(...)` occurrence with the - // synthetic local recorded in this closure's binding map. Nested closures - // push their own maps. - self.move_expr_bindings.push(bindings); - let mut stmts = Vec::with_capacity(lowered_occurrences.len()); - for (occurrence, pat, _) in &lowered_occurrences { + let initializers = MoveExprInitializerFinder::collect(body) + .into_iter() + .map(|initializer| (initializer.id, initializer.expr)) + .collect::>(); + let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len()); + let mut initializer_bindings = NodeMap::default(); + for occurrence in &move_expr_state.occurrences { // Evaluate the expression inside `move(...)` before creating the // closure and store it in a synthetic local: // `|| move(foo).bar` becomes roughly // `let __move_expr_0 = foo; || __move_expr_0.bar`. - let init = self.lower_expr(occurrence.expr); + let expr = initializers[&occurrence.id]; + let init = if initializer_bindings.is_empty() { + self.lower_expr(expr) + } else { + // Earlier entries cover nested `move(...)` expressions that + // appear inside this initializer, as in + // `move(move(foo.clone()))`. + let (init, _) = self.with_move_expr_bindings( + Some(MoveExprState { + bindings: initializer_bindings.clone(), + occurrences: Vec::new(), + }), + |this| this.lower_expr(expr), + ); + init + }; stmts.push(self.stmt_let_pat( None, - occurrence.expr.span, + expr.span, Some(init), - *pat, + occurrence.pat, hir::LocalSource::Normal, )); + initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding)); } - let explicit_captures = self.arena.alloc_from_iter( - lowered_occurrences - .iter() - // Force the generated locals to be captured by value even if - // the lowered closure body only borrows them, as in - // `move(foo).clone()`. - .map(|(_, _, binding)| hir::ExplicitCapture { var_hir_id: *binding }), - ); - - // Lower the closure itself while `move_expr_bindings` contains this - // closure's substitutions, so each `move(...)` in the body is replaced - // with its generated local. let closure_expr = self.arena.alloc(hir::Expr { hir_id: expr_hir_id, - kind: self.lower_expr_closure( - attrs, - binder, - capture_clause, - closure_id, - constness, - movability, - decl, - body, - fn_decl_span, - fn_arg_span, - explicit_captures, - ), + kind: closure_kind, span: self.lower_span(whole_span), }); - // Restore the enclosing closure's substitution map before lowering the - // block that contains the synthetic `let`s. - self.move_expr_bindings.pop(); let stmts = self.arena.alloc_from_iter(stmts); let block = self.block_all(whole_span, stmts, Some(closure_expr)); @@ -1267,26 +1294,37 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Expr, fn_decl_span: Span, fn_arg_span: Span, - explicit_captures: &'hir [hir::ExplicitCapture], - ) -> hir::ExprKind<'hir> { + ) -> (hir::ExprKind<'hir>, MoveExprState<'hir>) { let closure_def_id = self.local_def_id(closure_id); let (binder_clause, generic_params) = self.lower_closure_binder(binder); - let (body_id, closure_kind) = self.with_new_scopes(fn_decl_span, move |this| { + let ((body_id, closure_kind), move_expr_state) = self.with_new_scopes(fn_decl_span, move |this| { let mut coroutine_kind = find_attr!(attrs, Coroutine => hir::CoroutineKind::Coroutine(Movability::Movable)); - // FIXME(contracts): Support contracts on closures? - let body_id = this.lower_fn_body(decl, None, |this| { - this.coroutine_kind = coroutine_kind; - let e = this.lower_expr_mut(body); - coroutine_kind = this.coroutine_kind; - e - }); - let coroutine_option = - this.closure_movability_for_fn(decl, fn_decl_span, coroutine_kind, movability); - (body_id, coroutine_option) + this.with_move_expr_bindings(Some(MoveExprState::default()), |this| { + // FIXME(contracts): Support contracts on closures? + let body_id = this.lower_fn_body(decl, None, |this| { + this.coroutine_kind = coroutine_kind; + let e = this.lower_expr_mut(body); + coroutine_kind = this.coroutine_kind; + e + }); + let coroutine_option = + this.closure_movability_for_fn(decl, fn_decl_span, coroutine_kind, movability); + (body_id, coroutine_option) + }) }); + let Some(move_expr_state) = move_expr_state else { + span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state"); + }; + let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter( + move_expr_state.occurrences.iter().filter_map(|occurrence| { + occurrence + .explicit_capture + .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding }) + }), + ); let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); // Lower outside new scope to preserve `is_in_loop_condition`. @@ -1306,7 +1344,7 @@ impl<'hir> LoweringContext<'_, 'hir> { explicit_captures, }); - hir::ExprKind::Closure(c) + (hir::ExprKind::Closure(c), move_expr_state) } fn closure_movability_for_fn( @@ -1385,14 +1423,16 @@ impl<'hir> LoweringContext<'_, 'hir> { // Transform `async |x: u8| -> X { ... }` into // `|x: u8| || -> X { ... }`. let body_id = this.lower_body(|this| { - let (parameters, expr) = this.lower_coroutine_body_with_moved_arguments( - &inner_decl, - |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), - fn_decl_span, - body.span, - coroutine_kind, - hir::CoroutineSource::Closure, - ); + let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| { + this.lower_coroutine_body_with_moved_arguments( + &inner_decl, + |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), + fn_decl_span, + body.span, + coroutine_kind, + hir::CoroutineSource::Closure, + ) + }); this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 43c24c6a61268..d8a7b6e93ab0c 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -158,10 +158,10 @@ struct LoweringContext<'a, 'hir> { allow_async_fn_traits: Arc<[Symbol]>, delayed_lints: Vec, - /// Stack of per-closure `move(...)` substitution maps. Each map is keyed by - /// the AST `NodeId` of a `move(...)` occurrence and points to the synthetic - /// local used while lowering that closure body. - move_expr_bindings: Vec>, + /// Stack of `move(...)` collection states. A plain closure body pushes + /// `Some`, so `move(...)` expressions can record the generated locals they + /// should lower to. Nested bodies that cannot use `move(...)` push `None`. + move_expr_bindings: Vec>>, attribute_parser: AttributeParser<'hir>, } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 0a7aecb6470a0..84836e2a25c49 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1687,7 +1687,7 @@ pub struct Closure<'hir> { /// A HIR local that must be captured by value even if ordinary closure capture /// analysis would infer a weaker capture kind from its uses in the body. -#[derive(Debug, Clone, Copy, HashStable)] +#[derive(Debug, Clone, Copy, StableHash)] pub struct ExplicitCapture { pub var_hir_id: HirId, } diff --git a/tests/ui/move-expr/plain-closure.rs b/tests/ui/move-expr/plain-closure.rs index 2ec01e35569b4..dc3e3b2956a15 100644 --- a/tests/ui/move-expr/plain-closure.rs +++ b/tests/ui/move-expr/plain-closure.rs @@ -18,4 +18,12 @@ fn main() { println!("{} {}", x, y); }; c(); + + let v = "Hello, Ferris".to_string(); + let r = || { + || { + (move(move(v.clone()))).len() + } + }; + assert_eq!(r()(), v.len()); } From 24d5224eaa545d0de09efd5c78093e79b5e62e3c Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 7 May 2026 23:33:29 +0900 Subject: [PATCH 19/36] Preserve const block HIR id order --- compiler/rustc_ast_lowering/src/expr.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 148814f9c259a..9fc69470cf546 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -506,10 +506,11 @@ impl<'hir> LoweringContext<'_, 'hir> { pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock { self.with_new_scopes(c.value.span, |this| { let def_id = this.local_def_id(c.id); + let hir_id = this.lower_node_id(c.id); let (body, _) = this.with_move_expr_bindings(None, |this| { this.lower_const_body(c.value.span, Some(&c.value)) }); - hir::ConstBlock { def_id, hir_id: this.lower_node_id(c.id), body } + hir::ConstBlock { def_id, hir_id, body } }) } From 062ecd60a9539143352406c00e570c38e4a1bce1 Mon Sep 17 00:00:00 2001 From: Kjetil Kjeka Date: Sat, 2 May 2026 21:36:35 +0200 Subject: [PATCH 20/36] LLBC-linker: do not strip debug symbols for nvptx This was an issue for old ptx ISA versions. Those are not supported by rustc anymore --- src/tools/llvm-bitcode-linker/Cargo.toml | 2 +- src/tools/llvm-bitcode-linker/src/linker.rs | 14 ++++-------- src/tools/llvm-bitcode-linker/src/target.rs | 8 +++++++ tests/assembly-llvm/nvptx-debug.rs | 24 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 tests/assembly-llvm/nvptx-debug.rs diff --git a/src/tools/llvm-bitcode-linker/Cargo.toml b/src/tools/llvm-bitcode-linker/Cargo.toml index a9210b562f330..9f909346e2a5d 100644 --- a/src/tools/llvm-bitcode-linker/Cargo.toml +++ b/src/tools/llvm-bitcode-linker/Cargo.toml @@ -3,7 +3,7 @@ name = "llvm-bitcode-linker" version = "0.0.1" description = "A self-contained linker for llvm bitcode" license = "MIT OR Apache-2.0" -edition = "2021" +edition = "2024" publish = false [dependencies] diff --git a/src/tools/llvm-bitcode-linker/src/linker.rs b/src/tools/llvm-bitcode-linker/src/linker.rs index e682aecbbeed1..b5c8241b3be72 100644 --- a/src/tools/llvm-bitcode-linker/src/linker.rs +++ b/src/tools/llvm-bitcode-linker/src/linker.rs @@ -2,11 +2,10 @@ use std::path::PathBuf; use anyhow::Context; -use crate::{Optimization, Target}; +use crate::Optimization; #[derive(Debug)] pub struct Session { - target: Target, cpu: Option, feature: Option, symbols: Vec, @@ -32,8 +31,9 @@ impl Session { let opt_path = out_path.with_extension("optimized.o"); let sym_path = out_path.with_extension("symbols.txt"); + tracing::debug!(%target, ?cpu, ?feature, ?out_path, "new session created"); + Session { - target, cpu, feature, symbols: Vec::new(), @@ -86,15 +86,9 @@ impl Session { /// Optimize and compile to native format using `opt` and `llc` /// /// Before this can be called `link` needs to be called - fn optimize(&mut self, optimization: Optimization, mut debug: bool) -> anyhow::Result<()> { + fn optimize(&mut self, optimization: Optimization, debug: bool) -> anyhow::Result<()> { let mut passes = format!("default<{}>", optimization); - // FIXME(@kjetilkjeka) Debug symbol generation is broken for nvptx64 so we must remove them even in debug mode - if debug && self.target == crate::Target::Nvptx64NvidiaCuda { - tracing::warn!("nvptx64 target detected - stripping debug symbols"); - debug = false; - } - // We add an internalize pass as the rust compiler as we require exported symbols to be explicitly marked passes.push_str(",internalize,globaldce"); let symbol_file_content = self.symbols.iter().fold(String::new(), |s, x| s + &x + "\n"); diff --git a/src/tools/llvm-bitcode-linker/src/target.rs b/src/tools/llvm-bitcode-linker/src/target.rs index d9f8ff3852b60..d7a7c57ee32ea 100644 --- a/src/tools/llvm-bitcode-linker/src/target.rs +++ b/src/tools/llvm-bitcode-linker/src/target.rs @@ -18,3 +18,11 @@ impl std::str::FromStr for Target { } } } + +impl std::fmt::Display for Target { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Target::Nvptx64NvidiaCuda => f.write_str("nvptx64-nvidia-cuda"), + } + } +} diff --git a/tests/assembly-llvm/nvptx-debug.rs b/tests/assembly-llvm/nvptx-debug.rs new file mode 100644 index 0000000000000..77934d723ffdc --- /dev/null +++ b/tests/assembly-llvm/nvptx-debug.rs @@ -0,0 +1,24 @@ +//@ assembly-output: emit-asm +//@ compile-flags: --crate-type cdylib -C debuginfo=2 +//@ only-nvptx64 + +// Tests related to debug symbols for nvptx + +#![feature(abi_ptx)] +#![no_std] + +//@ aux-build: breakpoint-panic-handler.rs +extern crate breakpoint_panic_handler; + +#[no_mangle] +pub extern "ptx-kernel" fn foo() { + panic!("bar"); +} + +// We make sure that all debug sections are available and visit them +// CHECK: .section .debug_abbrev +// CHECK: .section .debug_info + +// Issue #99248 describes a bug where `.` was used as a seperator +// instead of `_` for `anon`s in `.debug_info` +// CHECK-NOT: anon. From 05f52c7a9907fd63972d2030d1d42042904d9a9e Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 11 Apr 2026 21:35:23 -0700 Subject: [PATCH 21/36] Have arrays' `drop_glue` just unsize and call the slice version --- compiler/rustc_middle/src/ty/instance.rs | 2 +- compiler/rustc_mir_transform/src/shim.rs | 44 +++++++++++- tests/codegen-llvm/array-drop-glue.rs | 67 +++++++++++++++++++ ...ring;42].AddMovesForPackedDrops.before.mir | 54 +-------------- 4 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 tests/codegen-llvm/array-drop-glue.rs diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs index 1d761c2f43b3d..b81e0b6f3471a 100644 --- a/compiler/rustc_middle/src/ty/instance.rs +++ b/compiler/rustc_middle/src/ty/instance.rs @@ -292,7 +292,7 @@ impl<'tcx> InstanceKind<'tcx> { use rustc_hir::definitions::DefPathData; let def_id = match *self { ty::InstanceKind::Item(def) => def, - ty::InstanceKind::DropGlue(_, Some(_)) => return false, + ty::InstanceKind::DropGlue(_, Some(ty)) => return ty.is_array(), ty::InstanceKind::AsyncDropGlueCtorShim(_, ty) => return ty.is_coroutine(), ty::InstanceKind::FutureDropPollShim(_, _, _) => return false, ty::InstanceKind::AsyncDropGlue(_, _) => return false, diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index 459d87d74cd7b..7f92199dec455 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -317,16 +317,56 @@ fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option>) let block = |blocks: &mut IndexVec<_, _>, kind| { blocks.push(BasicBlockData::new(Some(Terminator { source_info, kind }), false)) }; - block(&mut blocks, TerminatorKind::Goto { target: return_block }); + if ty.is_some() { + block(&mut blocks, TerminatorKind::Goto { target: return_block }); + } block(&mut blocks, TerminatorKind::Return); let source = MirSource::from_instance(ty::InstanceKind::DropGlue(def_id, ty)); let mut body = new_body(source, blocks, local_decls_for_sig(&sig, span), sig.inputs().len(), span); + let Some(ty) = ty else { + return body; + }; + let dropee_ptr = Place::from(Local::arg(0)); - if ty.is_some() { + if let ty::Array(ety, _len) = *ty.kind() { + // Don't write out the elaboration for each array type. + // Instead, just delegate to the slice version. + let slice_ty = Ty::new_slice(tcx, ety); + let mut_slice_ty = Ty::new_ref(tcx, tcx.lifetimes.re_erased, slice_ty, ty::Mutability::Mut); + let erased_local = body.local_decls.push(LocalDecl::new(mut_slice_ty, span)); + + let start = &mut body.basic_blocks_mut()[START_BLOCK]; + start.statements.push(Statement::new( + source_info, + StatementKind::Assign(Box::new(( + Place::from(erased_local), + Rvalue::Cast( + CastKind::PointerCoercion( + ty::adjustment::PointerCoercion::Unsize, + CoercionSource::Implicit, + ), + Operand::Move(dropee_ptr), + mut_slice_ty, + ), + ))), + )); + start.terminator = Some(Terminator { + source_info, + kind: TerminatorKind::Call { + func: Operand::function_handle(tcx, def_id, [ty::GenericArg::from(slice_ty)], span), + args: Box::new([Spanned { span, node: Operand::Move(Place::from(erased_local)) }]), + destination: Place::from(RETURN_PLACE), + target: Some(return_block), + unwind: UnwindAction::Continue, + call_source: CallSource::Misc, + fn_span: span, + }, + }); + } else { let patch = { let typing_env = ty::TypingEnv::post_analysis(tcx, def_id); let mut elaborator = DropShimElaborator { diff --git a/tests/codegen-llvm/array-drop-glue.rs b/tests/codegen-llvm/array-drop-glue.rs new file mode 100644 index 0000000000000..994879cb65cf1 --- /dev/null +++ b/tests/codegen-llvm/array-drop-glue.rs @@ -0,0 +1,67 @@ +//@ revisions: RAW OPT +//@ compile-flags: -C opt-level=z -C panic=abort +//@[RAW] compile-flags: -C no-prepopulate-passes -Z inline-mir + +#![crate_type = "lib"] + +// Ensure all the different array drop_glue functions just delegate to the slice one, +// rather than emitting two loops in each of the three. + +// When this test was first written, the array drop glues came out in the +// seemingly-arbitrary order of 42, then 7, then 13, so to avoid potential +// fragility from that changing we don't check any particular order. + +// RAW: ; core::ptr::drop_glue::<[array_drop_glue::NeedsDrop; [[N:7|13|42]]]> +// RAW-NEXT: inlinehint +// RAW: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> +// RAW-NEXT: noundef [[N]]) +// RAW: } + +// RAW: ; core::ptr::drop_glue::<[array_drop_glue::NeedsDrop; [[N:7|13|42]]]> +// RAW-NEXT: inlinehint +// RAW: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> +// RAW-NEXT: noundef [[N]]) +// RAW: } + +// RAW: ; core::ptr::drop_glue::<[array_drop_glue::NeedsDrop; [[N:7|13|42]]]> +// RAW-NEXT: inlinehint +// RAW: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> +// RAW-NEXT: noundef [[N]]) +// RAW: } + +// CHECK-LABEL: ; core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> +// CHECK-NOT: inlinehint +// OPT: add nuw nsw {{.+}} 1 +// CHECK: } + +#[no_mangle] +// CHECK-LABEL: @drop_arrays +pub fn drop_arrays(x: [NeedsDrop; 7], y: [NeedsDrop; 13], z: [NeedsDrop; 42]) { + // I don't remember the parameter drop order, so write out the order the test expects. + + // RAW: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop; 7]> + // OPT: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> + // OPT-NEXT: noundef 7) + drop(x); + // RAW: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop; 13]> + // OPT: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> + // OPT-NEXT: noundef 13) + drop(y); + // RAW: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop; 42]> + // OPT: call core::ptr::drop_glue::<[array_drop_glue::NeedsDrop]> + // OPT-NEXT: noundef 42) + drop(z); +} + +struct NeedsDrop(u32); + +impl Drop for NeedsDrop { + #[inline] + fn drop(&mut self) { + do_the_drop(self); + } +} + +unsafe extern "Rust" { + safe fn do_the_drop(_: &mut NeedsDrop); +} diff --git a/tests/mir-opt/slice_drop_shim.core.ptr-drop_glue.[String;42].AddMovesForPackedDrops.before.mir b/tests/mir-opt/slice_drop_shim.core.ptr-drop_glue.[String;42].AddMovesForPackedDrops.before.mir index 66591dcc91df2..1d4b0f033e0be 100644 --- a/tests/mir-opt/slice_drop_shim.core.ptr-drop_glue.[String;42].AddMovesForPackedDrops.before.mir +++ b/tests/mir-opt/slice_drop_shim.core.ptr-drop_glue.[String;42].AddMovesForPackedDrops.before.mir @@ -2,62 +2,14 @@ fn std::ptr::drop_glue(_1: &mut [String; 42]) -> () { let mut _0: (); - let mut _2: *mut [std::string::String; 42]; - let mut _3: *mut [std::string::String]; - let mut _4: usize; - let mut _5: usize; - let mut _6: *mut std::string::String; - let mut _7: bool; - let mut _8: *mut std::string::String; - let mut _9: bool; + let mut _2: &mut [std::string::String]; bb0: { - goto -> bb9; + _2 = move _1 as &mut [std::string::String] (PointerCoercion(Unsize, Implicit)); + _0 = std::ptr::drop_glue::<[String]>(move _2) -> [return: bb1, unwind continue]; } bb1: { return; } - - bb2 (cleanup): { - resume; - } - - bb3 (cleanup): { - _6 = &raw mut (*_3)[_5]; - _5 = Add(move _5, const 1_usize); - drop((*_6)) -> [return: bb4, unwind terminate(cleanup)]; - } - - bb4 (cleanup): { - _7 = Eq(copy _5, copy _4); - switchInt(move _7) -> [0: bb3, otherwise: bb2]; - } - - bb5: { - _8 = &raw mut (*_3)[_5]; - _5 = Add(move _5, const 1_usize); - drop((*_8)) -> [return: bb6, unwind: bb4]; - } - - bb6: { - _9 = Eq(copy _5, copy _4); - switchInt(move _9) -> [0: bb5, otherwise: bb1]; - } - - bb7: { - _4 = PtrMetadata(copy _3); - _5 = const 0_usize; - goto -> bb6; - } - - bb8: { - goto -> bb7; - } - - bb9: { - _2 = &raw mut (*_1); - _3 = move _2 as *mut [std::string::String] (PointerCoercion(Unsize, Implicit)); - goto -> bb8; - } } From c1965b598de990be96b63011d258f331eb7ed3d2 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Sun, 10 May 2026 03:31:22 +0800 Subject: [PATCH 22/36] rustdoc: report unresolved paths that appear invalid - Check whether an unresolved path is in fact invalid. This prevents rustdoc from emitting nonsensical diagnostics like: - "no item named `std:` in scope" - "no item named `` in scope" Instead rustdoc will now say "has invalid path separator" (the same message from `MalformedGenerics::InvalidPathSeparator`) This is done by checking whether each path segment is empty or contains extra ":" after it's been split by the path separator "::". - Add a dedicated test `tests/rustdoc-ui/intra-doc/invalid-path-separator.rs` for this. - Some unrelated test snapshots have been updated because of this new check. --- .../passes/collect_intra_doc_links.rs | 26 ++++++++++++- .../intra-doc/invalid-path-separator.rs | 18 +++++++++ .../intra-doc/invalid-path-separator.stderr | 39 +++++++++++++++++++ .../intra-doc/malformed-paths.stderr | 2 +- tests/rustdoc-ui/intra-doc/warning.stderr | 2 +- 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 tests/rustdoc-ui/intra-doc/invalid-path-separator.rs create mode 100644 tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 099c998c8ab69..4cd054c21d374 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2044,17 +2044,37 @@ fn resolution_failure( // Check if _any_ parent of the path gets resolved. // If so, report it and say the first which failed; if not, say the first path segment didn't resolve. + // Also check if `path_str` is an invalid path. + + // Examples of `path_str` that are invalid: + // - "std::::path", during splitting this would yield an empty segment + // - "std:::path", this would eventually yield "std:" + let mut path_is_invalid = false; + let is_invalid_segment = + |segment: &str| segment.is_empty() || segment.contains(':'); + let mut name = path_str; 'outer: loop { // FIXME(jynelson): this might conflict with my `Self` fix in #76467 let Some((start, end)) = name.rsplit_once("::") else { // `name` is now the first path segment, which didn't resolve. // avoid bug that marked [Quux::Z] as missing Z, not Quux + if is_invalid_segment(name) { + path_is_invalid = true; + break; + } if partial_res.is_none() { *unresolved = name.into(); + // If `partial_res` somehow had a value, we preserve the original `unresolved`. } break; }; + if is_invalid_segment(end) { + // If any segment is invalid, stop and say so, instead of saying + // "no item named ...", which would look nonsensical. + path_is_invalid = true; + break; + } for ns in [TypeNS, ValueNS, MacroNS] { if let Ok(v_res) = collector.resolve(start, ns, None, item_id, module_id) @@ -2067,10 +2087,10 @@ fn resolution_failure( } } } - *unresolved = end.into(); if start.is_empty() && partial_res.is_none() { // `start` being empty means `path_str` was written like "::path::to::item". // In this case, `end` is the first path segment that we should report. + *unresolved = end.into(); break; } name = start; @@ -2083,7 +2103,9 @@ fn resolution_failure( }; // See if this was a module: `[path]` or `[std::io::nope]` if let Some(module) = last_found_module { - let note = if partial_res.is_some() { + let note = if path_is_invalid { + "has invalid path separator".into() + } else if partial_res.is_some() { // Part of the link resolved; e.g. `std::io::nonexistent` let module_name = tcx.item_name(module); format!("no item named `{unresolved}` in module `{module_name}`") diff --git a/tests/rustdoc-ui/intra-doc/invalid-path-separator.rs b/tests/rustdoc-ui/intra-doc/invalid-path-separator.rs new file mode 100644 index 0000000000000..177c379ac19da --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/invalid-path-separator.rs @@ -0,0 +1,18 @@ +// This test shows that `broken_intra_doc_links` emits a more meaningful message when +// it encounters a path that is unresolvable due to being invalid. +#![deny(rustdoc::broken_intra_doc_links)] + +//! [std:path] +//~^ ERROR +// +//! [std:::path] +//~^ ERROR +// +//! [std::::path] +//~^ ERROR +// +//! [std:::::path] +//~^ ERROR +// +//! [std2::::path] +//~^ ERROR diff --git a/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr b/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr new file mode 100644 index 0000000000000..ae3708cf3a82b --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr @@ -0,0 +1,39 @@ +error: unresolved link to `std:path` + --> $DIR/invalid-path-separator.rs:5:6 + | +LL | //! [std:path] + | ^^^^^^^^ has invalid path separator + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/invalid-path-separator.rs:3:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unresolved link to `std:::path` + --> $DIR/invalid-path-separator.rs:8:6 + | +LL | //! [std:::path] + | ^^^^^^^^^^ has invalid path separator + +error: unresolved link to `std::::path` + --> $DIR/invalid-path-separator.rs:11:6 + | +LL | //! [std::::path] + | ^^^^^^^^^^^ has invalid path separator + +error: unresolved link to `std:::::path` + --> $DIR/invalid-path-separator.rs:14:6 + | +LL | //! [std:::::path] + | ^^^^^^^^^^^^ has invalid path separator + +error: unresolved link to `std2::::path` + --> $DIR/invalid-path-separator.rs:17:6 + | +LL | //! [std2::::path] + | ^^^^^^^^^^^^ has invalid path separator + +error: aborting due to 5 previous errors + diff --git a/tests/rustdoc-ui/intra-doc/malformed-paths.stderr b/tests/rustdoc-ui/intra-doc/malformed-paths.stderr index d38e7c2a73e48..25f02c31aeefa 100644 --- a/tests/rustdoc-ui/intra-doc/malformed-paths.stderr +++ b/tests/rustdoc-ui/intra-doc/malformed-paths.stderr @@ -2,7 +2,7 @@ error: unresolved link to `Type::` --> $DIR/malformed-paths.rs:5:7 | LL | //! [`Type::`] - | ^^^^^^ the struct `Type` has no field or associated item named `` + | ^^^^^^ has invalid path separator | note: the lint level is defined here --> $DIR/malformed-paths.rs:2:9 diff --git a/tests/rustdoc-ui/intra-doc/warning.stderr b/tests/rustdoc-ui/intra-doc/warning.stderr index 9b3f6f822d75b..bc199740c4a27 100644 --- a/tests/rustdoc-ui/intra-doc/warning.stderr +++ b/tests/rustdoc-ui/intra-doc/warning.stderr @@ -40,7 +40,7 @@ warning: unresolved link to `Qux:Y` --> $DIR/warning.rs:14:13 | LL | /// [Qux:Y] - | ^^^^^ no item named `Qux:Y` in scope + | ^^^^^ has invalid path separator | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` From 8012b9b5057b13e7056706e71c94c141faa8c7a4 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Sun, 10 May 2026 03:38:26 +0800 Subject: [PATCH 23/36] rustdoc: adjustment in `collect_intra_doc_links` Replace a hardcoded constant with `mem::variant_count` to keep in-sync with the `ResolutionFailure` enum. --- src/librustdoc/lib.rs | 1 + src/librustdoc/passes/collect_intra_doc_links.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 87de4244b5c85..649059529ca64 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -13,6 +13,7 @@ #![feature(rustc_private)] #![feature(test)] #![feature(trim_prefix_suffix)] +#![feature(variant_count)] #![recursion_limit = "256"] #![warn(rustc::internal)] // tidy-alphabetical-end diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 4cd054c21d374..c3a1e48a3ce21 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2022,7 +2022,8 @@ fn resolution_failure( ) }; // ignore duplicates - let mut variants_seen = SmallVec::<[_; 3]>::new(); + let mut variants_seen = + SmallVec::<[_; const { mem::variant_count::>() }]>::new(); for mut failure in kinds { let variant = mem::discriminant(&failure); if variants_seen.contains(&variant) { From 52ecad938e244612a2ae8fe179839ddc92cdebfc Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sun, 10 May 2026 16:53:48 -0700 Subject: [PATCH 24/36] Add a mir-opt test for `==` on arrays --- ...q.eq_ipv4.PreCodegen.after.panic-abort.mir | 21 ++++++++++++++++ ....eq_ipv4.PreCodegen.after.panic-unwind.mir | 21 ++++++++++++++++ ...q.eq_ipv6.PreCodegen.after.panic-abort.mir | 21 ++++++++++++++++ ....eq_ipv6.PreCodegen.after.panic-unwind.mir | 21 ++++++++++++++++ ...dd_length.PreCodegen.after.panic-abort.mir | 21 ++++++++++++++++ ...d_length.PreCodegen.after.panic-unwind.mir | 21 ++++++++++++++++ tests/mir-opt/pre-codegen/array_eq.rs | 25 +++++++++++++++++++ 7 files changed, 151 insertions(+) create mode 100644 tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir create mode 100644 tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir create mode 100644 tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir create mode 100644 tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir create mode 100644 tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-abort.mir create mode 100644 tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-unwind.mir create mode 100644 tests/mir-opt/pre-codegen/array_eq.rs diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir new file mode 100644 index 0000000000000..85162b3a8dd2d --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir @@ -0,0 +1,21 @@ +// MIR for `eq_ipv4` after PreCodegen + +fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + scope 1 (inlined std::cmp::impls::::eq) { + scope 2 (inlined array::equality::::eq) { + scope 3 (inlined >::spec_eq) { + } + } + } + + bb0: { + _0 = raw_eq::<[u8; 4]>(move _1, move _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir new file mode 100644 index 0000000000000..85162b3a8dd2d --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir @@ -0,0 +1,21 @@ +// MIR for `eq_ipv4` after PreCodegen + +fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + scope 1 (inlined std::cmp::impls::::eq) { + scope 2 (inlined array::equality::::eq) { + scope 3 (inlined >::spec_eq) { + } + } + } + + bb0: { + _0 = raw_eq::<[u8; 4]>(move _1, move _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir new file mode 100644 index 0000000000000..2eef5e72755f0 --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir @@ -0,0 +1,21 @@ +// MIR for `eq_ipv6` after PreCodegen + +fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + scope 1 (inlined std::cmp::impls::::eq) { + scope 2 (inlined array::equality::::eq) { + scope 3 (inlined >::spec_eq) { + } + } + } + + bb0: { + _0 = raw_eq::<[u16; 8]>(move _1, move _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir new file mode 100644 index 0000000000000..2eef5e72755f0 --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir @@ -0,0 +1,21 @@ +// MIR for `eq_ipv6` after PreCodegen + +fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + scope 1 (inlined std::cmp::impls::::eq) { + scope 2 (inlined array::equality::::eq) { + scope 3 (inlined >::spec_eq) { + } + } + } + + bb0: { + _0 = raw_eq::<[u16; 8]>(move _1, move _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-abort.mir new file mode 100644 index 0000000000000..06d7f222df14c --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-abort.mir @@ -0,0 +1,21 @@ +// MIR for `eq_odd_length` after PreCodegen + +fn eq_odd_length(_1: &[u8; 3], _2: &[u8; 3]) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + scope 1 (inlined std::cmp::impls::::eq) { + scope 2 (inlined array::equality::::eq) { + scope 3 (inlined >::spec_eq) { + } + } + } + + bb0: { + _0 = raw_eq::<[u8; 3]>(move _1, move _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-unwind.mir new file mode 100644 index 0000000000000..06d7f222df14c --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.eq_odd_length.PreCodegen.after.panic-unwind.mir @@ -0,0 +1,21 @@ +// MIR for `eq_odd_length` after PreCodegen + +fn eq_odd_length(_1: &[u8; 3], _2: &[u8; 3]) -> bool { + debug a => _1; + debug b => _2; + let mut _0: bool; + scope 1 (inlined std::cmp::impls::::eq) { + scope 2 (inlined array::equality::::eq) { + scope 3 (inlined >::spec_eq) { + } + } + } + + bb0: { + _0 = raw_eq::<[u8; 3]>(move _1, move _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.rs b/tests/mir-opt/pre-codegen/array_eq.rs new file mode 100644 index 0000000000000..41ec79acf77b4 --- /dev/null +++ b/tests/mir-opt/pre-codegen/array_eq.rs @@ -0,0 +1,25 @@ +//@ compile-flags: -O -Zmir-opt-level=2 +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY + +#![crate_type = "lib"] + +// EMIT_MIR array_eq.eq_odd_length.PreCodegen.after.mir +pub unsafe fn eq_odd_length(a: &[u8; 3], b: &[u8; 3]) -> bool { + // CHECK-LABEL: fn eq_odd_length(_1: &[u8; 3], _2: &[u8; 3]) -> bool + // CHECK: _0 = raw_eq::<[u8; 3]>(move _1, move _2) + a == b +} + +// EMIT_MIR array_eq.eq_ipv4.PreCodegen.after.mir +pub unsafe fn eq_ipv4(a: &[u8; 4], b: &[u8; 4]) -> bool { + // CHECK-LABEL: fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool + // CHECK: _0 = raw_eq::<[u8; 4]>(move _1, move _2) + a == b +} + +// EMIT_MIR array_eq.eq_ipv6.PreCodegen.after.mir +pub unsafe fn eq_ipv6(a: &[u16; 8], b: &[u16; 8]) -> bool { + // CHECK-LABEL: fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool + // CHECK: _0 = raw_eq::<[u16; 8]>(move _1, move _2) + a == b +} From 1892ccf428e3caf9ff88146ed5b9835033cc9442 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Mon, 11 May 2026 09:54:47 +0800 Subject: [PATCH 25/36] rustdoc: change lint message to "invalid path separator" for consistency --- src/librustdoc/passes/collect_intra_doc_links.rs | 4 ++-- .../rustdoc-ui/intra-doc/invalid-path-separator.stderr | 10 +++++----- tests/rustdoc-ui/intra-doc/malformed-generics.stderr | 2 +- tests/rustdoc-ui/intra-doc/malformed-paths.stderr | 2 +- tests/rustdoc-ui/intra-doc/warning.stderr | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index c3a1e48a3ce21..92ba584fbd521 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2105,7 +2105,7 @@ fn resolution_failure( // See if this was a module: `[path]` or `[std::io::nope]` if let Some(module) = last_found_module { let note = if path_is_invalid { - "has invalid path separator".into() + "invalid path separator".into() } else if partial_res.is_some() { // Part of the link resolved; e.g. `std::io::nonexistent` let module_name = tcx.item_name(module); @@ -2340,7 +2340,7 @@ fn report_malformed_generics( ); "fully-qualified syntax is unsupported" } - MalformedGenerics::InvalidPathSeparator => "has invalid path separator", + MalformedGenerics::InvalidPathSeparator => "invalid path separator", MalformedGenerics::TooManyAngleBrackets => "too many angle brackets", MalformedGenerics::EmptyAngleBrackets => "empty angle brackets", }; diff --git a/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr b/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr index ae3708cf3a82b..12ef2d9497267 100644 --- a/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr +++ b/tests/rustdoc-ui/intra-doc/invalid-path-separator.stderr @@ -2,7 +2,7 @@ error: unresolved link to `std:path` --> $DIR/invalid-path-separator.rs:5:6 | LL | //! [std:path] - | ^^^^^^^^ has invalid path separator + | ^^^^^^^^ invalid path separator | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` note: the lint level is defined here @@ -15,25 +15,25 @@ error: unresolved link to `std:::path` --> $DIR/invalid-path-separator.rs:8:6 | LL | //! [std:::path] - | ^^^^^^^^^^ has invalid path separator + | ^^^^^^^^^^ invalid path separator error: unresolved link to `std::::path` --> $DIR/invalid-path-separator.rs:11:6 | LL | //! [std::::path] - | ^^^^^^^^^^^ has invalid path separator + | ^^^^^^^^^^^ invalid path separator error: unresolved link to `std:::::path` --> $DIR/invalid-path-separator.rs:14:6 | LL | //! [std:::::path] - | ^^^^^^^^^^^^ has invalid path separator + | ^^^^^^^^^^^^ invalid path separator error: unresolved link to `std2::::path` --> $DIR/invalid-path-separator.rs:17:6 | LL | //! [std2::::path] - | ^^^^^^^^^^^^ has invalid path separator + | ^^^^^^^^^^^^ invalid path separator error: aborting due to 5 previous errors diff --git a/tests/rustdoc-ui/intra-doc/malformed-generics.stderr b/tests/rustdoc-ui/intra-doc/malformed-generics.stderr index 08349fef88d38..fe918865a2e06 100644 --- a/tests/rustdoc-ui/intra-doc/malformed-generics.stderr +++ b/tests/rustdoc-ui/intra-doc/malformed-generics.stderr @@ -62,7 +62,7 @@ error: unresolved link to `Vec::new` --> $DIR/malformed-generics.rs:17:6 | LL | //! [Vec::new()] - | ^^^^^^^^^^^^^ has invalid path separator + | ^^^^^^^^^^^^^ invalid path separator error: unresolved link to `Vec<>` --> $DIR/malformed-generics.rs:19:6 diff --git a/tests/rustdoc-ui/intra-doc/malformed-paths.stderr b/tests/rustdoc-ui/intra-doc/malformed-paths.stderr index 25f02c31aeefa..9a50de114b417 100644 --- a/tests/rustdoc-ui/intra-doc/malformed-paths.stderr +++ b/tests/rustdoc-ui/intra-doc/malformed-paths.stderr @@ -2,7 +2,7 @@ error: unresolved link to `Type::` --> $DIR/malformed-paths.rs:5:7 | LL | //! [`Type::`] - | ^^^^^^ has invalid path separator + | ^^^^^^ invalid path separator | note: the lint level is defined here --> $DIR/malformed-paths.rs:2:9 diff --git a/tests/rustdoc-ui/intra-doc/warning.stderr b/tests/rustdoc-ui/intra-doc/warning.stderr index bc199740c4a27..b0b76f325da42 100644 --- a/tests/rustdoc-ui/intra-doc/warning.stderr +++ b/tests/rustdoc-ui/intra-doc/warning.stderr @@ -40,7 +40,7 @@ warning: unresolved link to `Qux:Y` --> $DIR/warning.rs:14:13 | LL | /// [Qux:Y] - | ^^^^^ has invalid path separator + | ^^^^^ invalid path separator | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` From c468ee3386798ffdc8bbcff172e3ea58f25e7150 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sun, 10 May 2026 19:28:57 -0700 Subject: [PATCH 26/36] Simplify `raw_eq` to `Transmute`+`Eq` for sizes with primitives --- .../rustc_mir_transform/src/instsimplify.rs | 69 +++++++++++++++++-- ..._array.InstSimplify-after-simplifycfg.diff | 25 +++++++ tests/mir-opt/instsimplify/raw_eq.rs | 27 ++++++++ ...q.eq_ipv4.PreCodegen.after.panic-abort.mir | 9 +-- ....eq_ipv4.PreCodegen.after.panic-unwind.mir | 9 +-- ...q.eq_ipv6.PreCodegen.after.panic-abort.mir | 9 +-- ....eq_ipv6.PreCodegen.after.panic-unwind.mir | 9 +-- tests/mir-opt/pre-codegen/array_eq.rs | 8 ++- 8 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 tests/mir-opt/instsimplify/raw_eq.inner_array.InstSimplify-after-simplifycfg.diff create mode 100644 tests/mir-opt/instsimplify/raw_eq.rs diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs index 8489535fb1bc6..5853f72aec8da 100644 --- a/compiler/rustc_mir_transform/src/instsimplify.rs +++ b/compiler/rustc_mir_transform/src/instsimplify.rs @@ -1,11 +1,12 @@ //! Performs various peephole optimizations. -use rustc_abi::ExternAbi; +use rustc_abi::{ExternAbi, Integer}; use rustc_hir::{LangItem, find_attr}; +use rustc_index::IndexVec; use rustc_middle::bug; use rustc_middle::mir::visit::MutVisitor; use rustc_middle::mir::*; -use rustc_middle::ty::layout::ValidityRequirement; +use rustc_middle::ty::layout::{IntegerExt, ValidityRequirement}; use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, layout}; use rustc_span::{Symbol, sym}; @@ -33,10 +34,10 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify { if !preserve_ub_checks { SimplifyUbCheck { tcx }.visit_body(body); } - let ctx = InstSimplifyContext { + let mut ctx = InstSimplifyContext { tcx, - local_decls: &body.local_decls, typing_env: body.typing_env(tcx), + local_decls: &mut body.local_decls, }; for block in body.basic_blocks.as_mut() { for statement in block.statements.iter_mut() { @@ -55,6 +56,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify { let terminator = block.terminator.as_mut().unwrap(); ctx.simplify_primitive_clone(terminator, &mut block.statements); ctx.simplify_size_or_align_of_val(terminator, &mut block.statements); + ctx.simplify_raw_eq(terminator, &mut block.statements); ctx.simplify_intrinsic_assert(terminator); ctx.simplify_nounwind_call(terminator); simplify_duplicate_switch_targets(terminator); @@ -68,7 +70,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify { struct InstSimplifyContext<'a, 'tcx> { tcx: TyCtxt<'tcx>, - local_decls: &'a LocalDecls<'tcx>, + local_decls: &'a mut IndexVec>, typing_env: ty::TypingEnv<'tcx>, } @@ -318,6 +320,63 @@ impl<'tcx> InstSimplifyContext<'_, 'tcx> { } } + /// Simplify `raw_eq` intrinsic calls to `Eq` when the type has the size of a primitive. + /// + /// For example, replace `raw_eq::<[u8; 4]>(a, b)` with `Eq(Transmute(a), Transmute(b))`. + fn simplify_raw_eq( + &mut self, + terminator: &mut Terminator<'tcx>, + statements: &mut Vec>, + ) { + let tcx = self.tcx; + let source_info = terminator.source_info; + let span = source_info.span; + if let TerminatorKind::Call { + func, args, destination, target: Some(destination_block), .. + } = &terminator.kind + && args.len() == 2 + && let Some((fn_def_id, generics)) = func.const_fn_def() + && tcx.is_intrinsic(fn_def_id, sym::raw_eq) + && let generic_ty = generics.type_at(0) + && let Ok(layout) = tcx.layout_of(self.typing_env.as_query_input(generic_ty)) + && let Ok(integer) = Integer::from_size(layout.size) + { + let ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, generic_ty); + let uint_ty = integer.to_ty(tcx, false); + + let mut transmute_operand = |op: &Operand<'tcx>| -> Operand<'tcx> { + let ref_local = self.local_decls.push(LocalDecl::new(ref_ty, span)); + statements.push(Statement::new( + source_info, + StatementKind::Assign(Box::new(( + Place::from(ref_local), + Rvalue::Use(op.clone(), WithRetag::Yes), + ))), + )); + let place = Place::from(ref_local).project_deeper(&[ProjectionElem::Deref], tcx); + let int_local = self.local_decls.push(LocalDecl::new(uint_ty, span)); + statements.push(Statement::new( + source_info, + StatementKind::Assign(Box::new(( + Place::from(int_local), + Rvalue::Cast(CastKind::Transmute, Operand::Copy(place), uint_ty), + ))), + )); + Operand::Move(Place::from(int_local)) + }; + let lhs_op = transmute_operand(&args[0].node); + let rhs_op = transmute_operand(&args[1].node); + statements.push(Statement::new( + source_info, + StatementKind::Assign(Box::new(( + *destination, + Rvalue::BinaryOp(BinOp::Eq, Box::new((lhs_op, rhs_op))), + ))), + )); + terminator.kind = TerminatorKind::Goto { target: *destination_block }; + } + } + fn simplify_nounwind_call(&self, terminator: &mut Terminator<'tcx>) { let TerminatorKind::Call { ref func, ref mut unwind, .. } = terminator.kind else { return; diff --git a/tests/mir-opt/instsimplify/raw_eq.inner_array.InstSimplify-after-simplifycfg.diff b/tests/mir-opt/instsimplify/raw_eq.inner_array.InstSimplify-after-simplifycfg.diff new file mode 100644 index 0000000000000..03881bc3f6afc --- /dev/null +++ b/tests/mir-opt/instsimplify/raw_eq.inner_array.InstSimplify-after-simplifycfg.diff @@ -0,0 +1,25 @@ +- // MIR for `inner_array` before InstSimplify-after-simplifycfg ++ // MIR for `inner_array` after InstSimplify-after-simplifycfg + + fn inner_array(_1: &&[i32; 2], _2: &&[i32; 2]) -> bool { + let mut _0: bool; ++ let mut _3: &[i32; 2]; ++ let mut _4: u64; ++ let mut _5: &[i32; 2]; ++ let mut _6: u64; + + bb0: { +- _0 = raw_eq::<[i32; 2]>(copy (*_1), copy (*_2)) -> [return: bb1, unwind unreachable]; ++ _3 = copy (*_1); ++ _4 = copy (*_3) as u64 (Transmute); ++ _5 = copy (*_2); ++ _6 = copy (*_5) as u64 (Transmute); ++ _0 = Eq(move _4, move _6); ++ goto -> bb1; + } + + bb1: { + return; + } + } + diff --git a/tests/mir-opt/instsimplify/raw_eq.rs b/tests/mir-opt/instsimplify/raw_eq.rs new file mode 100644 index 0000000000000..3a6dd2c26346e --- /dev/null +++ b/tests/mir-opt/instsimplify/raw_eq.rs @@ -0,0 +1,27 @@ +//@ test-mir-pass: InstSimplify-after-simplifycfg +#![crate_type = "lib"] +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +// Custom MIR so we can get an argument that's not just a local directly +use std::intrinsics::mir::*; +use std::intrinsics::raw_eq; + +// EMIT_MIR raw_eq.inner_array.InstSimplify-after-simplifycfg.diff +#[custom_mir(dialect = "runtime")] +pub fn inner_array(a: &&[i32; 2], b: &&[i32; 2]) -> bool { + // CHECK-LABEL: fn inner_array(_1: &&[i32; 2], _2: &&[i32; 2]) -> bool + // CHECK: [[AREF:_.+]] = copy (*_1); + // CHECK: [[AINT:_.+]] = copy (*[[AREF]]) as u64 (Transmute); + // CHECK: [[BREF:_.+]] = copy (*_2); + // CHECK: [[BINT:_.+]] = copy (*[[BREF]]) as u64 (Transmute); + // CHECK: _0 = Eq(move [[AINT]], move [[BINT]]); + mir! { + { + Call(RET = raw_eq(*a, *b), ReturnTo(ret), UnwindUnreachable()) + } + ret = { + Return() + } + } +} diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir index 85162b3a8dd2d..258be57f67c91 100644 --- a/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-abort.mir @@ -4,6 +4,8 @@ fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool { debug a => _1; debug b => _2; let mut _0: bool; + let mut _3: u32; + let mut _4: u32; scope 1 (inlined std::cmp::impls::::eq) { scope 2 (inlined array::equality::::eq) { scope 3 (inlined >::spec_eq) { @@ -12,10 +14,9 @@ fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool { } bb0: { - _0 = raw_eq::<[u8; 4]>(move _1, move _2) -> [return: bb1, unwind unreachable]; - } - - bb1: { + _3 = copy (*_1) as u32 (Transmute); + _4 = copy (*_2) as u32 (Transmute); + _0 = Eq(move _3, move _4); return; } } diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir index 85162b3a8dd2d..258be57f67c91 100644 --- a/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv4.PreCodegen.after.panic-unwind.mir @@ -4,6 +4,8 @@ fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool { debug a => _1; debug b => _2; let mut _0: bool; + let mut _3: u32; + let mut _4: u32; scope 1 (inlined std::cmp::impls::::eq) { scope 2 (inlined array::equality::::eq) { scope 3 (inlined >::spec_eq) { @@ -12,10 +14,9 @@ fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool { } bb0: { - _0 = raw_eq::<[u8; 4]>(move _1, move _2) -> [return: bb1, unwind unreachable]; - } - - bb1: { + _3 = copy (*_1) as u32 (Transmute); + _4 = copy (*_2) as u32 (Transmute); + _0 = Eq(move _3, move _4); return; } } diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir index 2eef5e72755f0..e367813450830 100644 --- a/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-abort.mir @@ -4,6 +4,8 @@ fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool { debug a => _1; debug b => _2; let mut _0: bool; + let mut _3: u128; + let mut _4: u128; scope 1 (inlined std::cmp::impls::::eq) { scope 2 (inlined array::equality::::eq) { scope 3 (inlined >::spec_eq) { @@ -12,10 +14,9 @@ fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool { } bb0: { - _0 = raw_eq::<[u16; 8]>(move _1, move _2) -> [return: bb1, unwind unreachable]; - } - - bb1: { + _3 = copy (*_1) as u128 (Transmute); + _4 = copy (*_2) as u128 (Transmute); + _0 = Eq(move _3, move _4); return; } } diff --git a/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir index 2eef5e72755f0..e367813450830 100644 --- a/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/array_eq.eq_ipv6.PreCodegen.after.panic-unwind.mir @@ -4,6 +4,8 @@ fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool { debug a => _1; debug b => _2; let mut _0: bool; + let mut _3: u128; + let mut _4: u128; scope 1 (inlined std::cmp::impls::::eq) { scope 2 (inlined array::equality::::eq) { scope 3 (inlined >::spec_eq) { @@ -12,10 +14,9 @@ fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool { } bb0: { - _0 = raw_eq::<[u16; 8]>(move _1, move _2) -> [return: bb1, unwind unreachable]; - } - - bb1: { + _3 = copy (*_1) as u128 (Transmute); + _4 = copy (*_2) as u128 (Transmute); + _0 = Eq(move _3, move _4); return; } } diff --git a/tests/mir-opt/pre-codegen/array_eq.rs b/tests/mir-opt/pre-codegen/array_eq.rs index 41ec79acf77b4..0bfe34eb37618 100644 --- a/tests/mir-opt/pre-codegen/array_eq.rs +++ b/tests/mir-opt/pre-codegen/array_eq.rs @@ -13,13 +13,17 @@ pub unsafe fn eq_odd_length(a: &[u8; 3], b: &[u8; 3]) -> bool { // EMIT_MIR array_eq.eq_ipv4.PreCodegen.after.mir pub unsafe fn eq_ipv4(a: &[u8; 4], b: &[u8; 4]) -> bool { // CHECK-LABEL: fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool - // CHECK: _0 = raw_eq::<[u8; 4]>(move _1, move _2) + // CHECK: [[A:_.+]] = copy (*_1) as u32 (Transmute); + // CHECK: [[B:_.+]] = copy (*_2) as u32 (Transmute); + // CHECK: _0 = Eq(move [[A]], move [[B]]); a == b } // EMIT_MIR array_eq.eq_ipv6.PreCodegen.after.mir pub unsafe fn eq_ipv6(a: &[u16; 8], b: &[u16; 8]) -> bool { // CHECK-LABEL: fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool - // CHECK: _0 = raw_eq::<[u16; 8]>(move _1, move _2) + // CHECK: [[A:_.+]] = copy (*_1) as u128 (Transmute); + // CHECK: [[B:_.+]] = copy (*_2) as u128 (Transmute); + // CHECK: _0 = Eq(move [[A]], move [[B]]); a == b } From 22d13e690a8c1ae5dd4cb9caa8148ca84e39f269 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 11 May 2026 16:19:53 +0900 Subject: [PATCH 27/36] document move expression closure lowering --- compiler/rustc_ast_lowering/src/expr.rs | 43 +++++++++++++++++++++++-- compiler/rustc_ast_lowering/src/lib.rs | 1 + 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 9fc69470cf546..143410f62a3d0 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1157,8 +1157,10 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span)) } - // Lowers closure expressions, including the `move(...)` desugaring for - // plain closures. + // Entry point for `ExprKind::Closure`. Plain closures go through + // `lower_expr_plain_closure_with_move_exprs`, which can wrap the lowered + // closure in `let` initializers for `move(...)`. Coroutine closures keep the + // existing coroutine-specific path and reject `move(...)` for now. fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> { let expr_hir_id = self.lower_node_id(e.id); let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); @@ -1199,6 +1201,36 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + /// Lowers a plain closure expression and wraps it in an outer block if the + /// closure body used `move(...)`. + /// + /// The lowering is split this way because `move(...)` initializers must be + /// evaluated before the closure is created, but the closure body must still + /// lower each `move(...)` occurrence as a use of the synthetic local that + /// will be introduced by that outer block. For example: + /// + /// ```ignore (illustrative) + /// || (move(move(foo.clone()))).len() + /// ``` + /// + /// first lowers the closure body roughly as `|| __move_expr_1.len()` while + /// recording two occurrences: + /// + /// ```ignore (illustrative) + /// move(foo.clone()) -> __move_expr_0 + /// move(move(foo.clone())) -> __move_expr_1 + /// ``` + /// + /// This method then lowers the recorded initializers in order and builds the + /// surrounding block: + /// + /// ```ignore (illustrative) + /// { + /// let __move_expr_0 = foo.clone(); + /// let __move_expr_1 = __move_expr_0; + /// || __move_expr_1.len() + /// } + /// ``` fn lower_expr_plain_closure_with_move_exprs( &mut self, expr_hir_id: HirId, @@ -1283,6 +1315,9 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(whole_span, hir::ExprKind::Block(block, None)) } + // Lowers the actual plain closure node and body. The body is lowered while a + // `MoveExprState` is active, so `move(...)` occurrences become synthetic + // local uses and the caller can later add the matching initializers. fn lower_expr_closure( &mut self, attrs: &[rustc_hir::Attribute], @@ -1393,6 +1428,10 @@ impl<'hir> LoweringContext<'_, 'hir> { (binder, params) } + // Coroutine closures are lowered separately because they build a different + // body shape. This path pushes `None` for `move_expr_bindings`, so any + // `move(...)` in the coroutine body gets a targeted unsupported-position + // error instead of being collected like a plain closure occurrence. fn lower_expr_coroutine_closure( &mut self, binder: &ClosureBinder, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index d8a7b6e93ab0c..6abc42213069a 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -158,6 +158,7 @@ struct LoweringContext<'a, 'hir> { allow_async_fn_traits: Arc<[Symbol]>, delayed_lints: Vec, + /// Stack of `move(...)` collection states. A plain closure body pushes /// `Some`, so `move(...)` expressions can record the generated locals they /// should lower to. Nested bodies that cannot use `move(...)` push `None`. From ae79767b92de422e3faedfa16fe1cf569c355094 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 11 May 2026 16:20:19 +0900 Subject: [PATCH 28/36] split nested move expression coverage --- tests/ui/move-expr/nested-move-expr.rs | 12 ++++++++++++ tests/ui/move-expr/plain-closure.rs | 7 ------- 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 tests/ui/move-expr/nested-move-expr.rs diff --git a/tests/ui/move-expr/nested-move-expr.rs b/tests/ui/move-expr/nested-move-expr.rs new file mode 100644 index 0000000000000..cf3364c50aad7 --- /dev/null +++ b/tests/ui/move-expr/nested-move-expr.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let v = "Hello, Ferris".to_string(); + let r = || { + || (move(move(v.clone()))).len() + }; + + assert_eq!(r()(), v.len()); +} diff --git a/tests/ui/move-expr/plain-closure.rs b/tests/ui/move-expr/plain-closure.rs index dc3e3b2956a15..788c631cf5fdf 100644 --- a/tests/ui/move-expr/plain-closure.rs +++ b/tests/ui/move-expr/plain-closure.rs @@ -19,11 +19,4 @@ fn main() { }; c(); - let v = "Hello, Ferris".to_string(); - let r = || { - || { - (move(move(v.clone()))).len() - } - }; - assert_eq!(r()(), v.len()); } From 632db42affee2af29125c468294eb611597c79b3 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 11 May 2026 16:21:14 +0900 Subject: [PATCH 29/36] extend nested closure move expression tests --- .../nested-closures-mutate-before-call.rs | 17 +++++++++++++++++ .../nested-closures-mutate-before-call.stderr | 17 +++++++++++++++++ tests/ui/move-expr/nested-closures.rs | 18 +++++++++++++++--- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 tests/ui/move-expr/nested-closures-mutate-before-call.rs create mode 100644 tests/ui/move-expr/nested-closures-mutate-before-call.stderr diff --git a/tests/ui/move-expr/nested-closures-mutate-before-call.rs b/tests/ui/move-expr/nested-closures-mutate-before-call.rs new file mode 100644 index 0000000000000..52fae1d96c223 --- /dev/null +++ b/tests/ui/move-expr/nested-closures-mutate-before-call.rs @@ -0,0 +1,17 @@ +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let mut x = String::from("hello"); + + let outer = || { + let inner = || move(x.clone()); + let y = inner(); + assert_eq!(y, "hello"); + assert_eq!(x, "hello"); + }; + + x.push_str("more test"); //~ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable + + outer(); +} diff --git a/tests/ui/move-expr/nested-closures-mutate-before-call.stderr b/tests/ui/move-expr/nested-closures-mutate-before-call.stderr new file mode 100644 index 0000000000000..f222f8be63c35 --- /dev/null +++ b/tests/ui/move-expr/nested-closures-mutate-before-call.stderr @@ -0,0 +1,17 @@ +error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable + --> $DIR/nested-closures-mutate-before-call.rs:14:5 + | +LL | let outer = || { + | -- immutable borrow occurs here +LL | let inner = || move(x.clone()); + | - first borrow occurs due to use of `x` in closure +... +LL | x.push_str("more test"); + | ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +LL | +LL | outer(); + | ----- immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/move-expr/nested-closures.rs b/tests/ui/move-expr/nested-closures.rs index b472dca4c0024..feb474f7d0e1d 100644 --- a/tests/ui/move-expr/nested-closures.rs +++ b/tests/ui/move-expr/nested-closures.rs @@ -2,14 +2,26 @@ #![allow(incomplete_features)] #![feature(move_expr)] +use std::sync::Arc; + fn main() { - let x = String::from("hello"); + let x = Arc::new(String::from("hello")); + assert_eq!(Arc::strong_count(&x), 1); + let outer = || { + assert_eq!(Arc::strong_count(&x), 1); let inner = || move(x.clone()); + assert_eq!(Arc::strong_count(&x), 2); let y = inner(); - assert_eq!(y, "hello"); - assert_eq!(x, "hello"); + assert_eq!(&*y, "hello"); + assert_eq!(Arc::strong_count(&x), 2); + drop(y); + assert_eq!(Arc::strong_count(&x), 1); }; + assert_eq!(Arc::strong_count(&x), 1); + // `outer` captures `x` by reference, and `inner` takes ownership of a clone. + println!("{x}"); outer(); + assert_eq!(Arc::strong_count(&x), 1); } From cb384bfd1e4c2788c7c043390f43febfaa293f84 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 11 May 2026 16:21:46 +0900 Subject: [PATCH 30/36] reject move expressions in async closures --- tests/ui/move-expr/async-closures.rs | 11 +++++++++++ tests/ui/move-expr/async-closures.stderr | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/ui/move-expr/async-closures.rs create mode 100644 tests/ui/move-expr/async-closures.stderr diff --git a/tests/ui/move-expr/async-closures.rs b/tests/ui/move-expr/async-closures.rs new file mode 100644 index 0000000000000..eea93f02b807a --- /dev/null +++ b/tests/ui/move-expr/async-closures.rs @@ -0,0 +1,11 @@ +//@ edition: 2021 +#![allow(incomplete_features)] +#![feature(move_expr)] + +fn main() { + let s = String::from("hello"); + let _ = async || { + move(s); + //~^ ERROR `move(expr)` is only supported in plain closures + }; +} diff --git a/tests/ui/move-expr/async-closures.stderr b/tests/ui/move-expr/async-closures.stderr new file mode 100644 index 0000000000000..d0fd5c8ee7df0 --- /dev/null +++ b/tests/ui/move-expr/async-closures.stderr @@ -0,0 +1,8 @@ +error: `move(expr)` is only supported in plain closures + --> $DIR/async-closures.rs:8:9 + | +LL | move(s); + | ^^^^ + +error: aborting due to 1 previous error + From 2af21c3f0d2fc260d7fedc462c45c9e428d7753f Mon Sep 17 00:00:00 2001 From: Flakebi Date: Wed, 6 May 2026 10:40:26 +0200 Subject: [PATCH 31/36] Show intrinsics::gpu in docs It was previously not visible (unless compiling docs for amdgpu, which I guess nobody does). Follow other specific intrinsics and always include them in docs. --- library/core/src/intrinsics/gpu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/intrinsics/gpu.rs b/library/core/src/intrinsics/gpu.rs index 43cb7251c3c88..8ea894b3955d4 100644 --- a/library/core/src/intrinsics/gpu.rs +++ b/library/core/src/intrinsics/gpu.rs @@ -47,7 +47,7 @@ #[rustc_intrinsic] #[rustc_nounwind] #[unstable(feature = "gpu_launch_sized_workgroup_mem", issue = "135513")] -#[cfg(any(target_arch = "amdgpu", target_arch = "nvptx64"))] +#[cfg(any(doc, target_arch = "amdgpu", target_arch = "nvptx64"))] pub fn gpu_launch_sized_workgroup_mem() -> *mut T; /// Returns a pointer to the HSA kernel dispatch packet. @@ -63,6 +63,6 @@ pub fn gpu_launch_sized_workgroup_mem() -> *mut T; /// [hsa.h]: https://github.com/ROCm/rocm-systems/blob/rocm-7.1.0/projects/rocr-runtime/runtime/hsa-runtime/inc/hsa.h#L2959 #[rustc_nounwind] #[rustc_intrinsic] -#[cfg(target_arch = "amdgpu")] +#[cfg(any(doc, target_arch = "amdgpu"))] #[must_use = "returns a pointer that does nothing unless used"] pub fn amdgpu_dispatch_ptr() -> *const (); From b37dbc42e7d7d7e2624244dbae215ed6379ce8ac Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 11 May 2026 13:28:19 +0200 Subject: [PATCH 32/36] (f)chmod: add missing isolation checks --- src/tools/miri/src/shims/unix/fs.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 8df54616b5625..419157aac6d4e 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -863,6 +863,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let path = this.read_path_from_c_str(path_ptr)?; + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`chmod`", reject_with)?; + return this.set_last_error_and_return_i32(LibcError("EACCES")); + } + let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; if let Err(err) = fs::set_permissions(path, permissions) { return this.set_last_error_and_return_i32(IoError::HostError(err)); @@ -885,6 +891,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("`fchmod` is only supported on regular files") }; + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`fchmod`", reject_with)?; + return this.set_last_error_and_return_i32(LibcError("EACCES")); + } + let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; if let Err(err) = file.file.set_permissions(permissions) { return this.set_last_error_and_return_i32(IoError::HostError(err)); From ec2eaffd5a90e4a1fb854ddb53a78c365a05f268 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 11 May 2026 22:15:12 +0900 Subject: [PATCH 33/36] move closure lowering into expr::closure --- compiler/rustc_ast_lowering/src/expr.rs | 366 +---------------- .../rustc_ast_lowering/src/expr/closure.rs | 377 ++++++++++++++++++ 2 files changed, 384 insertions(+), 359 deletions(-) create mode 100644 compiler/rustc_ast_lowering/src/expr/closure.rs diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 143410f62a3d0..87e1d9aa7a114 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -17,17 +17,19 @@ use rustc_span::{ByteSymbol, DUMMY_SP, DesugaringKind, Ident, Span, Spanned, Sym use thin_vec::{ThinVec, thin_vec}; use visit::{Visitor, walk_expr}; +mod closure; + use super::errors::{ - AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, ClosureCannotBeStatic, - CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment, - InclusiveRangeWithNoEnd, MatchArmWithNoBody, MoveExprOnlyInPlainClosures, NeverPatternWithBody, - NeverPatternWithGuard, UnderscoreExprLhsAssign, + AsyncCoroutinesNotSupported, AwaitOnlyInAsyncFnAndBlocks, + FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody, + MoveExprOnlyInPlainClosures, NeverPatternWithBody, NeverPatternWithGuard, + UnderscoreExprLhsAssign, }; use super::{ GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode, ResolverAstLoweringExt, }; use crate::errors::{InvalidLegacyConstGenericArg, UseConstGenericArg, YieldInClosure}; -use crate::{AllowReturnTypeNotation, FnDeclKind, ImplTraitPosition, TryBlockScope}; +use crate::{AllowReturnTypeNotation, ImplTraitPosition, TryBlockScope}; pub(super) struct WillCreateDefIdsVisitor; @@ -1157,360 +1159,6 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Use(self.lower_expr(expr), self.lower_span(use_kw_span)) } - // Entry point for `ExprKind::Closure`. Plain closures go through - // `lower_expr_plain_closure_with_move_exprs`, which can wrap the lowered - // closure in `let` initializers for `move(...)`. Coroutine closures keep the - // existing coroutine-specific path and reject `move(...)` for now. - fn lower_expr_closure_expr(&mut self, e: &Expr, closure: &Closure) -> hir::Expr<'hir> { - let expr_hir_id = self.lower_node_id(e.id); - let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); - - match closure.coroutine_kind { - // FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too. - // For the first step, we only support plain closures. - Some(coroutine_kind) => hir::Expr { - hir_id: expr_hir_id, - kind: self.lower_expr_coroutine_closure( - &closure.binder, - closure.capture_clause, - e.id, - expr_hir_id, - coroutine_kind, - closure.constness, - &closure.fn_decl, - &closure.body, - closure.fn_decl_span, - closure.fn_arg_span, - ), - span: self.lower_span(e.span), - }, - None => self.lower_expr_plain_closure_with_move_exprs( - expr_hir_id, - attrs, - &closure.binder, - closure.capture_clause, - e.id, - closure.constness, - closure.movability, - &closure.fn_decl, - &closure.body, - closure.fn_decl_span, - closure.fn_arg_span, - e.span, - ), - } - } - - /// Lowers a plain closure expression and wraps it in an outer block if the - /// closure body used `move(...)`. - /// - /// The lowering is split this way because `move(...)` initializers must be - /// evaluated before the closure is created, but the closure body must still - /// lower each `move(...)` occurrence as a use of the synthetic local that - /// will be introduced by that outer block. For example: - /// - /// ```ignore (illustrative) - /// || (move(move(foo.clone()))).len() - /// ``` - /// - /// first lowers the closure body roughly as `|| __move_expr_1.len()` while - /// recording two occurrences: - /// - /// ```ignore (illustrative) - /// move(foo.clone()) -> __move_expr_0 - /// move(move(foo.clone())) -> __move_expr_1 - /// ``` - /// - /// This method then lowers the recorded initializers in order and builds the - /// surrounding block: - /// - /// ```ignore (illustrative) - /// { - /// let __move_expr_0 = foo.clone(); - /// let __move_expr_1 = __move_expr_0; - /// || __move_expr_1.len() - /// } - /// ``` - fn lower_expr_plain_closure_with_move_exprs( - &mut self, - expr_hir_id: HirId, - attrs: &[rustc_hir::Attribute], - binder: &ClosureBinder, - capture_clause: CaptureBy, - closure_id: NodeId, - constness: Const, - movability: Movability, - decl: &FnDecl, - body: &Expr, - fn_decl_span: Span, - fn_arg_span: Span, - whole_span: Span, - ) -> hir::Expr<'hir> { - let (closure_kind, move_expr_state) = self.lower_expr_closure( - attrs, - binder, - capture_clause, - closure_id, - constness, - movability, - decl, - body, - fn_decl_span, - fn_arg_span, - ); - - if move_expr_state.occurrences.is_empty() { - return hir::Expr { - hir_id: expr_hir_id, - kind: closure_kind, - span: self.lower_span(whole_span), - }; - } - - let initializers = MoveExprInitializerFinder::collect(body) - .into_iter() - .map(|initializer| (initializer.id, initializer.expr)) - .collect::>(); - let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len()); - let mut initializer_bindings = NodeMap::default(); - for occurrence in &move_expr_state.occurrences { - // Evaluate the expression inside `move(...)` before creating the - // closure and store it in a synthetic local: - // `|| move(foo).bar` becomes roughly - // `let __move_expr_0 = foo; || __move_expr_0.bar`. - let expr = initializers[&occurrence.id]; - let init = if initializer_bindings.is_empty() { - self.lower_expr(expr) - } else { - // Earlier entries cover nested `move(...)` expressions that - // appear inside this initializer, as in - // `move(move(foo.clone()))`. - let (init, _) = self.with_move_expr_bindings( - Some(MoveExprState { - bindings: initializer_bindings.clone(), - occurrences: Vec::new(), - }), - |this| this.lower_expr(expr), - ); - init - }; - stmts.push(self.stmt_let_pat( - None, - expr.span, - Some(init), - occurrence.pat, - hir::LocalSource::Normal, - )); - initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding)); - } - - let closure_expr = self.arena.alloc(hir::Expr { - hir_id: expr_hir_id, - kind: closure_kind, - span: self.lower_span(whole_span), - }); - - let stmts = self.arena.alloc_from_iter(stmts); - let block = self.block_all(whole_span, stmts, Some(closure_expr)); - self.expr(whole_span, hir::ExprKind::Block(block, None)) - } - - // Lowers the actual plain closure node and body. The body is lowered while a - // `MoveExprState` is active, so `move(...)` occurrences become synthetic - // local uses and the caller can later add the matching initializers. - fn lower_expr_closure( - &mut self, - attrs: &[rustc_hir::Attribute], - binder: &ClosureBinder, - capture_clause: CaptureBy, - closure_id: NodeId, - constness: Const, - movability: Movability, - decl: &FnDecl, - body: &Expr, - fn_decl_span: Span, - fn_arg_span: Span, - ) -> (hir::ExprKind<'hir>, MoveExprState<'hir>) { - let closure_def_id = self.local_def_id(closure_id); - let (binder_clause, generic_params) = self.lower_closure_binder(binder); - - let ((body_id, closure_kind), move_expr_state) = self.with_new_scopes(fn_decl_span, move |this| { - let mut coroutine_kind = - find_attr!(attrs, Coroutine => hir::CoroutineKind::Coroutine(Movability::Movable)); - - this.with_move_expr_bindings(Some(MoveExprState::default()), |this| { - // FIXME(contracts): Support contracts on closures? - let body_id = this.lower_fn_body(decl, None, |this| { - this.coroutine_kind = coroutine_kind; - let e = this.lower_expr_mut(body); - coroutine_kind = this.coroutine_kind; - e - }); - let coroutine_option = - this.closure_movability_for_fn(decl, fn_decl_span, coroutine_kind, movability); - (body_id, coroutine_option) - }) - }); - let Some(move_expr_state) = move_expr_state else { - span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state"); - }; - let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter( - move_expr_state.occurrences.iter().filter_map(|occurrence| { - occurrence - .explicit_capture - .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding }) - }), - ); - - let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); - // Lower outside new scope to preserve `is_in_loop_condition`. - let fn_decl = self.lower_fn_decl(decl, closure_id, fn_decl_span, FnDeclKind::Closure, None); - - let c = self.arena.alloc(hir::Closure { - def_id: closure_def_id, - binder: binder_clause, - capture_clause: self.lower_capture_clause(capture_clause), - bound_generic_params, - fn_decl, - body: body_id, - fn_decl_span: self.lower_span(fn_decl_span), - fn_arg_span: Some(self.lower_span(fn_arg_span)), - kind: closure_kind, - constness: self.lower_constness(constness), - explicit_captures, - }); - - (hir::ExprKind::Closure(c), move_expr_state) - } - - fn closure_movability_for_fn( - &mut self, - decl: &FnDecl, - fn_decl_span: Span, - coroutine_kind: Option, - movability: Movability, - ) -> hir::ClosureKind { - match coroutine_kind { - Some(hir::CoroutineKind::Coroutine(_)) => { - if decl.inputs.len() > 1 { - self.dcx().emit_err(CoroutineTooManyParameters { fn_decl_span }); - } - hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(movability)) - } - Some( - hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _) - | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _) - | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _), - ) => { - panic!("non-`async`/`gen` closure body turned `async`/`gen` during lowering"); - } - None => { - if movability == Movability::Static { - self.dcx().emit_err(ClosureCannotBeStatic { fn_decl_span }); - } - hir::ClosureKind::Closure - } - } - } - - fn lower_closure_binder<'c>( - &mut self, - binder: &'c ClosureBinder, - ) -> (hir::ClosureBinder, &'c [GenericParam]) { - let (binder, params) = match binder { - ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]), - ClosureBinder::For { span, generic_params } => { - let span = self.lower_span(*span); - (hir::ClosureBinder::For { span }, &**generic_params) - } - }; - - (binder, params) - } - - // Coroutine closures are lowered separately because they build a different - // body shape. This path pushes `None` for `move_expr_bindings`, so any - // `move(...)` in the coroutine body gets a targeted unsupported-position - // error instead of being collected like a plain closure occurrence. - fn lower_expr_coroutine_closure( - &mut self, - binder: &ClosureBinder, - capture_clause: CaptureBy, - closure_id: NodeId, - closure_hir_id: HirId, - coroutine_kind: CoroutineKind, - constness: Const, - decl: &FnDecl, - body: &Expr, - fn_decl_span: Span, - fn_arg_span: Span, - ) -> hir::ExprKind<'hir> { - let closure_def_id = self.local_def_id(closure_id); - let (binder_clause, generic_params) = self.lower_closure_binder(binder); - - let coroutine_desugaring = match coroutine_kind { - CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async, - CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen, - CoroutineKind::AsyncGen { span, .. } => { - span_bug!(span, "only async closures and `iter!` closures are supported currently") - } - }; - - let body = self.with_new_scopes(fn_decl_span, |this| { - let inner_decl = - FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) }; - - // Transform `async |x: u8| -> X { ... }` into - // `|x: u8| || -> X { ... }`. - let body_id = this.lower_body(|this| { - let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| { - this.lower_coroutine_body_with_moved_arguments( - &inner_decl, - |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), - fn_decl_span, - body.span, - coroutine_kind, - hir::CoroutineSource::Closure, - ) - }); - - this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id); - - (parameters, expr) - }); - body_id - }); - - let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); - // We need to lower the declaration outside the new scope, because we - // have to conserve the state of being inside a loop condition for the - // closure argument types. - let fn_decl = - self.lower_fn_decl(&decl, closure_id, fn_decl_span, FnDeclKind::Closure, None); - - if let Const::Yes(span) = constness { - self.dcx().span_err(span, "const coroutines are not supported"); - } - - let c = self.arena.alloc(hir::Closure { - def_id: closure_def_id, - binder: binder_clause, - capture_clause: self.lower_capture_clause(capture_clause), - bound_generic_params, - fn_decl, - body, - fn_decl_span: self.lower_span(fn_decl_span), - fn_arg_span: Some(self.lower_span(fn_arg_span)), - // Lower this as a `CoroutineClosure`. That will ensure that HIR typeck - // knows that a `FnDecl` output type like `-> &str` actually means - // "coroutine that returns &str", rather than directly returning a `&str`. - kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring), - constness: self.lower_constness(constness), - explicit_captures: &[], - }); - hir::ExprKind::Closure(c) - } - /// Destructure the LHS of complex assignments. /// For instance, lower `(a, b) = t` to `{ let (lhs1, lhs2) = t; a = lhs1; b = lhs2; }`. fn lower_expr_assign( diff --git a/compiler/rustc_ast_lowering/src/expr/closure.rs b/compiler/rustc_ast_lowering/src/expr/closure.rs new file mode 100644 index 0000000000000..1c2a22e475232 --- /dev/null +++ b/compiler/rustc_ast_lowering/src/expr/closure.rs @@ -0,0 +1,377 @@ +use rustc_ast::node_id::NodeMap; +use rustc_ast::*; +use rustc_hir as hir; +use rustc_hir::{HirId, Target, find_attr}; +use rustc_middle::span_bug; +use rustc_span::Span; + +use super::{LoweringContext, MoveExprInitializerFinder, MoveExprState}; +use crate::FnDeclKind; +use crate::errors::{ClosureCannotBeStatic, CoroutineTooManyParameters}; + +impl<'hir> LoweringContext<'_, 'hir> { + // Entry point for `ExprKind::Closure`. Plain closures go through + // `lower_expr_plain_closure_with_move_exprs`, which can wrap the lowered + // closure in `let` initializers for `move(...)`. Coroutine closures keep the + // existing coroutine-specific path and reject `move(...)` for now. + pub(super) fn lower_expr_closure_expr( + &mut self, + e: &Expr, + closure: &Closure, + ) -> hir::Expr<'hir> { + let expr_hir_id = self.lower_node_id(e.id); + let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); + + match closure.coroutine_kind { + // FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too. + // For the first step, we only support plain closures. + Some(coroutine_kind) => hir::Expr { + hir_id: expr_hir_id, + kind: self.lower_expr_coroutine_closure( + &closure.binder, + closure.capture_clause, + e.id, + expr_hir_id, + coroutine_kind, + closure.constness, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + ), + span: self.lower_span(e.span), + }, + None => self.lower_expr_plain_closure_with_move_exprs( + expr_hir_id, + attrs, + &closure.binder, + closure.capture_clause, + e.id, + closure.constness, + closure.movability, + &closure.fn_decl, + &closure.body, + closure.fn_decl_span, + closure.fn_arg_span, + e.span, + ), + } + } + + /// Lowers a plain closure expression and wraps it in an outer block if the + /// closure body used `move(...)`. + /// + /// The lowering is split this way because `move(...)` initializers must be + /// evaluated before the closure is created, but the closure body must still + /// lower each `move(...)` occurrence as a use of the synthetic local that + /// will be introduced by that outer block. For example: + /// + /// ```ignore (illustrative) + /// || (move(move(foo.clone()))).len() + /// ``` + /// + /// first lowers the closure body roughly as `|| __move_expr_1.len()` while + /// recording two occurrences: + /// + /// ```ignore (illustrative) + /// move(foo.clone()) -> __move_expr_0 + /// move(move(foo.clone())) -> __move_expr_1 + /// ``` + /// + /// This method then lowers the recorded initializers in order and builds the + /// surrounding block: + /// + /// ```ignore (illustrative) + /// { + /// let __move_expr_0 = foo.clone(); + /// let __move_expr_1 = __move_expr_0; + /// || __move_expr_1.len() + /// } + /// ``` + fn lower_expr_plain_closure_with_move_exprs( + &mut self, + expr_hir_id: HirId, + attrs: &[hir::Attribute], + binder: &ClosureBinder, + capture_clause: CaptureBy, + closure_id: NodeId, + constness: Const, + movability: Movability, + decl: &FnDecl, + body: &Expr, + fn_decl_span: Span, + fn_arg_span: Span, + whole_span: Span, + ) -> hir::Expr<'hir> { + let (closure_kind, move_expr_state) = self.lower_expr_closure( + attrs, + binder, + capture_clause, + closure_id, + constness, + movability, + decl, + body, + fn_decl_span, + fn_arg_span, + ); + + if move_expr_state.occurrences.is_empty() { + return hir::Expr { + hir_id: expr_hir_id, + kind: closure_kind, + span: self.lower_span(whole_span), + }; + } + + let initializers = MoveExprInitializerFinder::collect(body) + .into_iter() + .map(|initializer| (initializer.id, initializer.expr)) + .collect::>(); + let mut stmts = Vec::with_capacity(move_expr_state.occurrences.len()); + let mut initializer_bindings = NodeMap::default(); + for occurrence in &move_expr_state.occurrences { + // Evaluate the expression inside `move(...)` before creating the + // closure and store it in a synthetic local: + // `|| move(foo).bar` becomes roughly + // `let __move_expr_0 = foo; || __move_expr_0.bar`. + let expr = initializers[&occurrence.id]; + let init = if initializer_bindings.is_empty() { + self.lower_expr(expr) + } else { + // Earlier entries cover nested `move(...)` expressions that + // appear inside this initializer, as in + // `move(move(foo.clone()))`. + let (init, _) = self.with_move_expr_bindings( + Some(MoveExprState { + bindings: initializer_bindings.clone(), + occurrences: Vec::new(), + }), + |this| this.lower_expr(expr), + ); + init + }; + stmts.push(self.stmt_let_pat( + None, + expr.span, + Some(init), + occurrence.pat, + hir::LocalSource::Normal, + )); + initializer_bindings.insert(occurrence.id, (occurrence.ident, occurrence.binding)); + } + + let closure_expr = self.arena.alloc(hir::Expr { + hir_id: expr_hir_id, + kind: closure_kind, + span: self.lower_span(whole_span), + }); + + let stmts = self.arena.alloc_from_iter(stmts); + let block = self.block_all(whole_span, stmts, Some(closure_expr)); + self.expr(whole_span, hir::ExprKind::Block(block, None)) + } + + // Lowers the actual plain closure node and body. The body is lowered while a + // `MoveExprState` is active, so `move(...)` occurrences become synthetic + // local uses and the caller can later add the matching initializers. + fn lower_expr_closure( + &mut self, + attrs: &[hir::Attribute], + binder: &ClosureBinder, + capture_clause: CaptureBy, + closure_id: NodeId, + constness: Const, + movability: Movability, + decl: &FnDecl, + body: &Expr, + fn_decl_span: Span, + fn_arg_span: Span, + ) -> (hir::ExprKind<'hir>, MoveExprState<'hir>) { + let closure_def_id = self.local_def_id(closure_id); + let (binder_clause, generic_params) = self.lower_closure_binder(binder); + + let ((body_id, closure_kind), move_expr_state) = + self.with_new_scopes(fn_decl_span, move |this| { + let mut coroutine_kind = find_attr!( + attrs, + Coroutine => hir::CoroutineKind::Coroutine(Movability::Movable) + ); + + this.with_move_expr_bindings(Some(MoveExprState::default()), |this| { + // FIXME(contracts): Support contracts on closures? + let body_id = this.lower_fn_body(decl, None, |this| { + this.coroutine_kind = coroutine_kind; + let e = this.lower_expr_mut(body); + coroutine_kind = this.coroutine_kind; + e + }); + let coroutine_option = this.closure_movability_for_fn( + decl, + fn_decl_span, + coroutine_kind, + movability, + ); + (body_id, coroutine_option) + }) + }); + let Some(move_expr_state) = move_expr_state else { + span_bug!(fn_decl_span, "plain closure lowering did not return `move(...)` state"); + }; + let explicit_captures: &'hir [hir::ExplicitCapture] = self.arena.alloc_from_iter( + move_expr_state.occurrences.iter().filter_map(|occurrence| { + occurrence + .explicit_capture + .then_some(hir::ExplicitCapture { var_hir_id: occurrence.binding }) + }), + ); + + let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); + // Lower outside new scope to preserve `is_in_loop_condition`. + let fn_decl = self.lower_fn_decl(decl, closure_id, fn_decl_span, FnDeclKind::Closure, None); + + let c = self.arena.alloc(hir::Closure { + def_id: closure_def_id, + binder: binder_clause, + capture_clause: self.lower_capture_clause(capture_clause), + bound_generic_params, + fn_decl, + body: body_id, + fn_decl_span: self.lower_span(fn_decl_span), + fn_arg_span: Some(self.lower_span(fn_arg_span)), + kind: closure_kind, + constness: self.lower_constness(constness), + explicit_captures, + }); + + (hir::ExprKind::Closure(c), move_expr_state) + } + + fn closure_movability_for_fn( + &mut self, + decl: &FnDecl, + fn_decl_span: Span, + coroutine_kind: Option, + movability: Movability, + ) -> hir::ClosureKind { + match coroutine_kind { + Some(hir::CoroutineKind::Coroutine(_)) => { + if decl.inputs.len() > 1 { + self.dcx().emit_err(CoroutineTooManyParameters { fn_decl_span }); + } + hir::ClosureKind::Coroutine(hir::CoroutineKind::Coroutine(movability)) + } + Some( + hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _) + | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _) + | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::AsyncGen, _), + ) => { + panic!("non-`async`/`gen` closure body turned `async`/`gen` during lowering"); + } + None => { + if movability == Movability::Static { + self.dcx().emit_err(ClosureCannotBeStatic { fn_decl_span }); + } + hir::ClosureKind::Closure + } + } + } + + fn lower_closure_binder<'c>( + &mut self, + binder: &'c ClosureBinder, + ) -> (hir::ClosureBinder, &'c [GenericParam]) { + let (binder, params) = match binder { + ClosureBinder::NotPresent => (hir::ClosureBinder::Default, &[][..]), + ClosureBinder::For { span, generic_params } => { + let span = self.lower_span(*span); + (hir::ClosureBinder::For { span }, &**generic_params) + } + }; + + (binder, params) + } + + // Coroutine closures are lowered separately because they build a different + // body shape. This path pushes `None` for `move_expr_bindings`, so any + // `move(...)` in the coroutine body gets a targeted unsupported-position + // error instead of being collected like a plain closure occurrence. + fn lower_expr_coroutine_closure( + &mut self, + binder: &ClosureBinder, + capture_clause: CaptureBy, + closure_id: NodeId, + closure_hir_id: HirId, + coroutine_kind: CoroutineKind, + constness: Const, + decl: &FnDecl, + body: &Expr, + fn_decl_span: Span, + fn_arg_span: Span, + ) -> hir::ExprKind<'hir> { + let closure_def_id = self.local_def_id(closure_id); + let (binder_clause, generic_params) = self.lower_closure_binder(binder); + + let coroutine_desugaring = match coroutine_kind { + CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async, + CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen, + CoroutineKind::AsyncGen { span, .. } => { + span_bug!(span, "only async closures and `iter!` closures are supported currently") + } + }; + + let body = self.with_new_scopes(fn_decl_span, |this| { + let inner_decl = + FnDecl { inputs: decl.inputs.clone(), output: FnRetTy::Default(fn_decl_span) }; + + // Transform `async |x: u8| -> X { ... }` into + // `|x: u8| || -> X { ... }`. + let body_id = this.lower_body(|this| { + let ((parameters, expr), _) = this.with_move_expr_bindings(None, |this| { + this.lower_coroutine_body_with_moved_arguments( + &inner_decl, + |this| this.with_new_scopes(fn_decl_span, |this| this.lower_expr_mut(body)), + fn_decl_span, + body.span, + coroutine_kind, + hir::CoroutineSource::Closure, + ) + }); + + this.maybe_forward_track_caller(body.span, closure_hir_id, expr.hir_id); + + (parameters, expr) + }); + body_id + }); + + let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params); + // We need to lower the declaration outside the new scope, because we + // have to conserve the state of being inside a loop condition for the + // closure argument types. + let fn_decl = + self.lower_fn_decl(&decl, closure_id, fn_decl_span, FnDeclKind::Closure, None); + + if let Const::Yes(span) = constness { + self.dcx().span_err(span, "const coroutines are not supported"); + } + + let c = self.arena.alloc(hir::Closure { + def_id: closure_def_id, + binder: binder_clause, + capture_clause: self.lower_capture_clause(capture_clause), + bound_generic_params, + fn_decl, + body, + fn_decl_span: self.lower_span(fn_decl_span), + fn_arg_span: Some(self.lower_span(fn_arg_span)), + // Lower this as a `CoroutineClosure`. That will ensure that HIR typeck + // knows that a `FnDecl` output type like `-> &str` actually means + // "coroutine that returns &str", rather than directly returning a `&str`. + kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring), + constness: self.lower_constness(constness), + explicit_captures: &[], + }); + hir::ExprKind::Closure(c) + } +} From fdcbe90f3d39944aeaba22a9a90fa10d660a5af1 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Mon, 11 May 2026 17:42:43 +0200 Subject: [PATCH 34/36] LLVM 23: Accept float (instead of hex) literals in codegen tests --- tests/codegen-llvm/const-vector.rs | 4 ++-- tests/codegen-llvm/sparc-struct-abi.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/codegen-llvm/const-vector.rs b/tests/codegen-llvm/const-vector.rs index 25d25d3e8ff35..0459d0145e26f 100644 --- a/tests/codegen-llvm/const-vector.rs +++ b/tests/codegen-llvm/const-vector.rs @@ -65,10 +65,10 @@ pub fn do_call() { // CHECK: call void @test_i8x2_arr(<2 x i8> test_i8x2_arr(const { i8x2::from_array([32, 64]) }); - // CHECK: call void @test_f32x2(<2 x float> + // CHECK: call void @test_f32x2(<2 x float> test_f32x2(const { f32x2::from_array([0.32, 0.64]) }); - // CHECK: void @test_f32x2_arr(<2 x float> + // CHECK: void @test_f32x2_arr(<2 x float> test_f32x2_arr(const { f32x2::from_array([0.32, 0.64]) }); // CHECK: call void @test_simd(<4 x i32> diff --git a/tests/codegen-llvm/sparc-struct-abi.rs b/tests/codegen-llvm/sparc-struct-abi.rs index 85725803e1b53..6a31fe0d37f15 100644 --- a/tests/codegen-llvm/sparc-struct-abi.rs +++ b/tests/codegen-llvm/sparc-struct-abi.rs @@ -31,7 +31,7 @@ pub struct BoolFloat { // CHECK: define inreg { i32, float } @structboolfloat() // CHECK-NEXT: start: -// CHECK-NEXT: ret { i32, float } { i32 16777216, float 0x40091EB860000000 } +// CHECK-NEXT: ret { i32, float } { i32 16777216, float {{0x40091EB860000000|3.140000e\+00}} } #[no_mangle] pub extern "C" fn structboolfloat() -> BoolFloat { BoolFloat { b: true, f: 3.14 } @@ -70,7 +70,7 @@ pub struct FloatLongFloat { // CHECK: define inreg { float, i32, i64, float, i32 } @structfloatlongfloat() // CHECK-NEXT: start: -// CHECK-NEXT: ret { float, i32, i64, float, i32 } { float 0x3FB99999A0000000, i32 undef, i64 123, float 0x40091EB860000000, i32 undef } +// CHECK-NEXT: ret { float, i32, i64, float, i32 } { float {{0x3FB99999A0000000|1.000000e-01}}, i32 undef, i64 123, float {{0x40091EB860000000|3.140000e\+00}}, i32 undef } #[no_mangle] pub extern "C" fn structfloatlongfloat() -> FloatLongFloat { FloatLongFloat { f: 0.1, i: 123, g: 3.14 } @@ -90,7 +90,7 @@ pub struct NestedStructs { // CHECK: define inreg { float, float, float, float } @structnestestructs() // CHECK-NEXT: start: -// CHECK-NEXT: ret { float, float, float, float } { float 0x3FB99999A0000000, float 0x3FF19999A0000000, float 0x40019999A0000000, float 0x400A666660000000 } +// CHECK-NEXT: ret { float, float, float, float } { float {{0x3FB99999A0000000|1.000000e-01}}, float {{0x3FF19999A0000000|1.100000e\+00}}, float {{0x40019999A0000000|2.200000e\+00}}, float {{0x400A666660000000|3.300000e\+00}} } #[no_mangle] pub extern "C" fn structnestestructs() -> NestedStructs { NestedStructs { a: FloatFloat { f: 0.1, g: 1.1 }, b: FloatFloat { f: 2.2, g: 3.3 } } From 8bd0b7a7a23e63b03754f556aa0b7dcde826170d Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Mon, 11 May 2026 17:26:33 +0200 Subject: [PATCH 35/36] LLVM 23: Specify `returnaddress` intrinsic return type --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 9 ++++++++- tests/codegen-llvm/intrinsics/return_address.rs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 312ff597c110a..9b7744cc81c10 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -860,7 +860,14 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { _ => { let ty = self.type_ix(32); let val = self.const_int(ty, 0); - self.call_intrinsic("llvm.returnaddress", &[], &[val]) + + let type_params: &[&'ll Type] = if llvm_version < (23, 0, 0) { + &[] + } else { + &[self.type_ptr()] + }; + + self.call_intrinsic("llvm.returnaddress", type_params, &[val]) } } } diff --git a/tests/codegen-llvm/intrinsics/return_address.rs b/tests/codegen-llvm/intrinsics/return_address.rs index 5aa731d6383f5..6cd37595d6531 100644 --- a/tests/codegen-llvm/intrinsics/return_address.rs +++ b/tests/codegen-llvm/intrinsics/return_address.rs @@ -7,6 +7,6 @@ #[no_mangle] #[inline(never)] pub fn call_return_address_intrinsic() -> *const () { - // CHECK: call ptr @llvm.returnaddress(i32 0) + // CHECK: call ptr @llvm.returnaddress{{(.p0)?}}(i32 0) core::intrinsics::return_address() } From a194d65213a1fd0732aa81a9df0cdb662995d689 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Mon, 11 May 2026 11:12:33 -0700 Subject: [PATCH 36/36] Refactor `CheckAttrVisitor` so rustfmt can format it. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The key change is that the do-nothing cases are now individual match arms instead of an enormous or-pattern. In order to achieve this cleanly, I moved the code handling `Attribute::Parsed` into a separate function matching `AttributeKind`s rather than `Attribute`s. This also fixes `RustcAllowIncoherentImpl` being non-alphabetical because it was spelled without a leading “|”. --- compiler/rustc_passes/src/check_attr.rs | 539 ++++++++++++------------ 1 file changed, 273 insertions(+), 266 deletions(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 49aba4c505cf6..f6765d604acb3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -123,283 +123,41 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ) { let attrs = self.tcx.hir_attrs(hir_id); for attr in attrs { - let mut style = None; match attr { - Attribute::Parsed(AttributeKind::ProcMacro) => { - self.check_proc_macro(hir_id, target, ProcMacroKind::FunctionLike) + Attribute::Parsed(attr_kind) => { + self.check_one_parsed_attribute(hir_id, span, target, item, attrs, attr_kind); + self.check_unused_attribute(hir_id, attr, None); } - Attribute::Parsed(AttributeKind::ProcMacroAttribute) => { - self.check_proc_macro(hir_id, target, ProcMacroKind::Attribute); - } - Attribute::Parsed(AttributeKind::ProcMacroDerive { .. }) => { - self.check_proc_macro(hir_id, target, ProcMacroKind::Derive) - } - Attribute::Parsed(AttributeKind::Inline(InlineAttr::Force { .. }, ..)) => {} // handled separately below - Attribute::Parsed(AttributeKind::Inline(kind, attr_span)) => { - self.check_inline(hir_id, *attr_span, kind, target) - } - Attribute::Parsed(AttributeKind::LoopMatch(attr_span)) => { - self.check_loop_match(hir_id, *attr_span, target) - } - Attribute::Parsed(AttributeKind::ConstContinue(attr_span)) => { - self.check_const_continue(hir_id, *attr_span, target) - } - Attribute::Parsed(AttributeKind::AllowInternalUnsafe(attr_span) | AttributeKind::AllowInternalUnstable(.., attr_span)) => { - self.check_macro_only_attr(*attr_span, span, target, attrs) - } - Attribute::Parsed(AttributeKind::RustcAllowConstFnUnstable(_, first_span)) => { - self.check_rustc_allow_const_fn_unstable(hir_id, *first_span, span, target) - } - Attribute::Parsed(AttributeKind::Deprecated { span: attr_span, .. }) => { - self.check_deprecated(hir_id, *attr_span, target) - } - Attribute::Parsed(AttributeKind::TargetFeature{ attr_span, ..}) => { - self.check_target_feature(hir_id, *attr_span, target, attrs) - } - Attribute::Parsed(AttributeKind::RustcDumpObjectLifetimeDefaults) => { - self.check_dump_object_lifetime_defaults(hir_id); - } - &Attribute::Parsed(AttributeKind::RustcPubTransparent(attr_span)) => { - self.check_rustc_pub_transparent(attr_span, span, attrs) - } - Attribute::Parsed(AttributeKind::RustcAlign {..}) => { - - } - Attribute::Parsed(AttributeKind::Naked(..)) => { - self.check_naked(hir_id, target) - } - Attribute::Parsed(AttributeKind::TrackCaller(attr_span)) => { - self.check_track_caller(hir_id, *attr_span, attrs, target) - } - Attribute::Parsed(AttributeKind::NonExhaustive(attr_span)) => { - self.check_non_exhaustive(*attr_span, span, target, item) - } - &Attribute::Parsed(AttributeKind::FfiPure(attr_span)) => { - self.check_ffi_pure(attr_span, attrs) - } - Attribute::Parsed(AttributeKind::MayDangle(attr_span)) => { - self.check_may_dangle(hir_id, *attr_span) - } - &Attribute::Parsed(AttributeKind::Sanitize { on_set, off_set, rtsan: _, span: attr_span}) => { - self.check_sanitize(attr_span, on_set | off_set, span, target); - }, - Attribute::Parsed(AttributeKind::Link(_, attr_span)) => { - self.check_link(hir_id, *attr_span, span, target) - }, - Attribute::Parsed(AttributeKind::MacroExport { span, .. }) => { - self.check_macro_export(hir_id, *span, target) - }, - Attribute::Parsed(AttributeKind::RustcLegacyConstGenerics{attr_span, fn_indexes}) => { - self.check_rustc_legacy_const_generics(item, *attr_span, fn_indexes) - }, - Attribute::Parsed(AttributeKind::Doc(attr)) => self.check_doc_attrs(attr, hir_id, target), - Attribute::Parsed(AttributeKind::EiiImpls(impls)) => { - self.check_eii_impl(impls, target) - }, - Attribute::Parsed(AttributeKind::RustcMustImplementOneOf { attr_span, fn_names }) => { - self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id,target) - }, - Attribute::Parsed(AttributeKind::OnUnimplemented{directive}) => {self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)}, - Attribute::Parsed(AttributeKind::OnMove { directive }) => { - self.check_diagnostic_on_move(hir_id, directive.as_deref()) - }, - Attribute::Parsed( - // tidy-alphabetical-start - AttributeKind::RustcAllowIncoherentImpl(..) - | AttributeKind::AutomaticallyDerived - | AttributeKind::CfgAttrTrace - | AttributeKind::CfgTrace(..) - | AttributeKind::CfiEncoding { .. } - | AttributeKind::Cold - | AttributeKind::CollapseDebugInfo(..) - | AttributeKind::CompilerBuiltins - | AttributeKind::Coroutine - | AttributeKind::Coverage (..) - | AttributeKind::CrateName { .. } - | AttributeKind::CrateType(..) - | AttributeKind::CustomMir(..) - | AttributeKind::DebuggerVisualizer(..) - | AttributeKind::DefaultLibAllocator - | AttributeKind::DoNotRecommend - // `#[doc]` is actually a lot more than just doc comments, so is checked below - | AttributeKind::DocComment {..} - | AttributeKind::EiiDeclaration { .. } - | AttributeKind::ExportName { .. } - | AttributeKind::ExportStable - | AttributeKind::Feature(..) - | AttributeKind::FfiConst - | AttributeKind::Fundamental - | AttributeKind::Ignore { .. } - | AttributeKind::InstructionSet(..) - | AttributeKind::Lang(..) - | AttributeKind::LinkName { .. } - | AttributeKind::LinkOrdinal { .. } - | AttributeKind::LinkSection { .. } - | AttributeKind::Linkage(..) - | AttributeKind::MacroEscape - | AttributeKind::MacroUse { .. } - | AttributeKind::Marker - | AttributeKind::MoveSizeLimit { .. } - | AttributeKind::MustNotSupend { .. } - | AttributeKind::MustUse { .. } - | AttributeKind::NeedsAllocator - | AttributeKind::NeedsPanicRuntime - | AttributeKind::NoBuiltins - | AttributeKind::NoCore { .. } - | AttributeKind::NoImplicitPrelude - | AttributeKind::NoLink - | AttributeKind::NoMain - | AttributeKind::NoMangle(..) - | AttributeKind::NoStd { .. } - | AttributeKind::OnUnknown { .. } - | AttributeKind::OnUnmatchArgs { .. } - | AttributeKind::Optimize(..) - | AttributeKind::PanicRuntime - | AttributeKind::PatchableFunctionEntry { .. } - | AttributeKind::Path(..) - | AttributeKind::PatternComplexityLimit { .. } - | AttributeKind::PinV2(..) - | AttributeKind::PreludeImport - | AttributeKind::ProfilerRuntime - | AttributeKind::RecursionLimit { .. } - | AttributeKind::ReexportTestHarnessMain(..) - | AttributeKind::RegisterTool(..) - // handled below this loop and elsewhere - | AttributeKind::Repr { .. } - | AttributeKind::RustcAbi { .. } - | AttributeKind::RustcAllocator - | AttributeKind::RustcAllocatorZeroed - | AttributeKind::RustcAllocatorZeroedVariant { .. } - | AttributeKind::RustcAsPtr - | AttributeKind::RustcAutodiff(..) - | AttributeKind::RustcBodyStability { .. } - | AttributeKind::RustcBuiltinMacro { .. } - | AttributeKind::RustcCaptureAnalysis - | AttributeKind::RustcCguTestAttr(..) - | AttributeKind::RustcClean(..) - | AttributeKind::RustcCoherenceIsCore - | AttributeKind::RustcCoinductive - | AttributeKind::RustcConfusables { .. } - | AttributeKind::RustcConstStability { .. } - | AttributeKind::RustcConstStableIndirect - | AttributeKind::RustcConversionSuggestion - | AttributeKind::RustcDeallocator - | AttributeKind::RustcDelayedBugFromInsideQuery - | AttributeKind::RustcDenyExplicitImpl - | AttributeKind::RustcDeprecatedSafe2024 {..} - | AttributeKind::RustcDiagnosticItem(..) - | AttributeKind::RustcDoNotConstCheck - | AttributeKind::RustcDocPrimitive(..) - | AttributeKind::RustcDummy - | AttributeKind::RustcDumpDefParents - | AttributeKind::RustcDumpDefPath(..) - | AttributeKind::RustcDumpHiddenTypeOfOpaques - | AttributeKind::RustcDumpInferredOutlives - | AttributeKind::RustcDumpItemBounds - | AttributeKind::RustcDumpLayout(..) - | AttributeKind::RustcDumpPredicates - | AttributeKind::RustcDumpSymbolName(..) - | AttributeKind::RustcDumpUserArgs - | AttributeKind::RustcDumpVariances - | AttributeKind::RustcDumpVariancesOfOpaques - | AttributeKind::RustcDumpVtable(..) - | AttributeKind::RustcDynIncompatibleTrait(..) - | AttributeKind::RustcEffectiveVisibility - | AttributeKind::RustcEiiForeignItem - | AttributeKind::RustcEvaluateWhereClauses - | AttributeKind::RustcHasIncoherentInherentImpls - | AttributeKind::RustcIfThisChanged(..) - | AttributeKind::RustcInheritOverflowChecks - | AttributeKind::RustcInsignificantDtor - | AttributeKind::RustcIntrinsic - | AttributeKind::RustcIntrinsicConstStableIndirect - | AttributeKind::RustcLintOptDenyFieldAccess { .. } - | AttributeKind::RustcLintOptTy - | AttributeKind::RustcLintQueryInstability - | AttributeKind::RustcLintUntrackedQueryInformation - | AttributeKind::RustcMacroTransparency(_) - | AttributeKind::RustcMain - | AttributeKind::RustcMir(_) - | AttributeKind::RustcMustMatchExhaustively(..) - | AttributeKind::RustcNeverReturnsNullPtr - | AttributeKind::RustcNeverTypeOptions {..} - | AttributeKind::RustcNoImplicitAutorefs - | AttributeKind::RustcNoImplicitBounds - | AttributeKind::RustcNoMirInline - | AttributeKind::RustcNoWritable - | AttributeKind::RustcNonConstTraitMethod - | AttributeKind::RustcNonnullOptimizationGuaranteed - | AttributeKind::RustcNounwind - | AttributeKind::RustcObjcClass { .. } - | AttributeKind::RustcObjcSelector { .. } - | AttributeKind::RustcOffloadKernel - | AttributeKind::RustcParenSugar - | AttributeKind::RustcPassByValue - | AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) - | AttributeKind::RustcPreserveUbChecks - | AttributeKind::RustcProcMacroDecls - | AttributeKind::RustcReallocator - | AttributeKind::RustcRegions - | AttributeKind::RustcReservationImpl(..) - | AttributeKind::RustcScalableVector { .. } - | AttributeKind::RustcShouldNotBeCalledOnConstItems - | AttributeKind::RustcSimdMonomorphizeLaneLimit(..) - | AttributeKind::RustcSkipDuringMethodDispatch { .. } - | AttributeKind::RustcSpecializationTrait - | AttributeKind::RustcStdInternalSymbol - | AttributeKind::RustcStrictCoherence(..) - | AttributeKind::RustcTestMarker(..) - | AttributeKind::RustcThenThisWouldNeed(..) - | AttributeKind::RustcTrivialFieldReads - | AttributeKind::RustcUnsafeSpecializationMarker - | AttributeKind::ShouldPanic { .. } - | AttributeKind::Stability { .. } - | AttributeKind::TestRunner(..) - | AttributeKind::ThreadLocal - | AttributeKind::TypeLengthLimit { .. } - | AttributeKind::UnstableFeatureBound(..) - | AttributeKind::UnstableRemoved(..) - | AttributeKind::Used { .. } - | AttributeKind::WindowsSubsystem(..) - // tidy-alphabetical-end - ) => { /* do nothing */ } Attribute::Unparsed(attr_item) => { - style = Some(attr_item.style); match attr.path().as_slice() { - [ - // ok - sym::allow - | sym::expect - | sym::warn - | sym::deny - | sym::forbid, - .. - ] => {} - [name, rest@..] => { - match BUILTIN_ATTRIBUTE_MAP.get(name) { - Some(_) => { - if rest.len() > 0 && AttributeParser::is_parsed_attribute(slice::from_ref(name)) { - // Check if we tried to use a builtin attribute as an attribute namespace, like `#[must_use::skip]`. - // This check is here to solve https://github.com/rust-lang/rust/issues/137590 - // An error is already produced for this case elsewhere - continue - } - - span_bug!( - attr.span(), - "builtin attribute {name:?} not handled by `CheckAttrVisitor`" - ) + // ok + [sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid, ..] => {} + + [name, rest @ ..] => { + if let Some(_) = BUILTIN_ATTRIBUTE_MAP.get(name) { + if rest.len() > 0 + && AttributeParser::is_parsed_attribute(slice::from_ref(name)) + { + // Check if we tried to use a builtin attribute as an attribute + // namespace, like `#[must_use::skip]`. This check is here to + // solve . + // An error is already produced for this case elsewhere. + return; } - None => (), + + span_bug!( + attr.span(), + "builtin attribute {name:?} not handled by `CheckAttrVisitor`" + ) } } + [] => unreachable!(), } + + self.check_unused_attribute(hir_id, attr, Some(attr_item.style)); } } - - self.check_unused_attribute(hir_id, attr, style) } self.check_repr(attrs, span, target, item, hir_id); @@ -407,6 +165,255 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_mix_no_mangle_export(hir_id, attrs); } + /// Called by [`Self::check_attributes()`] to check a single attribute which is + /// [`Attribute::Parsed`]. + /// + /// This is a separate function to help with comprehensibility and rustfmt-ability. + fn check_one_parsed_attribute( + &self, + hir_id: HirId, + span: Span, + target: Target, + item: Option>, + attrs: &[Attribute], + attr: &AttributeKind, + ) { + match attr { + AttributeKind::ProcMacro => { + self.check_proc_macro(hir_id, target, ProcMacroKind::FunctionLike) + } + AttributeKind::ProcMacroAttribute => { + self.check_proc_macro(hir_id, target, ProcMacroKind::Attribute); + } + AttributeKind::ProcMacroDerive { .. } => { + self.check_proc_macro(hir_id, target, ProcMacroKind::Derive) + } + AttributeKind::Inline(InlineAttr::Force { .. }, ..) => {} // handled separately below + AttributeKind::Inline(kind, attr_span) => { + self.check_inline(hir_id, *attr_span, kind, target) + } + AttributeKind::LoopMatch(attr_span) => { + self.check_loop_match(hir_id, *attr_span, target) + } + AttributeKind::ConstContinue(attr_span) => { + self.check_const_continue(hir_id, *attr_span, target) + } + AttributeKind::AllowInternalUnsafe(attr_span) + | AttributeKind::AllowInternalUnstable(.., attr_span) => { + self.check_macro_only_attr(*attr_span, span, target, attrs) + } + AttributeKind::RustcAllowConstFnUnstable(_, first_span) => { + self.check_rustc_allow_const_fn_unstable(hir_id, *first_span, span, target) + } + AttributeKind::Deprecated { span: attr_span, .. } => { + self.check_deprecated(hir_id, *attr_span, target) + } + AttributeKind::TargetFeature { attr_span, .. } => { + self.check_target_feature(hir_id, *attr_span, target, attrs) + } + AttributeKind::RustcDumpObjectLifetimeDefaults => { + self.check_dump_object_lifetime_defaults(hir_id); + } + &AttributeKind::RustcPubTransparent(attr_span) => { + self.check_rustc_pub_transparent(attr_span, span, attrs) + } + AttributeKind::RustcAlign { .. } => {} + AttributeKind::Naked(..) => self.check_naked(hir_id, target), + AttributeKind::TrackCaller(attr_span) => { + self.check_track_caller(hir_id, *attr_span, attrs, target) + } + AttributeKind::NonExhaustive(attr_span) => { + self.check_non_exhaustive(*attr_span, span, target, item) + } + &AttributeKind::FfiPure(attr_span) => self.check_ffi_pure(attr_span, attrs), + AttributeKind::MayDangle(attr_span) => self.check_may_dangle(hir_id, *attr_span), + &AttributeKind::Sanitize { on_set, off_set, rtsan: _, span: attr_span } => { + self.check_sanitize(attr_span, on_set | off_set, span, target); + } + AttributeKind::Link(_, attr_span) => self.check_link(hir_id, *attr_span, span, target), + AttributeKind::MacroExport { span, .. } => { + self.check_macro_export(hir_id, *span, target) + } + AttributeKind::RustcLegacyConstGenerics { attr_span, fn_indexes } => { + self.check_rustc_legacy_const_generics(item, *attr_span, fn_indexes) + } + AttributeKind::Doc(attr) => self.check_doc_attrs(attr, hir_id, target), + AttributeKind::EiiImpls(impls) => self.check_eii_impl(impls, target), + AttributeKind::RustcMustImplementOneOf { attr_span, fn_names } => { + self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id, target) + } + AttributeKind::OnUnimplemented { directive } => { + self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref()) + } + AttributeKind::OnConst { span, .. } => { + self.check_diagnostic_on_const(*span, hir_id, target, item) + } + AttributeKind::OnMove { directive } => { + self.check_diagnostic_on_move(hir_id, directive.as_deref()) + } + + // All of the following attributes have no specific checks. + // tidy-alphabetical-start + AttributeKind::AutomaticallyDerived => (), + AttributeKind::CfgAttrTrace => (), + AttributeKind::CfgTrace(..) => (), + AttributeKind::CfiEncoding { .. } => (), + AttributeKind::Cold => (), + AttributeKind::CollapseDebugInfo(..) => (), + AttributeKind::CompilerBuiltins => (), + AttributeKind::Coroutine => (), + AttributeKind::Coverage(..) => (), + AttributeKind::CrateName { .. } => (), + AttributeKind::CrateType(..) => (), + AttributeKind::CustomMir(..) => (), + AttributeKind::DebuggerVisualizer(..) => (), + AttributeKind::DefaultLibAllocator => (), + AttributeKind::DoNotRecommend => (), + // `#[doc]` is actually a lot more than just doc comments, so is checked below + AttributeKind::DocComment { .. } => (), + AttributeKind::EiiDeclaration { .. } => (), + AttributeKind::ExportName { .. } => (), + AttributeKind::ExportStable => (), + AttributeKind::Feature(..) => (), + AttributeKind::FfiConst => (), + AttributeKind::Fundamental => (), + AttributeKind::Ignore { .. } => (), + AttributeKind::InstructionSet(..) => (), + AttributeKind::Lang(..) => (), + AttributeKind::LinkName { .. } => (), + AttributeKind::LinkOrdinal { .. } => (), + AttributeKind::LinkSection { .. } => (), + AttributeKind::Linkage(..) => (), + AttributeKind::MacroEscape => (), + AttributeKind::MacroUse { .. } => (), + AttributeKind::Marker => (), + AttributeKind::MoveSizeLimit { .. } => (), + AttributeKind::MustNotSupend { .. } => (), + AttributeKind::MustUse { .. } => (), + AttributeKind::NeedsAllocator => (), + AttributeKind::NeedsPanicRuntime => (), + AttributeKind::NoBuiltins => (), + AttributeKind::NoCore { .. } => (), + AttributeKind::NoImplicitPrelude => (), + AttributeKind::NoLink => (), + AttributeKind::NoMain => (), + AttributeKind::NoMangle(..) => (), + AttributeKind::NoStd { .. } => (), + AttributeKind::OnUnknown { .. } => (), + AttributeKind::OnUnmatchArgs { .. } => (), + AttributeKind::Optimize(..) => (), + AttributeKind::PanicRuntime => (), + AttributeKind::PatchableFunctionEntry { .. } => (), + AttributeKind::Path(..) => (), + AttributeKind::PatternComplexityLimit { .. } => (), + AttributeKind::PinV2(..) => (), + AttributeKind::PreludeImport => (), + AttributeKind::ProfilerRuntime => (), + AttributeKind::RecursionLimit { .. } => (), + AttributeKind::ReexportTestHarnessMain(..) => (), + AttributeKind::RegisterTool(..) => (), + // handled below this loop and elsewhere + AttributeKind::Repr { .. } => (), + AttributeKind::RustcAbi { .. } => (), + AttributeKind::RustcAllocator => (), + AttributeKind::RustcAllocatorZeroed => (), + AttributeKind::RustcAllocatorZeroedVariant { .. } => (), + AttributeKind::RustcAllowIncoherentImpl(..) => (), + AttributeKind::RustcAsPtr => (), + AttributeKind::RustcAutodiff(..) => (), + AttributeKind::RustcBodyStability { .. } => (), + AttributeKind::RustcBuiltinMacro { .. } => (), + AttributeKind::RustcCaptureAnalysis => (), + AttributeKind::RustcCguTestAttr(..) => (), + AttributeKind::RustcClean(..) => (), + AttributeKind::RustcCoherenceIsCore => (), + AttributeKind::RustcCoinductive => (), + AttributeKind::RustcConfusables { .. } => (), + AttributeKind::RustcConstStability { .. } => (), + AttributeKind::RustcConstStableIndirect => (), + AttributeKind::RustcConversionSuggestion => (), + AttributeKind::RustcDeallocator => (), + AttributeKind::RustcDelayedBugFromInsideQuery => (), + AttributeKind::RustcDenyExplicitImpl => (), + AttributeKind::RustcDeprecatedSafe2024 { .. } => (), + AttributeKind::RustcDiagnosticItem(..) => (), + AttributeKind::RustcDoNotConstCheck => (), + AttributeKind::RustcDocPrimitive(..) => (), + AttributeKind::RustcDummy => (), + AttributeKind::RustcDumpDefParents => (), + AttributeKind::RustcDumpDefPath(..) => (), + AttributeKind::RustcDumpHiddenTypeOfOpaques => (), + AttributeKind::RustcDumpInferredOutlives => (), + AttributeKind::RustcDumpItemBounds => (), + AttributeKind::RustcDumpLayout(..) => (), + AttributeKind::RustcDumpPredicates => (), + AttributeKind::RustcDumpSymbolName(..) => (), + AttributeKind::RustcDumpUserArgs => (), + AttributeKind::RustcDumpVariances => (), + AttributeKind::RustcDumpVariancesOfOpaques => (), + AttributeKind::RustcDumpVtable(..) => (), + AttributeKind::RustcDynIncompatibleTrait(..) => (), + AttributeKind::RustcEffectiveVisibility => (), + AttributeKind::RustcEiiForeignItem => (), + AttributeKind::RustcEvaluateWhereClauses => (), + AttributeKind::RustcHasIncoherentInherentImpls => (), + AttributeKind::RustcIfThisChanged(..) => (), + AttributeKind::RustcInheritOverflowChecks => (), + AttributeKind::RustcInsignificantDtor => (), + AttributeKind::RustcIntrinsic => (), + AttributeKind::RustcIntrinsicConstStableIndirect => (), + AttributeKind::RustcLintOptDenyFieldAccess { .. } => (), + AttributeKind::RustcLintOptTy => (), + AttributeKind::RustcLintQueryInstability => (), + AttributeKind::RustcLintUntrackedQueryInformation => (), + AttributeKind::RustcMacroTransparency(_) => (), + AttributeKind::RustcMain => (), + AttributeKind::RustcMir(_) => (), + AttributeKind::RustcMustMatchExhaustively(..) => (), + AttributeKind::RustcNeverReturnsNullPtr => (), + AttributeKind::RustcNeverTypeOptions { .. } => (), + AttributeKind::RustcNoImplicitAutorefs => (), + AttributeKind::RustcNoImplicitBounds => (), + AttributeKind::RustcNoMirInline => (), + AttributeKind::RustcNoWritable => (), + AttributeKind::RustcNonConstTraitMethod => (), + AttributeKind::RustcNonnullOptimizationGuaranteed => (), + AttributeKind::RustcNounwind => (), + AttributeKind::RustcObjcClass { .. } => (), + AttributeKind::RustcObjcSelector { .. } => (), + AttributeKind::RustcOffloadKernel => (), + AttributeKind::RustcParenSugar => (), + AttributeKind::RustcPassByValue => (), + AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) => (), + AttributeKind::RustcPreserveUbChecks => (), + AttributeKind::RustcProcMacroDecls => (), + AttributeKind::RustcReallocator => (), + AttributeKind::RustcRegions => (), + AttributeKind::RustcReservationImpl(..) => (), + AttributeKind::RustcScalableVector { .. } => (), + AttributeKind::RustcShouldNotBeCalledOnConstItems => (), + AttributeKind::RustcSimdMonomorphizeLaneLimit(..) => (), + AttributeKind::RustcSkipDuringMethodDispatch { .. } => (), + AttributeKind::RustcSpecializationTrait => (), + AttributeKind::RustcStdInternalSymbol => (), + AttributeKind::RustcStrictCoherence(..) => (), + AttributeKind::RustcTestMarker(..) => (), + AttributeKind::RustcThenThisWouldNeed(..) => (), + AttributeKind::RustcTrivialFieldReads => (), + AttributeKind::RustcUnsafeSpecializationMarker => (), + AttributeKind::ShouldPanic { .. } => (), + AttributeKind::Stability { .. } => (), + AttributeKind::TestRunner(..) => (), + AttributeKind::ThreadLocal => (), + AttributeKind::TypeLengthLimit { .. } => (), + AttributeKind::UnstableFeatureBound(..) => (), + AttributeKind::UnstableRemoved(..) => (), + AttributeKind::Used { .. } => (), + AttributeKind::WindowsSubsystem(..) => (), + // tidy-alphabetical-end + } + } + fn check_rustc_must_implement_one_of( &self, attr_span: Span,