From 93630386128aeeb8d098e0b926362aa2a24b6474 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Thu, 18 Jun 2026 15:23:02 +0530 Subject: [PATCH 01/24] bootstrap: fix RUSTFLAGS with spaces in paths via CARGO_ENCODED_RUSTFLAGS --- src/bootstrap/src/core/builder/cargo.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 24d7a24152a32..8e135a3c20a30 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -14,18 +14,18 @@ use crate::{ RemapScheme, TargetSelection, command, prepare_behaviour_dump_dir, t, }; -/// Represents flag values in `String` form with whitespace delimiter to pass it to the compiler -/// later. +/// Represents flag values passed to the compiler, stored as individual arguments to support +/// values that contain spaces (such as paths from `llvm-config --libdir`). /// /// `-Z crate-attr` flags will be applied recursively on the target code using the /// `rustc_parse::parser::Parser`. See `rustc_builtin_macros::cmdline_attrs::inject` for more /// information. #[derive(Debug, Clone)] -struct Rustflags(String, TargetSelection); +struct Rustflags(Vec, TargetSelection); impl Rustflags { fn new(target: TargetSelection) -> Rustflags { - Rustflags(String::new(), target) + Rustflags(Vec::new(), target) } /// By default, cargo will pick up on various variables in the environment. However, bootstrap @@ -51,11 +51,9 @@ impl Rustflags { } fn arg(&mut self, arg: &str) -> &mut Self { - assert_eq!(arg.split(' ').count(), 1); - if !self.0.is_empty() { - self.0.push(' '); + if !arg.is_empty() { + self.0.push(arg.to_owned()); } - self.0.push_str(arg); self } @@ -459,12 +457,14 @@ impl From for BootstrapCommand { let rustflags = &cargo.rustflags.0; if !rustflags.is_empty() { - cargo.command.env("RUSTFLAGS", rustflags); + // CARGO_ENCODED_RUSTFLAGS uses \x1f (unit separator) as delimiter, which allows + // individual flags to contain spaces (e.g. paths from `llvm-config --libdir`). + cargo.command.env("CARGO_ENCODED_RUSTFLAGS", rustflags.join("\x1f")); } let rustdocflags = &cargo.rustdocflags.0; if !rustdocflags.is_empty() { - cargo.command.env("RUSTDOCFLAGS", rustdocflags); + cargo.command.env("RUSTDOCFLAGS", rustdocflags.join(" ")); } let encoded_hostflags = cargo.hostflags.encode(); @@ -1181,8 +1181,9 @@ impl Builder<'_> { if (mode == Mode::ToolRustcPrivate || mode == Mode::Codegen) && let Some(llvm_config) = self.llvm_config(target) { - let llvm_libdir = + let llvm_libdir_raw = command(llvm_config).cached().arg("--libdir").run_capture_stdout(self).stdout(); + let llvm_libdir = llvm_libdir_raw.trim(); if target.is_msvc() { rustflags.arg(&format!("-Clink-arg=-LIBPATH:{llvm_libdir}")); } else { From 63d3395c4c69e71a942ae0cf7c1a9986f1f1120d Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Thu, 18 Jun 2026 16:42:33 +0530 Subject: [PATCH 02/24] bootstrap: also use CARGO_ENCODED_RUSTDOCFLAGS to support spaces in paths --- src/bootstrap/src/core/builder/cargo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 8e135a3c20a30..b025bfdecbf20 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -464,7 +464,7 @@ impl From for BootstrapCommand { let rustdocflags = &cargo.rustdocflags.0; if !rustdocflags.is_empty() { - cargo.command.env("RUSTDOCFLAGS", rustdocflags.join(" ")); + cargo.command.env("CARGO_ENCODED_RUSTDOCFLAGS", rustdocflags.join("\x1f")); } let encoded_hostflags = cargo.hostflags.encode(); From 386748b45ac3d0756fb2dcf997896f11c4eae617 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Thu, 18 Jun 2026 17:44:33 +0530 Subject: [PATCH 03/24] =?UTF-8?q?=E2=8F=BA=20bootstrap:=20reset=20CARGO=5F?= =?UTF-8?q?ENCODED=5FRUSTFLAGS=20in=20Miri=20test=20runner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/miri/tests/ui.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 633452f6052d7..3e46ee17c8a65 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -182,8 +182,9 @@ fn miri_config( .map(Into::into) .collect(), envs: vec![ - // Reset `RUSTFLAGS` to work around . + // Reset `RUSTFLAGS`/`CARGO_ENCODED_RUSTFLAGS` to work around . ("RUSTFLAGS".into(), None), + ("CARGO_ENCODED_RUSTFLAGS".into(), None), // Reset `MIRIFLAGS` because it caused trouble in the past and should not be needed. ("MIRIFLAGS".into(), None), // Allow `cargo miri build`. From 8085cd3b44bf0c9683197c6082763a97c3dbb0f9 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Thu, 18 Jun 2026 23:11:08 +0530 Subject: [PATCH 04/24] bootstrap: use \x1f-delimited String for Rustflags instead of Vec --- src/bootstrap/src/core/builder/cargo.rs | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index b025bfdecbf20..cabcd5e86efa7 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -14,18 +14,21 @@ use crate::{ RemapScheme, TargetSelection, command, prepare_behaviour_dump_dir, t, }; -/// Represents flag values passed to the compiler, stored as individual arguments to support -/// values that contain spaces (such as paths from `llvm-config --libdir`). +/// Represents flag values in `String` form with a `\x1f` delimiter to pass to the compiler later. +/// +/// Flags are emitted via `CARGO_ENCODED_RUSTFLAGS` / `CARGO_ENCODED_RUSTDOCFLAGS`, +/// which use `\x1f` (ASCII Unit Separator) as the delimiter and therefore allow spaces +/// within individual flag values (e.g. paths from `llvm-config --libdir`). /// /// `-Z crate-attr` flags will be applied recursively on the target code using the /// `rustc_parse::parser::Parser`. See `rustc_builtin_macros::cmdline_attrs::inject` for more /// information. #[derive(Debug, Clone)] -struct Rustflags(Vec, TargetSelection); +struct Rustflags(String, TargetSelection); impl Rustflags { fn new(target: TargetSelection) -> Rustflags { - Rustflags(Vec::new(), target) + Rustflags(String::new(), target) } /// By default, cargo will pick up on various variables in the environment. However, bootstrap @@ -51,8 +54,15 @@ impl Rustflags { } fn arg(&mut self, arg: &str) -> &mut Self { + assert!( + !arg.contains('\x1f'), + "rustflag must not contain the ASCII unit separator (\\x1f): {arg:?}" + ); if !arg.is_empty() { - self.0.push(arg.to_owned()); + if !self.0.is_empty() { + self.0.push('\x1f'); + } + self.0.push_str(arg); } self } @@ -457,14 +467,12 @@ impl From for BootstrapCommand { let rustflags = &cargo.rustflags.0; if !rustflags.is_empty() { - // CARGO_ENCODED_RUSTFLAGS uses \x1f (unit separator) as delimiter, which allows - // individual flags to contain spaces (e.g. paths from `llvm-config --libdir`). - cargo.command.env("CARGO_ENCODED_RUSTFLAGS", rustflags.join("\x1f")); + cargo.command.env("CARGO_ENCODED_RUSTFLAGS", rustflags); } let rustdocflags = &cargo.rustdocflags.0; if !rustdocflags.is_empty() { - cargo.command.env("CARGO_ENCODED_RUSTDOCFLAGS", rustdocflags.join("\x1f")); + cargo.command.env("CARGO_ENCODED_RUSTDOCFLAGS", rustdocflags); } let encoded_hostflags = cargo.hostflags.encode(); From b4a7c3eaef31af895127408796c72744c18ec810 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Mon, 22 Jun 2026 02:55:59 +0530 Subject: [PATCH 05/24] unset RUSTFLAGS/RUSTDOCFLAGS when setting encoded forms --- src/bootstrap/src/core/builder/cargo.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index cabcd5e86efa7..b76dc77683f94 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -465,6 +465,13 @@ impl From for BootstrapCommand { cargo.command.args(cargo.args); + // Always unset the plain RUSTFLAGS/RUSTDOCFLAGS so that downstream + // tools (e.g. build.rs scripts) see only the encoded form. Any flags + // from the caller's environment have already been folded into the + // Rustflags struct via `propagate_cargo_env`. + cargo.command.env_remove("RUSTFLAGS"); + cargo.command.env_remove("RUSTDOCFLAGS"); + let rustflags = &cargo.rustflags.0; if !rustflags.is_empty() { cargo.command.env("CARGO_ENCODED_RUSTFLAGS", rustflags); From 2d1092203e6ccd4c2554bf6e55d0422e14090b0e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 05:15:11 +0900 Subject: [PATCH 06/24] add regression test for cfg_eval derive reparse recovery --- .../cfg-eval-derive-invalid-reparse-no-ice.rs | 17 +++++++++++ ...-eval-derive-invalid-reparse-no-ice.stderr | 28 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs create mode 100644 tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr diff --git a/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs new file mode 100644 index 0000000000000..1f0215afadc7b --- /dev/null +++ b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs @@ -0,0 +1,17 @@ +// Regression test for https://github.com/rust-lang/rust/issues/148891. + +macro_rules! values { + ($inner:ty) => { + #[derive(Debug)] + pub enum TokenKind { + #[cfg(test)] + STRING([u8; $inner]), + //~^ ERROR expected expression, found `ty` metavariable + //~| ERROR macro expansion ignores `)` and any tokens following + } + }; +} + +values!(String); + +fn main() {} diff --git a/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr new file mode 100644 index 0000000000000..0248b6636fb7a --- /dev/null +++ b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr @@ -0,0 +1,28 @@ +error: expected expression, found `ty` metavariable + --> $DIR/cfg-eval-derive-invalid-reparse-no-ice.rs:8:25 + | +LL | pub enum TokenKind { + | --------- while parsing this enum +LL | #[cfg(test)] +LL | STRING([u8; $inner]), + | ^^^^^^ expected expression +... +LL | values!(String); + | --------------- in this macro invocation + | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` + = note: this error originates in the macro `values` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: macro expansion ignores `)` and any tokens following + --> $DIR/cfg-eval-derive-invalid-reparse-no-ice.rs:8:32 + | +LL | STRING([u8; $inner]), + | ^ +... +LL | values!(String); + | --------------- caused by the macro expansion here + | + = note: the usage of `values!` is likely invalid in item context + +error: aborting due to 2 previous errors + From 14e53710dbce9037f90178fdf23585871416a73b Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 05:15:31 +0900 Subject: [PATCH 07/24] avoid ICE when cfg_eval reparse recovers no annotatable --- compiler/rustc_builtin_macros/src/cfg_eval.rs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 9f032fa588195..e2c62e60ecced 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -107,41 +107,42 @@ impl CfgEval<'_> { // our attribute target will correctly configure the tokens as well. let mut parser = Parser::new(&self.0.sess.psess, orig_tokens, None); parser.capture_cfg = true; - let res: PResult<'_, Annotatable> = try { - match annotatable { - Annotatable::Item(_) => { - let item = - parser.parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)?.unwrap(); - Annotatable::Item(self.flat_map_item(item).pop().unwrap()) - } + let res: PResult<'_, Option> = try { + match &annotatable { + Annotatable::Item(_) => parser + .parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)? + .and_then(|item| self.flat_map_item(item).pop().map(Annotatable::Item)), Annotatable::AssocItem(_, ctxt) => { - let item = parser.parse_trait_item(ForceCollect::Yes)?.unwrap().unwrap(); - Annotatable::AssocItem( - self.flat_map_assoc_item(item, ctxt).pop().unwrap(), - ctxt, - ) + parser.parse_trait_item(ForceCollect::Yes)?.flatten().and_then(|item| { + self.flat_map_assoc_item(item, *ctxt) + .pop() + .map(|item| Annotatable::AssocItem(item, *ctxt)) + }) } Annotatable::ForeignItem(_) => { - let item = parser.parse_foreign_item(ForceCollect::Yes)?.unwrap().unwrap(); - Annotatable::ForeignItem(self.flat_map_foreign_item(item).pop().unwrap()) - } - Annotatable::Stmt(_) => { - let stmt = parser - .parse_stmt_without_recovery(false, ForceCollect::Yes, false)? - .unwrap(); - Annotatable::Stmt(Box::new(self.flat_map_stmt(stmt).pop().unwrap())) + parser.parse_foreign_item(ForceCollect::Yes)?.flatten().and_then(|item| { + self.flat_map_foreign_item(item).pop().map(Annotatable::ForeignItem) + }) } + Annotatable::Stmt(_) => parser + .parse_stmt_without_recovery(false, ForceCollect::Yes, false)? + .and_then(|stmt| { + self.flat_map_stmt(stmt).pop().map(|stmt| Annotatable::Stmt(Box::new(stmt))) + }), Annotatable::Expr(_) => { let mut expr = parser.parse_expr_force_collect()?; self.visit_expr(&mut expr); - Annotatable::Expr(expr) + Some(Annotatable::Expr(expr)) } _ => unreachable!(), } }; match res { - Ok(ann) => ann, + Ok(Some(ann)) => ann, + // Parser recovery may emit errors without reconstructing an annotatable. + // Keep the original node so cfg-eval stays best-effort instead of ICEing. + Ok(None) => annotatable, Err(err) => { err.emit(); annotatable From f31f522f45c4abb91125d8537919c83df8a5d03b Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Sat, 27 Jun 2026 17:49:30 +0200 Subject: [PATCH 08/24] Support DefKind::InlineConst in UnevaluatedConst --- compiler/rustc_ast_lowering/src/lib.rs | 2 +- .../rustc_borrowck/src/universal_regions.rs | 2 +- compiler/rustc_hir/src/def.rs | 37 -------- compiler/rustc_hir_analysis/src/collect.rs | 6 +- .../src/hir_ty_lowering/mod.rs | 12 +-- compiler/rustc_hir_analysis/src/lib.rs | 8 +- compiler/rustc_hir_typeck/src/loops.rs | 5 ++ compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_middle/src/hir/map.rs | 8 +- compiler/rustc_middle/src/ty/context.rs | 9 +- .../src/ty/context/impl_interner.rs | 2 +- compiler/rustc_middle/src/ty/util.rs | 33 +++++++- .../rustc_mir_transform/src/trivial_const.rs | 9 +- compiler/rustc_resolve/src/def_collector.rs | 84 +++++-------------- compiler/rustc_resolve/src/lib.rs | 9 -- compiler/rustc_ty_utils/src/opaque_types.rs | 17 ++-- compiler/rustc_ty_utils/src/sig_types.rs | 38 +++++---- .../mgca/double-inline-const.rs | 11 +++ .../mgca/double-inline-const.stderr | 15 ++++ 19 files changed, 151 insertions(+), 158 deletions(-) create mode 100644 tests/ui/const-generics/mgca/double-inline-const.rs create mode 100644 tests/ui/const-generics/mgca/double-inline-const.stderr diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 06e83a7486100..ceee45db20544 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2791,7 +2791,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } ExprKind::ConstBlock(anon_const) => { let def_id = self.local_def_id(anon_const.id); - assert_eq!(DefKind::AnonConst, self.tcx.def_kind(def_id)); + assert_eq!(DefKind::InlineConst, self.tcx.def_kind(def_id)); self.lower_anon_const_to_const_arg(anon_const, span) } _ => overly_complex_const(self), diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 804d2757cf998..b0adbcd6db4ef 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -615,7 +615,7 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { match tcx.def_kind(self.mir_def) { - DefKind::InlineConst => { + DefKind::InlineConst if !tcx.is_type_system_inline_const(self.mir_def) => { // This is required for `AscribeUserType` canonical query, which will call // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes // into borrowck, which is ICE #78174. diff --git a/compiler/rustc_hir/src/def.rs b/compiler/rustc_hir/src/def.rs index e473db48b04c2..d0300d63d1213 100644 --- a/compiler/rustc_hir/src/def.rs +++ b/compiler/rustc_hir/src/def.rs @@ -448,43 +448,6 @@ impl DefKind { | DefKind::ExternCrate => false, } } - - /// Returns `true` if `self` is a kind of definition that does not have its own - /// type-checking context, i.e. closure, coroutine or inline const. - #[inline] - pub fn is_typeck_child(self) -> bool { - match self { - DefKind::Closure | DefKind::InlineConst | DefKind::SyntheticCoroutineBody => true, - DefKind::Mod - | DefKind::Struct - | DefKind::Union - | DefKind::Enum - | DefKind::Variant - | DefKind::Trait - | DefKind::TyAlias - | DefKind::ForeignTy - | DefKind::TraitAlias - | DefKind::AssocTy - | DefKind::TyParam - | DefKind::Fn - | DefKind::Const { .. } - | DefKind::ConstParam - | DefKind::Static { .. } - | DefKind::Ctor(_, _) - | DefKind::AssocFn - | DefKind::AssocConst { .. } - | DefKind::Macro(_) - | DefKind::ExternCrate - | DefKind::Use - | DefKind::ForeignMod - | DefKind::AnonConst - | DefKind::OpaqueTy - | DefKind::Field - | DefKind::LifetimeParam - | DefKind::GlobalAsm - | DefKind::Impl { .. } => false, - } - } } /// The resolution of a path or export. diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 275fed00a4f8a..05648bf7c2ece 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -15,7 +15,7 @@ //! crate as a kind of pass. This should eventually be factored away. use std::cell::Cell; -use std::{assert_matches, iter}; +use std::{assert_matches, debug_assert_matches, iter}; use rustc_abi::{ExternAbi, Size}; use rustc_ast::Recovered; @@ -1635,10 +1635,12 @@ fn const_param_default<'tcx>( } fn anon_const_kind<'tcx>(tcx: TyCtxt<'tcx>, def: LocalDefId) -> ty::AnonConstKind { + debug_assert_matches!(tcx.def_kind(def), DefKind::AnonConst | DefKind::InlineConst); let hir_id = tcx.local_def_id_to_hir_id(def); let const_arg_id = tcx.parent_hir_id(hir_id); match tcx.hir_node(const_arg_id) { - hir::Node::ConstArg(_) => { + hir::Node::ConstArg(const_arg) => { + debug_assert_matches!(const_arg.kind, hir::ConstArgKind::Anon(hir::AnonConst { def_id, .. }) if *def_id == def); let parent_hir_node = tcx.hir_node(tcx.parent_hir_id(const_arg_id)); if tcx.features().generic_const_exprs() { ty::AnonConstKind::GCE diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 95a91f1444404..eb9ccb719a84a 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -482,7 +482,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let tcx = self.tcx(); let parent_def_id = self.item_def_id(); if let Res::Def(DefKind::ConstParam, _) = res - && tcx.def_kind(parent_def_id) == DefKind::AnonConst + && matches!(tcx.def_kind(parent_def_id), DefKind::AnonConst | DefKind::InlineConst) && let ty::AnonConstKind::MCG = tcx.anon_const_kind(parent_def_id) { let folder = ForbidParamUsesFolder { @@ -511,15 +511,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Inline consts and closures can be nested inside anon consts that forbid generic // params (e.g. an enum discriminant). Walk up the def parent chain to find the // nearest enclosing AnonConst and use that to determine the context. + let parent_def_id = tcx.typeck_root_def_id(parent_def_id.into()); + let anon_const_def_id = match tcx.def_kind(parent_def_id) { DefKind::AnonConst => parent_def_id, - DefKind::InlineConst | DefKind::Closure => { - let root = tcx.typeck_root_def_id(parent_def_id.into()); - match tcx.def_kind(root) { - DefKind::AnonConst => root.expect_local(), - _ => return None, - } - } + DefKind::InlineConst if tcx.is_type_system_inline_const(parent_def_id) => parent_def_id, _ => return None, }; diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index c5a6ce75c0e41..ff00e18783706 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -186,9 +186,13 @@ pub fn check_crate(tcx: TyCtxt<'_>) { } _ => (), } - // Skip `AnonConst`s because we feed their `type_of`. + // Skip `AnonConst`s and type system `InlineConst`s because we feed their `type_of` in + // `feed_anon_const_type`. // Also skip items for which typeck forwards to parent typeck. - if !(matches!(def_kind, DefKind::AnonConst) || def_kind.is_typeck_child()) { + if !(def_kind == DefKind::AnonConst + || def_kind == DefKind::InlineConst && tcx.is_type_system_inline_const(item_def_id) + || tcx.is_typeck_child(item_def_id.to_def_id())) + { tcx.ensure_ok().typeck(item_def_id); } // Ensure we generate the new `DefId` before finishing `check_crate`. diff --git a/compiler/rustc_hir_typeck/src/loops.rs b/compiler/rustc_hir_typeck/src/loops.rs index 21aad64f58d38..ebd8842bd753f 100644 --- a/compiler/rustc_hir_typeck/src/loops.rs +++ b/compiler/rustc_hir_typeck/src/loops.rs @@ -84,6 +84,11 @@ pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir CheckLoopVisitor { tcx, cx_stack: vec![Normal], block_breaks: Default::default() }; let cx = match tcx.def_kind(def_id) { DefKind::AnonConst => AnonConst, + DefKind::InlineConst => { + // only type system inline consts are typeck roots + debug_assert!(tcx.is_type_system_inline_const(def_id)); + ConstBlock + } _ => Fn, }; check.with_context(cx, |v| v.visit_body(body)); diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index c232f595d4229..0f63c8469953d 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1624,7 +1624,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); } } - if let DefKind::AnonConst = def_kind { + if let DefKind::AnonConst | DefKind::InlineConst = def_kind { record!(self.tables.anon_const_kind[def_id] <- self.tcx.anon_const_kind(def_id)); } if should_encode_const_of_item(self.tcx, def_id, def_kind) { diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 3cab936c45c1f..a37a9d7b4a4e2 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -22,7 +22,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, with_metavar_spans}; use crate::hir::{ModuleItems, ProjectedMaybeOwner, nested_filter}; use crate::middle::debugger_visualizer::DebuggerVisualizerFile; use crate::query::{IntoQueryKey, LocalCrate}; -use crate::ty::TyCtxt; +use crate::ty::{self, TyCtxt}; /// An iterator that walks up the ancestor tree of a given `HirId`. /// Constructed using `tcx.hir_parent_iter(hir_id)`. @@ -1115,6 +1115,12 @@ impl<'tcx> TyCtxt<'tcx> { } } + pub fn is_type_system_inline_const(self, def_id: impl IntoQueryKey) -> bool { + let def_id = def_id.into_query_key(); + debug_assert_eq!(self.def_kind(def_id), DefKind::InlineConst); + self.anon_const_kind(def_id) != ty::AnonConstKind::NonTypeSystem + } + pub fn hir_maybe_get_struct_pattern_shorthand_field(self, expr: &Expr<'_>) -> Option { let local = match expr { Expr { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 569a1d5786095..3d7aef0b273f1 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -601,7 +601,14 @@ impl<'tcx> TyCtxt<'tcx> { /// effect. However, we do not want this as a general capability, so this interface restricts /// to the only allowed case. pub fn feed_anon_const_type(self, key: LocalDefId, value: ty::EarlyBinder<'tcx, Ty<'tcx>>) { - debug_assert_eq!(self.def_kind(key), DefKind::AnonConst); + if cfg!(debug_assertions) { + match self.def_kind(key) { + DefKind::AnonConst => (), + DefKind::InlineConst => assert!(self.is_type_system_inline_const(key)), + def_kind => bug!("unexpected DefKind in feed_anon_const_type: {def_kind:?}"), + } + } + TyCtxtFeed { tcx: self, key }.type_of(value) } diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 60c2acdb4c4f9..86a6c752d14fe 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -247,7 +247,7 @@ impl<'tcx> Interner for TyCtxt<'tcx> { DefKind::OpaqueTy => ty::AliasTermKind::OpaqueTy { def_id }, DefKind::TyAlias => ty::AliasTermKind::FreeTy { def_id }, DefKind::Const { .. } => ty::AliasTermKind::FreeConst { def_id }, - DefKind::AnonConst | DefKind::Ctor(_, CtorKind::Const) => { + DefKind::AnonConst | DefKind::InlineConst | DefKind::Ctor(_, CtorKind::Const) => { ty::AliasTermKind::AnonConst { def_id } } kind => bug!("unexpected DefKind in AliasTy: {kind:?}"), diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index ca3b3f0bbec5c..2c91555e3835f 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -597,7 +597,38 @@ impl<'tcx> TyCtxt<'tcx> { /// Returns `true` if `def_id` refers to a definition that does not have its own /// type-checking context, i.e. closure, coroutine or inline const. pub fn is_typeck_child(self, def_id: DefId) -> bool { - self.def_kind(def_id).is_typeck_child() + match self.def_kind(def_id) { + DefKind::InlineConst => !self.is_type_system_inline_const(def_id), + DefKind::Closure | DefKind::SyntheticCoroutineBody => true, + DefKind::Mod + | DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Variant + | DefKind::Trait + | DefKind::TyAlias + | DefKind::ForeignTy + | DefKind::TraitAlias + | DefKind::AssocTy + | DefKind::TyParam + | DefKind::Fn + | DefKind::Const { .. } + | DefKind::ConstParam + | DefKind::Static { .. } + | DefKind::Ctor(_, _) + | DefKind::AssocFn + | DefKind::AssocConst { .. } + | DefKind::Macro(_) + | DefKind::ExternCrate + | DefKind::Use + | DefKind::ForeignMod + | DefKind::AnonConst + | DefKind::OpaqueTy + | DefKind::Field + | DefKind::LifetimeParam + | DefKind::GlobalAsm + | DefKind::Impl { .. } => false, + } } /// Returns `true` if `def_id` refers to a trait (i.e., `trait Foo { ... }`). diff --git a/compiler/rustc_mir_transform/src/trivial_const.rs b/compiler/rustc_mir_transform/src/trivial_const.rs index 25d15c12a7b3b..4b66c757729d3 100644 --- a/compiler/rustc_mir_transform/src/trivial_const.rs +++ b/compiler/rustc_mir_transform/src/trivial_const.rs @@ -52,11 +52,10 @@ where F: FnOnce() -> B, B: Deref>, { - if !matches!( - tcx.def_kind(def), - DefKind::AssocConst { .. } | DefKind::Const { .. } | DefKind::AnonConst - ) { - return None; + match tcx.def_kind(def) { + DefKind::AssocConst { .. } | DefKind::Const { .. } | DefKind::AnonConst => (), + DefKind::InlineConst if tcx.is_type_system_inline_const(def) => (), + _ => return None, } // If there are impossible predicates then MIR passes will replace the body with diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index a5049339382f7..de3f8c380fc44 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -17,8 +17,7 @@ use tracing::{debug, instrument}; use crate::macros::MacroRulesScopeRef; use crate::{ - ConstArgContext, ImplTraitContext, InvocationParent, ParentScope, Resolver, with_owner, - with_owner_tables, + ImplTraitContext, InvocationParent, ParentScope, Resolver, with_owner, with_owner_tables, }; pub(crate) fn collect_definitions<'ra>( @@ -115,12 +114,6 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { self.invocation_parent.impl_trait_context = orig_itc; } - fn with_const_arg(&mut self, ctxt: ConstArgContext, f: F) { - let orig = mem::replace(&mut self.invocation_parent.const_arg_context, ctxt); - f(self); - self.invocation_parent.const_arg_context = orig; - } - fn collect_field(&mut self, field: &'a FieldDef, index: Option) { let index = |this: &Self| { index.unwrap_or_else(|| { @@ -430,6 +423,9 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { } fn visit_anon_const(&mut self, constant: &'a AnonConst) { + // Note that `visit_anon_const` is skipped for AnonConst nodes wrapped in an + // ExprKind::ConstBlock - these are handled in visit_expr, and are DefKind::InlineConst. + // `MgcaDisambiguation::Direct` is set even when MGCA is disabled, so // to avoid affecting stable we have to feature gate the not creating // anon consts @@ -441,16 +437,12 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { } match constant.mgca_disambiguation { - MgcaDisambiguation::Direct => self.with_const_arg(ConstArgContext::Direct, |this| { - visit::walk_anon_const(this, constant); - }), + MgcaDisambiguation::Direct => visit::walk_anon_const(self, constant), MgcaDisambiguation::AnonConst => { - self.with_const_arg(ConstArgContext::NonDirect, |this| { - let parent = this - .create_def(constant.id, None, DefKind::AnonConst, constant.value.span) - .def_id(); - this.with_parent(parent, |this| visit::walk_anon_const(this, constant)); - }) + let parent = self + .create_def(constant.id, None, DefKind::AnonConst, constant.value.span) + .def_id(); + self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); } }; } @@ -459,60 +451,28 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { fn visit_expr(&mut self, expr: &'a Expr) { debug!(?self.invocation_parent); - let parent_def = match &expr.kind { + match &expr.kind { ExprKind::MacCall(..) => { self.visit_macro_invoc(expr.id); self.visit_invoc(expr.id); - return; } ExprKind::Closure(..) | ExprKind::Gen(..) => { - self.create_def(expr.id, None, DefKind::Closure, expr.span).def_id() + let def = self.create_def(expr.id, None, DefKind::Closure, expr.span).def_id(); + self.with_parent(def, |this| visit::walk_expr(this, expr)); } ExprKind::ConstBlock(constant) => { - // Under `min_generic_const_args` a `const { }` block sometimes - // corresponds to an anon const rather than an inline const. - let def_kind = match self.invocation_parent.const_arg_context { - ConstArgContext::Direct => DefKind::AnonConst, - ConstArgContext::NonDirect => DefKind::InlineConst, - }; - - return self.with_const_arg(ConstArgContext::NonDirect, |this| { - for attr in &expr.attrs { - visit::walk_attribute(this, attr); - } - - let def = - this.create_def(constant.id, None, def_kind, constant.value.span).def_id(); - this.with_parent(def, |this| visit::walk_anon_const(this, constant)); - }); - } - - // Avoid overwriting `const_arg_context` as we may want to treat const blocks - // as being anon consts if we are inside a const argument. - ExprKind::Struct(_) | ExprKind::Call(..) | ExprKind::Tup(..) | ExprKind::Array(..) => { - return visit::walk_expr(self, expr); - } - // FIXME(mgca): we may want to handle block labels in some manner - ExprKind::Block(block, _) if let [stmt] = block.stmts.as_slice() => match stmt.kind { - // FIXME(mgca): this probably means that mac calls that expand - // to semi'd const blocks are handled differently to just writing - // out a semi'd const block. - StmtKind::Expr(..) | StmtKind::MacCall(..) => return visit::walk_expr(self, expr), - - // Fallback to normal behaviour - StmtKind::Let(..) | StmtKind::Item(..) | StmtKind::Semi(..) | StmtKind::Empty => { - self.invocation_parent.parent_def + for attr in &expr.attrs { + visit::walk_attribute(self, attr); } - }, - _ => self.invocation_parent.parent_def, - }; - - self.with_const_arg(ConstArgContext::NonDirect, |this| { - // Note in some cases the `parent_def` here may be the existing parent - // and this is actually a no-op `with_parent` call. - this.with_parent(parent_def, |this| visit::walk_expr(this, expr)) - }) + let def = self + .create_def(constant.id, None, DefKind::InlineConst, constant.value.span) + .def_id(); + // use specifically walk_anon_const, not walk_expr, to skip self.visit_anon_const + self.with_parent(def, |this| visit::walk_anon_const(this, constant)); + } + _ => visit::walk_expr(self, expr), + } } fn visit_ty(&mut self, ty: &'a Ty) { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a20a760f229ae..e7fdfcde0d572 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -186,7 +186,6 @@ struct InvocationParent { parent_def: LocalDefId, impl_trait_context: ImplTraitContext, in_attr: bool, - const_arg_context: ConstArgContext, owner: NodeId, } @@ -195,7 +194,6 @@ impl InvocationParent { parent_def: CRATE_DEF_ID, impl_trait_context: ImplTraitContext::Existential, in_attr: false, - const_arg_context: ConstArgContext::NonDirect, owner: CRATE_NODE_ID, }; } @@ -207,13 +205,6 @@ enum ImplTraitContext { InBinding, } -#[derive(Copy, Clone, Debug)] -enum ConstArgContext { - Direct, - /// Either inside of an `AnonConst` or not inside a const argument at all. - NonDirect, -} - /// Used for tracking import use types which will be used for redundant import checking. /// /// ### Used::Scope Example diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index 5920a1e900d19..bac5d47e98676 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -317,6 +317,10 @@ fn opaque_types_defined_by<'tcx>( tcx: TyCtxt<'tcx>, item: LocalDefId, ) -> &'tcx ty::List { + // Closures and coroutines are type checked with their parent + // Note that we also support `SyntheticCoroutineBody` since we create + // a MIR body for the def kind, and some MIR passes (like promotion) + // may require doing analysis using its typing env. if tcx.is_typeck_child(item.to_def_id()) { return tcx.opaque_types_defined_by(tcx.local_parent(item)); } @@ -332,7 +336,11 @@ fn opaque_types_defined_by<'tcx>( | DefKind::Static { .. } | DefKind::Const { .. } | DefKind::AssocConst { .. } - | DefKind::AnonConst => { + | DefKind::AnonConst + | DefKind::InlineConst => { + // Non-type-system inline consts should be caught by `if tcx.is_typeck_child` above + debug_assert!(kind != DefKind::InlineConst || tcx.is_type_system_inline_const(item)); + collector.collect_taits_declared_in_body(); } DefKind::AssocTy | DefKind::TyAlias | DefKind::GlobalAsm => {} @@ -345,15 +353,8 @@ fn opaque_types_defined_by<'tcx>( | DefKind::Trait | DefKind::ForeignTy | DefKind::TraitAlias - - // Closures and coroutines are type checked with their parent - // Note that we also support `SyntheticCoroutineBody` since we create - // a MIR body for the def kind, and some MIR passes (like promotion) - // may require doing analysis using its typing env. | DefKind::Closure - | DefKind::InlineConst | DefKind::SyntheticCoroutineBody - | DefKind::TyParam | DefKind::ConstParam | DefKind::Ctor(_, _) diff --git a/compiler/rustc_ty_utils/src/sig_types.rs b/compiler/rustc_ty_utils/src/sig_types.rs index ebec1888e6a95..f60a03fa71b26 100644 --- a/compiler/rustc_ty_utils/src/sig_types.rs +++ b/compiler/rustc_ty_utils/src/sig_types.rs @@ -21,6 +21,24 @@ pub fn walk_types<'tcx, V: SpannedTypeVisitor<'tcx>>( ) -> V::Result { let kind = tcx.def_kind(item); trace!(?kind); + let mut visit_alias = || { + if let Some(ty) = tcx.hir_node_by_def_id(item).ty() { + // If the type of the item uses `_`, we're gonna error out anyway, but + // typeck (which type_of invokes below), will call back into opaque_types_defined_by + // causing a cycle. So we just bail out in this case. + if ty.is_suggestable_infer_ty() { + return V::Result::output(); + } + // Associated types in traits don't necessarily have a type that we can visit + try_visit!( + visitor.visit(ty.span, tcx.type_of(item).instantiate_identity().skip_norm_wip()) + ); + } + for (pred, span) in tcx.explicit_predicates_of(item).instantiate_identity(tcx) { + try_visit!(visitor.visit(span, pred.skip_norm_wip())); + } + V::Result::output() + }; match kind { // Walk over the signature of the function DefKind::AssocFn | DefKind::Fn => { @@ -47,24 +65,8 @@ pub fn walk_types<'tcx, V: SpannedTypeVisitor<'tcx>>( | DefKind::Static { .. } | DefKind::Const { .. } | DefKind::AssocConst { .. } - | DefKind::AnonConst => { - if let Some(ty) = tcx.hir_node_by_def_id(item).ty() { - // If the type of the item uses `_`, we're gonna error out anyway, but - // typeck (which type_of invokes below), will call back into opaque_types_defined_by - // causing a cycle. So we just bail out in this case. - if ty.is_suggestable_infer_ty() { - return V::Result::output(); - } - // Associated types in traits don't necessarily have a type that we can visit - try_visit!( - visitor - .visit(ty.span, tcx.type_of(item).instantiate_identity().skip_norm_wip()) - ); - } - for (pred, span) in tcx.explicit_predicates_of(item).instantiate_identity(tcx) { - try_visit!(visitor.visit(span, pred.skip_norm_wip())); - } - } + | DefKind::AnonConst => return visit_alias(), + DefKind::InlineConst if tcx.is_type_system_inline_const(item) => return visit_alias(), DefKind::OpaqueTy => { for (pred, span) in tcx .explicit_item_bounds(item) diff --git a/tests/ui/const-generics/mgca/double-inline-const.rs b/tests/ui/const-generics/mgca/double-inline-const.rs new file mode 100644 index 0000000000000..9bfbd1819c695 --- /dev/null +++ b/tests/ui/const-generics/mgca/double-inline-const.rs @@ -0,0 +1,11 @@ +#![feature(min_generic_const_args)] + +struct S; + +impl S { + const Q: usize = 2; + fn foo(_: S<{ const { const { Self::Q } } }>) {} + //~^ ERROR generic `Self` types are currently not permitted in anonymous constants +} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/double-inline-const.stderr b/tests/ui/const-generics/mgca/double-inline-const.stderr new file mode 100644 index 0000000000000..bc73e3d93575e --- /dev/null +++ b/tests/ui/const-generics/mgca/double-inline-const.stderr @@ -0,0 +1,15 @@ +error: generic `Self` types are currently not permitted in anonymous constants + --> $DIR/double-inline-const.rs:7:35 + | +LL | fn foo(_: S<{ const { const { Self::Q } } }>) {} + | ^^^^ + | +note: not a concrete type + --> $DIR/double-inline-const.rs:5:22 + | +LL | impl S { + | ^^^^ + = help: add `#![feature(generic_const_args)]` to allow generic expressions as the RHS of const items + +error: aborting due to 1 previous error + From 9aaea562307a3b1866ba53d895fc13ab5073ea66 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sat, 27 Jun 2026 16:59:46 -0300 Subject: [PATCH 09/24] Recover deferred closure calls after errors --- compiler/rustc_hir_typeck/src/callee.rs | 17 +++++--- .../src/error_reporting/traits/suggestions.rs | 10 +++++ ...rred-closure-call-recovery-issue-157951.rs | 10 +++++ ...-closure-call-recovery-issue-157951.stderr | 39 +++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.rs create mode 100644 tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.stderr diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 57ab29ac752ad..57734d9be582d 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -9,11 +9,11 @@ use rustc_hir::{self as hir, HirId, LangItem, find_attr}; use rustc_hir_analysis::autoderef::Autoderef; use rustc_infer::infer::BoundRegionConversionTime; use rustc_infer::traits::{Obligation, ObligationCause, ObligationCauseCode}; +use rustc_middle::bug; use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, }; use rustc_middle::ty::{self, FnSig, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, Unnormalized}; -use rustc_middle::{bug, span_bug}; use rustc_span::def_id::LocalDefId; use rustc_span::{Ident, Span, sym}; use rustc_target::spec::{AbiMap, AbiMapping}; @@ -1161,11 +1161,16 @@ impl<'a, 'tcx> DeferredCallResolution<'tcx> { ); } None => { - span_bug!( - self.call_expr.span, - "Expected to find a suitable `Fn`/`FnMut`/`FnOnce` implementation for `{}`", - self.closure_ty - ) + let guar = fcx.tainted_by_errors().unwrap_or_else(|| { + fcx.dcx().span_delayed_bug( + self.call_expr.span, + format!( + "Expected to find a suitable `Fn`/`FnMut`/`FnOnce` implementation for `{}`", + self.closure_ty + ), + ) + }); + fcx.write_resolution(self.call_expr.hir_id, Err(guar)); } } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index c3f4a09b2d431..754fbe24a8464 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -2555,6 +2555,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { err.children.clear(); let mut span = obligation.cause.span; + let mut is_async_fn_return = false; if let DefKind::Closure = self.tcx.def_kind(obligation.cause.body_id) && let parent = self.tcx.local_parent(obligation.cause.body_id) && let DefKind::Fn | DefKind::AssocFn = self.tcx.def_kind(parent) @@ -2570,10 +2571,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // and // async fn foo() -> dyn Display Box span = fn_sig.decl.output.span(); + is_async_fn_return = true; err.span(span); } let body = self.tcx.hir_body_owned_by(obligation.cause.body_id); + if !is_async_fn_return + && let Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(closure), .. }) = + self.tcx.hir_node_by_def_id(obligation.cause.body_id) + && matches!(closure.fn_decl.output, hir::FnRetTy::DefaultReturn(_)) + { + return true; + } + let mut visitor = ReturnsVisitor::default(); visitor.visit_body(&body); diff --git a/tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.rs b/tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.rs new file mode 100644 index 0000000000000..d1e2a25b29521 --- /dev/null +++ b/tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.rs @@ -0,0 +1,10 @@ +//@ compile-flags: -Znext-solver=globally +//@ check-fail + +fn main() { + let f = |f: dyn Fn()| f; + //~^ ERROR the size for values of type `(dyn Fn() + 'static)` cannot be known at compilation time + //~| ERROR return type cannot be a trait object without pointer indirection + f(); + //~^ ERROR this function takes 1 argument but 0 arguments were supplied +} diff --git a/tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.stderr b/tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.stderr new file mode 100644 index 0000000000000..a20e5f331d31a --- /dev/null +++ b/tests/ui/traits/next-solver/deferred-closure-call-recovery-issue-157951.stderr @@ -0,0 +1,39 @@ +error[E0277]: the size for values of type `(dyn Fn() + 'static)` cannot be known at compilation time + --> $DIR/deferred-closure-call-recovery-issue-157951.rs:5:17 + | +LL | let f = |f: dyn Fn()| f; + | ^^^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `(dyn Fn() + 'static)` + = help: unsized fn params are gated as an unstable feature +help: function arguments must have a statically known size, borrowed types always have a known size + | +LL | let f = |f: &dyn Fn()| f; + | + + +error[E0746]: return type cannot be a trait object without pointer indirection + --> $DIR/deferred-closure-call-recovery-issue-157951.rs:5:27 + | +LL | let f = |f: dyn Fn()| f; + | ^ doesn't have a size known at compile-time + +error[E0057]: this function takes 1 argument but 0 arguments were supplied + --> $DIR/deferred-closure-call-recovery-issue-157951.rs:8:5 + | +LL | f(); + | ^-- argument #1 of type `(dyn Fn() + 'static)` is missing + | +note: closure defined here + --> $DIR/deferred-closure-call-recovery-issue-157951.rs:5:13 + | +LL | let f = |f: dyn Fn()| f; + | ^^^^^^^^^^^^^ +help: provide the argument + | +LL | f(/* (dyn Fn() + 'static) */); + | ++++++++++++++++++++++++++ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0057, E0277, E0746. +For more information about an error, try `rustc --explain E0057`. From a89705b027241bcd6a63684da1ab46c587461c2c Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Mon, 22 Jun 2026 10:28:15 -0400 Subject: [PATCH 10/24] Avoid panics bubbling out to proc macros Currently, rustc can emit a FatalError diagnostic during parsing of literals and tokenstreams. These are handled under the hood as a panic, which means that proc-macro code needed to catch_unwind if it wanted to fallibly parse some code. These still emit diagnostics, so in practice this isn't a full fix, but it at least makes the interface on the macro side a bit more uniform. This is primarily motivated by wasm proc macros which can't use catch_unwind and so this lets the test's output be the same with and without them. --- .../rustc_expand/src/proc_macro_server.rs | 21 +++++++----- .../auxiliary/nonfatal-parsing-body.rs | 34 ++++++++++--------- tests/ui/proc-macro/nonfatal-parsing.stderr | 18 +++++----- tests/ui/proc-macro/nonfatal-parsing.stdout | 6 +++- 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 41e550b8d6a3a..65af690611919 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -490,9 +490,11 @@ impl server::Server for Rustc<'_, '_> { fn literal_from_str(&mut self, s: &str) -> Result, String> { let name = FileName::proc_macro_source_code(s); - let mut parser = + let mut parser = rustc_errors::catch_fatal_errors(|| { new_parser_from_source_str(self.psess(), name, s.to_owned(), StripTokens::Nothing) - .map_err(cancel_diags_into_string)?; + }) + .map_err(|_| String::from("failed to parse to literal"))? + .map_err(cancel_diags_into_string)?; let first_span = parser.token.span.data(); let minus_present = parser.eat(exp!(Minus)); @@ -569,12 +571,15 @@ impl server::Server for Rustc<'_, '_> { } fn ts_from_str(&mut self, src: &str) -> Result { - source_str_to_stream( - self.psess(), - FileName::proc_macro_source_code(src), - src.to_string(), - Some(self.call_site), - ) + rustc_errors::catch_fatal_errors(|| { + source_str_to_stream( + self.psess(), + FileName::proc_macro_source_code(src), + src.to_string(), + Some(self.call_site), + ) + }) + .map_err(|_| String::from("failed to parse to tokenstream"))? .map_err(cancel_diags_into_string) } diff --git a/tests/ui/proc-macro/auxiliary/nonfatal-parsing-body.rs b/tests/ui/proc-macro/auxiliary/nonfatal-parsing-body.rs index 258f77067ce9c..b29c40770b0e7 100644 --- a/tests/ui/proc-macro/auxiliary/nonfatal-parsing-body.rs +++ b/tests/ui/proc-macro/auxiliary/nonfatal-parsing-body.rs @@ -7,12 +7,14 @@ use proc_macro::*; use self::Mode::*; // FIXME: all cases should become `NormalOk` or `NormalErr` +// +// And .stderr should be empty (no diagnostics should get emitted from fallible parsing in the proc +// macro). #[derive(PartialEq, Clone, Copy)] enum Mode { NormalOk, NormalErr, OtherError, - OtherWithPanic, } fn print_unspanned(s: &str) -> Result @@ -43,12 +45,11 @@ where assert!(t.is_err()); } OtherError => { - print_unspanned::(s); - } - OtherWithPanic => { - if catch_unwind(|| print_unspanned::(s)).is_ok() { - eprintln!("{s} did not panic"); - } + let t = print_unspanned::(s); + // For now we assert OK here, but in the future this should all move to NormalErr. + // Any case that's failing this should go to NormalErr, probably after verifying that a + // diagnostic did get emitted. + assert!(t.is_ok()); } } } @@ -136,9 +137,9 @@ pub fn run() { // FIXME: all of the cases below should return an Err and emit no diagnostics, but don't yet. // emits diagnostics and returns LexError - lit("r'r'", OtherError); - lit("c'r'", OtherError); - lit("\u{2000}", OtherError); + lit("r'r'", NormalErr); + lit("c'r'", NormalErr); + lit("\u{2000}", NormalErr); // emits diagnostic and returns a seemingly valid tokenstream stream("r'r'", OtherError); @@ -146,8 +147,8 @@ pub fn run() { stream("\u{2000}", OtherError); for parse in [stream as fn(&str, Mode), lit] { - // emits diagnostic(s), then panics - parse("r#", OtherWithPanic); + // emits diagnostic(s), then returns LexError + parse("r#", NormalErr); // emits diagnostic(s), then returns Ok(Literal { kind: ErrWithGuar, .. }) parse("0b2", OtherError); @@ -158,9 +159,10 @@ pub fn run() { "' '", OtherError, ); - parse(&format!("r{0}\"a\"{0}", "#".repeat(256)), OtherWithPanic); - - // emits diagnostic, then, when parsing as a lit, returns LexError, otherwise ErrWithGuar - parse("/*a*/ 0b2 //", OtherError); + parse(&format!("r{0}\"a\"{0}", "#".repeat(256)), NormalErr); } + + // emits diagnostic, then, when parsing as a lit, returns LexError, otherwise ErrWithGuar + lit("/*a*/ 0b2 //", NormalErr); + stream("/*a*/ 0b2 //", OtherError); } diff --git a/tests/ui/proc-macro/nonfatal-parsing.stderr b/tests/ui/proc-macro/nonfatal-parsing.stderr index cb6e243eebc9b..f015794e2ce67 100644 --- a/tests/ui/proc-macro/nonfatal-parsing.stderr +++ b/tests/ui/proc-macro/nonfatal-parsing.stderr @@ -130,15 +130,6 @@ LL | nonfatal_parsing::run!(); | = note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info) -error: invalid digit for a base 2 literal - --> $DIR/nonfatal-parsing.rs:15:5 - | -LL | nonfatal_parsing::run!(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - = note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info) - error: found invalid character; only `#` is allowed in raw string delimitation: \u{0} --> :1:1 | @@ -199,6 +190,15 @@ error: invalid digit for a base 2 literal LL | /*a*/ 0b2 // | ^ +error: invalid digit for a base 2 literal + --> $DIR/nonfatal-parsing.rs:15:5 + | +LL | nonfatal_parsing::run!(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + = note: this error originates in the macro `nonfatal_parsing::run` (in Nightly builds, run with -Z macro-backtrace for more info) + error: aborting due to 22 previous errors For more information about this error, try `rustc --explain E0768`. diff --git a/tests/ui/proc-macro/nonfatal-parsing.stdout b/tests/ui/proc-macro/nonfatal-parsing.stdout index a46ef66f0f9d0..fcefde22541ba 100644 --- a/tests/ui/proc-macro/nonfatal-parsing.stdout +++ b/tests/ui/proc-macro/nonfatal-parsing.stdout @@ -52,15 +52,19 @@ Err(LexError("not a literal")) Ok(TokenStream [Ident { ident: "r", span: Span }, Literal { kind: Char, symbol: "r", suffix: None, span: Span }]) Ok(TokenStream [Ident { ident: "c", span: Span }, Literal { kind: Char, symbol: "r", suffix: None, span: Span }]) Ok(TokenStream []) +Err(LexError("failed to parse to tokenstream")) Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: Span }]) Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b", suffix: Some("f32"), span: Span }]) Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b0.0", suffix: Some("f32"), span: Span }]) Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "'''", suffix: None, span: Span }]) Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "'\n'", suffix: None, span: Span }]) -Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: Span }]) +Err(LexError("failed to parse to tokenstream")) +Err(LexError("failed to parse to literal")) Ok(Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: Span }) Ok(Literal { kind: ErrWithGuar, symbol: "0b", suffix: Some("f32"), span: Span }) Ok(Literal { kind: ErrWithGuar, symbol: "0b0.0", suffix: Some("f32"), span: Span }) Ok(Literal { kind: ErrWithGuar, symbol: "'''", suffix: None, span: Span }) Ok(Literal { kind: ErrWithGuar, symbol: "'\n'", suffix: None, span: Span }) +Err(LexError("failed to parse to literal")) Err(LexError("comment or whitespace around literal")) +Ok(TokenStream [Literal { kind: ErrWithGuar, symbol: "0b2", suffix: None, span: Span }]) From bd938c5bfb48fe83ef8f2f54f562c2394c17d89f Mon Sep 17 00:00:00 2001 From: Predrag Gruevski Date: Sat, 27 Jun 2026 01:13:26 -0400 Subject: [PATCH 11/24] Include default-stability info in rustdoc JSON. --- src/librustdoc/json/conversions.rs | 69 ++++++-- src/rustdoc-json-types/lib.rs | 39 ++++- src/tools/jsondoclint/src/validator.rs | 43 ++++- src/tools/jsondoclint/src/validator/tests.rs | 154 +++++++++++++++++- .../attrs/stability/default_body.rs | 76 +++++++++ 5 files changed, 364 insertions(+), 17 deletions(-) create mode 100644 tests/rustdoc-json/attrs/stability/default_body.rs diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 882220a0cae03..7083d730de58c 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -270,6 +270,18 @@ impl FromClean for Stability { } } +impl FromClean for Box { + fn from_clean(stab: &hir::DefaultBodyStability, _renderer: &JsonRenderer<'_>) -> Self { + let hir::StabilityLevel::Unstable { .. } = stab.level else { + bug!( + "unexpected stable default-body stability, \ + there's no stable equivalent of `#[rustc_default_body_unstable]`" + ) + }; + Box::new(ProvidedDefaultUnstable { feature: stab.feature.to_string() }) + } +} + impl FromClean for Option> { fn from_clean(generic_args: &clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self { use clean::GenericArgs::*; @@ -353,18 +365,23 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum EnumItem(e) => ItemEnum::Enum(e.into_json(renderer)), VariantItem(v) => ItemEnum::Variant(v.into_json(renderer)), FunctionItem(f) => { - ItemEnum::Function(from_clean_function(f, true, header.unwrap(), renderer)) + ItemEnum::Function(from_clean_function(f, true, None, header.unwrap(), renderer)) } ForeignFunctionItem(f, _) => { - ItemEnum::Function(from_clean_function(f, false, header.unwrap(), renderer)) + ItemEnum::Function(from_clean_function(f, false, None, header.unwrap(), renderer)) } TraitItem(t) => ItemEnum::Trait(t.into_json(renderer)), TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_json(renderer)), - MethodItem(m, _) => { - ItemEnum::Function(from_clean_function(m, true, header.unwrap(), renderer)) - } + MethodItem(m, _) => ItemEnum::Function(from_clean_function( + m, + true, + default_body_stability_for_def_id(renderer.tcx, item.item_id.expect_def_id()) + .map(|stab| stab.into_json(renderer)), + header.unwrap(), + renderer, + )), RequiredMethodItem(m, _) => { - ItemEnum::Function(from_clean_function(m, false, header.unwrap(), renderer)) + ItemEnum::Function(from_clean_function(m, false, None, header.unwrap(), renderer)) } ImplItem(i) => ItemEnum::Impl(i.into_json(renderer)), StaticItem(s) => ItemEnum::Static(from_clean_static(s, rustc_hir::Safety::Safe, renderer)), @@ -385,23 +402,41 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum }) } // FIXME(generic_const_items): Add support for generic associated consts. - RequiredAssocConstItem(_generics, ty) => { - ItemEnum::AssocConst { type_: ty.into_json(renderer), value: None } - } + RequiredAssocConstItem(_generics, ty) => ItemEnum::AssocConst { + type_: ty.into_json(renderer), + value: None, + default_unstable: None, + }, // FIXME(generic_const_items): Add support for generic associated consts. - ProvidedAssocConstItem(ci) | ImplAssocConstItem(ci) => ItemEnum::AssocConst { + ProvidedAssocConstItem(ci) => ItemEnum::AssocConst { type_: ci.type_.into_json(renderer), value: Some(ci.kind.expr(renderer.tcx)), + default_unstable: default_body_stability_for_def_id( + renderer.tcx, + item.item_id.expect_def_id(), + ) + .map(|stab| stab.into_json(renderer)), + }, + ImplAssocConstItem(ci) => ItemEnum::AssocConst { + type_: ci.type_.into_json(renderer), + value: Some(ci.kind.expr(renderer.tcx)), + default_unstable: None, }, RequiredAssocTypeItem(g, b) => ItemEnum::AssocType { generics: g.into_json(renderer), bounds: b.into_json(renderer), type_: None, + default_unstable: None, }, AssocTypeItem(t, b) => ItemEnum::AssocType { generics: t.generics.into_json(renderer), bounds: b.into_json(renderer), type_: Some(t.item_type.as_ref().unwrap_or(&t.type_).into_json(renderer)), + default_unstable: default_body_stability_for_def_id( + renderer.tcx, + item.item_id.expect_def_id(), + ) + .map(|stab| stab.into_json(renderer)), }, // `convert_item` early returns `None` for stripped items, keywords, attributes and // "special" macro rules. @@ -815,6 +850,7 @@ impl FromClean for Impl { pub(crate) fn from_clean_function( clean::Function { decl, generics }: &clean::Function, has_body: bool, + default_unstable: Option>, header: rustc_hir::FnHeader, renderer: &JsonRenderer<'_>, ) -> Function { @@ -823,6 +859,7 @@ pub(crate) fn from_clean_function( generics: generics.into_json(renderer), header: header.into_json(renderer), has_body, + default_unstable, } } @@ -972,6 +1009,17 @@ impl FromClean for ItemKind { } } +fn default_body_stability_for_def_id( + tcx: TyCtxt<'_>, + def_id: DefId, +) -> Option { + let stability = tcx.lookup_default_body_stability(def_id)?; + match stability.level { + hir::StabilityLevel::Unstable { .. } => Some(stability), + hir::StabilityLevel::Stable { .. } => None, + } +} + fn const_stability_for_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option { if !tcx.is_conditionally_const(def_id) { // The item cannot be conditionally-const. No const stability here. @@ -1040,6 +1088,7 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) AK::Deprecated { .. } => return Vec::new(), // Handled separately into Item::deprecation. AK::Stability { .. } => return Vec::new(), // Handled separately into Item::stability AK::RustcConstStability { .. } => return Vec::new(), // Handled separately into Item::const_stability. + AK::RustcBodyStability { .. } => return Vec::new(), // Handled separately by `default_unstable`. AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"), diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index e6af2fa04bb10..be4921d888278 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -114,8 +114,8 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line // are deliberately not in a doc comment, because they need not be in public docs.) // -// Latest feature: Add `Item::const_stability`. -pub const FORMAT_VERSION: u32 = 59; +// Latest feature: Add default-body stability metadata. +pub const FORMAT_VERSION: u32 = 60; /// The root of the emitted JSON blob. /// @@ -288,6 +288,9 @@ pub struct Item { /// - `#[stable]` and `#[unstable]` attributes: see the [`Self::stability`] field instead. /// - `#[rustc_const_stable]` and `#[rustc_const_unstable]` attributes: /// see the [`Self::const_stability`] field instead. + /// - `#[rustc_default_body_unstable]` attributes: instead see `default_unstable` fields on + /// item kinds that can have unstable default values, such as [`Function::default_unstable`], + /// [`ItemEnum::AssocConst::default_unstable`], and [`ItemEnum::AssocType::default_unstable`]. /// /// Attributes appear in pretty-printed Rust form, regardless of their formatting /// in the original source code. For example: @@ -367,6 +370,19 @@ pub enum StabilityLevel { Unstable, } +/// Information about an unstable default provided by a trait item. +/// +/// Example unstable defaults include: +/// - a stable trait function or method whose body is not stable +/// - a stable trait associated type or const whose default value is not stable +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "rkyv_0_8", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "rkyv_0_8", rkyv(derive(Debug)))] +pub struct ProvidedDefaultUnstable { + /// The feature that must be enabled to use the provided default. + pub feature: String, +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "rkyv_0_8", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] #[cfg_attr(feature = "rkyv_0_8", rkyv(derive(Debug)))] @@ -379,6 +395,9 @@ pub enum StabilityLevel { /// - `#[stable]` and `#[unstable]`. These are in [`Item::stability`] instead. /// - `#[rustc_const_stable]` and `#[rustc_const_unstable]`. These are in /// [`Item::const_stability`] instead. +/// - `#[rustc_default_body_unstable]`. These are in the `default_unstable` field on the appropriate +/// item kinds: [`Function::default_unstable`], [`ItemEnum::AssocConst::default_unstable`], +/// and [`ItemEnum::AssocType::default_unstable`]. pub enum Attribute { /// `#[non_exhaustive]` NonExhaustive, @@ -875,6 +894,11 @@ pub enum ItemEnum { /// // ^^^^^^^^^^ /// ``` value: Option, + /// Metadata about an unstable default value provided for the associated constant, if any. + /// + /// Empty if the associated constant has no default (see [`ItemEnum::AssocConst::value`]), + /// or if the default value is stable. + default_unstable: Option>, }, /// An associated type of a trait or a type. AssocType { @@ -899,6 +923,11 @@ pub enum ItemEnum { /// ``` #[serde(rename = "type")] type_: Option, + /// Metadata about an unstable default value provided for the associated type, if any. + /// + /// Empty if the associated type has no default (see [`ItemEnum::AssocType::type_`]), + /// or if the default value is stable. + default_unstable: Option>, }, } @@ -1188,6 +1217,12 @@ pub struct Function { pub header: FunctionHeader, /// Whether the function has a body, i.e. an implementation. pub has_body: bool, + /// Metadata about a possible unstable provided default implementation for trait methods. + /// + /// Only populated for function items inside traits. Empty if the trait method + /// does not have a default implementation (see [`Function::has_body`]), + /// or if its default implementation is stable. + pub default_unstable: Option>, } /// Generic parameters accepted by an item and `where` clauses imposed on it and the parameters. diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index 01deafe20b354..76a47c4f4fecf 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -97,7 +97,7 @@ impl<'a> Validator<'a> { ItemEnum::StructField(x) => self.check_struct_field(x), ItemEnum::Enum(x) => self.check_enum(x), ItemEnum::Variant(x) => self.check_variant(x, id), - ItemEnum::Function(x) => self.check_function(x), + ItemEnum::Function(x) => self.check_function(x, id), ItemEnum::Trait(x) => self.check_trait(x, id), ItemEnum::TraitAlias(x) => self.check_trait_alias(x), ItemEnum::Impl(x) => self.check_impl(x, id), @@ -114,12 +114,35 @@ impl<'a> Validator<'a> { ItemEnum::Module(x) => self.check_module(x, id), // FIXME: Why don't these have their own structs? ItemEnum::ExternCrate { .. } => {} - ItemEnum::AssocConst { type_, value: _ } => self.check_type(type_), - ItemEnum::AssocType { generics, bounds, type_ } => { + ItemEnum::AssocConst { type_, value, default_unstable } => { + self.check_type(type_); + if value.is_none() + && let Some(default_unstable) = default_unstable + { + self.fail( + id, + ErrorKind::Custom(format!( + "`default_unstable` must be `None` when `value` is `None`, but \ + assoc const id {} had `default_unstable` with feature `{}`", + id.0, default_unstable.feature + )), + ); + } + } + ItemEnum::AssocType { generics, bounds, type_, default_unstable } => { self.check_generics(generics); bounds.iter().for_each(|b| self.check_generic_bound(b)); if let Some(ty) = type_ { self.check_type(ty); + } else if let Some(default_unstable) = default_unstable { + self.fail( + id, + ErrorKind::Custom(format!( + "`default_unstable` must be `None` when `type_` is `None`, but \ + assoc type id {} had `default_unstable` with feature `{}`", + id.0, default_unstable.feature + )), + ); } } } @@ -194,9 +217,21 @@ impl<'a> Validator<'a> { } } - fn check_function(&mut self, x: &'a Function) { + fn check_function(&mut self, x: &'a Function, id: &Id) { self.check_generics(&x.generics); self.check_function_signature(&x.sig); + if !x.has_body + && let Some(default_unstable) = &x.default_unstable + { + self.fail( + id, + ErrorKind::Custom(format!( + "`default_unstable` must be `None` when `has_body == false`, but \ + function item id {} had `default_unstable` with feature `{}`", + id.0, default_unstable.feature + )), + ); + } } fn check_trait(&mut self, x: &'a Trait, id: &Id) { diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs index ff2fae157f04c..f9b54a2cc0936 100644 --- a/src/tools/jsondoclint/src/validator/tests.rs +++ b/src/tools/jsondoclint/src/validator/tests.rs @@ -1,5 +1,7 @@ use rustc_hash::FxHashMap; -use rustdoc_json_types::{Abi, FORMAT_VERSION, FunctionHeader, Item, ItemKind, Visibility}; +use rustdoc_json_types::{ + Abi, FORMAT_VERSION, FunctionHeader, Item, ItemKind, ProvidedDefaultUnstable, Visibility, +}; use super::*; use crate::json_find::SelectorPart; @@ -221,6 +223,7 @@ fn errors_on_missing_path() { abi: Abi::Rust, }, has_body: true, + default_unstable: None, }), }, ), @@ -246,6 +249,155 @@ fn errors_on_missing_path() { ); } +fn krate_with_trait_item(inner: ItemEnum) -> Crate { + let item_id = Id(2); + Crate { + root: Id(0), + crate_version: None, + includes_private: false, + index: FxHashMap::from_iter([ + ( + Id(0), + Item { + id: Id(0), + crate_id: 0, + name: Some("root".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + stability: None, + const_stability: None, + inner: ItemEnum::Module(Module { + is_crate: true, + items: vec![Id(1)], + is_stripped: false, + }), + }, + ), + ( + Id(1), + Item { + id: Id(1), + crate_id: 0, + name: Some("Trait".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + stability: None, + const_stability: None, + inner: ItemEnum::Trait(Trait { + is_auto: false, + is_unsafe: false, + is_dyn_compatible: true, + items: vec![item_id], + generics: Generics { params: vec![], where_predicates: vec![] }, + bounds: vec![], + implementations: vec![], + }), + }, + ), + ( + item_id, + Item { + id: item_id, + crate_id: 0, + name: Some("TraitItem".to_owned()), + span: None, + visibility: Visibility::Public, + docs: None, + links: FxHashMap::default(), + attrs: Vec::new(), + deprecation: None, + stability: None, + const_stability: None, + inner, + }, + ), + ]), + paths: FxHashMap::default(), + external_crates: FxHashMap::default(), + target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] }, + format_version: FORMAT_VERSION, + } +} + +#[test] +fn errors_on_default_unstable_without_function_body() { + let krate = krate_with_trait_item(ItemEnum::Function(Function { + sig: FunctionSignature { inputs: vec![], output: None, is_c_variadic: false }, + generics: Generics { params: vec![], where_predicates: vec![] }, + header: FunctionHeader { + is_const: false, + is_unsafe: false, + is_async: false, + abi: Abi::Rust, + }, + has_body: false, + default_unstable: Some(Box::new(ProvidedDefaultUnstable { feature: "feature".to_owned() })), + })); + + check( + &krate, + &[Error { + id: Id(2), + kind: ErrorKind::Custom( + "`default_unstable` must be `None` when `has_body == false`, but \ + function item id 2 had `default_unstable` with feature `feature`" + .to_owned(), + ), + }], + ); +} + +#[test] +fn errors_on_default_unstable_without_assoc_const_value() { + let krate = krate_with_trait_item(ItemEnum::AssocConst { + type_: Type::Primitive("usize".to_owned()), + value: None, + default_unstable: Some(Box::new(ProvidedDefaultUnstable { feature: "feature".to_owned() })), + }); + + check( + &krate, + &[Error { + id: Id(2), + kind: ErrorKind::Custom( + "`default_unstable` must be `None` when `value` is `None`, but \ + assoc const id 2 had `default_unstable` with feature `feature`" + .to_owned(), + ), + }], + ); +} + +#[test] +fn errors_on_default_unstable_without_assoc_type_default() { + let krate = krate_with_trait_item(ItemEnum::AssocType { + generics: Generics { params: vec![], where_predicates: vec![] }, + bounds: vec![], + type_: None, + default_unstable: Some(Box::new(ProvidedDefaultUnstable { feature: "feature".to_owned() })), + }); + + check( + &krate, + &[Error { + id: Id(2), + kind: ErrorKind::Custom( + "`default_unstable` must be `None` when `type_` is `None`, but \ + assoc type id 2 had `default_unstable` with feature `feature`" + .to_owned(), + ), + }], + ); +} + #[test] #[should_panic = "LOCAL_CRATE_ID is wrong"] fn checks_local_crate_id_is_correct() { diff --git a/tests/rustdoc-json/attrs/stability/default_body.rs b/tests/rustdoc-json/attrs/stability/default_body.rs new file mode 100644 index 0000000000000..5a70e47d6c505 --- /dev/null +++ b/tests/rustdoc-json/attrs/stability/default_body.rs @@ -0,0 +1,76 @@ +#![feature(staged_api, rustc_attrs, associated_type_defaults)] + +#[stable(feature = "default_body_trait_feature", since = "1.0.0")] +pub trait TraitWithDefaults { + //@ is "$.index[?(@.docs=='method with unstable default body')].inner.function.has_body" true + //@ is "$.index[?(@.docs=='method with unstable default body')].inner.function.default_unstable.feature" '"method_default_body_feature"' + //@ is "$.index[?(@.docs=='method with unstable default body')].attrs" [] + /// method with unstable default body + #[stable(feature = "default_body_method_feature", since = "1.1.0")] + #[rustc_default_body_unstable(feature = "method_default_body_feature", issue = "none")] + fn method_with_unstable_default() {} + + //@ is "$.index[?(@.docs=='required method without default body')].inner.function.has_body" false + //@ is "$.index[?(@.docs=='required method without default body')].inner.function.default_unstable" null + /// required method without default body + #[stable(feature = "required_method_feature", since = "1.2.0")] + fn required_method(); + + //@ is "$.index[?(@.docs=='method with stable default body')].inner.function.has_body" true + //@ is "$.index[?(@.docs=='method with stable default body')].inner.function.default_unstable" null + /// method with stable default body + #[stable(feature = "stable_default_method_feature", since = "1.3.0")] + fn method_with_stable_default() {} + + //@ is "$.index[?(@.docs=='associated constant with unstable default value')].inner.assoc_const.value" '"0"' + //@ is "$.index[?(@.docs=='associated constant with unstable default value')].inner.assoc_const.default_unstable.feature" '"assoc_const_default_value_feature"' + //@ is "$.index[?(@.docs=='associated constant with unstable default value')].attrs" [] + /// associated constant with unstable default value + #[stable(feature = "assoc_const_with_unstable_default_feature", since = "1.4.0")] + #[rustc_default_body_unstable(feature = "assoc_const_default_value_feature", issue = "none")] + const UNSTABLE_DEFAULT_CONST: usize = 0; + + //@ is "$.index[?(@.docs=='required associated constant')].inner.assoc_const.value" null + //@ is "$.index[?(@.docs=='required associated constant')].inner.assoc_const.default_unstable" null + /// required associated constant + #[stable(feature = "required_assoc_const_feature", since = "1.5.0")] + const REQUIRED_CONST: usize; + + //@ is "$.index[?(@.docs=='associated type with unstable default type')].inner.assoc_type.default_unstable.feature" '"assoc_type_default_type_feature"' + //@ is "$.index[?(@.docs=='associated type with unstable default type')].attrs" [] + /// associated type with unstable default type + #[stable(feature = "assoc_type_with_unstable_default_feature", since = "1.6.0")] + #[rustc_default_body_unstable(feature = "assoc_type_default_type_feature", issue = "none")] + type UnstableDefaultType = usize; + + //@ is "$.index[?(@.docs=='required associated type')].inner.assoc_type.type" null + //@ is "$.index[?(@.docs=='required associated type')].inner.assoc_type.default_unstable" null + /// required associated type + #[stable(feature = "required_assoc_type_feature", since = "1.7.0")] + type RequiredType; +} + +#[stable(feature = "default_body_impl_target_feature", since = "2.0.0")] +pub struct ImplTarget; + +// Impl items provide their own definitions, so they do not use the trait's unstable defaults. +#[stable(feature = "default_body_impl_feature", since = "2.1.0")] +impl TraitWithDefaults for ImplTarget { + //@ is "$.index[?(@.docs=='impl override for unstable default body')].inner.function.default_unstable" null + /// impl override for unstable default body + fn method_with_unstable_default() {} + + fn required_method() {} + + //@ is "$.index[?(@.docs=='impl override for unstable default value')].inner.assoc_const.default_unstable" null + /// impl override for unstable default value + const UNSTABLE_DEFAULT_CONST: usize = 1; + + const REQUIRED_CONST: usize = 2; + + //@ is "$.index[?(@.docs=='impl override for unstable default type')].inner.assoc_type.default_unstable" null + /// impl override for unstable default type + type UnstableDefaultType = u8; + + type RequiredType = (); +} From 7f2574f608d339d42035ef200338af0652b4dd44 Mon Sep 17 00:00:00 2001 From: Justin Schilleman Date: Sun, 28 Jun 2026 22:02:48 +0000 Subject: [PATCH 12/24] Move attribute and keyword docs from `std` to `core` * refactor: move attribute and keywords docs files to core * fix references to `std` * tidy fixes * ignore doc tests w/ explicit_tail_calls * revert `unsafe` example and ignore specifically WASM for `become` doc tests * add explicit note about doube including the docs in `core` and `std` * missed refactoring of doc test ignore * conditionally exclude doc-test containing threading for `wasm-wasip1` * Change exclusion to just `wasi` target_os Co-authored-by: Justin Schilleman <97192655+jschillem@users.noreply.github.com> --- library/{std => core}/src/attribute_docs.rs | 0 library/{std => core}/src/keyword_docs.rs | 59 ++++++++++----------- library/core/src/lib.rs | 13 +++++ library/std/src/lib.rs | 21 ++++---- 4 files changed, 53 insertions(+), 40 deletions(-) rename library/{std => core}/src/attribute_docs.rs (100%) rename library/{std => core}/src/keyword_docs.rs (98%) diff --git a/library/std/src/attribute_docs.rs b/library/core/src/attribute_docs.rs similarity index 100% rename from library/std/src/attribute_docs.rs rename to library/core/src/attribute_docs.rs diff --git a/library/std/src/keyword_docs.rs b/library/core/src/keyword_docs.rs similarity index 98% rename from library/std/src/keyword_docs.rs rename to library/core/src/keyword_docs.rs index 5f94a13dad22a..3b0e3f21a0ac4 100644 --- a/library/std/src/keyword_docs.rs +++ b/library/core/src/keyword_docs.rs @@ -195,7 +195,7 @@ mod break_keyword {} /// to be most things that would be reasonable to have in a constant (barring `const fn`s). For /// example, you can't have a [`File`] as a `const`. /// -/// [`File`]: crate::fs::File +/// [`File`]: ../std/fs/struct.File.html /// /// The only lifetime allowed in a constant is `'static`, which is the lifetime that encompasses /// all others in a Rust program. For example, if you wanted to define a constant string, it would @@ -484,7 +484,7 @@ mod extern_keyword {} #[doc(keyword = "false")] // -/// A value of type [`bool`] representing logical **false**. +/// A value of type [`prim@bool`] representing logical **false**. /// /// `false` is the logical opposite of [`true`]. /// @@ -1060,7 +1060,8 @@ mod mod_keyword {} /// /// `move` is often used when [threads] are involved. /// -/// ```rust +#[cfg_attr(target_os = "wasi", doc = "```rust,ignore (thread::spawn not supported)")] +#[cfg_attr(not(target_os = "wasi"), doc = "```rust")] /// let data = vec![1, 2, 3]; /// /// std::thread::spawn(move || { @@ -1235,31 +1236,18 @@ mod ref_keyword {} /// `return` returns from the function immediately (an "early return"): /// /// ```no_run -/// use std::fs::File; -/// use std::io::{Error, ErrorKind, Read, Result}; +/// fn main() -> Result<(), &'static str> { +/// let contents = "Hello, world!"; /// -/// fn main() -> Result<()> { -/// let mut file = match File::open("foo.txt") { -/// Ok(f) => f, -/// Err(e) => return Err(e), -/// }; +/// if contents.contains("impossible!") { +/// return Err("oh no!"); +/// } /// -/// let mut contents = String::new(); -/// let size = match file.read_to_string(&mut contents) { -/// Ok(s) => s, -/// Err(e) => return Err(e), -/// }; -/// -/// if contents.contains("impossible!") { -/// return Err(Error::new(ErrorKind::Other, "oh no!")); -/// } +/// if contents.len() > 9000 { +/// return Err("over 9000!"); +/// } /// -/// if size > 9000 { -/// return Err(Error::new(ErrorKind::Other, "over 9000!")); -/// } -/// -/// assert_eq!(contents, "Hello, world!"); -/// Ok(()) +/// Ok(()) /// } /// ``` /// @@ -1306,7 +1294,11 @@ mod return_keyword {} /// manner to computed goto). /// /// Example of using `become` to implement functional-style `fold`: -/// ``` +#[cfg_attr( + target_family = "wasm", + doc = "```ignore (tail-call target feature not enabled by default on wasm)" +)] +#[cfg_attr(not(target_family = "wasm"), doc = "```")] /// #![feature(explicit_tail_calls)] /// #![expect(incomplete_features)] /// @@ -1360,7 +1352,11 @@ mod return_keyword {} /// (unless it's coerced to a function pointer) /// /// It is possible to tail-call a function pointer: -/// ``` +#[cfg_attr( + target_family = "wasm", + doc = "```ignore (tail-call target feature not enabled by default on wasm)" +)] +#[cfg_attr(not(target_family = "wasm"), doc = "```")] /// #![feature(explicit_tail_calls)] /// #![expect(incomplete_features)] /// @@ -1631,8 +1627,8 @@ mod self_upper_keyword {} /// [`extern`]: keyword.extern.html /// [`mut`]: keyword.mut.html /// [`unsafe`]: keyword.unsafe.html -/// [`Mutex`]: sync::Mutex -/// [`OnceLock`]: sync::OnceLock +/// [`Mutex`]: ../std/sync/struct.Mutex.html +/// [`OnceLock`]: ../std/sync/struct.OnceLock.html /// [`RefCell`]: cell::RefCell /// [atomic]: sync::atomic /// [Reference]: ../reference/items/static-items.html @@ -1959,7 +1955,7 @@ mod trait_keyword {} #[doc(keyword = "true")] // -/// A value of type [`bool`] representing logical **true**. +/// A value of type [`prim@bool`] representing logical **true**. /// /// Logically `true` is not equal to [`false`]. /// @@ -2312,6 +2308,7 @@ mod type_keyword {} /// [`static`]: keyword.static.html /// [`union`]: keyword.union.html /// [`impl`]: keyword.impl.html +/// [`Vec::set_len`]: ../std/vec/struct.Vec.html#method.set_len /// [raw pointers]: ../reference/types/pointer.html /// [memory safety]: ../book/ch19-01-unsafe-rust.html /// [Rustonomicon]: ../nomicon/index.html @@ -2502,7 +2499,7 @@ mod use_keyword {} /// ``` /// /// `where` is available anywhere generic and lifetime parameters are available, -/// as can be seen with the [`Cow`](crate::borrow::Cow) type from the standard +/// as can be seen with the [`Cow`](../std/borrow/enum.Cow.html) type from the standard /// library: /// /// ```rust diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index a26304c46ecea..124e871a32a3a 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -397,4 +397,17 @@ pub mod simd { pub use crate::core_simd::simd::*; } +// Include private modules that exist solely to provide rustdoc +// documentation for built-in attributes. Using `include!` because rustdoc +// only looks for these modules at the crate level. +include!("attribute_docs.rs"); + +// Include a number of private modules that exist solely to provide +// the rustdoc documentation for the existing keywords. Using `include!` +// because rustdoc only looks for these modules at the crate level. +include!("keyword_docs.rs"); + +// Include a number of private modules that exist solely to provide +// the rustdoc documentation for primitive types. Using `include!` +// because rustdoc only looks for these modules at the crate level. include!("primitive_docs.rs"); diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 1b27be8e2dde3..f24ce720e944f 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -778,20 +778,23 @@ pub mod from { pub use core::from::From; } -// Include a number of private modules that exist solely to provide -// the rustdoc documentation for primitive types. Using `include!` -// because rustdoc only looks for these modules at the crate level. -include!("../../core/src/primitive_docs.rs"); +// We include the following files here *again* (they are already included in libcore) +// so that they show up in search results for the std crate, and to avoid breaking +// existing links: + +// documentation for built-in attributes. Using `include!` because rustdoc +// only looks for these modules at the crate level. +include!("../../core/src/attribute_docs.rs"); // Include a number of private modules that exist solely to provide // the rustdoc documentation for the existing keywords. Using `include!` // because rustdoc only looks for these modules at the crate level. -include!("keyword_docs.rs"); +include!("../../core/src/keyword_docs.rs"); -// Include private modules that exist solely to provide rustdoc -// documentation for built-in attributes. Using `include!` because rustdoc -// only looks for these modules at the crate level. -include!("attribute_docs.rs"); +// Include a number of private modules that exist solely to provide +// the rustdoc documentation for primitive types. Using `include!` +// because rustdoc only looks for these modules at the crate level. +include!("../../core/src/primitive_docs.rs"); // This is required to avoid an unstable error when `restricted-std` is not // enabled. The use of #![feature(restricted_std)] in rustc-std-workspace-std From 1a7439cc0ff575fd39e71e1519b3f4c80b9140f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 20 Jun 2026 11:22:40 +0200 Subject: [PATCH 13/24] Fix `flag_if_supported` in `cc-rs` by configuring an `out_dir` --- src/bootstrap/src/utils/cc_detect.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index d010226f0dfdb..9524d5fd8672f 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -36,6 +36,8 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { .opt_level(2) .warnings(false) .debug(false) + // We have to configure out_dir, otherwise flag_if_supported will not work + .out_dir(build.tempdir().join("cc-rs-out-dir")) // Compress debuginfo .flag_if_supported("-gz") .target(&target.triple) From f832372b5e64a8c82fc703c2825b42325bad4f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 20 Jun 2026 11:23:07 +0200 Subject: [PATCH 14/24] Slightly refactor cc code --- src/bootstrap/src/utils/cc_detect.rs | 14 ++++++-------- src/bootstrap/src/utils/cc_detect/tests.rs | 4 ++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index 9524d5fd8672f..8a4f991bdafa5 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -102,17 +102,15 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) { let config = build.config.target_config.get(&target); if let Some(cc) = config .and_then(|c| c.cc.clone()) - .or_else(|| default_compiler(&mut cfg, Language::C, target, build)) + .or_else(|| default_compiler(&cfg, Language::C, target, build)) { cfg.compiler(cc); } let compiler = cfg.get_compiler(); - let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) { - ar - } else { - cfg.try_get_archiver().map(|c| PathBuf::from(c.get_program())).ok() - }; + let ar = config + .and_then(|c| c.ar.clone()) + .or_else(|| cfg.try_get_archiver().map(|c| PathBuf::from(c.get_program())).ok()); build.cc.insert(target, compiler.clone()); let mut cflags = build.cc_handled_clags(target, CLang::C); @@ -124,7 +122,7 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) { cfg.cpp(true); let cxx_configured = if let Some(cxx) = config .and_then(|c| c.cxx.clone()) - .or_else(|| default_compiler(&mut cfg, Language::CPlusPlus, target, build)) + .or_else(|| default_compiler(&cfg, Language::CPlusPlus, target, build)) { cfg.compiler(cxx); true @@ -160,7 +158,7 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) { /// Determines the default compiler for a given target and language when not explicitly /// configured in `bootstrap.toml`. fn default_compiler( - cfg: &mut cc::Build, + cfg: &cc::Build, compiler: Language, target: TargetSelection, build: &Build, diff --git a/src/bootstrap/src/utils/cc_detect/tests.rs b/src/bootstrap/src/utils/cc_detect/tests.rs index a6233e6b61c1f..30757a39caa1c 100644 --- a/src/bootstrap/src/utils/cc_detect/tests.rs +++ b/src/bootstrap/src/utils/cc_detect/tests.rs @@ -86,7 +86,7 @@ fn test_default_compiler_wasi() { build.wasi_sdk_path = Some(wasi_sdk.clone()); let mut cfg = cc::Build::new(); - if let Some(result) = default_compiler(&mut cfg, Language::C, target.clone(), &build) { + if let Some(result) = default_compiler(&cfg, Language::C, target.clone(), &build) { let expected = { let compiler = format!("{}-clang", target.triple); wasi_sdk.join("bin").join(compiler) @@ -105,7 +105,7 @@ fn test_default_compiler_fallback() { let build = Build::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let mut cfg = cc::Build::new(); - let result = default_compiler(&mut cfg, Language::C, target, &build); + let result = default_compiler(&cfg, Language::C, target, &build); assert!(result.is_none(), "default_compiler should return None for generic targets"); } From ae0fb2792bd0e992ed558c3e69ed7357e7406a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 20 Jun 2026 12:54:03 +0200 Subject: [PATCH 15/24] Add missing zlib library to `x86_64-gnu-distcheck` --- src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile index 5ab44df7a8033..48be0b9f96e9d 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile @@ -28,6 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ xz-utils \ libssl-dev \ pkg-config \ + zlib1g-dev \ && rm -rf /var/lib/apt/lists/* COPY scripts/sccache.sh /scripts/ From 65f06572fb81da89a1a87373aa3e6128060210c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sun, 21 Jun 2026 13:42:16 +0200 Subject: [PATCH 16/24] Add `compress-debuginfo` option to bootstrap and fix debuginfo compression with LLD. --- bootstrap.example.toml | 14 ++++++ src/bootstrap/src/core/builder/cargo.rs | 11 +++-- src/bootstrap/src/core/config/config.rs | 14 +++++- src/bootstrap/src/core/config/mod.rs | 48 ++++++++++++++++++++ src/bootstrap/src/core/config/toml/rust.rs | 5 +- src/bootstrap/src/core/config/toml/target.rs | 4 +- src/bootstrap/src/utils/cc_detect.rs | 12 +++-- src/bootstrap/src/utils/change_tracker.rs | 5 ++ 8 files changed, 103 insertions(+), 10 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 25e2ca5948bfb..a52d2e945536e 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -697,6 +697,13 @@ # FIXME(#61117): Some tests fail when this option is enabled. #rust.debuginfo-level-tests = 0 +# Compress debuginfo of Rust and C/C++ code. +# Valid options: +# - "off" or false: disable compression +# - true: compress debuginfo with the default compression method (currently zlib) +# - "zlib": compress debuginfo with zlib +#rust.compress-debuginfo = "off" + # Should rustc and the standard library be built with split debuginfo? Default # is platform dependent. # @@ -1019,6 +1026,13 @@ # and enabling it causes issues. #split-debuginfo = if linux || windows-gnu { off } else if windows-msvc { packed } else if apple { unpacked } +# Compress debuginfo for Rust and C/C++ code of this target. +# Valid options: +# - "off" or false: disable compression +# - true: compress debuginfo with the default compression method (currently zlib) +# - "zlib": compress debuginfo with zlib +#compress-debuginfo = "off" + # Path to the `llvm-config` binary of the installation of a custom LLVM to link # against. Note that if this is specified we don't compile LLVM at all for this # target. diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index e7fe8bd1f3858..9122815d8db32 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -5,8 +5,8 @@ use std::path::{Path, PathBuf}; use super::{Builder, Kind}; use crate::core::build_steps::test; use crate::core::build_steps::tool::SourceType; -use crate::core::config::SplitDebuginfo; use crate::core::config::flags::Color; +use crate::core::config::{CompressDebuginfo, SplitDebuginfo}; use crate::utils::build_stamp; use crate::utils::helpers::{self, LldThreads, check_cfg_arg, linker_flags}; use crate::{ @@ -340,8 +340,13 @@ impl Cargo { self.rustdocflags.arg(&arg); } - if !builder.config.dry_run() && builder.cc[&target].args().iter().any(|arg| arg == "-gz") { - self.rustflags.arg("-Clink-arg=-gz"); + if !builder.config.dry_run() { + match builder.config.compress_debuginfo(target) { + CompressDebuginfo::Zlib => { + self.rustflags.arg("-Clink-arg=-Wl,--compress-debug-sections=zlib"); + } + CompressDebuginfo::Off => {} + } } // Ignore linker warnings for now. These are complicated to fix and don't affect the build. diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 5a9c7264c006f..ed5dd9c9ee9fa 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -49,8 +49,8 @@ use crate::core::config::toml::target::{ DefaultLinuxLinkerOverride, Target, TomlTarget, default_linux_linker_overrides, }; use crate::core::config::{ - CompilerBuiltins, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt, - RustcLto, SplitDebuginfo, StringOrBool, threads_from_config, + CompilerBuiltins, CompressDebuginfo, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, + ReplaceOpt, RustcLto, SplitDebuginfo, StringOrBool, threads_from_config, }; use crate::core::download::{ DownloadContext, download_beta_toolchain, is_download_ci_available, maybe_download_rustfmt, @@ -210,6 +210,7 @@ pub struct Config { pub rust_debuginfo_level_std: DebuginfoLevel, pub rust_debuginfo_level_tools: DebuginfoLevel, pub rust_debuginfo_level_tests: DebuginfoLevel, + pub rust_compress_debuginfo: CompressDebuginfo, pub rust_rpath: bool, pub rust_strip: bool, pub rust_frame_pointers: bool, @@ -550,6 +551,7 @@ impl Config { debuginfo_level_std: rust_debuginfo_level_std, debuginfo_level_tools: rust_debuginfo_level_tools, debuginfo_level_tests: rust_debuginfo_level_tests, + compress_debuginfo: rust_compress_debuginfo, backtrace: rust_backtrace, incremental: rust_incremental, randomize_layout: rust_randomize_layout, @@ -1469,6 +1471,7 @@ impl Config { .unwrap_or(vec![CodegenBackendKind::Llvm]), rust_codegen_units: rust_codegen_units.map(threads_from_config), rust_codegen_units_std: rust_codegen_units_std.map(threads_from_config), + rust_compress_debuginfo: rust_compress_debuginfo.unwrap_or_default(), rust_debug_logging: rust_debug_logging .or(rust_rustc_debug_assertions) .unwrap_or(rust_debug == Some(true)), @@ -1925,6 +1928,13 @@ impl Config { .unwrap_or_else(|| SplitDebuginfo::default_for_platform(target)) } + pub fn compress_debuginfo(&self, target: TargetSelection) -> CompressDebuginfo { + self.target_config + .get(&target) + .and_then(|t| t.compress_debuginfo) + .unwrap_or(self.rust_compress_debuginfo) + } + /// Checks if the given target is the same as the host target. pub fn is_host_target(&self, target: TargetSelection) -> bool { self.host_target == target diff --git a/src/bootstrap/src/core/config/mod.rs b/src/bootstrap/src/core/config/mod.rs index 7651def62672a..c29849cf5a5d6 100644 --- a/src/bootstrap/src/core/config/mod.rs +++ b/src/bootstrap/src/core/config/mod.rs @@ -32,6 +32,7 @@ use std::path::PathBuf; use build_helper::exit; pub use config::*; +use serde::de::Unexpected; use serde::{Deserialize, Deserializer}; use serde_derive::Deserialize; pub use target_selection::TargetSelection; @@ -378,6 +379,53 @@ impl std::str::FromStr for SplitDebuginfo { } } +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +pub enum CompressDebuginfo { + Zlib, + #[default] + Off, +} + +impl CompressDebuginfo { + fn default_on() -> Self { + Self::Zlib + } +} + +impl std::str::FromStr for CompressDebuginfo { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "zlib" => Ok(CompressDebuginfo::Zlib), + "off" => Ok(CompressDebuginfo::Off), + _ => Err(()), + } + } +} + +impl<'de> Deserialize<'de> for CompressDebuginfo { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de::Error; + + Ok(match Deserialize::deserialize(deserializer)? { + StringOrBool::Bool(value) => { + if value { + CompressDebuginfo::default_on() + } else { + CompressDebuginfo::Off + } + } + StringOrBool::String(value) => CompressDebuginfo::from_str(&value).map_err(|_| { + D::Error::invalid_value(Unexpected::Str(&value), &"`zlib` or `off`") + })?, + }) + } +} + /// Describes how to handle conflicts in merging two `TomlConfig` #[derive(Copy, Clone, Debug)] pub enum ReplaceOpt { diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index a872671343405..f190eec3761ed 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -5,7 +5,7 @@ use build_helper::ci::CiEnv; use serde::{Deserialize, Deserializer}; use crate::core::config::toml::TomlConfig; -use crate::core::config::{DebuginfoLevel, Merge, ReplaceOpt, StringOrBool}; +use crate::core::config::{CompressDebuginfo, DebuginfoLevel, Merge, ReplaceOpt, StringOrBool}; use crate::{BTreeSet, CodegenBackendKind, HashSet, PathBuf, TargetSelection, define_config, exit}; define_config! { @@ -28,6 +28,7 @@ define_config! { debuginfo_level_std: Option = "debuginfo-level-std", debuginfo_level_tools: Option = "debuginfo-level-tools", debuginfo_level_tests: Option = "debuginfo-level-tests", + compress_debuginfo: Option = "compress-debuginfo", backtrace: Option = "backtrace", incremental: Option = "incremental", default_linker: Option = "default-linker", @@ -322,6 +323,7 @@ pub fn check_incompatible_options_for_ci_rustc( randomize_layout, debug_logging, debuginfo_level_rustc, + compress_debuginfo, llvm_tools, llvm_bitcode_linker, stack_protector, @@ -389,6 +391,7 @@ pub fn check_incompatible_options_for_ci_rustc( err!(current_rust_config.optimize, optimize, "rust"); err!(current_rust_config.randomize_layout, randomize_layout, "rust"); + err!(current_rust_config.compress_debuginfo, compress_debuginfo, "rust"); err!(current_rust_config.debug_logging, debug_logging, "rust"); err!(current_rust_config.debuginfo_level_rustc, debuginfo_level_rustc, "rust"); err!(current_rust_config.rpath, rpath, "rust"); diff --git a/src/bootstrap/src/core/config/toml/target.rs b/src/bootstrap/src/core/config/toml/target.rs index 847b75e696b47..311d9add9a144 100644 --- a/src/bootstrap/src/core/config/toml/target.rs +++ b/src/bootstrap/src/core/config/toml/target.rs @@ -15,7 +15,8 @@ use serde::de::Error; use serde::{Deserialize, Deserializer}; use crate::core::config::{ - CompilerBuiltins, LlvmLibunwind, Merge, ReplaceOpt, SplitDebuginfo, StringOrBool, + CompilerBuiltins, CompressDebuginfo, LlvmLibunwind, Merge, ReplaceOpt, SplitDebuginfo, + StringOrBool, }; use crate::{CodegenBackendKind, HashSet, PathBuf, define_config, exit}; @@ -68,6 +69,7 @@ pub struct Target { pub default_linker_linux_override: DefaultLinuxLinkerOverride, pub linker: Option, pub split_debuginfo: Option, + pub compress_debuginfo: Option, pub sanitizers: Option, pub profiler: Option, pub rpath: Option, diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index 8a4f991bdafa5..320dfcc4e69ed 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -25,7 +25,7 @@ use std::collections::HashSet; use std::iter; use std::path::{Path, PathBuf}; -use crate::core::config::TargetSelection; +use crate::core::config::{CompressDebuginfo, TargetSelection}; use crate::utils::exec::{BootstrapCommand, command}; use crate::{Build, CLang, GitRepo}; @@ -38,10 +38,16 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { .debug(false) // We have to configure out_dir, otherwise flag_if_supported will not work .out_dir(build.tempdir().join("cc-rs-out-dir")) - // Compress debuginfo - .flag_if_supported("-gz") .target(&target.triple) .host(&build.host_target.triple); + + match build.config.compress_debuginfo(target) { + CompressDebuginfo::Zlib => { + cfg.flag_if_supported("-gz"); + } + CompressDebuginfo::Off => {} + } + match build.crt_static(target) { Some(a) => { cfg.static_crt(a); diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 566ad003ebe01..b34c1b55700f8 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -631,4 +631,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "New `--verbose-run-make-subprocess-output` flag for `x.py test` (defaults to true). Set `--verbose-run-make-subprocess-output=false` to suppress verbose subprocess output for passing run-make tests when using `--no-capture`.", }, + ChangeInfo { + change_id: 158169, + severity: ChangeSeverity::Info, + summary: "New option `rust.compress-debuginfo` allows configuring whether Rust and C/C++ debuginfo should be compressed.", + }, ]; From 10c2c9decb27dcfb846da377269c3e5d991d9866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sun, 21 Jun 2026 13:44:49 +0200 Subject: [PATCH 17/24] Enable debuginfo compression by default in dist builds --- src/bootstrap/defaults/bootstrap.dist.toml | 2 ++ src/ci/run.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/bootstrap/defaults/bootstrap.dist.toml b/src/bootstrap/defaults/bootstrap.dist.toml index cce3f068aabcd..03e012079b079 100644 --- a/src/bootstrap/defaults/bootstrap.dist.toml +++ b/src/bootstrap/defaults/bootstrap.dist.toml @@ -26,6 +26,8 @@ download-rustc = false llvm-bitcode-linker = true # Required to make builds reproducible. remap-debuginfo = true +# Compress debuginfo in generated artifacts to reduce their size +compress-debuginfo = "zlib" [dist] # Use better compression when preparing tarballs. diff --git a/src/ci/run.sh b/src/ci/run.sh index 18b5bf2e2bbb3..883cc20ba0014 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -145,6 +145,8 @@ if [ "$DEPLOY$DEPLOY_ALT" = "1" ]; then fi else RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.remap-debuginfo=false" + # No need to compress debuginfo for tests, and we do not want to depend on zlib + RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.compress-debuginfo=off" # We almost always want debug assertions enabled, but sometimes this takes too # long for too little benefit, so we just turn them off. From cca8cd1736adb36920f6721e540bd9df4298c80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 29 Jun 2026 12:32:03 +0200 Subject: [PATCH 18/24] Use doctest attribute `ignore-wasm` instead of manual `cfg_attr` --- library/core/src/keyword_docs.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/library/core/src/keyword_docs.rs b/library/core/src/keyword_docs.rs index 3b0e3f21a0ac4..596765be5e2dd 100644 --- a/library/core/src/keyword_docs.rs +++ b/library/core/src/keyword_docs.rs @@ -1294,11 +1294,8 @@ mod return_keyword {} /// manner to computed goto). /// /// Example of using `become` to implement functional-style `fold`: -#[cfg_attr( - target_family = "wasm", - doc = "```ignore (tail-call target feature not enabled by default on wasm)" -)] -#[cfg_attr(not(target_family = "wasm"), doc = "```")] +/// +/// ```ignore-wasm (tail-call target feature not enabled by default on wasm) /// #![feature(explicit_tail_calls)] /// #![expect(incomplete_features)] /// @@ -1352,11 +1349,8 @@ mod return_keyword {} /// (unless it's coerced to a function pointer) /// /// It is possible to tail-call a function pointer: -#[cfg_attr( - target_family = "wasm", - doc = "```ignore (tail-call target feature not enabled by default on wasm)" -)] -#[cfg_attr(not(target_family = "wasm"), doc = "```")] +/// +/// ```ignore-wasm (tail-call target feature not enabled by default on wasm) /// #![feature(explicit_tail_calls)] /// #![expect(incomplete_features)] /// From 96f3774b0da81331a2c2b129f01dc10428fa2619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 29 Jun 2026 14:35:33 +0200 Subject: [PATCH 19/24] Record step creation locations in bootstrap tracing DOT graph --- src/bootstrap/src/utils/step_graph.rs | 59 +++++++++++++++++++-------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/utils/step_graph.rs b/src/bootstrap/src/utils/step_graph.rs index cffe0dbb3e3a5..d6a2d89482ee4 100644 --- a/src/bootstrap/src/utils/step_graph.rs +++ b/src/bootstrap/src/utils/step_graph.rs @@ -1,10 +1,12 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::fmt::Debug; use std::io::BufWriter; +use std::panic::Location; use std::path::Path; use crate::core::builder::{AnyDebug, Step, pretty_step_name}; use crate::t; +use crate::utils::tracing::format_location; /// Records the executed steps and their dependencies in a directed graph, /// which can then be rendered into a DOT file for visualization. @@ -20,6 +22,7 @@ pub struct StepGraph { } impl StepGraph { + #[track_caller] pub fn register_step_execution( &mut self, step: &S, @@ -57,6 +60,7 @@ impl StepGraph { } } + #[track_caller] pub fn register_cached_step( &mut self, step: &S, @@ -97,12 +101,18 @@ struct Node { struct NodeHandle(usize); /// Represents a dependency between two bootstrap steps. -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] -struct Edge { - src: NodeHandle, - dst: NodeHandle, +#[derive(Default)] +struct EdgeData { // Was the corresponding execution of a step cached, or was the step actually executed? cached: bool, + // Locations from where the step was called + locations: Vec>, +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +struct EdgeKey { + src: NodeHandle, + dst: NodeHandle, } // We could use a library for this, but they either: @@ -114,8 +124,8 @@ struct Edge { #[derive(Default)] struct DotGraph { nodes: Vec, + edges: HashMap, /// The `NodeHandle` represents an index within `self.nodes` - edges: HashSet, key_to_index: HashMap, } @@ -127,16 +137,19 @@ impl DotGraph { handle } + #[track_caller] fn add_edge(&mut self, src: NodeHandle, dst: NodeHandle) { - self.edges.insert(Edge { src, dst, cached: false }); + let key = EdgeKey { src, dst }; + let edge = self.edges.entry(key).or_default(); + edge.locations.push(*Location::caller()); } + #[track_caller] fn add_cached_edge(&mut self, src: NodeHandle, dst: NodeHandle) { - // There's no point in rendering both cached and uncached edge - let uncached = Edge { src, dst, cached: false }; - if !self.edges.contains(&uncached) { - self.edges.insert(Edge { src, dst, cached: true }); - } + let key = EdgeKey { src, dst }; + let edge = self.edges.entry(key).or_default(); + edge.cached = true; + edge.locations.push(*Location::caller()); } fn get_handle_by_key(&self, key: &str) -> Option { @@ -157,11 +170,23 @@ impl DotGraph { )?; } - let mut edges: Vec<&Edge> = self.edges.iter().collect(); - edges.sort(); - for edge in edges { - let style = if edge.cached { "dashed" } else { "solid" }; - writeln!(file, r#"{} -> {} [style="{style}"]"#, edge.src.0, edge.dst.0)?; + let mut edges: Vec<(&EdgeKey, &EdgeData)> = self.edges.iter().collect(); + edges.sort_by_key(|(key, _)| **key); + for (key, data) in edges { + let style = if data.cached { "dashed" } else { "solid" }; + let mut locations = data + .locations + .iter() + .map(|location| format_location(*location)) + .collect::>(); + locations.sort(); + locations.dedup(); + let locations = locations.join(", "); + writeln!( + file, + r#"{} -> {} [style="{style}", tooltip="{locations}"]"#, + key.src.0, key.dst.0, + )?; } writeln!(file, "}}") From e6dc840c594f73aa4ec8e2f0917e997e0628830f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 29 Jun 2026 14:36:25 +0200 Subject: [PATCH 20/24] Add a few `#[track_caller]` annotations to make tracing locations more accurate --- src/bootstrap/src/core/builder/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 048fcc5c80145..2b842121e61fd 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -1150,6 +1150,7 @@ impl<'a> Builder<'a> { /// compiler will run on, *not* the target it will build code for). Explicitly does not take /// `Compiler` since all `Compiler` instances are meant to be obtained through this function, /// since it ensures that they are valid (i.e., built and assembled). + #[track_caller] #[cfg_attr( feature = "tracing", instrument( @@ -1183,6 +1184,7 @@ impl<'a> Builder<'a> { /// /// However, without this optimization, we would also build stage 2 rustc for **target1**, /// which is completely wasteful. + #[track_caller] pub fn compiler_for_std(&self, stage: u32) -> Compiler { if compile::Std::should_be_uplifted_from_stage_1(self, stage) { self.compiler(1, self.host_target) @@ -1202,6 +1204,7 @@ impl<'a> Builder<'a> { /// sysroot. /// /// See `force_use_stage1` and `force_use_stage2` for documentation on what each argument is. + #[track_caller] #[cfg_attr( feature = "tracing", instrument( @@ -1249,6 +1252,7 @@ impl<'a> Builder<'a> { /// Prefer using this method rather than manually invoking `Std::new`. /// /// Returns an optional build stamp, if libstd was indeed built. + #[track_caller] #[cfg_attr( feature = "tracing", instrument( @@ -1297,17 +1301,20 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler } } + #[track_caller] pub fn sysroot(&self, compiler: Compiler) -> PathBuf { self.ensure(compile::Sysroot::new(compiler)) } /// Returns the bindir for a compiler's sysroot. + #[track_caller] pub fn sysroot_target_bindir(&self, compiler: Compiler, target: TargetSelection) -> PathBuf { self.ensure(Libdir { compiler, target }).join(target).join("bin") } /// Returns the libdir where the standard library and other artifacts are /// found for a compiler's sysroot. + #[track_caller] pub fn sysroot_target_libdir(&self, compiler: Compiler, target: TargetSelection) -> PathBuf { self.ensure(Libdir { compiler, target }).join(target).join("lib") } @@ -1416,6 +1423,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler /// Returns a path to `Rustdoc` that "belongs" to the `target_compiler`. /// It can be either a stage0 rustdoc or a locally built rustdoc that *links* to /// `target_compiler`. + #[track_caller] pub fn rustdoc_for_compiler(&self, target_compiler: Compiler) -> PathBuf { self.ensure(tool::Rustdoc { target_compiler }) } @@ -1532,6 +1540,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler /// Ensure that a given step is built, returning its output. This will /// cache the step, so it is safe (and good!) to call this as often as /// needed to ensure that all dependencies are built. + #[track_caller] pub fn ensure(&'a self, step: S) -> S::Output { { let mut stack = self.stack.borrow_mut(); From ee4ea972e086dc1d463235d0b957c97f86ad964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 29 Jun 2026 14:39:13 +0200 Subject: [PATCH 21/24] Better record locations of executed steps Before it would just show the `ensure` function --- src/bootstrap/src/core/builder/mod.rs | 3 ++- src/bootstrap/src/utils/tracing.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 2b842121e61fd..66e14dae9b9ec 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -1598,7 +1598,8 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler // in the step_name field. "step", step_name = pretty_step_name::(), - args = step_debug_args(&step) + args = step_debug_args(&step), + location = crate::utils::tracing::format_location(*std::panic::Location::caller()) ); span.entered() }; diff --git a/src/bootstrap/src/utils/tracing.rs b/src/bootstrap/src/utils/tracing.rs index b1226ed7de7be..4e75347f4cbae 100644 --- a/src/bootstrap/src/utils/tracing.rs +++ b/src/bootstrap/src/utils/tracing.rs @@ -351,7 +351,7 @@ mod inner { let field = &values.fields[0]; write!(writer, " {{{}}}", field.1)?; } - write_location(writer, span.metadata())?; + write_with_location(writer)?; } // Executed command COMMAND_SPAN_TARGET => { From 69f0187c13d890481ecf80e325441cb332265921 Mon Sep 17 00:00:00 2001 From: Yukang Date: Sat, 27 Jun 2026 21:03:04 +0800 Subject: [PATCH 22/24] Avoid building rustdoc for tests without doctests --- src/bootstrap/src/core/builder/cargo.rs | 5 ++++- src/bootstrap/src/core/builder/tests.rs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index e7fe8bd1f3858..b1366ed3aa4d8 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -918,7 +918,10 @@ impl Builder<'_> { } let rustdoc_path = match cmd_kind { - Kind::Doc | Kind::Test | Kind::MiriTest => self.rustdoc_for_compiler(compiler), + Kind::Doc => self.rustdoc_for_compiler(compiler), + Kind::Test | Kind::MiriTest if self.test_target.runs_doctests() => { + self.rustdoc_for_compiler(compiler) + } _ => PathBuf::from("/path/to/nowhere/rustdoc/not/required"), }; diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 1a59e54a95cbf..176a61b15c988 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2423,6 +2423,19 @@ mod snapshot { } } + #[test] + fn test_library_tests_only_does_not_build_rustdoc() { + let ctx = TestCtx::new(); + let host = TargetSelection::from_user(&host_target()); + insta::assert_snapshot!( + ctx.config("test").args(&["--tests", "library/core"]).render_steps(), + @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + "); + } + #[test] fn test_cargo_stage_1() { let ctx = TestCtx::new(); From c340e8a2ba6ec43522044af10fc03258d9abc462 Mon Sep 17 00:00:00 2001 From: Paul Murphy Date: Wed, 3 Jun 2026 16:40:12 -0500 Subject: [PATCH 23/24] Allow section override when using patchable-function-entries Sometimes it is necessary to group patchable function entrypoint records in distinct linker sections. This is the case for some bpf functions within the linux kernel which shouldn't be visible to ftrace. Extend `-Zpatchable-function-entry` to accept an argument of the form `prefix_nops,total_nops,record_section`, which places all entry record into a user specified section. Likewise, extend the `patchable_function_entry` attribute to accept an optional `section="name"` option to place a function into a specific section. This is made possible by llvm attribute `patchable-function-entry-section` added in llvm 21. --- .../src/attributes/codegen_attrs.rs | 62 +++++++++++-------- .../src/session_diagnostics.rs | 14 +++++ compiler/rustc_codegen_llvm/src/attributes.rs | 32 ++++++++-- compiler/rustc_codegen_llvm/src/context.rs | 8 +-- .../rustc_codegen_ssa/src/codegen_attrs.rs | 6 +- .../rustc_hir/src/attrs/data_structures.rs | 5 +- compiler/rustc_interface/src/tests.rs | 2 +- .../src/middle/codegen_fn_attrs.rs | 25 +++++--- compiler/rustc_session/src/config.rs | 15 ++++- compiler/rustc_session/src/options.rs | 18 +++--- compiler/rustc_span/src/symbol.rs | 1 + .../patchable-function-entry.md | 9 ++- .../patchable-function-entry-both-flags.rs | 32 ++++++++++ .../patchable-function-entry-section.rs | 20 ++++++ tests/ui/attributes/malformed-attrs.stderr | 4 +- .../patchable-function-entry-attribute.rs | 16 +++++ .../patchable-function-entry-attribute.stderr | 60 +++++++++++++++--- .../patchable-function-entry-flags.stderr | 2 +- 18 files changed, 257 insertions(+), 74 deletions(-) create mode 100644 tests/codegen-llvm/patchable-function-entry/patchable-function-entry-section.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index e3865345cae84..f48fbe6324834 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -8,9 +8,9 @@ use rustc_span::edition::Edition::Edition2024; use super::prelude::*; use crate::attributes::AttributeSafety; use crate::session_diagnostics::{ - EmptyExportName, NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, - NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, - SanitizeInvalidStatic, TargetFeatureOnLangItem, + EmptyExportName, EmptySection, NakedFunctionIncompatibleAttribute, NullOnExport, + NullOnObjcClass, NullOnObjcSelector, NullOnSection, ObjcClassExpectedStringLiteral, + ObjcSelectorExpectedStringLiteral, SanitizeInvalidStatic, TargetFeatureOnLangItem, }; use crate::target_checking::Policy::AllowSilent; @@ -795,7 +795,8 @@ pub(crate) struct PatchableFunctionEntryParser; impl SingleAttributeParser for PatchableFunctionEntryParser { const PATH: &[Symbol] = &[sym::patchable_function_entry]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Fn)]); - const TEMPLATE: AttributeTemplate = template!(List: &["prefix_nops = m, entry_nops = n"]); + const TEMPLATE: AttributeTemplate = + template!(List: &["prefix_nops = m, entry_nops = n, section = \"section\""]); const STABILITY: AttributeStability = unstable!(patchable_function_entry); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { @@ -803,74 +804,85 @@ impl SingleAttributeParser for PatchableFunctionEntryParser { let mut prefix = None; let mut entry = None; + let mut section = None; if meta_item_list.len() == 0 { cx.adcx().expected_at_least_one_argument(meta_item_list.span); return None; } - let mut errored = false; - for item in meta_item_list.mixed() { let Some((ident, value)) = cx.expect_name_value(item, item.span(), None) else { - continue; + return None; }; let attrib_to_write = match ident.name { sym::prefix_nops => { // Duplicate prefixes are not allowed if prefix.is_some() { - errored = true; cx.adcx().duplicate_key(ident.span, sym::prefix_nops); - continue; + return None; } &mut prefix } sym::entry_nops => { // Duplicate entries are not allowed if entry.is_some() { - errored = true; cx.adcx().duplicate_key(ident.span, sym::entry_nops); - continue; + return None; } &mut entry } + sym::section => { + // Duplicate entries are not allowed + if section.is_some() { + cx.adcx().duplicate_key(ident.span, sym::section); + return None; + } + // Only a string type value is allowed. + let Some(value_str) = value.value_as_str() else { + cx.adcx().expect_string_literal(value); + return None; + }; + // The section name does not allow null characters. + if value_str.as_str().contains('\0') { + cx.emit_err(NullOnSection { span: value.value_span }); + } + // The section name is not allowed to be empty, LLVM does + // not allow them. + if value_str.is_empty() { + cx.emit_err(EmptySection { span: value.value_span }); + } + section = Some(value_str); + // Integer parsing is not needed, process next item. + continue; + } _ => { - errored = true; cx.adcx().expected_specific_argument( ident.span, &[sym::prefix_nops, sym::entry_nops], ); - continue; + return None; } }; let rustc_ast::LitKind::Int(val, _) = value.value_as_lit().kind else { - errored = true; cx.adcx().expected_integer_literal(value.value_span); - continue; + return None; }; let Ok(val) = val.get().try_into() else { - errored = true; cx.adcx().expected_integer_literal_in_range( value.value_span, u8::MIN as isize, u8::MAX as isize, ); - continue; + return None; }; *attrib_to_write = Some(val); } - if errored { - None - } else { - Some(AttributeKind::PatchableFunctionEntry { - prefix: prefix.unwrap_or(0), - entry: entry.unwrap_or(0), - }) - } + Some(AttributeKind::PatchableFunctionEntry { prefix, entry, section }) } } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 63e33e0ae4882..9e5d7bb2cf375 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -292,6 +292,13 @@ pub(crate) struct EmptyExportName { pub span: Span, } +#[derive(Diagnostic)] +#[diag("`section` may not be empty")] +pub(crate) struct EmptySection { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`export_name` may not contain null characters", code = E0648)] pub(crate) struct NullOnExport { @@ -327,6 +334,13 @@ pub(crate) struct NullOnObjcSelector { pub span: Span, } +#[derive(Diagnostic)] +#[diag("`section` may not contain null characters", code = E0648)] +pub(crate) struct NullOnSection { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("`objc::class!` expected a string literal")] pub(crate) struct ObjcClassExpectedStringLiteral { diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index fe36a9865485d..20e712174e0e9 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -86,11 +86,26 @@ fn patchable_function_entry_attrs<'ll>( attr: Option, ) -> SmallVec<[&'ll Attribute; 2]> { let mut attrs = SmallVec::new(); - let patchable_spec = attr.unwrap_or_else(|| { - PatchableFunctionEntry::from_config(sess.opts.unstable_opts.patchable_function_entry) - }); - let entry = patchable_spec.entry(); - let prefix = patchable_spec.prefix(); + + let mut entry = sess.opts.unstable_opts.patchable_function_entry.entry(); + let mut prefix = sess.opts.unstable_opts.patchable_function_entry.prefix(); + let mut section = sess.opts.unstable_opts.patchable_function_entry.section(); + let section_sym; + + // Apply attribute specified overrides, if any. + if let Some(patchable_spec) = attr { + if let Some(sym) = patchable_spec.section() { + section_sym = sym; + section = Some(section_sym.as_str()); + } + // Override the nop counts if either is present. If only one is present, the + // other count is implied to be 0. + if patchable_spec.entry().is_some() || patchable_spec.prefix().is_some() { + entry = patchable_spec.entry().unwrap_or(0); + prefix = patchable_spec.prefix().unwrap_or(0); + } + } + if entry > 0 { attrs.push(llvm::CreateAttrStringValue( cx.llcx, @@ -105,6 +120,13 @@ fn patchable_function_entry_attrs<'ll>( &format!("{}", prefix), )); } + if let Some(section) = section { + attrs.push(llvm::CreateAttrStringValue( + cx.llcx, + "patchable-function-entry-section", + section, + )); + } attrs } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 6198a98e5f7ae..80f13a7a35255 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -14,7 +14,6 @@ use rustc_data_structures::base_n::{ALPHANUMERIC_ONLY, ToBaseN}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::small_c_str::SmallCStr; use rustc_hir::def_id::DefId; -use rustc_middle::middle::codegen_fn_attrs::PatchableFunctionEntry; use rustc_middle::mono::CodegenUnit; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers, @@ -343,14 +342,13 @@ pub(crate) unsafe fn create_module<'ll>( // Add "kcfi-offset" module flag with -Z patchable-function-entry (See // https://reviews.llvm.org/D141172). - let pfe = - PatchableFunctionEntry::from_config(sess.opts.unstable_opts.patchable_function_entry); - if pfe.prefix() > 0 { + let patchable_prefix_nops = sess.opts.unstable_opts.patchable_function_entry.prefix(); + if patchable_prefix_nops > 0 { llvm::add_module_flag_u32( llmod, llvm::ModuleFlagMergeBehavior::Override, "kcfi-offset", - pfe.prefix().into(), + patchable_prefix_nops.into(), ); } diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 4d271447746c1..46bb1182c298c 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -290,9 +290,11 @@ fn process_builtin_attrs( AttributeKind::RustcOffloadKernel => { codegen_fn_attrs.flags |= CodegenFnAttrFlags::OFFLOAD_KERNEL } - AttributeKind::PatchableFunctionEntry { prefix, entry } => { + AttributeKind::PatchableFunctionEntry { prefix, entry, section } => { codegen_fn_attrs.patchable_function_entry = - Some(PatchableFunctionEntry::from_prefix_and_entry(*prefix, *entry)); + Some(PatchableFunctionEntry::from_prefix_entry_and_section( + *prefix, *entry, *section, + )); } AttributeKind::InstrumentFn(instrument_fn) => { codegen_fn_attrs.instrument_fn = match instrument_fn { diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index acae158acb2b4..17d00863d99d5 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1271,8 +1271,9 @@ pub enum AttributeKind { /// Represents `#[patchable_function_entry]` PatchableFunctionEntry { - prefix: u8, - entry: u8, + prefix: Option, + entry: Option, + section: Option, }, /// Represents `#[path]` diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 5933d0fd4b356..a3264d3cc3311 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -866,7 +866,7 @@ fn test_unstable_options_tracking_hash() { tracked!(panic_in_drop, PanicStrategy::Abort); tracked!( patchable_function_entry, - PatchableFunctionEntry::from_total_and_prefix_nops(10, 5) + PatchableFunctionEntry::from_parts(10, 5, None) .expect("total must be greater than or equal to prefix") ); tracked!(plt, Some(true)); diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index 713c6597c1966..b6ae4a98a34e3 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -114,7 +114,7 @@ pub struct CodegenFnAttrs { // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity pub alignment: Option, /// The `#[patchable_function_entry(...)]` attribute. Indicates how many nops should be around - /// the function entry. + /// the function entry, or override default section to record entry location. pub patchable_function_entry: Option, /// The `#[rustc_objc_class = "..."]` attribute. pub objc_class: Option, @@ -162,24 +162,33 @@ pub struct TargetFeature { #[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, StableHash)] pub struct PatchableFunctionEntry { /// Nops to prepend to the function - prefix: u8, + prefix: Option, /// Nops after entry, but before body - entry: u8, + entry: Option, + /// Optional, specific section to record entry location in + section: Option, } impl PatchableFunctionEntry { - pub fn from_config(config: rustc_session::config::PatchableFunctionEntry) -> Self { - Self { prefix: config.prefix(), entry: config.entry() } + pub fn from_prefix_entry_and_section( + prefix: Option, + entry: Option, + section: Option, + ) -> Self { + Self { prefix, entry, section } } pub fn from_prefix_and_entry(prefix: u8, entry: u8) -> Self { - Self { prefix, entry } + Self { prefix: Some(prefix), entry: Some(entry), section: None } } - pub fn prefix(&self) -> u8 { + pub fn prefix(&self) -> Option { self.prefix } - pub fn entry(&self) -> u8 { + pub fn entry(&self) -> Option { self.entry } + pub fn section(&self) -> Option { + self.section + } } #[derive(Clone, Copy, PartialEq, Eq, TyEncodable, TyDecodable, StableHash)] diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index c7da2d9a1fc38..4492cdf2d2f8a 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3342,23 +3342,29 @@ impl DumpMonoStatsFormat { /// `-Z patchable-function-entry` representation - how many nops to put before and after function /// entry. -#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)] +#[derive(Clone, PartialEq, Hash, Debug, Default)] pub struct PatchableFunctionEntry { /// Nops before the entry prefix: u8, /// Nops after the entry entry: u8, + /// An optional section name to record the entry location + section: Option, } impl PatchableFunctionEntry { - pub fn from_total_and_prefix_nops( + pub fn from_parts( total_nops: u8, prefix_nops: u8, + section: Option, ) -> Option { if total_nops < prefix_nops { None + // Section name cannot contain null characters. + } else if section.as_ref().map(|x| x.contains('\0') || x.is_empty()).unwrap_or(false) { + None } else { - Some(Self { prefix: prefix_nops, entry: total_nops - prefix_nops }) + Some(Self { prefix: prefix_nops, entry: total_nops - prefix_nops, section }) } } pub fn prefix(&self) -> u8 { @@ -3367,6 +3373,9 @@ impl PatchableFunctionEntry { pub fn entry(&self) -> u8 { self.entry } + pub fn section(&self) -> Option<&str> { + self.section.as_ref().map(|x| x.as_str()) + } } /// `-Zpolonius` values, enabling the borrow checker polonius analysis, and which version: legacy, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index efc8a70f4feb2..b7668bd47d8ec 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -784,7 +784,7 @@ mod desc { pub(crate) const parse_passes: &str = "a space-separated list of passes, or `all`"; pub(crate) const parse_panic_strategy: &str = "either `unwind`, `abort`, or `immediate-abort`"; pub(crate) const parse_on_broken_pipe: &str = "either `kill`, `error`, or `inherit`"; - pub(crate) const parse_patchable_function_entry: &str = "either two comma separated integers (total_nops,prefix_nops), with prefix_nops <= total_nops, or one integer (total_nops)"; + pub(crate) const parse_patchable_function_entry: &str = "a comma separated list of (prefix_nops,total_nops,section_name), (prefix_nops,total_nops), or (total_nops). Where prefix_nops <= total_nops where 0 < total_nops <= 255 and prefix_nops <= total_nops"; pub(crate) const parse_opt_panic_strategy: &str = parse_panic_strategy; pub(crate) const parse_relro_level: &str = "one of: `full`, `partial`, or `off`"; pub(crate) const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `dataflow`, `hwaddress`, `kcfi`, `kernel-address`, `kernel-hwaddress`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, `thread`, or 'realtime'"; @@ -1206,20 +1206,24 @@ pub mod parse { ) -> bool { let mut total_nops = 0; let mut prefix_nops = 0; + let mut section = None; if !parse_number(&mut total_nops, v) { - let parts = v.and_then(|v| v.split_once(',')).unzip(); - if !parse_number(&mut total_nops, parts.0) { + let parts: Vec<_> = v.unwrap_or("").split(',').collect(); + if parts.len() < 2 || parts.len() > 3 { return false; } - if !parse_number(&mut prefix_nops, parts.1) { + + if !parse_number(&mut total_nops, Some(parts[0])) { + return false; + } + if !parse_number(&mut prefix_nops, Some(parts[1])) { return false; } + section = parts.get(2).map(|x| x.to_string()); } - if let Some(pfe) = - PatchableFunctionEntry::from_total_and_prefix_nops(total_nops, prefix_nops) - { + if let Some(pfe) = PatchableFunctionEntry::from_parts(total_nops, prefix_nops, section) { *slot = pfe; return true; } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c75da250efd5f..4bada9712d2a9 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1874,6 +1874,7 @@ symbols! { saturating_sub, sdylib, search_unbox, + section, select_unpredictable, self_in_typedefs, self_struct_ctor, diff --git a/src/doc/unstable-book/src/compiler-flags/patchable-function-entry.md b/src/doc/unstable-book/src/compiler-flags/patchable-function-entry.md index 4a9bf47a29011..80f0e54907872 100644 --- a/src/doc/unstable-book/src/compiler-flags/patchable-function-entry.md +++ b/src/doc/unstable-book/src/compiler-flags/patchable-function-entry.md @@ -2,10 +2,13 @@ -------------------- -The `-Z patchable-function-entry=total_nops,prefix_nops` or `-Z patchable-function-entry=total_nops` +The `-Z patchable-function-entry=total_nops,prefix_nops,record_section`, + `-Z patchable-function-entry=total_nops,prefix_nops`, or + `-Z patchable-function-entry=total_nops` compiler flag enables nop padding of function entries with 'total_nops' nops, with -an offset for the entry of the function at 'prefix_nops' nops. In the second form, -'prefix_nops' defaults to 0. +an offset for the entry of the function at 'prefix_nops' nops. In the third form, +'prefix_nops' defaults to 0. record\_section can specify a specific linker section +to place entry record in, the default is `__patchable_function_entries`. As an illustrative example, `-Z patchable-function-entry=3,2` would produce: diff --git a/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-both-flags.rs b/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-both-flags.rs index 72204c78a4906..45aa46d24fe94 100644 --- a/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-both-flags.rs +++ b/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-both-flags.rs @@ -39,6 +39,26 @@ pub fn fun5() {} #[patchable_function_entry(prefix_nops = 4)] pub fn fun6() {} +// The attribute should override patchable-function-prefix to 4 +// and patchable-function-entry to the default of 0, clearing it entirely, +// while setting patchable-function-entry-section. +#[no_mangle] +#[patchable_function_entry(prefix_nops = 4, section = "foo")] +pub fn fun7() {} + +// The attribute should override patchable-function-entry-section, +// while passing through the commandline options. +#[no_mangle] +#[patchable_function_entry(section = "bar")] +pub fn fun8() {} + +// The attribute should override patchable-function-entry to 5 +// and patchable-function-prefix to the default of 0, clearing it entirely, +// while setting patchable-function-entry-section. +#[no_mangle] +#[patchable_function_entry(entry_nops = 5, section = "baz")] +pub fn fun9() {} + // CHECK: @fun0() unnamed_addr #0 // CHECK: @fun1() unnamed_addr #1 // CHECK: @fun2() unnamed_addr #2 @@ -46,6 +66,9 @@ pub fn fun6() {} // CHECK: @fun4() unnamed_addr #4 // CHECK: @fun5() unnamed_addr #5 // CHECK: @fun6() unnamed_addr #6 +// CHECK: @fun7() unnamed_addr #7 +// CHECK: @fun8() unnamed_addr #8 +// CHECK: @fun9() unnamed_addr #9 // CHECK: attributes #0 = { {{.*}}"patchable-function-entry"="5"{{.*}}"patchable-function-prefix"="10" {{.*}} } // CHECK: attributes #1 = { {{.*}}"patchable-function-entry"="2"{{.*}}"patchable-function-prefix"="1" {{.*}} } @@ -62,3 +85,12 @@ pub fn fun6() {} // CHECK: attributes #6 = { {{.*}}"patchable-function-prefix"="4"{{.*}} } // CHECK-NOT: attributes #6 = { {{.*}}patchable-function-entry{{.*}} } +// +// CHECK: attributes #7 = { {{.*}}"patchable-function-entry-section"="foo"{{.*}}"patchable-function-prefix"="4" {{.*}} } +// CHECK-NOT: attributes #7 = { {{.*}}"patchable-function-entry"{{.*}} } +// +// CHECK: attributes #8 = { {{.*}}"patchable-function-entry-section"="bar"{{.*}} } +// CHECK-NOT: attributes #8 = { {{.*}}"patchable-function-entry"{{.*}} } +// +// CHECK: attributes #9 = { {{.*}}"patchable-function-entry"="5"{{.*}}"patchable-function-entry-section"="baz" {{.*}} } +// CHECK-NOT: attributes #9 = { {{.*}}"patchable-function-prefix{{.*}} } diff --git a/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-section.rs b/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-section.rs new file mode 100644 index 0000000000000..9ffff5ca7537c --- /dev/null +++ b/tests/codegen-llvm/patchable-function-entry/patchable-function-entry-section.rs @@ -0,0 +1,20 @@ +//@ compile-flags: -Z patchable-function-entry=15,10,default_foo_section +// + +#![feature(patchable_function_entry)] +#![crate_type = "lib"] + +// This should have the default, as set by the compile flags +#[no_mangle] +pub fn fun0() {} + +// This should override the default section name +#[no_mangle] +#[patchable_function_entry(section = "bar_section")] +pub fn fun1() {} + +// CHECK: @fun0() unnamed_addr #0 +// CHECK: @fun1() unnamed_addr #1 + +// CHECK: attributes #0 = { {{.*}}"patchable-function-entry"="5"{{.*}}"patchable-function-entry-section"="default_foo_section"{{.*}}"patchable-function-prefix"="10" {{.*}} } +// CHECK: attributes #1 = { {{.*}}"patchable-function-entry"="5"{{.*}}"patchable-function-entry-section"="bar_section"{{.*}}"patchable-function-prefix"="10" {{.*}} } diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr index 7d8bd3700d4bb..96012d8df936b 100644 --- a/tests/ui/attributes/malformed-attrs.stderr +++ b/tests/ui/attributes/malformed-attrs.stderr @@ -550,8 +550,8 @@ LL | #[patchable_function_entry] | help: must be of the form | -LL | #[patchable_function_entry(prefix_nops = m, entry_nops = n)] - | +++++++++++++++++++++++++++++++++ +LL | #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] + | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0565]: malformed `coroutine` attribute input --> $DIR/malformed-attrs.rs:118:5 diff --git a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs index 5cbeccf1b0e4f..1223c0c5b05f9 100644 --- a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs +++ b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs @@ -24,3 +24,19 @@ pub fn no_parameters_given() {} #[patchable_function_entry(prefix_nops = 255, prefix_nops = 255)] //~^ ERROR malformed pub fn duplicate_parameter() {} + +#[patchable_function_entry(section = 255)] +//~^ ERROR malformed +pub fn invalid_section_parameter() {} + +#[patchable_function_entry(section = "foo", section = "bar")] +//~^ ERROR malformed +pub fn duplicate_section_parameter() {} + +#[patchable_function_entry(section = "fo\0o")] +//~^ ERROR null characters +pub fn nul_in_section_parameter() {} + +#[patchable_function_entry(section = "")] +//~^ ERROR empty +pub fn empty_section_parameter() {} diff --git a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr index 6c32a76834c69..c3f09ff12384a 100644 --- a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr +++ b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr @@ -9,7 +9,7 @@ LL | #[patchable_function_entry(prefix_nops = 256, entry_nops = 0)] help: must be of the form | LL - #[patchable_function_entry(prefix_nops = 256, entry_nops = 0)] -LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n)] +LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] | error[E0539]: malformed `patchable_function_entry` attribute input @@ -23,7 +23,7 @@ LL | #[patchable_function_entry(prefix_nops = "stringvalue", entry_nops = 0)] help: must be of the form | LL - #[patchable_function_entry(prefix_nops = "stringvalue", entry_nops = 0)] -LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n)] +LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] | error[E0539]: malformed `patchable_function_entry` attribute input @@ -34,8 +34,8 @@ LL | #[patchable_function_entry] | help: must be of the form | -LL | #[patchable_function_entry(prefix_nops = m, entry_nops = n)] - | +++++++++++++++++++++++++++++++++ +LL | #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] + | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0539]: malformed `patchable_function_entry` attribute input --> $DIR/patchable-function-entry-attribute.rs:16:1 @@ -48,7 +48,7 @@ LL | #[patchable_function_entry(prefix_nops = 10, something = 0)] help: must be of the form | LL - #[patchable_function_entry(prefix_nops = 10, something = 0)] -LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n)] +LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] | error[E0539]: malformed `patchable_function_entry` attribute input @@ -61,8 +61,8 @@ LL | #[patchable_function_entry()] | help: must be of the form | -LL | #[patchable_function_entry(prefix_nops = m, entry_nops = n)] - | +++++++++++++++++++++++++++++++ +LL | #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] + | ++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0538]: malformed `patchable_function_entry` attribute input --> $DIR/patchable-function-entry-attribute.rs:24:1 @@ -75,10 +75,50 @@ LL | #[patchable_function_entry(prefix_nops = 255, prefix_nops = 255)] help: must be of the form | LL - #[patchable_function_entry(prefix_nops = 255, prefix_nops = 255)] -LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n)] +LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] | -error: aborting due to 6 previous errors +error[E0539]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:28:1 + | +LL | #[patchable_function_entry(section = 255)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---^^ + | | + | expected a string literal here + | +help: must be of the form + | +LL - #[patchable_function_entry(section = 255)] +LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] + | + +error[E0538]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:32:1 + | +LL | #[patchable_function_entry(section = "foo", section = "bar")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^^^^^^^^^ + | | + | found `section` used as a key more than once + | +help: must be of the form + | +LL - #[patchable_function_entry(section = "foo", section = "bar")] +LL + #[patchable_function_entry(prefix_nops = m, entry_nops = n, section = "section")] + | + +error[E0648]: `section` may not contain null characters + --> $DIR/patchable-function-entry-attribute.rs:36:38 + | +LL | #[patchable_function_entry(section = "fo\0o")] + | ^^^^^^^ + +error: `section` may not be empty + --> $DIR/patchable-function-entry-attribute.rs:40:38 + | +LL | #[patchable_function_entry(section = "")] + | ^^ + +error: aborting due to 10 previous errors -Some errors have detailed explanations: E0538, E0539. +Some errors have detailed explanations: E0538, E0539, E0648. For more information about an error, try `rustc --explain E0538`. diff --git a/tests/ui/patchable-function-entry/patchable-function-entry-flags.stderr b/tests/ui/patchable-function-entry/patchable-function-entry-flags.stderr index b09af94a61541..bde7f4aa9dff6 100644 --- a/tests/ui/patchable-function-entry/patchable-function-entry-flags.stderr +++ b/tests/ui/patchable-function-entry/patchable-function-entry-flags.stderr @@ -1,2 +1,2 @@ -error: incorrect value `1,2` for unstable option `patchable-function-entry` - either two comma separated integers (total_nops,prefix_nops), with prefix_nops <= total_nops, or one integer (total_nops) was expected +error: incorrect value `1,2` for unstable option `patchable-function-entry` - a comma separated list of (prefix_nops,total_nops,section_name), (prefix_nops,total_nops), or (total_nops). Where prefix_nops <= total_nops where 0 < total_nops <= 255 and prefix_nops <= total_nops was expected From 91a762bd474be0c3605f9a8b5969f376c1400652 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Mon, 29 Jun 2026 16:53:58 +0300 Subject: [PATCH 24/24] Inline information of whether this segment is delegation's child segment into `PathSegment` --- compiler/rustc_ast_lowering/src/delegation.rs | 7 ++++++- .../rustc_ast_lowering/src/delegation/generics.rs | 12 ++++++++++-- compiler/rustc_ast_lowering/src/lib.rs | 1 + compiler/rustc_ast_lowering/src/path.rs | 1 + compiler/rustc_hir/src/hir.rs | 14 +++++++++++++- compiler/rustc_hir/src/intravisit.rs | 3 ++- .../src/hir_ty_lowering/bounds.rs | 1 + .../src/hir_ty_lowering/errors.rs | 1 + .../src/hir_ty_lowering/generics.rs | 2 +- .../rustc_hir_analysis/src/hir_ty_lowering/mod.rs | 2 +- compiler/rustc_middle/src/hir/map.rs | 7 ------- 11 files changed, 37 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index 078ebd6987ae3..7021ac8cfedaf 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -55,7 +55,9 @@ use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::symbol::kw; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; -use crate::delegation::generics::{GenericsGenerationResult, GenericsGenerationResults}; +use crate::delegation::generics::{ + GenericsGenerationResult, GenericsGenerationResults, GenericsPosition, +}; use crate::diagnostics::{ CycleInDelegationSignatureResolution, DelegationAttemptedBlockWithDefsDeletion, DelegationBlockSpecifiedWhenNoParams, UnresolvedDelegationCallee, @@ -505,6 +507,7 @@ impl<'hir> LoweringContext<'_, 'hir> { res: Res::Local(param_id), args: None, infer_args: false, + delegation_child_segment: false, })); let path = self.arena.alloc(hir::Path { span, res: Res::Local(param_id), segments }); @@ -714,6 +717,8 @@ impl<'hir> LoweringContext<'_, 'hir> { result.args_segment_id = segment.hir_id; result.use_for_sig_inheritance = !result.generics.is_trait_impl(); + segment.delegation_child_segment = result.generics.pos() == GenericsPosition::Child; + segment } diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index 79a3961c22a53..2a751ffae3eb7 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -12,7 +12,7 @@ use rustc_span::{Ident, Span, sym}; use crate::LoweringContext; use crate::diagnostics::DelegationInfersMismatch; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub(super) enum GenericsPosition { Parent, Child, @@ -155,7 +155,7 @@ impl<'hir> DelegationGenericArgsIterator<'hir> { impl<'hir> HirOrTyGenerics<'hir> { pub(super) fn into_hir_generics(&mut self, ctx: &mut LoweringContext<'_, 'hir>, span: Span) { if let HirOrTyGenerics::Ty(ty) = self { - let rename_self = matches!(ty.pos, GenericsPosition::Child); + let rename_self = ty.pos == GenericsPosition::Child; let params = ctx.uplift_delegation_generic_params(span, &ty.data, rename_self); *self = HirOrTyGenerics::Hir(DelegationGenerics { @@ -218,6 +218,13 @@ impl<'hir> HirOrTyGenerics<'hir> { .expect("`Self` generic param is not found while expected"), } } + + pub(crate) fn pos(&self) -> GenericsPosition { + match self { + HirOrTyGenerics::Ty(ty) => ty.pos, + HirOrTyGenerics::Hir(hir) => hir.pos, + } + } } impl<'hir> GenericsGenerationResult<'hir> { @@ -590,6 +597,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ident: p.name.ident(), infer_args: false, res, + delegation_child_segment: false, }]), res, span: p.span, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 06e83a7486100..1bf798c357bd2 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1014,6 +1014,7 @@ impl<'hir> LoweringContext<'_, 'hir> { res, args, infer_args: args.is_none(), + delegation_child_segment: false, }]), }) } diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 42c4af5add12d..18e037527836a 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -412,6 +412,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } else { Some(generic_args.into_generic_args(self)) }, + delegation_child_segment: false, } } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 6d2e68d20bf30..254d5fdd5f80c 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -387,12 +387,24 @@ pub struct PathSegment<'hir> { /// out of those only the segments with no type parameters /// to begin with, e.g., `Vec::new` is `>::new::<..>`. pub infer_args: bool, + + /// Whether this segment is a delegation's child segment: + /// `reuse Trait::foo`, in this case `foo` is a delegation's child segment. + /// Used for faster check during generic args lowering. + pub delegation_child_segment: bool, } impl<'hir> PathSegment<'hir> { /// Converts an identifier to the corresponding segment. pub fn new(ident: Ident, hir_id: HirId, res: Res) -> PathSegment<'hir> { - PathSegment { ident, hir_id, res, infer_args: true, args: None } + PathSegment { + ident, + hir_id, + res, + infer_args: true, + args: None, + delegation_child_segment: false, + } } pub fn invalid() -> Self { diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 9e0eaef596420..f1f542e9ce1e9 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1468,7 +1468,8 @@ pub fn walk_path_segment<'v, V: Visitor<'v>>( visitor: &mut V, segment: &'v PathSegment<'v>, ) -> V::Result { - let PathSegment { ident, hir_id, res: _, args, infer_args: _ } = segment; + let PathSegment { ident, hir_id, res: _, args, infer_args: _, delegation_child_segment: _ } = + segment; try_visit!(visitor.visit_ident(*ident)); try_visit!(visitor.visit_id(*hir_id)); visit_opt!(visitor, visit_generic_args, *args); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 810e14a7d6d0b..d730683b132d4 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -467,6 +467,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { res: Res::Err, args: Some(constraint.gen_args), infer_args: false, + delegation_child_segment: false, }; let alias_args = self.lower_generic_args_of_assoc_item( diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 32678e8a5a12f..9d22d3060e1ff 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -471,6 +471,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { res: Res::Err, args: Some(constraint.gen_args), infer_args: false, + delegation_child_segment: false, }; let alias_args = self.lower_generic_args_of_assoc_item( diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs index a36bb7bce9bab..45c2ed205c74d 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs @@ -434,7 +434,7 @@ pub(crate) fn check_generic_arg_count( // Suppress this warning for delegations as it is compiler generated and lifetimes are // propagated while late-bound lifetimes may be present. - let explicit_late_bound = match tcx.hir_is_delegation_child_segment(seg) { + let explicit_late_bound = match seg.delegation_child_segment { true => ExplicitLateBound::No, false => prohibit_explicit_late_bound_lifetimes(cx, gen_params, gen_args, gen_pos), }; diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index e8a7156b41fe4..7edcb40be616e 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -870,7 +870,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { span, generic_args: segment.args(), infer_args: segment.infer_args, - create_synth_args: tcx.hir_is_delegation_child_segment(segment), + create_synth_args: segment.delegation_child_segment, incorrect_args: &arg_count.correct, }; diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 3cab936c45c1f..444433dcb6c54 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -879,13 +879,6 @@ impl<'tcx> TyCtxt<'tcx> { self.hir_opt_delegation_info(delegation_id).expect("processing delegation") } - pub fn hir_is_delegation_child_segment(self, segment: &PathSegment<'_>) -> bool { - let parent_def = self.hir_get_parent_item(segment.hir_id).def_id; - - self.hir_opt_delegation_info(parent_def) - .is_some_and(|info| info.child_seg_id == segment.hir_id) - } - #[inline] fn hir_opt_ident(self, id: HirId) -> Option { match self.hir_node(id) {