From 1bd4fa0b6fc4c4f791e26b31d731466890bcab06 Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 24 Jul 2026 12:14:01 +0800 Subject: [PATCH 01/45] Add tuple never coercion collection regression test --- ...ever-to-any-coercion-in-collection-issue-100727.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs diff --git a/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs b/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs new file mode 100644 index 0000000000000..f44c486e0f50b --- /dev/null +++ b/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs @@ -0,0 +1,11 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/100727. +//! A tuple element of type `!` should be coerced using constraints from `collect`. + +//@ check-pass +//@ edition: 2021 + +#![allow(unreachable_code)] + +fn main() { + let _: Vec<(i32,)> = [(todo!(),)].into_iter().collect(); +} From bae7e21f42ac7bb04d75bd0ba37d746f35b0cc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Fri, 24 Jul 2026 00:59:09 +0800 Subject: [PATCH 02/45] Check unsafe impls on safe EIIs --- compiler/rustc_ast_lowering/src/item.rs | 5 +- .../rustc_hir/src/attrs/data_structures.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 69 +++++++++++-------- compiler/rustc_passes/src/diagnostics.rs | 12 +++- tests/ui/eii/default/auxiliary/impl1.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl1.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl2.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl3.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl4.rs | 2 +- .../eii/duplicate/dylib_default_duplicate.rs | 2 +- tests/ui/eii/safe_eii_unsafe_impl.rs | 14 ++++ tests/ui/eii/safe_eii_unsafe_impl.stderr | 10 +++ .../eii/type_checking/cross_crate_type_ok.rs | 2 +- .../eii/type_checking/cross_crate_wrong_ty.rs | 2 +- .../type_checking/cross_crate_wrong_ty.stderr | 4 +- 15 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 tests/ui/eii/safe_eii_unsafe_impl.rs create mode 100644 tests/ui/eii/safe_eii_unsafe_impl.stderr diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index caa554c4aa669..34c7137b8676d 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -155,7 +155,10 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::attrs::EiiImpl { span: self.lower_span(*span), inner_span: self.lower_span(*inner_span), - impl_marked_unsafe: self.lower_safety(*impl_safety, hir::Safety::Safe).is_unsafe(), + impl_unsafe_span: match *impl_safety { + Safety::Unsafe(span) => Some(self.lower_span(span)), + Safety::Safe(_) | Safety::Default => None, + }, is_default: *is_default, resolution, } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index db492475a3aa4..b438cdc212bc6 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -40,7 +40,7 @@ pub enum EiiImplResolution { #[derive(Copy, Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] pub struct EiiImpl { pub resolution: EiiImplResolution, - pub impl_marked_unsafe: bool, + pub impl_unsafe_span: Option, pub span: Span, pub inner_span: Span, pub is_default: bool, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index f9b508476689e..8f835c9e502f2 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -473,7 +473,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } fn check_eii_impl(&self, impls: &[EiiImpl], target: Target) { - for EiiImpl { span, inner_span, resolution, impl_marked_unsafe, is_default: _ } in impls { + for EiiImpl { span, inner_span, resolution, impl_unsafe_span, is_default: _ } in impls { match target { Target::Fn | Target::Static => {} _ => { @@ -481,35 +481,48 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - let needs_unsafe = match resolution { - EiiImplResolution::Macro(eii_macro) => { - find_attr!(self.tcx, *eii_macro, EiiDeclaration(EiiDecl { impl_unsafe, .. }) if *impl_unsafe) - } - EiiImplResolution::Known(foreign_item_did) => { - let foreign_item_did = *foreign_item_did; - self.tcx - .externally_implementable_items(foreign_item_did.krate) - .get(&foreign_item_did) - .map(|(decl, _)| decl.impl_unsafe) - .unwrap_or(false) - } - EiiImplResolution::Error(_) => false, + let impl_unsafe = match resolution { + EiiImplResolution::Macro(eii_macro) => find_attr!( + self.tcx, + *eii_macro, + EiiDeclaration(EiiDecl { impl_unsafe, .. }) => *impl_unsafe + ), + EiiImplResolution::Known(foreign_item_did) => self + .tcx + .externally_implementable_items(foreign_item_did.krate) + .get(foreign_item_did) + .map(|(decl, _)| decl.impl_unsafe), + EiiImplResolution::Error(_) => None, + }; + let Some(needs_unsafe) = impl_unsafe else { + continue; }; - if needs_unsafe && !impl_marked_unsafe { - let name = match resolution { - EiiImplResolution::Macro(eii_macro) => self.tcx.item_name(*eii_macro), - EiiImplResolution::Known(def_id) => self.tcx.item_name(*def_id), - EiiImplResolution::Error(_) => unreachable!(), - }; - self.dcx().emit_err(diagnostics::EiiImplRequiresUnsafe { - span: *span, - name, - suggestion: diagnostics::EiiImplRequiresUnsafeSuggestion { - left: inner_span.shrink_to_lo(), - right: inner_span.shrink_to_hi(), - }, - }); + let name = match resolution { + EiiImplResolution::Macro(eii_macro) => self.tcx.item_name(*eii_macro), + EiiImplResolution::Known(def_id) => self.tcx.item_name(*def_id), + EiiImplResolution::Error(_) => unreachable!(), + }; + + match (needs_unsafe, *impl_unsafe_span) { + (true, None) => { + self.dcx().emit_err(diagnostics::EiiImplRequiresUnsafe { + span: *span, + name, + suggestion: diagnostics::EiiImplRequiresUnsafeSuggestion { + left: inner_span.shrink_to_lo(), + right: inner_span.shrink_to_hi(), + }, + }); + } + (false, Some(unsafe_span)) => { + self.dcx().emit_err(diagnostics::EiiImplCannotBeUnsafe { + impl_span: *span, + unsafe_span, + name, + }); + } + _ => {} } } } diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 34fa0b264319d..d8e33f87966c8 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -515,10 +515,10 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for NoMainErr { if self.add_teach_note { diag.note(msg!("if you don't know the basics of Rust, you can go look to the Rust Book to get started: https://doc.rust-lang.org/book/")); } + diag } } - pub(crate) struct DuplicateLangItem { pub local_span: Option, pub lang_item_name: Symbol, @@ -1082,6 +1082,16 @@ pub(crate) struct EiiImplRequiresUnsafeSuggestion { pub right: Span, } +#[derive(Diagnostic)] +#[diag("`{$name}` is not unsafe to implement")] +pub(crate) struct EiiImplCannotBeUnsafe { + #[primary_span] + pub impl_span: Span, + #[label("`unsafe` is not allowed here")] + pub unsafe_span: Span, + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag("`#[{$name}]` {$kind} required, but not found")] pub(crate) struct EiiWithoutImpl { diff --git a/tests/ui/eii/default/auxiliary/impl1.rs b/tests/ui/eii/default/auxiliary/impl1.rs index 3510ea1eb3f27..5ed6ab639a883 100644 --- a/tests/ui/eii/default/auxiliary/impl1.rs +++ b/tests/ui/eii/default/auxiliary/impl1.rs @@ -6,7 +6,7 @@ extern crate decl_with_default as decl; -#[unsafe(decl::eii1)] //~ ERROR multiple implementations of `#[eii1]` +#[decl::eii1] //~ ERROR multiple implementations of `#[eii1]` fn other(x: u64) { println!("1{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl1.rs b/tests/ui/eii/duplicate/auxiliary/impl1.rs index ffa2cd79818cc..86017eec20763 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl1.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl1.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("1{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl2.rs b/tests/ui/eii/duplicate/auxiliary/impl2.rs index 592234f53fd40..928433d832c2d 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl2.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl2.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("2{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl3.rs b/tests/ui/eii/duplicate/auxiliary/impl3.rs index 5e9fdaba0bb6b..3f0be04377fce 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl3.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl3.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("3{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl4.rs b/tests/ui/eii/duplicate/auxiliary/impl4.rs index 068cc18d78e6a..959746f5117f3 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl4.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl4.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("4{x}"); } diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.rs b/tests/ui/eii/duplicate/dylib_default_duplicate.rs index 0ac3669715f85..28c79ae35dd97 100644 --- a/tests/ui/eii/duplicate/dylib_default_duplicate.rs +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.rs @@ -11,7 +11,7 @@ extern crate dylib_default; -#[unsafe(dylib_default::eii1)] +#[dylib_default::eii1] fn other(x: u64) { //~^ ERROR multiple implementations of `#[eii1]` println!("1{x}"); diff --git a/tests/ui/eii/safe_eii_unsafe_impl.rs b/tests/ui/eii/safe_eii_unsafe_impl.rs new file mode 100644 index 0000000000000..e30be843d5f76 --- /dev/null +++ b/tests/ui/eii/safe_eii_unsafe_impl.rs @@ -0,0 +1,14 @@ +// Tests that safe EIIs reject `unsafe(...)` implementation attributes. +#![feature(extern_item_impls)] + +#[eii] +fn foo(x: u64) -> u64; + +#[unsafe(foo)] //~ ERROR `foo` is not unsafe to implement +fn foo_impl(x: u64) -> u64 { + x +} + +fn main() { + foo(0); +} diff --git a/tests/ui/eii/safe_eii_unsafe_impl.stderr b/tests/ui/eii/safe_eii_unsafe_impl.stderr new file mode 100644 index 0000000000000..78501e5c0080e --- /dev/null +++ b/tests/ui/eii/safe_eii_unsafe_impl.stderr @@ -0,0 +1,10 @@ +error: `foo` is not unsafe to implement + --> $DIR/safe_eii_unsafe_impl.rs:7:1 + | +LL | #[unsafe(foo)] + | ^^------^^^^^^ + | | + | `unsafe` is not allowed here + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/type_checking/cross_crate_type_ok.rs b/tests/ui/eii/type_checking/cross_crate_type_ok.rs index 157d659bfd64b..41b756095b074 100644 --- a/tests/ui/eii/type_checking/cross_crate_type_ok.rs +++ b/tests/ui/eii/type_checking/cross_crate_type_ok.rs @@ -9,7 +9,7 @@ extern crate cross_crate_eii_declaration; -#[unsafe(cross_crate_eii_declaration::foo)] +#[cross_crate_eii_declaration::foo] fn other(x: u64) -> u64 { x } diff --git a/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs b/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs index 7ae1a5995a86e..7e3306870a2e2 100644 --- a/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs +++ b/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs @@ -8,7 +8,7 @@ extern crate cross_crate_eii_declaration; -#[unsafe(cross_crate_eii_declaration::foo)] +#[cross_crate_eii_declaration::foo] fn other() -> u64 { //~^ ERROR `other` has 0 parameters but #[foo] requires it to have 1 0 diff --git a/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr b/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr index b2190a075b631..fd0141754b819 100644 --- a/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr +++ b/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr @@ -1,8 +1,8 @@ error[E0806]: `other` has 0 parameters but #[foo] requires it to have 1 --> $DIR/cross_crate_wrong_ty.rs:12:1 | -LL | #[unsafe(cross_crate_eii_declaration::foo)] - | ------------------------------------------- required because of this attribute +LL | #[cross_crate_eii_declaration::foo] + | ----------------------------------- required because of this attribute LL | fn other() -> u64 { | ^^^^^^^^^^^^^^^^^ expected 1 parameter, found 0 | From 4bd52e9b0997811bab7232d3789fdd5b6ab8989d Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:52:51 +0200 Subject: [PATCH 03/45] Remove redundant `#[rustc_paren_sugar]` feature gate --- .../src/attributes/traits.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 3 --- compiler/rustc_hir_analysis/src/diagnostics.rs | 10 ---------- .../feature-gate-unboxed-closures.rs | 4 ++++ .../feature-gate-unboxed-closures.stderr | 18 ++++++++++++++---- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index a2348eef975cf..69bdccb85c5cd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -53,7 +53,7 @@ pub(crate) struct RustcParenSugarParser; impl NoArgsAttributeParser for RustcParenSugarParser { const PATH: &[Symbol] = &[sym::rustc_paren_sugar]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Trait)]); - const STABILITY: AttributeStability = unstable!(rustc_attrs); + const STABILITY: AttributeStability = unstable!(unboxed_closures); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcParenSugar; } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 605b6e3751ef3..6c393ed3dfc6b 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -947,9 +947,6 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { let attrs = tcx.get_all_attrs(def_id); let paren_sugar = find_attr!(attrs, RustcParenSugar); - if paren_sugar && !tcx.features().unboxed_closures() { - tcx.dcx().emit_err(diagnostics::ParenSugarAttribute { span: item.span }); - } // Only regular traits can be marker. let is_marker = !is_alias && find_attr!(attrs, Marker); diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index a6e274ceb4bc6..0ea353cf14cee 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -862,16 +862,6 @@ pub(crate) struct EnumDiscriminantOverflowed { pub wrapped_discr: String, } -#[derive(Diagnostic)] -#[diag( - "the `#[rustc_paren_sugar]` attribute is a temporary means of controlling which traits can use parenthetical notation" -)] -#[help("add `#![feature(unboxed_closures)]` to the crate attributes to use it")] -pub(crate) struct ParenSugarAttribute { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("use of SIMD type{$snip} in FFI is highly experimental and may result in invalid code")] #[help("add `#![feature(simd_ffi)]` to the crate attributes to enable")] diff --git a/tests/ui/feature-gates/feature-gate-unboxed-closures.rs b/tests/ui/feature-gates/feature-gate-unboxed-closures.rs index 74cc20c581cb9..fd4ea70bb350c 100644 --- a/tests/ui/feature-gates/feature-gate-unboxed-closures.rs +++ b/tests/ui/feature-gates/feature-gate-unboxed-closures.rs @@ -2,6 +2,10 @@ struct Test; + +#[rustc_paren_sugar] +//~^ ERROR the `rustc_paren_sugar` attribute is an experimental feature +pub trait Fn {} impl FnOnce<(u32, u32)> for Test { //~^ ERROR the precise format of `Fn`-family traits' type parameters is subject to change //~| ERROR manual implementations of `FnOnce` are experimental diff --git a/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr b/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr index 36932c1f86f9f..66a3a92c07901 100644 --- a/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr +++ b/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr @@ -1,5 +1,15 @@ +error[E0658]: the `rustc_paren_sugar` attribute is an experimental feature + --> $DIR/feature-gate-unboxed-closures.rs:6:3 + | +LL | #[rustc_paren_sugar] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #29625 for more information + = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error[E0658]: the extern "rust-call" ABI is experimental and subject to change - --> $DIR/feature-gate-unboxed-closures.rs:10:12 + --> $DIR/feature-gate-unboxed-closures.rs:14:12 | LL | extern "rust-call" fn call_once(self, (a, b): (u32, u32)) -> u32 { | ^^^^^^^^^^^ @@ -9,7 +19,7 @@ LL | extern "rust-call" fn call_once(self, (a, b): (u32, u32)) -> u32 { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change - --> $DIR/feature-gate-unboxed-closures.rs:5:6 + --> $DIR/feature-gate-unboxed-closures.rs:9:6 | LL | impl FnOnce<(u32, u32)> for Test { | ^^^^^^^^^^^^^^^^^^ @@ -19,14 +29,14 @@ LL | impl FnOnce<(u32, u32)> for Test { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0183]: manual implementations of `FnOnce` are experimental - --> $DIR/feature-gate-unboxed-closures.rs:5:6 + --> $DIR/feature-gate-unboxed-closures.rs:9:6 | LL | impl FnOnce<(u32, u32)> for Test { | ^^^^^^^^^^^^^^^^^^ manual implementations of `FnOnce` are experimental | = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors Some errors have detailed explanations: E0183, E0658. For more information about an error, try `rustc --explain E0183`. From 75529d4b8e485945858e2811d2db0c898b0ec450 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 24 Jul 2026 16:48:22 +0300 Subject: [PATCH 04/45] rustc_parse: Stop returning `Option` from statement parsing --- compiler/rustc_attr_parsing/src/parser.rs | 6 +- compiler/rustc_builtin_macros/src/cfg_eval.rs | 5 +- compiler/rustc_expand/src/expand.rs | 4 +- compiler/rustc_parse/src/parser/expr.rs | 6 +- compiler/rustc_parse/src/parser/mod.rs | 9 +- .../rustc_parse/src/parser/nonterminal.rs | 2 +- compiler/rustc_parse/src/parser/stmt.rs | 102 +++++++++--------- 7 files changed, 58 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index ed926330ed289..c4b2a5b509051 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -660,9 +660,8 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { // don't `uninterpolate` the token to avoid suggesting anything butchered or questionable // when macro metavariables are involved. let snapshot = self.parser.create_snapshot_for_diagnostic(); - let stmt = self.parser.parse_stmt_without_recovery(false, ForceCollect::No, false); - match stmt { - Ok(Some(stmt)) => { + match self.parser.parse_stmt_without_recovery(false, ForceCollect::No, false) { + Ok(stmt) => { // The user tried to write something like // `#[deprecated(note = concat!("a", "b"))]`. err.descr = stmt.kind.descr().to_string(); @@ -692,7 +691,6 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { }); } } - Ok(None) => {} Err(e) => { e.cancel(); self.parser.restore_snapshot(snapshot); diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 60854d67ec946..2fb89939181e6 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -126,9 +126,8 @@ impl CfgEval<'_> { Annotatable::ForeignItem(self.flat_map_foreign_item(item).pop().unwrap()) } Annotatable::Stmt(_) => { - let stmt = parser - .parse_stmt_without_recovery(false, ForceCollect::Yes, false)? - .unwrap(); + let stmt = + parser.parse_stmt_without_recovery(false, ForceCollect::Yes, false)?; Annotatable::Stmt(Box::new(self.flat_map_stmt(stmt).pop().unwrap())) } Annotatable::Expr(_) => { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 0c775e22ccf23..4f55e0506e799 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1120,9 +1120,7 @@ pub fn parse_ast_fragment<'a>( let mut stmts = SmallVec::new(); // Won't make progress on a `}`. while this.token != token::Eof && this.token != token::CloseBrace { - if let Some(stmt) = this.parse_full_stmt(AttemptLocalParseRecovery::Yes)? { - stmts.push(stmt); - } + stmts.push(this.parse_full_stmt(AttemptLocalParseRecovery::Yes)?); } AstFragment::Stmts(stmts) } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 9798138b48b80..94d3c7dccfc6c 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3331,13 +3331,9 @@ impl<'a> Parser<'a> { self.restore_snapshot(pre_pat_snapshot); match self.parse_stmt_without_recovery(true, ForceCollect::No, false) { // Consume statements for as long as possible. - Ok(Some(stmt)) => { + Ok(stmt) => { stmts.push(stmt); } - Ok(None) => { - self.restore_snapshot(start_snapshot); - break; - } // We couldn't parse either yet another statement missing it's // enclosing block nor the next arm's pattern or closing brace. Err(stmt_err) => { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index fc539405249a6..ef62316abf941 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -987,12 +987,9 @@ impl<'a> Parser<'a> { let initial_semicolon = self.token.span; while self.eat(exp!(Semi)) { - let _ = self - .parse_stmt_without_recovery(false, ForceCollect::No, false) - .unwrap_or_else(|e| { - e.cancel(); - None - }); + if let Err(e) = self.parse_stmt_without_recovery(false, ForceCollect::No, false) { + e.cancel(); + } } expect_err diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index e4602abf7779f..9f9545c194082 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -139,7 +139,7 @@ impl<'a> Parser<'a> { this.parse_block().map(|block| WithTokens::new(block)) })?)) } - NonterminalKind::Stmt => match self.parse_stmt(ForceCollect::Yes)? { + NonterminalKind::Stmt => match self.parse_stmt_nonterminal(ForceCollect::Yes) { Some(stmt) => Ok(ParseNtResult::Stmt(Box::new(stmt))), None => { Err(self.dcx().create_err(UnexpectedNonterminal::Statement(self.token.span))) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 27a047f598513..6dddc8b2407dc 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -27,18 +27,22 @@ use crate::diagnostics::{self, MalformedLoopLabel}; use crate::exp; impl<'a> Parser<'a> { - /// Parses a statement. This stops just before trailing semicolons on everything but items. + /// Parses a statement nonterminal, which has a peculiar syntax preserved for backward + /// compatibility. The parsing stops just before trailing semicolons on everything but items. /// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed. /// /// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of /// whether or not we have attributes. // Public for rustfmt usage. - pub fn parse_stmt(&mut self, force_collect: ForceCollect) -> PResult<'a, Option> { - Ok(self.parse_stmt_without_recovery(false, force_collect, false).unwrap_or_else(|e| { - e.emit(); - self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); - None - })) + pub fn parse_stmt_nonterminal(&mut self, force_collect: ForceCollect) -> Option { + match self.parse_stmt_without_recovery(false, force_collect, false) { + Ok(stmt) => Some(stmt), + Err(e) => { + e.emit(); + self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); + None + } + } } /// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of @@ -49,19 +53,18 @@ impl<'a> Parser<'a> { capture_semi: bool, force_collect: ForceCollect, force_full_expr: bool, - ) -> PResult<'a, Option> { + ) -> PResult<'a, Stmt> { let pre_attr_pos = self.collect_pos(); let attrs = self.parse_outer_attributes()?; let lo = self.token.span; - if let Some(stmt) = self.eat_metavar_seq(MetaVarKind::Stmt, |this| { + if let Some(mut stmt) = self.eat_metavar_seq(MetaVarKind::Stmt, |this| { this.parse_stmt_without_recovery(false, ForceCollect::Yes, false) }) { - let mut stmt = stmt.expect("an actual statement"); stmt.visit_attrs(|stmt_attrs| { attrs.prepend_to_nt_inner(stmt_attrs); }); - return Ok(Some(stmt)); + return Ok(stmt); } if self.token.is_keyword(kw::Mut) && self.is_keyword_ahead(1, &[kw::Let]) { @@ -161,9 +164,12 @@ impl<'a> Parser<'a> { self.mk_stmt(lo.to(item.span), StmtKind::Item(Box::new(item))) } else if self.eat(exp!(Semi)) { // Do not attempt to parse an expression if we're done here. - self.error_outer_attrs(attrs); + self.error_outer_attrs(attrs)?; self.mk_stmt(lo, StmtKind::Empty) - } else if self.token != token::CloseBrace { + } else if self.token == token::CloseBrace { + self.error_outer_attrs(attrs)?; + self.dcx().span_bug(self.token.span, "don't parse a statement if you see `}`"); + } else { // Remainder are line-expr stmts. This is similar to the `parse_stmt_path_start` case // above. let restrictions = @@ -185,13 +191,10 @@ impl<'a> Parser<'a> { .emit_err(diagnostics::AssignmentElseNotAllowed { span: e.span.to(bl.span) }); } self.mk_stmt(lo.to(e.span), StmtKind::Expr(e)) - } else { - self.error_outer_attrs(attrs); - return Ok(None); }; self.maybe_augment_stashed_expr_in_pats_with_suggestions(&stmt); - Ok(Some(stmt)) + Ok(stmt) } fn parse_stmt_path_start(&mut self, lo: Span, attrs: AttrWrapper) -> PResult<'a, Stmt> { @@ -272,20 +275,21 @@ impl<'a> Parser<'a> { /// Error on outer attributes in this context. /// Also error if the previous token was a doc comment. - fn error_outer_attrs(&self, attrs: AttrWrapper) { - if !attrs.is_empty() - && let attrs @ [.., last] = &*attrs.take_for_recovery(self.psess) - { - if last.is_doc_comment() { - self.dcx().emit_err(diagnostics::DocCommentDoesNotDocumentAnything { - span: last.span, - missing_comma: None, - }); - } else if attrs.iter().any(|a| a.style == AttrStyle::Outer) { - self.dcx() - .emit_err(diagnostics::ExpectedStatementAfterOuterAttr { span: last.span }); - } + fn error_outer_attrs(&self, attrs: AttrWrapper) -> PResult<'a, ()> { + if attrs.is_empty() { + return Ok(()); } + let attrs = attrs.take_for_recovery(self.psess); + let last = attrs.last().unwrap(); + Err(if last.is_doc_comment() { + self.dcx().create_err(diagnostics::DocCommentDoesNotDocumentAnything { + span: last.span, + missing_comma: None, + }) + } else { + assert_eq!(last.style, AttrStyle::Outer); + self.dcx().create_err(diagnostics::ExpectedStatementAfterOuterAttr { span: last.span }) + }) } fn recover_stmt_local_after_let( @@ -521,6 +525,10 @@ impl<'a> Parser<'a> { let sp = self.token.span; let mut err = self.dcx().struct_span_err(sp, msg); self.label_expected_raw_ref(&mut err); + err.span_label(sp, "expected `{`"); + if self.token == token::CloseBrace { + return err; + } let do_not_suggest_help = self.token.is_keyword(kw::In) || self.token == token::Colon @@ -547,13 +555,13 @@ impl<'a> Parser<'a> { // since we want to protect against: // `if 1 1 + 1 {` being suggested as `if { 1 } 1 + 1 {` // + + - Ok(Some(_)) + Ok(_) if (!self.token.is_keyword(kw::Else) && self.look_ahead(1, |t| t == &token::OpenBrace)) || do_not_suggest_help => {} // Do not suggest `if foo println!("") {;}` (as would be seen in test for #46836). - Ok(Some(Stmt { kind: StmtKind::Empty, .. })) => {} - Ok(Some(stmt)) => { + Ok(Stmt { kind: StmtKind::Empty, .. }) => {} + Ok(stmt) => { let stmt_own_line = self.psess.source_map().is_line_before_span_empty(sp); let stmt_span = if stmt_own_line && self.eat(exp!(Semi)) { // Expand the span to include the semicolon. @@ -571,9 +579,7 @@ impl<'a> Parser<'a> { Err(e) => { e.delay_as_bug(); } - _ => {} } - err.span_label(sp, "expected `{`"); err } @@ -773,17 +779,12 @@ impl<'a> Parser<'a> { let guar = err.emit(); self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore); - Some(self.mk_stmt_err(self.token.span, guar)) + self.mk_stmt_err(self.token.span, guar) } Ok(stmt) => stmt, Err(err) => return Err(err), }; - if let Some(stmt) = stmt { - stmts.push(stmt); - } else { - // Found only `;` or `}`. - continue; - }; + stmts.push(stmt); } Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span))) } @@ -943,10 +944,7 @@ impl<'a> Parser<'a> { } /// Parses a statement, including the trailing semicolon. - pub fn parse_full_stmt( - &mut self, - recover: AttemptLocalParseRecovery, - ) -> PResult<'a, Option> { + pub fn parse_full_stmt(&mut self, recover: AttemptLocalParseRecovery) -> PResult<'a, Stmt> { // Skip looking for a trailing semicolon when we have a metavar seq. if let Some(stmt) = self.eat_metavar_seq(MetaVarKind::Stmt, |this| { // Why pass `true` for `force_full_expr`? Statement expressions are less expressive @@ -959,14 +957,10 @@ impl<'a> Parser<'a> { // will reparse successfully. this.parse_stmt_without_recovery(false, ForceCollect::No, true) }) { - let stmt = stmt.expect("an actual statement"); - return Ok(Some(stmt)); + return Ok(stmt); } - let Some(mut stmt) = self.parse_stmt_without_recovery(true, ForceCollect::No, false)? - else { - return Ok(None); - }; + let mut stmt = self.parse_stmt_without_recovery(true, ForceCollect::No, false)?; let mut eat_semi = true; let mut add_semi_to_stmt = false; @@ -1086,7 +1080,7 @@ impl<'a> Parser<'a> { StmtKind::Expr(_) | StmtKind::MacCall(_) => {} StmtKind::Let(local) => { if self.try_recover_let_missing_semi(local).is_some() { - return Ok(Some(stmt)); + return Ok(stmt); } if let Err(mut e) = self.expect_semi() { // We might be at the `,` in `let x = foo;`. Try to recover. @@ -1159,7 +1153,7 @@ impl<'a> Parser<'a> { } stmt.span = stmt.span.to(self.prev_token.span); - Ok(Some(stmt)) + Ok(stmt) } pub(super) fn mk_block( From a32fa442efe45fb9af7a5260b63ede856edc8e7f Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Fri, 24 Jul 2026 12:55:03 -0400 Subject: [PATCH 05/45] Updated expect messages for CString struct and method documentation --- library/alloc/src/ffi/c_str.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index e6e6fcf5420f3..b340cf9566f2e 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -84,7 +84,7 @@ use crate::vec::Vec; /// /// // We are certain that our string doesn't have 0 bytes in the middle, /// // so we can .expect() -/// let c_to_print = CString::new("Hello, world!").expect("CString::new failed"); +/// let c_to_print = CString::new("Hello, world!").expect("we provided a string without NUL bytes, so CString::new should not fail"); /// unsafe { /// my_printer(c_to_print.as_ptr()); /// } @@ -242,7 +242,7 @@ impl CString { /// /// extern "C" { fn puts(s: *const c_char); } /// - /// let to_print = CString::new("Hello!").expect("CString::new failed"); + /// let to_print = CString::new("Hello!").expect("we provided a string without NUL bytes, so CString::new should not fail"); /// unsafe { /// puts(to_print.as_ptr()); /// } @@ -466,12 +466,12 @@ impl CString { /// use std::ffi::CString; /// /// let valid_utf8 = vec![b'f', b'o', b'o']; - /// let cstring = CString::new(valid_utf8).expect("CString::new failed"); - /// assert_eq!(cstring.into_string().expect("into_string() call failed"), "foo"); + /// let cstring = CString::new(valid_utf8).expect("we provided bytes that do not have a NUL byte, so CString::new should not fail"); + /// assert_eq!(cstring.into_string().expect("we provided bytes that are valid UTF-8, so `into_string` should not fail"), "foo"); /// /// let invalid_utf8 = vec![b'f', 0xff, b'o', b'o']; - /// let cstring = CString::new(invalid_utf8).expect("CString::new failed"); - /// let err = cstring.into_string().err().expect("into_string().err() failed"); + /// let cstring = CString::new(invalid_utf8).expect("we provided bytes that do not have a NUL byte, so CString::new should not fail"); + /// let err = cstring.into_string().expect_err("we provided bytes that are invalid UTF-8, so `into_string` should fail"); /// assert_eq!(err.utf8_error().valid_up_to(), 1); /// ``` #[stable(feature = "cstring_into", since = "1.7.0")] @@ -577,7 +577,7 @@ impl CString { /// let c_string = CString::from(c"foo"); /// let cstr = c_string.as_c_str(); /// assert_eq!(cstr, - /// CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed")); + /// CStr::from_bytes_with_nul(b"foo\0").expect("we provided bytes that has one NUL byte exactly at the end, so CStr::from_bytes_with_nul should not fail")); /// ``` #[inline] #[must_use] @@ -660,7 +660,7 @@ impl CString { /// use std::ffi::CString; /// assert_eq!( /// CString::from_vec_with_nul(b"abc\0".to_vec()) - /// .expect("CString::from_vec_with_nul failed"), + /// .expect("we provided bytes that has one NUL byte exactly at the end, so CString::from_vec_with_nul should not fail"), /// c"abc".to_owned() /// ); /// ``` From c9e24292ef70afae99d8c69c41cbea4a2fda0565 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 25 Jul 2026 13:28:49 +1000 Subject: [PATCH 06/45] Remove obsolete option `build.compiletest-use-stage0-libtest` --- src/bootstrap/src/core/config/config.rs | 2 -- src/bootstrap/src/core/config/toml/build.rs | 3 --- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 74be2409570f8..d516c89fba03c 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -532,8 +532,6 @@ impl Config { optimized_compiler_builtins: build_optimized_compiler_builtins, jobs: build_jobs, compiletest_diff_tool: build_compiletest_diff_tool, - // No longer has any effect; kept (for now) to avoid breaking people's configs. - compiletest_use_stage0_libtest: _, tidy_extra_checks: build_tidy_extra_checks, ccache: build_ccache, exclude: build_exclude, diff --git a/src/bootstrap/src/core/config/toml/build.rs b/src/bootstrap/src/core/config/toml/build.rs index 877dde676c605..d0fdbbed4e82d 100644 --- a/src/bootstrap/src/core/config/toml/build.rs +++ b/src/bootstrap/src/core/config/toml/build.rs @@ -72,9 +72,6 @@ define_config! { jobs: Option = "jobs", compiletest_diff_tool: Option = "compiletest-diff-tool", compiletest_allow_stage0: Option = "compiletest-allow-stage0", - /// No longer has any effect; kept (for now) to avoid breaking people's configs. - /// FIXME(#146929): Remove this in 2026. - compiletest_use_stage0_libtest: Option = "compiletest-use-stage0-libtest", tidy_extra_checks: Option = "tidy-extra-checks", ccache: Option = "ccache", exclude: Option> = "exclude", diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 4252f2683b3d9..f5ab16c2e0a9a 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -651,4 +651,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "A new `build.sde` configuration option has been added to support intrinsic-test.", }, + ChangeInfo { + change_id: 159878, + severity: ChangeSeverity::Warning, + summary: "Obsolete option `build.compiletest-use-stage0-libtest` has no effect and has been removed.", + }, ]; From 36ef11a72c9fb1a5de60b0c8880c025f87111ddd Mon Sep 17 00:00:00 2001 From: Cole Kauder-McMurrich Date: Sat, 25 Jul 2026 00:50:42 -0400 Subject: [PATCH 07/45] Update expect messages in library/alloc/boxed.rs and library/alloc/string.rs to follow the style guide --- library/alloc/src/boxed.rs | 2 +- library/alloc/src/string.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 58996703023ce..5190baaab8a26 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1368,7 +1368,7 @@ impl Box { /// /// unsafe { /// let non_null = NonNull::new(alloc(Layout::new::()).cast::()) - /// .expect("allocation failed"); + /// .expect("alloc should have successfully allocated memory"); /// // In general .write is required to avoid attempting to destruct /// // the (uninitialized) previous contents of `non_null`. /// non_null.write(5); diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 6c19ba816050d..cc321660e6ea4 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -1314,7 +1314,7 @@ impl String { /// /// Ok(output) /// } - /// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?"); + /// # process_data("rust").expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { @@ -1355,7 +1355,7 @@ impl String { /// /// Ok(output) /// } - /// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?"); + /// # process_data("rust").expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { From 05bb184cd682e2ffcedf904c7dd16994dcc39e0b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 11:41:29 +0200 Subject: [PATCH 08/45] sembr src/variance.md --- src/doc/rustc-dev-guide/src/variance.md | 189 ++++++++++++------------ 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index 7aa0140715517..a8a72b44e87ad 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -4,30 +4,32 @@ For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html -During type checking we must infer the variance of type and lifetime -parameters. The algorithm is taken from Section 4 of the paper ["Taming the +During type checking we must infer the variance of type and lifetime parameters. +The algorithm is taken from Section 4 of the paper ["Taming the Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf -This inference is explicitly designed *not* to consider the uses of -types within code. To determine the variance of type parameters +This inference is explicitly designed *not* to consider the uses of types within code. +To determine the variance of type parameters defined on type `X`, we only consider the definition of the type `X` and the definitions of any types it references. -We only infer variance for type parameters found on *data types* -like structs and enums. In these cases, there is a fairly straightforward -explanation for what variance means. The variance of the type -or lifetime parameters defines whether `T` is a subtype of `T` -(resp. `T<'a>` and `T<'b>`) based on the relationship of `A` and `B` -(resp. `'a` and `'b`). +We only infer variance for type parameters found on *data types* like structs and enums. +In these cases, there is a fairly straightforward explanation for what variance means. +The variance of the type or lifetime parameters defines whether `T` is a subtype of `T` +(resp. +`T<'a>` and `T<'b>`) based on the relationship of `A` and `B` (resp. +`'a` and `'b`). We do not infer variance for type parameters found on traits, functions, -or impls. Variance on trait parameters can indeed make sense +or impls. +Variance on trait parameters can indeed make sense (and we used to compute it) but it is actually rather subtle in -meaning and not that useful in practice, so we removed it. See the -[addendum] for some details. Variances on function/impl parameters, on the +meaning and not that useful in practice, so we removed it. +See the [addendum] for some details. +Variances on function/impl parameters, on the other hand, doesn't make sense because these parameters are instantiated and then forgotten, they don't persist in types or compiled byproducts. @@ -44,13 +46,12 @@ then forgotten, they don't persist in types or compiled byproducts. ## The algorithm -The basic idea is quite straightforward. We iterate over the types -defined and, for each use of a type parameter `X`, accumulate a -constraint indicating that the variance of `X` must be valid for the -variance of that use site. We then iteratively refine the variance of -`X` until all constraints are met. There is *always* a solution, because at -the limit we can declare all type parameters to be invariant and all -constraints will be satisfied. +The basic idea is quite straightforward. +We iterate over the types defined and, for each use of a type parameter `X`, accumulate a +constraint indicating that the variance of `X` must be valid for the variance of that use site. +We then iteratively refine the variance of `X` until all constraints are met. +There is *always* a solution, because at +the limit we can declare all type parameters to be invariant and all constraints will be satisfied. As a simple example, consider: @@ -71,8 +72,8 @@ Here, we will generate the constraints: These indicate that (1) the variance of A must be at most covariant; (2) the variance of B must be at most contravariant; and (3, 4) the -variance of C must be at most covariant *and* contravariant. All of these -results are based on a variance lattice defined as follows: +variance of C must be at most covariant *and* contravariant. +All of these results are based on a variance lattice defined as follows: ```text * Top (bivariant) @@ -80,14 +81,13 @@ results are based on a variance lattice defined as follows: o Bottom (invariant) ``` -Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the -optimal solution. Note that there is always a naive solution which -just declares all variables to be invariant. +Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the optimal solution. +Note that there is always a naive solution which just declares all variables to be invariant. -You may be wondering why fixed-point iteration is required. The reason -is that the variance of a use site may itself be a function of the -variance of other type parameters. In full generality, our constraints -take the form: +You may be wondering why fixed-point iteration is required. +The reason is that the variance of a use site may itself be a function of the +variance of other type parameters. +In full generality, our constraints take the form: ```text V(X) <= Term @@ -95,8 +95,8 @@ Term := + | - | * | o | V(X) | Term x Term ``` Here the notation `V(X)` indicates the variance of a type/region -parameter `X` with respect to its defining class. `Term x Term` -represents the "variance transform" as defined in the paper: +parameter `X` with respect to its defining class. +`Term x Term` represents the "variance transform" as defined in the paper: > If the variance of a type variable `X` in type expression `E` is `V2` and the definition-site variance of the corresponding type parameter @@ -112,19 +112,21 @@ struct Foo { ... } ``` you might wonder whether the variance of `T` with respect to `Bar` affects the -variance `T` with respect to `Foo`. I claim no. The reason: assume that `T` is -invariant with respect to `Bar` but covariant with respect to `Foo`. And then -we have a `Foo` that is upcast to `Foo`, where `X <: Y`. However, while -`X : Bar`, `Y : Bar` does not hold. In that case, the upcast will be illegal, +variance `T` with respect to `Foo`. +I claim no. + The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. +And then we have a `Foo` that is upcast to `Foo`, where `X <: Y`. +However, while `X : Bar`, `Y : Bar` does not hold. + In that case, the upcast will be illegal, but not because of a variance failure, but rather because the target type -`Foo` is itself just not well-formed. Basically we get to assume -well-formedness of all types involved before considering variance. +`Foo` is itself just not well-formed. +Basically we get to assume well-formedness of all types involved before considering variance. ### Dependency graph management Because variance is a whole-crate inference, its dependency graph -can become quite muddled if we are not careful. To resolve this, we refactor -into two queries: +can become quite muddled if we are not careful. +To resolve this, we refactor into two queries: - `crate_variances` computes the variance for all items in the current crate. - `variances_of` accesses the variance for an individual reading; it @@ -133,7 +135,8 @@ into two queries: If you limit yourself to reading `variances_of`, your code will only depend then on the inference of that particular item. -Ultimately, this setup relies on the [red-green algorithm][rga]. In particular, +Ultimately, this setup relies on the [red-green algorithm][rga]. +In particular, every variance query effectively depends on all type definitions in the entire crate (through `crate_variances`), but since most changes will not result in a change to the actual results from variance inference, the `variances_of` query @@ -145,19 +148,18 @@ will wind up being considered green after it is re-evaluated. ## Addendum: Variance on traits -As mentioned above, we used to permit variance on traits. This was -computed based on the appearance of trait type parameters in +As mentioned above, we used to permit variance on traits. +This was computed based on the appearance of trait type parameters in method signatures and was used to represent the compatibility of -vtables in trait objects (and also "virtual" vtables or dictionary -in trait bounds). One complication was that variance for +vtables in trait objects (and also "virtual" vtables or dictionary in trait bounds). +One complication was that variance for associated types is less obvious, since they can be projected out and put to myriad uses, so it's not clear when it is safe to allow -`X::Bar` to vary (or indeed just what that means). Moreover (as -covered below) all inputs on any trait with an associated type had -to be invariant, limiting the applicability. Finally, the -annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all -trait type parameters had a variance were confusing and annoying -for little benefit. +`X::Bar` to vary (or indeed just what that means). +Moreover (as covered below) all inputs on any trait with an associated type had +to be invariant, limiting the applicability. +Finally, the annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all +trait type parameters had a variance were confusing and annoying for little benefit. Just for historical reference, I am going to preserve some text indicating how one could interpret variance and trait matching. @@ -166,13 +168,12 @@ one could interpret variance and trait matching. Just as with structs and enums, we can decide the subtyping relationship between two object types `&Trait` and `&Trait` -based on the relationship of `A` and `B`. Note that for object -types we ignore the `Self` type parameter – it is unknown, and +based on the relationship of `A` and `B`. +Note that for object types we ignore the `Self` type parameter – it is unknown, and the nature of dynamic dispatch ensures that we will always call a -function that is expected the appropriate `Self` type. However, we -must be careful with the other type parameters, or else we could -end up calling a function that is expecting one type but provided -another. +function that is expected the appropriate `Self` type. +However, we must be careful with the other type parameters, or else we could +end up calling a function that is expecting one type but provided another. To see what I mean, consider a trait like so: @@ -184,29 +185,29 @@ trait ConvertTo { Intuitively, If we had one object `O=&ConvertTo` and another `S=&ConvertTo`, then `S <: O` because `String <: Object` -(presuming Java-like "string" and "object" types, my go to examples -for subtyping). The actual algorithm would be to compare the +(presuming Java-like "string" and "object" types, my go to examples for subtyping). +The actual algorithm would be to compare the (explicit) type parameters pairwise respecting their variance: here, the type parameter A is covariant (it appears only in a return position), and hence we require that `String <: Object`. You'll note though that we did not consider the binding for the -(implicit) `Self` type parameter: in fact, it is unknown, so that's -good. The reason we can ignore that parameter is precisely because we +(implicit) `Self` type parameter: in fact, it is unknown, so that's good. +The reason we can ignore that parameter is precisely because we don't need to know its value until a call occurs, and at that time (as you said) the dynamic nature of virtual dispatch means the code we run will be correct for whatever value `Self` happens to be bound to for -the particular object whose method we called. `Self` is thus different -from `A`, because the caller requires that `A` be known in order to -know the return type of the method `convertTo()`. (As an aside, we -have rules preventing methods where `Self` appears outside of the +the particular object whose method we called. +`Self` is thus different from `A`, because the caller requires that `A` be known in order to +know the return type of the method `convertTo()`. +(As an aside, we have rules preventing methods where `Self` appears outside of the receiver position from being called via an object.) ### Trait variance and vtable resolution -But traits aren't only used with objects. They're also used when -deciding whether a given impl satisfies a given trait bound. To set the -scene here, imagine I had a function: +But traits aren't only used with objects. +They're also used when deciding whether a given impl satisfies a given trait bound. +To set the scene here, imagine I had a function: ```rust,ignore fn convertAll>(v: &[T]) { ... } @@ -218,8 +219,8 @@ Now imagine that I have an implementation of `ConvertTo` for `Object`: impl ConvertTo for Object { ... } ``` -And I want to call `convertAll` on an array of strings. Suppose -further that for whatever reason I specifically supply the value of +And I want to call `convertAll` on an array of strings. +Suppose further that for whatever reason I specifically supply the value of `String` for the type parameter `T`: ```rust,ignore @@ -227,26 +228,25 @@ let mut vector = vec!["string", ...]; convertAll::(vector); ``` -Is this legal? To put another way, can we apply the `impl` for -`Object` to the type `String`? The answer is yes, but to see why -we have to expand out what will happen: +Is this legal? +To put another way, can we apply the `impl` for `Object` to the type `String`? +The answer is yes, but to see why we have to expand out what will happen: - `convertAll` will create a pointer to one of the entries in the vector, which will have type `&String` -- It will then call the impl of `convertTo()` that is intended - for use with objects. This has the type `fn(self: &Object) -> i32`. +- It will then call the impl of `convertTo()` that is intended for use with objects. + This has the type `fn(self: &Object) -> i32`. - It is OK to provide a value for `self` of type `&String` because - `&String <: &Object`. + It is OK to provide a value for `self` of type `&String` because `&String <: &Object`. OK, so intuitively we want this to be legal, so let's bring this back -to variance and see whether we are computing the correct result. We -must first figure out how to phrase the question "is an impl for +to variance and see whether we are computing the correct result. +We must first figure out how to phrase the question "is an impl for `Object,i32` usable where an impl for `String,i32` is expected?" -Maybe it's helpful to think of a dictionary-passing implementation of -type classes. In that case, `convertAll()` takes an implicit parameter -representing the impl. In short, we *have* an impl of type: +Maybe it's helpful to think of a dictionary-passing implementation of type classes. +In that case, `convertAll()` takes an implicit parameter representing the impl. +In short, we *have* an impl of type: ```text V_O = ConvertTo for Object @@ -259,9 +259,10 @@ V_S = ConvertTo for String ``` As with any argument, this is legal if the type of the value given -(`V_O`) is a subtype of the type expected (`V_S`). So is `V_O <: V_S`? -The answer will depend on the variance of the various parameters. In -this case, because the `Self` parameter is contravariant and `A` is +(`V_O`) is a subtype of the type expected (`V_S`). +So is `V_O <: V_S`? +The answer will depend on the variance of the various parameters. +In this case, because the `Self` parameter is contravariant and `A` is covariant, it means that: ```text @@ -275,27 +276,26 @@ These conditions are satisfied and so we are happy. ### Variance and associated types Traits with associated types – or at minimum projection -expressions – must be invariant with respect to all of their -inputs. To see why this makes sense, consider what subtyping for a -trait reference means: +expressions – must be invariant with respect to all of their inputs. +To see why this makes sense, consider what subtyping for a trait reference means: ```text <: ``` -means that if I know that `T as Trait`, I also know that `U as -Trait`. Moreover, if you think of it as dictionary passing style, +means that if I know that `T as Trait`, I also know that `U as Trait`. +Moreover, if you think of it as dictionary passing style, it means that a dictionary for `` is safe to use where a dictionary for `` is expected. The problem is that when you can project types out from ``, the relationship to types projected out of `` -is completely unknown unless `T==U` (see #21726 for more -details). Making `Trait` invariant ensures that this is true. +is completely unknown unless `T==U` (see #21726 for more details). +Making `Trait` invariant ensures that this is true. Another related reason is that if we didn't make traits with -associated types invariant, then projection is no longer a -function with a single result. Consider: +associated types invariant, then projection is no longer a function with a single result. +Consider: ```rust,ignore trait Identity { type Out; fn foo(&self); } @@ -313,4 +313,5 @@ if 'static : 'a -- Subtyping rules for relations This change otoh means that `<'static () as Identity>::Out` is always `&'static ()` (which might then be upcast to `'a ()`, -separately). This was helpful in solving #21750. +separately). +This was helpful in solving #21750. From 5d19c9290758240c9bbcfee9eebfb47f5360792b Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Sat, 25 Jul 2026 09:42:21 +0000 Subject: [PATCH 09/45] Prepare for merging from rust-lang/rust This updates the rust-version file to da86f4d0726be475afbbffe40cb2f65741c51ad3. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 25a5238f538ee..1a74dff9d23d8 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -390279b302ca98ae270f434100ae3730531d1246 +da86f4d0726be475afbbffe40cb2f65741c51ad3 From acb94075972be23872ca1c010029bf8f2c399a10 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:01:39 +0200 Subject: [PATCH 10/45] Split multiline derives into std/rustc macros --- compiler/rustc_data_structures/src/svh.rs | 13 +----- .../rustc_hir/src/attrs/data_structures.rs | 15 +------ compiler/rustc_lint_defs/src/lib.rs | 5 +-- .../rustc_middle/src/dep_graph/dep_node.rs | 5 +-- compiler/rustc_span/src/lib.rs | 6 +-- compiler/rustc_target/src/asm/amdgpu.rs | 45 +++---------------- 6 files changed, 17 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_data_structures/src/svh.rs b/compiler/rustc_data_structures/src/svh.rs index 2c4e00c824d80..67594f6dae79d 100644 --- a/compiler/rustc_data_structures/src/svh.rs +++ b/compiler/rustc_data_structures/src/svh.rs @@ -11,17 +11,8 @@ use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash}; use crate::fingerprint::Fingerprint; -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Encodable_NoContext, - Decodable_NoContext, - Hash, - StableHash -)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(Encodable_NoContext, Decodable_NoContext, StableHash)] pub struct Svh { hash: Fingerprint, } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index db492475a3aa4..efe3657f21ea7 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -370,19 +370,8 @@ pub enum PeImportNameType { Undecorated, } -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Encodable, - Decodable, - PrintAttribute -)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Encodable, Decodable, PrintAttribute)] #[derive(StableHash)] pub enum NativeLibKind { /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 6949090781cf6..767fa647612af 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -149,9 +149,8 @@ impl From for LintExpectationId { /// Setting for how to handle a lint. /// /// See: -#[derive( - Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable, StableHash -)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub enum Level { /// The `allow` level will not issue any message. Allow, diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 391bd41be828c..f460fd42cb31c 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -217,9 +217,8 @@ pub struct DepKindVTable<'tcx> { /// some independent path or string that persists between runs without /// the need to be mapped or unmapped. (This ensures we can serialize /// them even in the absence of a tcx.) -#[derive( - Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable, StableHash -)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub struct WorkProductId { hash: Fingerprint, } diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 318d0b60e240d..b8cf472309ba5 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -2126,10 +2126,8 @@ impl fmt::Debug for SourceFile { /// /// When `SourceFile`s are exported in crate metadata, the `StableSourceFileId` /// is updated to incorporate the `StableCrateId` of the exporting crate. -#[derive( - Debug, Clone, Copy, Hash, PartialEq, Eq, StableHash, Encodable, Decodable, Default, PartialOrd, - Ord -)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Default, Ord)] +#[derive(StableHash, Encodable, Decodable)] pub struct StableSourceFileId(Hash128); impl StableSourceFileId { diff --git a/compiler/rustc_target/src/asm/amdgpu.rs b/compiler/rustc_target/src/asm/amdgpu.rs index cc6f612cdb8b5..0f24ae5dea225 100644 --- a/compiler/rustc_target/src/asm/amdgpu.rs +++ b/compiler/rustc_target/src/asm/amdgpu.rs @@ -1,5 +1,6 @@ use std::fmt; +use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Symbol; use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; @@ -9,19 +10,8 @@ use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; /// Amdgpu register classes /// /// The number is the size of the register class in bits. -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] -#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(StableHash, Encodable, Decodable)] pub enum AmdgpuInlineAsmRegClass { Sgpr(u16), Vgpr(u16), @@ -267,18 +257,8 @@ impl AmdgpuInlineAsmRegClass { /// Start index of a register. /// /// Together with the register size this gives the range occupied by a register. -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Encodable, Decodable, StableHash)] enum AmdgpuRegStart { /// Low 16-bit of the register at this index Low(u16), @@ -288,19 +268,8 @@ enum AmdgpuRegStart { Full(u16), } -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] -#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub struct AmdgpuInlineAsmReg { class: AmdgpuInlineAsmRegClass, range: AmdgpuRegStart, From d57ee2221b032116c9edac9666d720e3760dc9ef Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:03:12 +0200 Subject: [PATCH 11/45] improve variance.md --- src/doc/rustc-dev-guide/src/variance.md | 35 +++++++++++-------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index a8a72b44e87ad..96fde1d87cca5 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -4,7 +4,7 @@ For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html -During type checking we must infer the variance of type and lifetime parameters. +During type checking, we must infer the variance of type and lifetime parameters. The algorithm is taken from Section 4 of the paper ["Taming the Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. @@ -12,26 +12,23 @@ PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf This inference is explicitly designed *not* to consider the uses of types within code. -To determine the variance of type parameters -defined on type `X`, we only consider the definition of the type `X` -and the definitions of any types it references. +To determine the variance of type parameters defined on type `X`, +we only consider the definition of the type `X` and the definitions of any types it references. We only infer variance for type parameters found on *data types* like structs and enums. In these cases, there is a fairly straightforward explanation for what variance means. The variance of the type or lifetime parameters defines whether `T` is a subtype of `T` -(resp. -`T<'a>` and `T<'b>`) based on the relationship of `A` and `B` (resp. -`'a` and `'b`). +(`T<'a>` and `T<'b>` respectively) +based on the relationship of `A` and `B` (`'a` and `'b` respectively). -We do not infer variance for type parameters found on traits, functions, -or impls. +We do not infer variance for type parameters found on traits, functions, or impls. Variance on trait parameters can indeed make sense (and we used to compute it) but it is actually rather subtle in meaning and not that useful in practice, so we removed it. See the [addendum] for some details. -Variances on function/impl parameters, on the -other hand, doesn't make sense because these parameters are instantiated and -then forgotten, they don't persist in types or compiled byproducts. +Variances on function/impl parameters, on the other hand, +doesn't make sense because these parameters are instantiated and then forgotten; +they don't persist in types or compiled byproducts. [addendum]: #addendum @@ -111,16 +108,16 @@ If I have a struct or enum with where clauses: struct Foo { ... } ``` -you might wonder whether the variance of `T` with respect to `Bar` affects the +You might wonder whether the variance of `T` with respect to `Bar` affects the variance `T` with respect to `Foo`. I claim no. - The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. +The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. And then we have a `Foo` that is upcast to `Foo`, where `X <: Y`. However, while `X : Bar`, `Y : Bar` does not hold. - In that case, the upcast will be illegal, +In that case, the upcast will be illegal, but not because of a variance failure, but rather because the target type `Foo` is itself just not well-formed. -Basically we get to assume well-formedness of all types involved before considering variance. +Basically, we get to assume well-formedness of all types involved before considering variance. ### Dependency graph management @@ -230,7 +227,7 @@ convertAll::(vector); Is this legal? To put another way, can we apply the `impl` for `Object` to the type `String`? -The answer is yes, but to see why we have to expand out what will happen: +The answer is yes, but to see why, we have to expand out what will happen: - `convertAll` will create a pointer to one of the entries in the vector, which will have type `&String` @@ -252,7 +249,7 @@ In short, we *have* an impl of type: V_O = ConvertTo for Object ``` -and the function prototype expects an impl of type: +And the function prototype expects an impl of type: ```text V_S = ConvertTo for String @@ -283,7 +280,7 @@ To see why this makes sense, consider what subtyping for a trait reference means <: ``` -means that if I know that `T as Trait`, I also know that `U as Trait`. +It means that if I know that `T as Trait`, I also know that `U as Trait`. Moreover, if you think of it as dictionary passing style, it means that a dictionary for `` is safe to use where a dictionary for `` is expected. From 41962835685e134a52b6e43df81a173fb2adf39b Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 25 Jul 2026 17:46:44 +0800 Subject: [PATCH 12/45] Add warning for breakage hazard for introducing new builtin attrs --- src/doc/rustc-dev-guide/src/attributes.md | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/attributes.md b/src/doc/rustc-dev-guide/src/attributes.md index 1056a257c2ee8..8dce9b8c17b18 100644 --- a/src/doc/rustc-dev-guide/src/attributes.md +++ b/src/doc/rustc-dev-guide/src/attributes.md @@ -25,6 +25,34 @@ For more information on these attributes, see the chapter about [attribute parsi [attr-parsing-chapter]: ./hir/attribute-parsing.md +### Note on adding new builtin attributes + +
+ +**Warning: Name resolution ambiguity potential when adding new builtin attributes** + +Please note that adding **new builtin attributes** (whose name is not reserved, i.e. a new builtin +attribute whose name does not start with `rustc`), even if *unstable*-gated, can introduce breakage +from name resolution ambiguity in stable code if (1) the stable code has a macro of the same name +which gets re-exported, or (2) or a proc-macro derive helper attribute of the same name. + +Typically, the builtin attributes probably has to start out as `#[rustc_foo]` instead of `#[foo]` to +avoid colliding with user-defined macros and proc-macro helper attributes. Then, prior to +stabilization, a rename to `#[foo]` should be done separately with a crater run to assess fallout, +with a deliberate breakage FCP proposal for T-lang to consider. + +Remember also that crater is *not* exhaustive and does not contain all existing stable code. + +See: +- [Built-in attributes are treated differently vs prelude attributes, unstable built-in attributes + can name-collide with stable macro, and built-in attributes can break back-compat + #134963](https://github.com/rust-lang/rust/issues/134963) and backlinks within this issue, + including design discussions on how to fix this kind of breakage hazard. +- [Broken build after updating: coverage is ambiguous; ambiguous because of a name conflict with a + builtin attribute #121157](https://github.com/rust-lang/rust/issues/121157). +- [Regression: align is ambiguous #143834](https://github.com/rust-lang/rust/issues/143834). +
+ ## 'Non-builtin'/'active' attributes These attributes are defined by a crate - either the standard library, or a proc-macro crate. From f36f2b96c6526b53eb3db56af6e8cfde596ff966 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:16:13 +0200 Subject: [PATCH 13/45] sembr src/rustdoc-internals/rustdoc-gui-test-suite.md --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index d18021bf278d9..e4909110d0aa6 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -5,7 +5,8 @@ This page is about the test suite named `rustdoc-gui` used to test the "GUI" of `rustdoc` (i.e., the HTML/JS/CSS as rendered in a browser). For other rustdoc-specific test suites, see [Rustdoc test suites]. -These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. +For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From 0d9c6a4e5a06df464a3187d613ac97e9d5d69406 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:17:11 +0200 Subject: [PATCH 14/45] overlong --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index e4909110d0aa6..bce0fe4b0dc54 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -6,7 +6,8 @@ This page is about the test suite named `rustdoc-gui` used to test the "GUI" of For other rustdoc-specific test suites, see [Rustdoc test suites]. These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. -For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +For information on how to write this form of test, +see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From c5659980e31f84cd0ea029fc33ebe52225b4bbbb Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:17:30 +0200 Subject: [PATCH 15/45] punctuation --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index bce0fe4b0dc54..951a541548b23 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -7,7 +7,7 @@ For other rustdoc-specific test suites, see [Rustdoc test suites]. These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. For information on how to write this form of test, -see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script]. [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From 54c2643a9bb0946123d8a8efe21c493f503c548f Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:18:02 +0200 Subject: [PATCH 16/45] sembr src/rustdoc-internals/search.md --- .../src/rustdoc-internals/search.md | 78 ++++++++----------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md index e88d2703e3a23..d2235e84073b9 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md @@ -1,16 +1,13 @@ # Rustdoc search -Rustdoc Search is two programs: `search_index.rs` -and `search.js`. The first generates a nasty JSON -file with a full list of items and function signatures +Rustdoc Search is two programs: `search_index.rs` and `search.js`. +The first generates a nasty JSON file with a full list of items and function signatures in the crates in the doc bundle, and the second reads -it, turns it into some in-memory structures, and -scans them linearly to search. +it, turns it into some in-memory structures, and scans them linearly to search. ## Search index format -`search.js` calls this Raw, because it turns it into -a more normal object tree after loading it. +`search.js` calls this Raw, because it turns it into a more normal object tree after loading it. For space savings, it's also written without newlines or spaces. ```json @@ -44,8 +41,7 @@ For space savings, it's also written without newlines or spaces. ] ``` -[`src/librustdoc/html/static/js/rustdoc.d.ts`] -defines an actual schema in a TypeScript `type`. +[`src/librustdoc/html/static/js/rustdoc.d.ts`] defines an actual schema in a TypeScript `type`. | Key | Name | Description | | --- | -------------------- | ------------ | @@ -75,10 +71,8 @@ It makes a lot of compromises: * The `rustdoc` compiler runs on one crate at a time, so each crate has an essentially separate search index. - It [merges] them by having each crate on one line - and looking at the first quoted string. -* Names in the search index are given - in their original case and with underscores. + It [merges] them by having each crate on one line and looking at the first quoted string. +* Names in the search index are given in their original case and with underscores. When the search index is loaded, `search.js` stores the original names for display, but also folds them to lowercase and strips underscores for search. @@ -156,10 +150,8 @@ for (i, entry) in search_index.iter().enumerate() { } ``` -This is valid because everything has a parent module -(even if it's just the crate itself), -and is easy to assemble because the rustdoc generator sorts by path -before serializing. +This is valid because everything has a parent module (even if it's just the crate itself), +and is easy to assemble because the rustdoc generator sorts by path before serializing. Doing this allows rustdoc to not only make the search index smaller, but reuse the same string representing the parent path across multiple in-memory items. @@ -233,21 +225,21 @@ work through a "sandwich workload" of three steps: Reducing the amount of data downloaded here will almost always increase latency, by delaying the decision of what to download behind other work and/or adding -data dependencies where something can't be downloaded without first downloading -something else. In this case, we can't start downloading descriptions until +data dependencies where something can't be downloaded without first downloading something else. +In this case, we can't start downloading descriptions until after the search is done, because that's what allows it to decide *which* descriptions to download (it needs to sort the results then truncate to 200). To do this, two columns are stored in the search index, building on both Roaring Bitmaps and on VLQ Hex. -* `e` is an index of **e**mpty descriptions. It's a [roaring bitmap] of - each item (the crate itself is item 0, the rest start at 1). +* `e` is an index of **e**mpty descriptions. + It's a [roaring bitmap] of each item (the crate itself is item 0, the rest start at 1). * `D` is a shard list, stored in [VLQ hex] as flat list of integers. Each integer gives you the number of descriptions in the shard. As the decoder walks the index, it checks if the description is empty. - if it's not, then it's in the "current" shard. When all items are - exhausted, it goes on to the next shard. + if it's not, then it's in the "current" shard. + When all items are exhausted, it goes on to the next shard. Inside each shard is a newline-delimited list of descriptions, wrapped in a JSONP-style function call. @@ -280,8 +272,7 @@ Because of zigzag encoding, `` ` `` is +0, `a` is -0 (which is not used), ## Searching by name -Searching by name works by looping through the search index -and running these functions on each: +Searching by name works by looping through the search index and running these functions on each: * [`editDistance`] is always used to determine a match (unless quotes are specified, which would use simple equality instead). @@ -295,20 +286,19 @@ and running these functions on each: If it returns anything other than -1, the result is added, even if `editDistance` exceeds its threshold, and the index is stored for ranking. -* [`checkPath`] is used if, and only if, a parent path is specified - in the query. For example, `vec` has no parent path, but `vec::vec` does. +* [`checkPath`] is used if, and only if, a parent path is specified in the query. + For example, `vec` has no parent path, but `vec::vec` does. Within checkPath, editDistance and indexOf are used, and the path query has its own heuristic threshold, too. If it's not within the threshold, the entry is rejected, even if the first two pass. - If it's within the threshold, the path distance is stored - for ranking. + If it's within the threshold, the path distance is stored for ranking. * [`checkType`] is used only if there's a type filter, - like the struct in `struct:vec`. If it fails, + like the struct in `struct:vec`. + If it fails, the entry is rejected. -If all four criteria pass -(plus the crate filter, which isn't technically part of the query), +If all four criteria pass (plus the crate filter, which isn't technically part of the query), the results are sorted by [`sortResults`]. [`editDistance`]: https://github.com/rust-lang/rust/blob/79b710c13968a1a48d94431d024d2b1677940866/src/librustdoc/html/static/js/search.js#L137 @@ -336,17 +326,18 @@ is going to match the same things `T, u32` matches (though rustdoc will detect this particular problem and warn about it). Then, when actually looping over each item, -the bloom filter will probably reject entries that don't have every -type mentioned in the query. +the bloom filter will probably reject entries that don't have every type mentioned in the query. For example, the bloom query allows a query of `i32 -> u32` to match a function with the type `i32, u32 -> bool`, but unification will reject it later. The unification filter ensures that: -* Bag semantics are respected. If you query says `i32, i32`, +* Bag semantics are respected. + If you query says `i32, i32`, then the function has to mention *two* i32s, not just one. -* Nesting semantics are respected. If your query says `vec