From cd061c73afab36f3ecd6df4c46064c56551117ab Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 11 Apr 2026 15:19:20 +0000 Subject: [PATCH 01/17] Extend `core::char`'s documentation of casing issues * Extend `core::char`'s documentation of casing issues * Fix typos * Fix typo Co-authored-by: GrigorenkoPV * Document maximum 3x character expansion This is guaranteed by Unicode. * Fix error in `str` casing method docs --- library/alloc/src/str.rs | 19 +++- library/core/src/char/methods.rs | 161 ++++++++++++++++++++++++++++--- 2 files changed, 161 insertions(+), 19 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index 8a3326c7d76a7..d7dd616fce776 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -335,13 +335,19 @@ impl str { /// Returns the lowercase equivalent of this string slice, as a new [`String`]. /// - /// 'Lowercase' is defined according to the terms of the Unicode Derived Core Property - /// `Lowercase`. + /// 'Lowercase' is defined according to the terms of + /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34432) + /// of the Unicode standard. /// /// Since some characters can expand into multiple characters when changing /// the case, this function returns a [`String`] instead of modifying the /// parameter in-place. /// + /// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent + /// casing of Greek sigma. However, like that method, it does not handle locale-specific + /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation + /// for more information. + /// /// # Examples /// /// Basic usage: @@ -426,13 +432,18 @@ impl str { /// Returns the uppercase equivalent of this string slice, as a new [`String`]. /// - /// 'Uppercase' is defined according to the terms of the Unicode Derived Core Property - /// `Uppercase`. + /// 'Uppercase' is defined according to the terms of + /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34431) + /// of the Unicode standard. /// /// Since some characters can expand into multiple characters when changing /// the case, this function returns a [`String`] instead of modifying the /// parameter in-place. /// + /// Like [`char::to_uppercase()`] this method does not handle language-specific + /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation + /// for more information. + /// /// # Examples /// /// Basic usage: diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 46d48afbf5a14..27567e8cd3c14 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -1149,13 +1149,14 @@ impl char { /// [ucd]: https://www.unicode.org/reports/tr44/ /// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt /// - /// If this `char` requires special considerations (e.g. multiple `char`s) the iterator yields - /// the `char`(s) given by [`SpecialCasing.txt`]. + /// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by + /// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3. /// /// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt /// /// This operation performs an unconditional mapping without tailoring. That is, the conversion - /// is independent of context and language. + /// is independent of context and language. See [below](#notes-on-context-and-locale) + /// for more information. /// /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in /// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion. @@ -1197,6 +1198,48 @@ impl char { /// // convert into themselves. /// assert_eq!('山'.to_lowercase().to_string(), "山"); /// ``` + /// # Notes on context and locale + /// + /// As stated earlier, this method does not take into account language or context. + /// Below is a non-exhaustive list of situations where this can be relevant. + /// If you need to handle locale-depedendent casing in your code, consider using + /// an external crate, like [`icu_casemap`](https://crates.io/crates/icu_casemap) + /// which is developed by Unicode. + /// + /// ## Greek sigma + /// + /// In Greek, the letter simga (uppercase Σ) has two lowercase forms: + /// ς which is used only at the end of a word, and σ which is used everywhere else. + /// `to_lowercase()` always uses the second form: + /// + /// ``` + /// assert_eq!('Σ'.to_lowercase().to_string(), "σ"); + /// ``` + /// + /// ## Turkish and Azeri I/ı/İ/i + /// + /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: + /// + /// * 'Dotless': I / ı, sometimes written ï + /// * 'Dotted': İ / i + /// + /// Note that the uppercase undotted 'I' is the same as the Latin. Therefore: + /// + /// ``` + /// let lower_i = 'I'.to_lowercase().to_string(); + /// ``` + /// + /// The value of `lower_i` here relies on the language of the text: if we're + /// in `en-US`, it should be `"i"`, but if we're in `tr-TR` or `az-AZ`, it should + /// be `"ı"`. `to_lowercase()` does not take this into account, and so: + /// + /// ``` + /// let lower_i = 'I'.to_lowercase().to_string(); + /// + /// assert_eq!(lower_i, "i"); + /// ``` + /// + /// holds across languages. #[must_use = "this returns the lowercased character as a new iterator, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] @@ -1209,8 +1252,10 @@ impl char { /// `char`s. /// /// This is usually, but not always, equivalent to the uppercase mapping - /// returned by [`Self::to_uppercase`]. Prefer this method when seeking to capitalize - /// Only The First Letter of a word, but use [`Self::to_uppercase`] for ALL CAPS. + /// returned by [`to_uppercase()`]. Prefer this method when seeking to capitalize + /// Only The First Letter of a word, but use [`to_uppercase()`] for ALL CAPS. + /// See [below](#difference-from-uppercase) for a thorough explanation + /// of the difference between the two methods. /// /// If this `char` does not have a titlecase mapping, the iterator yields the same `char`. /// @@ -1220,13 +1265,14 @@ impl char { /// [ucd]: https://www.unicode.org/reports/tr44/ /// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt /// - /// If this `char` requires special considerations (e.g. multiple `char`s) the iterator yields - /// the `char`(s) given by [`SpecialCasing.txt`]. + /// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by + /// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3. /// /// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt /// /// This operation performs an unconditional mapping without tailoring. That is, the conversion - /// is independent of context and language. + /// is independent of context and language. See [below](#note-on-locale) + /// for more information. /// /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in /// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion. @@ -1263,8 +1309,9 @@ impl char { /// ``` /// #![feature(titlecase)] /// assert_eq!('c'.to_titlecase().to_string(), "C"); + /// assert_eq!('ა'.to_titlecase().to_string(), "ა"); /// assert_eq!('dž'.to_titlecase().to_string(), "Dž"); - /// assert_eq!('ῼ'.to_titlecase().to_string(), "ῼ"); + /// assert_eq!('ᾨ'.to_titlecase().to_string(), "ᾨ"); /// /// // Sometimes the result is more than one character: /// assert_eq!('ß'.to_titlecase().to_string(), "Ss"); @@ -1274,8 +1321,78 @@ impl char { /// assert_eq!('山'.to_titlecase().to_string(), "山"); /// ``` /// + /// # Difference from uppercase + /// + /// Currently, there are three classes of characters where [`to_uppercase()`] + /// and `to_titlecase()` give different results: + /// + /// ## Georgian script + /// + /// Each letter in the modern Georgian alphabet can be written in one of two forms: + /// the typical lowercase-like "mkhedruli" form, and a variant uppercase-like "mtavruli" + /// form. However, unlike uppercase in most cased scripts, mtavruli is not typically used + /// to start sentences, denote proper nouns, or for any other purpose + /// in running text. It is instead confined to titles and headings, which are written entirely + /// in mtavruli. For this reason, [`to_uppercase()`] applied to a Georgian letter + /// will return the mtavruli form, but `to_titlecase()` will return the mkhedruli form. + /// + /// ``` + /// #![feature(titlecase)] + /// let ani = 'ა'; // First letter of the Georgian alphabet, in mkhedruli form + /// + /// // Titlecasing mkhedruli maps it to itself... + /// assert_eq!(ani.to_titlecase().to_string(), ani.to_string()); + /// + /// // but uppercasing it maps it to mtavruli + /// assert_eq!(ani.to_uppercase().to_string(), "Ა"); + /// ``` + /// + /// ## Compatibility digraphs for Latin-alphabet Serbo-Croatian + /// + /// The standard Latin alphabet for the Serbo-Croatian language + /// (Bosnian, Croatian, Montenegrin, and Serbian) contains + /// three digraphs: Dž, Lj, and Nj. These are usually represented as + /// two characters. However, for compatibility with older character sets, + /// Unicode includes single-character versions of these digraphs. + /// Each has a uppercase, titlecase, and lowercase version: + /// + /// - `'DŽ'`, `'Dž'`, `'dž'` + /// - `'LJ'`, `'Lj'`, `'lj'` + /// - `'NJ'`, `'Nj'`, `'nj'` + /// + /// Unicode additionally encodes a casing triad for the Dz digraph + /// without the caron: `'DZ'`, `'Dz'`, `'dz'`. + /// + /// ## Iota-subscritped Greek vowels + /// + /// In ancient Greek, the long vowels alpha (α), eta (η), and omega (ω) + /// were sometimes followed by an iota (ι), forming a diphthong. Over time, + /// the diphthong pronunciation was slowly lost, with the iota becoming mute. + /// Eventually, the ι disappeared from the spelling as well. + /// However, there remains a need to represent ancient texts faithfully. + /// + /// Modern editions of ancient Greek texts commonly use a reduced-sized + /// ι symbol to denote mute iotas, while distinguishing them from ιs + /// which continued to affect pronunciation. The exact standard differs + /// between different publications. Some render the mute ι below its associated + /// vowel (subscript), while others place it to the right of said vowel (adscript). + /// The interaction of mute ι symbols with casing also varies. + /// + /// The Unicode Standard, for its default casing rules, chose to make lowercase + /// Greek vowels with iota subscipt (e.g. `'ᾠ'`) titlecase to the uppercase vowel + /// with iota subscript (`'ᾨ'`) but uppercase to the uppercase vowel followed by + /// full-size uppercase iota (`"ὨΙ"`). This is just one convention among many + /// in common use, but it is the one Unicode settled on, + /// so it is what this method does also. + /// /// # Note on locale /// + /// As stated above, this method is locale-insensitive. + /// If you need locale support, consider using an external crate, + /// like [`icu_casemap`](https://crates.io/crates/icu_casemap) + /// which is developed by Unicode. A description of a common + /// locale-dependent casing issue follows: + /// /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: /// /// * 'Dotless': I / ı, sometimes written ï @@ -1300,6 +1417,8 @@ impl char { /// ``` /// /// holds across languages. + /// + /// [`to_uppercase()`]: Self::to_uppercase() #[must_use = "this returns the titlecased character as a new iterator, \ without modifying the original"] #[unstable(feature = "titlecase", issue = "153892")] @@ -1311,8 +1430,9 @@ impl char { /// Returns an iterator that yields the uppercase mapping of this `char` as one or more /// `char`s. /// - /// Prefer this method when converting a word into ALL CAPS, but consider [`Self::to_titlecase`] - /// instead if you seek to capitalize Only The First Letter. + /// Prefer this method when converting a word into ALL CAPS, but consider [`to_titlecase()`] + /// instead if you seek to capitalize Only The First Letter. See that method's documentation + /// for more information on the difference between the two. /// /// If this `char` does not have an uppercase mapping, the iterator yields the same `char`. /// @@ -1322,13 +1442,14 @@ impl char { /// [ucd]: https://www.unicode.org/reports/tr44/ /// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt /// - /// If this `char` requires special considerations (e.g. multiple `char`s) the iterator yields - /// the `char`(s) given by [`SpecialCasing.txt`]. + /// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by + /// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3. /// /// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt /// /// This operation performs an unconditional mapping without tailoring. That is, the conversion - /// is independent of context and language. + /// is independent of context and language. See [below](#note-on-locale) + /// for more information. /// /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in /// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion. @@ -1336,6 +1457,7 @@ impl char { /// [Unicode Standard]: https://www.unicode.org/versions/latest/ /// /// # Examples + /// /// `'ſt'` (U+FB05) is a single Unicode code point (a ligature) that maps to "ST" in uppercase. /// /// As an iterator: @@ -1363,11 +1485,12 @@ impl char { /// /// ``` /// assert_eq!('c'.to_uppercase().to_string(), "C"); + /// assert_eq!('ა'.to_uppercase().to_string(), "Ა"); /// assert_eq!('dž'.to_uppercase().to_string(), "DŽ"); /// /// // Sometimes the result is more than one character: /// assert_eq!('ſt'.to_uppercase().to_string(), "ST"); - /// assert_eq!('ῼ'.to_uppercase().to_string(), "ΩΙ"); + /// assert_eq!('ᾨ'.to_uppercase().to_string(), "ὨΙ"); /// /// // Characters that do not have both uppercase and lowercase /// // convert into themselves. @@ -1376,6 +1499,12 @@ impl char { /// /// # Note on locale /// + /// As stated above, this method is locale-insensitive. + /// If you need locale support, consider using an external crate, + /// like [`icu_casemap`](https://crates.io/crates/icu_casemap) + /// which is developed by Unicode. A description of a common + /// locale-dependent casing issue follows: + /// /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: /// /// * 'Dotless': I / ı, sometimes written ï @@ -1398,6 +1527,8 @@ impl char { /// ``` /// /// holds across languages. + /// + /// [`to_titlecase()`]: Self::to_titlecase() #[must_use = "this returns the uppercased character as a new iterator, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] From 96fb37ae6013ae97ca16a73ed9cf44b8b289fa94 Mon Sep 17 00:00:00 2001 From: lms0806 Date: Wed, 15 Apr 2026 12:52:23 +0900 Subject: [PATCH 02/17] add : new UI test --- ...owck-for-loop-deref-pattern-assignment.stderr | 5 +++-- .../borrowck_for_loop_pattern_assignment.rs | 9 +++++++++ .../borrowck_for_loop_pattern_assignment.stderr | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/ui/borrowck/borrowck_for_loop_pattern_assignment.rs create mode 100644 tests/ui/borrowck/borrowck_for_loop_pattern_assignment.stderr diff --git a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr index fa230134df555..3c4d0e966136d 100644 --- a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr +++ b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr @@ -8,8 +8,9 @@ LL | num *= 2; | help: consider making this binding mutable | -LL | for &(mut num) num in nums { - | +++++++++ +LL - for &num in nums { +LL + for &(mut num) in nums { + | error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck_for_loop_pattern_assignment.rs b/tests/ui/borrowck/borrowck_for_loop_pattern_assignment.rs new file mode 100644 index 0000000000000..93cbea820861d --- /dev/null +++ b/tests/ui/borrowck/borrowck_for_loop_pattern_assignment.rs @@ -0,0 +1,9 @@ +//! regression test for + +fn main() { + let nums: [u32; 3] = [1, 2, 3]; + for num in nums { + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + println!("{num}"); + } +} diff --git a/tests/ui/borrowck/borrowck_for_loop_pattern_assignment.stderr b/tests/ui/borrowck/borrowck_for_loop_pattern_assignment.stderr new file mode 100644 index 0000000000000..1dffe2b5e6436 --- /dev/null +++ b/tests/ui/borrowck/borrowck_for_loop_pattern_assignment.stderr @@ -0,0 +1,16 @@ +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck_for_loop_pattern_assignment.rs:6:9 + | +LL | for num in nums { + | --- first assignment to `num` +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL | for mut num in nums { + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0384`. From be3f77c7fffa997849938f5cb4cd1a2db3598f0e Mon Sep 17 00:00:00 2001 From: lms0806 Date: Wed, 15 Apr 2026 13:23:08 +0900 Subject: [PATCH 03/17] resolve : addressing incorrect recommendation methods --- .../src/diagnostics/conflict_errors.rs | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index b8eefa4dd0714..07331a99b1752 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -4022,23 +4022,74 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { if let Some(decl) = local_decl && decl.can_be_made_mutable() { - let is_for_loop = matches!( - decl.local_info(), - LocalInfo::User(BindingForm::Var(VarBindingForm { - opt_match_place: Some((_, match_span)), - .. - })) if matches!(match_span.desugaring_kind(), Some(DesugaringKind::ForLoop)) - ); - let message = if is_for_loop + let mut is_for_loop = false; + let mut is_ref_pattern = false; + if let LocalInfo::User(BindingForm::Var(VarBindingForm { + opt_match_place: Some((_, match_span)), + .. + })) = *decl.local_info() + { + if matches!(match_span.desugaring_kind(), Some(DesugaringKind::ForLoop)) { + is_for_loop = true; + + if let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(self.mir_def_id()) { + struct RefPatternFinder<'tcx> { + tcx: TyCtxt<'tcx>, + binding_span: Span, + is_ref_pattern: bool, + } + + impl<'tcx> Visitor<'tcx> for RefPatternFinder<'tcx> { + type NestedFilter = OnlyBodies; + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.tcx + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + if !self.is_ref_pattern + && let hir::PatKind::Binding(_, _, ident, _) = pat.kind + && ident.span == self.binding_span + { + self.is_ref_pattern = + self.tcx.hir_parent_iter(pat.hir_id).any(|(_, node)| { + matches!( + node, + hir::Node::Pat(hir::Pat { + kind: hir::PatKind::Ref(..), + .. + }) + ) + }); + } + hir::intravisit::walk_pat(self, pat); + } + } + + let mut finder = RefPatternFinder { + tcx: self.infcx.tcx, + binding_span: decl.source_info.span, + is_ref_pattern: false, + }; + + finder.visit_body(body); + is_ref_pattern = finder.is_ref_pattern; + } + } + } + + let (span, message) = if is_for_loop + && is_ref_pattern && let Ok(binding_name) = self.infcx.tcx.sess.source_map().span_to_snippet(decl.source_info.span) { - format!("(mut {}) ", binding_name) + (decl.source_info.span, format!("(mut {})", binding_name)) } else { - "mut ".to_string() + (decl.source_info.span.shrink_to_lo(), "mut ".to_string()) }; + err.span_suggestion_verbose( - decl.source_info.span.shrink_to_lo(), + span, "consider making this binding mutable", message, Applicability::MachineApplicable, From 6236ddec5a47c2dc05dd98e9373ed6ca7d42d850 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sat, 4 Apr 2026 14:49:19 +0200 Subject: [PATCH 04/17] Remove AttributeSafety from BUILTIN_ATTRIBUTES --- .../rustc_attr_parsing/src/attributes/cfg.rs | 2 + .../src/attributes/cfg_select.rs | 2 + .../src/attributes/codegen_attrs.rs | 6 + .../src/attributes/link_attrs.rs | 5 + .../rustc_attr_parsing/src/attributes/mod.rs | 20 + compiler/rustc_attr_parsing/src/context.rs | 4 +- compiler/rustc_attr_parsing/src/interface.rs | 35 +- compiler/rustc_attr_parsing/src/lib.rs | 1 + compiler/rustc_attr_parsing/src/safety.rs | 24 +- compiler/rustc_builtin_macros/src/cfg.rs | 4 +- compiler/rustc_expand/src/config.rs | 5 +- compiler/rustc_expand/src/expand.rs | 5 +- compiler/rustc_feature/src/builtin_attrs.rs | 467 ++++-------------- compiler/rustc_feature/src/lib.rs | 2 +- 14 files changed, 181 insertions(+), 401 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 6410d0c0cf702..84c83be8b4a5d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -19,6 +19,7 @@ use rustc_session::parse::{ParseSess, feature_err}; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; use thin_vec::ThinVec; +use crate::attributes::AttributeSafety; use crate::context::{AcceptContext, ShouldEmit, Stage}; use crate::parser::{ AllowExprMetavar, ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser, @@ -410,6 +411,7 @@ fn parse_cfg_attr_internal<'a>( attribute.style, AttrPath { segments: attribute.path().into_boxed_slice(), span: attribute.span }, Some(attribute.get_normal_item().unsafety), + AttributeSafety::Normal, ParsedDescription::Attribute, pred_span, lint_node_id, diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 4ff224006ca89..918fd0a4582b7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -12,6 +12,7 @@ use rustc_session::Session; use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; +use crate::attributes::AttributeSafety; use crate::parser::{AllowExprMetavar, MetaItemOrLitParser}; use crate::{AttributeParser, ParsedDescription, ShouldEmit, errors, parse_cfg_entry}; @@ -105,6 +106,7 @@ pub fn parse_cfg_select( AttrStyle::Inner, AttrPath { segments: vec![sym::cfg_select].into_boxed_slice(), span: cfg_span }, None, + AttributeSafety::Normal, ParsedDescription::Macro, cfg_span, lint_node_id, diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 73b2727fdab0a..53d02d09bb514 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -1,7 +1,9 @@ use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy}; use rustc_session::parse::feature_err; +use rustc_span::edition::Edition::Edition2024; use super::prelude::*; +use crate::attributes::AttributeSafety; use crate::session_diagnostics::{ NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, @@ -103,6 +105,7 @@ pub(crate) struct ExportNameParser; impl SingleAttributeParser for ExportNameParser { const PATH: &[rustc_span::Symbol] = &[sym::export_name]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Static), Allow(Target::Fn), @@ -220,6 +223,7 @@ impl AttributeParser for NakedParser { this.span = Some(cx.attr_span); } })]; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), @@ -340,6 +344,7 @@ pub(crate) struct NoMangleParser; impl NoArgsAttributeParser for NoMangleParser { const PATH: &[Symbol] = &[sym::no_mangle]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::Fn), Allow(Target::Static), @@ -542,6 +547,7 @@ pub(crate) struct ForceTargetFeatureParser; impl CombineAttributeParser for ForceTargetFeatureParser { type Item = (Symbol, Span); const PATH: &[Symbol] = &[sym::force_target_feature]; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const CONVERT: ConvertFn = |items, span| AttributeKind::TargetFeature { features: items, attr_span: span, diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 8aa7759daa043..b6ba7f9e21d49 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -5,11 +5,13 @@ use rustc_hir::attrs::*; use rustc_session::Session; use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT; use rustc_session::parse::feature_err; +use rustc_span::edition::Edition::Edition2024; use rustc_span::kw; use rustc_target::spec::{Arch, BinaryFormat}; use super::prelude::*; use super::util::parse_single_integer; +use crate::attributes::AttributeSafety; use crate::attributes::cfg::parse_cfg_entry; use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, @@ -463,6 +465,7 @@ pub(crate) struct LinkSectionParser; impl SingleAttributeParser for LinkSectionParser { const PATH: &[Symbol] = &[sym::link_section]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::Static), Allow(Target::Fn), @@ -508,6 +511,7 @@ pub(crate) struct FfiConstParser; impl NoArgsAttributeParser for FfiConstParser { const PATH: &[Symbol] = &[sym::ffi_const]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiConst; } @@ -516,6 +520,7 @@ pub(crate) struct FfiPureParser; impl NoArgsAttributeParser for FfiPureParser { const PATH: &[Symbol] = &[sym::ffi_pure]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index d7f64ff2319a9..ad5a541d3a25d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -18,6 +18,7 @@ use std::marker::PhantomData; use rustc_feature::{AttributeTemplate, template}; use rustc_hir::attrs::AttributeKind; +use rustc_span::edition::Edition; use rustc_span::{Span, Symbol}; use thin_vec::ThinVec; @@ -97,6 +98,7 @@ pub(crate) trait AttributeParser: Default + 'static { /// If an attribute has this symbol, the `accept` function will be called on it. const ATTRIBUTES: AcceptMapping; const ALLOWED_TARGETS: AllowedTargets; + const SAFETY: AttributeSafety = AttributeSafety::Normal; /// The parser has gotten a chance to accept the attributes on an item, /// here it can produce an attribute. @@ -127,6 +129,7 @@ pub(crate) trait SingleAttributeParser: 'static { /// Configures what to do when when the same attribute is /// applied more than once on the same syntax node. const ON_DUPLICATE: OnDuplicate; + const SAFETY: AttributeSafety = AttributeSafety::Normal; const ALLOWED_TARGETS: AllowedTargets; @@ -165,6 +168,7 @@ impl, S: Stage> AttributeParser for Single }, )]; const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; + const SAFETY: AttributeSafety = T::SAFETY; fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { Some(self.1?.0) @@ -217,6 +221,18 @@ impl OnDuplicate { } } +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum AttributeSafety { + /// Normal attribute that does not need `#[unsafe(...)]` + Normal, + /// Unsafe attribute that requires safety obligations to be discharged. + /// + /// An error is emitted when `#[unsafe(...)]` is omitted, except when the attribute's edition + /// is less than the one stored in `unsafe_since`. This handles attributes that were safe in + /// earlier editions, but become unsafe in later ones. + Unsafe { unsafe_since: Option }, +} + /// An even simpler version of [`SingleAttributeParser`]: /// now automatically check that there are no arguments provided to the attribute. /// @@ -226,6 +242,7 @@ pub(crate) trait NoArgsAttributeParser: 'static { const PATH: &[Symbol]; const ON_DUPLICATE: OnDuplicate; const ALLOWED_TARGETS: AllowedTargets; + const SAFETY: AttributeSafety = AttributeSafety::Normal; /// Create the [`AttributeKind`] given attribute's [`Span`]. const CREATE: fn(Span) -> AttributeKind; @@ -242,6 +259,7 @@ impl, S: Stage> Default for WithoutArgs { impl, S: Stage> SingleAttributeParser for WithoutArgs { const PATH: &[Symbol] = T::PATH; const ON_DUPLICATE: OnDuplicate = T::ON_DUPLICATE; + const SAFETY: AttributeSafety = T::SAFETY; const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const TEMPLATE: AttributeTemplate = template!(Word); @@ -271,6 +289,7 @@ pub(crate) trait CombineAttributeParser: 'static { /// For example, individual representations from `#[repr(...)]` attributes into an `AttributeKind::Repr(x)`, /// where `x` is a vec of these individual reprs. const CONVERT: ConvertFn; + const SAFETY: AttributeSafety = AttributeSafety::Normal; const ALLOWED_TARGETS: AllowedTargets; @@ -312,6 +331,7 @@ impl, S: Stage> AttributeParser for Combine) -> Option { if let Some(first_span) = self.first_span { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 3f722bef5bf35..647c816247bf9 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -59,7 +59,7 @@ use crate::attributes::stability::*; use crate::attributes::test_attrs::*; use crate::attributes::traits::*; use crate::attributes::transparency::*; -use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs}; +use crate::attributes::{AttributeParser as _, AttributeSafety, Combine, Single, WithoutArgs}; use crate::parser::{ArgParser, MetaItemOrLitParser, RefPathParser}; use crate::session_diagnostics::{ AttributeParseError, AttributeParseErrorReason, AttributeParseErrorSuggestions, @@ -76,6 +76,7 @@ pub(super) struct GroupTypeInnerAccept { pub(super) template: AttributeTemplate, pub(super) accept_fn: AcceptFn, pub(super) allowed_targets: AllowedTargets, + pub(super) safety: AttributeSafety, pub(super) finalizer: FinalizeFn, } @@ -126,6 +127,7 @@ macro_rules! attribute_parsers { accept_fn(s, cx, args) }) }), + safety: <$names as crate::attributes::AttributeParser<$stage>>::SAFETY, allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS, finalizer: Box::new(|cx| { let state = STATE_OBJECT.take(); diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 68016d81c954c..85e714a1a917c 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -12,6 +12,7 @@ use rustc_session::Session; use rustc_session::lint::LintId; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; +use crate::attributes::AttributeSafety; use crate::context::{AcceptContext, FinalizeContext, FinalizeFn, SharedContext, Stage}; use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState}; use crate::parser::{AllowExprMetavar, ArgParser, PathParser, RefPathParser}; @@ -135,6 +136,7 @@ impl<'sess> AttributeParser<'sess, Early> { parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser) -> Option, template: &AttributeTemplate, allow_expr_metavar: AllowExprMetavar, + expected_safety: AttributeSafety, ) -> Option { let ast::AttrKind::Normal(normal_attr) = &attr.kind else { panic!("parse_single called on a doc attr") @@ -157,6 +159,7 @@ impl<'sess> AttributeParser<'sess, Early> { attr.style, path, Some(normal_attr.item.unsafety), + expected_safety, ParsedDescription::Attribute, target_span, target_node_id, @@ -178,6 +181,7 @@ impl<'sess> AttributeParser<'sess, Early> { attr_style: AttrStyle, attr_path: AttrPath, attr_safety: Option, + expected_safety: AttributeSafety, parsed_description: ParsedDescription, target_span: Span, target_node_id: NodeId, @@ -199,7 +203,13 @@ impl<'sess> AttributeParser<'sess, Early> { sess.psess.buffer_lint(lint_id.lint, span, target_node_id, kind) }; if let Some(safety) = attr_safety { - parser.check_attribute_safety(&attr_path, inner_span, safety, &mut emit_lint) + parser.check_attribute_safety( + &attr_path, + inner_span, + safety, + expected_safety, + &mut emit_lint, + ) } let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext { shared: SharedContext { @@ -314,17 +324,18 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { } }; - self.check_attribute_safety( - &attr_path, - lower_span(n.item.span()), - n.item.unsafety, - &mut emit_lint, - ); - let parts = n.item.path.segments.iter().map(|seg| seg.ident.name).collect::>(); if let Some(accept) = S::parsers().accepters.get(parts.as_slice()) { + self.check_attribute_safety( + &attr_path, + lower_span(n.item.span()), + n.item.unsafety, + accept.safety, + &mut emit_lint, + ); + let Some(args) = ArgParser::from_attr_args( args, &parts, @@ -397,6 +408,14 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { span: attr_span, }; + self.check_attribute_safety( + &attr_path, + lower_span(n.item.span()), + n.item.unsafety, + AttributeSafety::Normal, + &mut emit_lint, + ); + if !matches!(self.stage.should_emit(), ShouldEmit::Nothing) && target == Target::Crate { diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 93eb5a0c3ab73..1b08ed3c49b78 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -106,6 +106,7 @@ mod session_diagnostics; mod target_checking; pub mod validate_attr; +pub use attributes::AttributeSafety; pub use attributes::cfg::{ CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg, parse_cfg_attr, parse_cfg_entry, }; diff --git a/compiler/rustc_attr_parsing/src/safety.rs b/compiler/rustc_attr_parsing/src/safety.rs index 262c9c7723eeb..26212ee5f4ca2 100644 --- a/compiler/rustc_attr_parsing/src/safety.rs +++ b/compiler/rustc_attr_parsing/src/safety.rs @@ -1,12 +1,12 @@ use rustc_ast::Safety; use rustc_errors::MultiSpan; -use rustc_feature::{AttributeSafety, BUILTIN_ATTRIBUTE_MAP}; use rustc_hir::AttrPath; use rustc_hir::lints::AttributeLintKind; use rustc_session::lint::LintId; use rustc_session::lint::builtin::UNSAFE_ATTR_OUTSIDE_UNSAFE; use rustc_span::Span; +use crate::attributes::AttributeSafety; use crate::context::Stage; use crate::{AttributeParser, ShouldEmit}; @@ -16,28 +16,23 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { attr_path: &AttrPath, attr_span: Span, attr_safety: Safety, + expected_safety: AttributeSafety, emit_lint: &mut impl FnMut(LintId, MultiSpan, AttributeLintKind), ) { if matches!(self.stage.should_emit(), ShouldEmit::Nothing) { return; } - let name = (attr_path.segments.len() == 1).then_some(attr_path.segments[0]); - - // FIXME: We should retrieve this information from the attribute parsers instead of from `BUILTIN_ATTRIBUTE_MAP` - let builtin_attr_info = name.and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name)); - let builtin_attr_safety = builtin_attr_info.map(|x| x.safety); - - match (builtin_attr_safety, attr_safety) { + match (expected_safety, attr_safety) { // - Unsafe builtin attribute // - User wrote `#[unsafe(..)]`, which is permitted on any edition - (Some(AttributeSafety::Unsafe { .. }), Safety::Unsafe(..)) => { + (AttributeSafety::Unsafe { .. }, Safety::Unsafe(..)) => { // OK } // - Unsafe builtin attribute // - User did not write `#[unsafe(..)]` - (Some(AttributeSafety::Unsafe { unsafe_since }), Safety::Default) => { + (AttributeSafety::Unsafe { unsafe_since }, Safety::Default) => { let path_span = attr_path.span; // If the `attr_item`'s span is not from a macro, then just suggest @@ -96,7 +91,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { // - Normal builtin attribute // - Writing `#[unsafe(..)]` is not permitted on normal builtin attributes - (None | Some(AttributeSafety::Normal), Safety::Unsafe(unsafe_span)) => { + (AttributeSafety::Normal, Safety::Unsafe(unsafe_span)) => { self.stage.emit_err( self.sess, crate::session_diagnostics::InvalidAttrUnsafe { @@ -108,14 +103,11 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { // - Normal builtin attribute // - No explicit `#[unsafe(..)]` written. - (None | Some(AttributeSafety::Normal), Safety::Default) => { + (AttributeSafety::Normal, Safety::Default) => { // OK } - ( - Some(AttributeSafety::Unsafe { .. } | AttributeSafety::Normal) | None, - Safety::Safe(..), - ) => { + (_, Safety::Safe(..)) => { self.sess.dcx().span_delayed_bug( attr_span, "`check_attribute_safety` does not expect `Safety::Safe` on attributes", diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index c4a458089f2d2..2872cff0fdc7a 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -6,7 +6,8 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrStyle, token}; use rustc_attr_parsing::parser::{AllowExprMetavar, MetaItemOrLitParser}; use rustc_attr_parsing::{ - self as attr, AttributeParser, CFG_TEMPLATE, ParsedDescription, ShouldEmit, parse_cfg_entry, + self as attr, AttributeParser, AttributeSafety, CFG_TEMPLATE, ParsedDescription, ShouldEmit, + parse_cfg_entry, }; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; use rustc_hir::attrs::CfgEntry; @@ -53,6 +54,7 @@ fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result StripUnconfigured<'a> { parse_cfg, &CFG_TEMPLATE, AllowExprMetavar::Yes, + AttributeSafety::Normal, ) else { // Cfg attribute was not parsable, give up return EvalConfigResult::True; diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 5901f318ff3a9..804d3c02b413d 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -15,8 +15,8 @@ use rustc_ast::{ use rustc_ast_pretty::pprust; use rustc_attr_parsing::parser::AllowExprMetavar; use rustc_attr_parsing::{ - AttributeParser, CFG_TEMPLATE, Early, EvalConfigResult, ShouldEmit, eval_config_entry, - parse_cfg, validate_attr, + AttributeParser, AttributeSafety, CFG_TEMPLATE, Early, EvalConfigResult, ShouldEmit, + eval_config_entry, parse_cfg, validate_attr, }; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -2331,6 +2331,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { parse_cfg, &CFG_TEMPLATE, AllowExprMetavar::Yes, + AttributeSafety::Normal, ) else { // Cfg attribute was not parsable, give up return EvalConfigResult::True; diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 1c1bca0cbc3cf..144c9f6d0c4dc 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -5,7 +5,6 @@ use std::sync::LazyLock; use AttributeGate::*; use rustc_data_structures::fx::FxHashMap; use rustc_hir::AttrStyle; -use rustc_span::edition::Edition; use rustc_span::{Symbol, sym}; use crate::Features; @@ -67,23 +66,6 @@ pub fn find_gated_cfg(pred: impl Fn(Symbol) -> bool) -> Option<&'static GatedCfg GATED_CFGS.iter().find(|(cfg_sym, ..)| pred(*cfg_sym)) } -// If you change this, please modify `src/doc/unstable-book` as well. You must -// move that documentation into the relevant place in the other docs, and -// remove the chapter on the flag. - -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum AttributeSafety { - /// Normal attribute that does not need `#[unsafe(...)]` - Normal, - - /// Unsafe attribute that requires safety obligations to be discharged. - /// - /// An error is emitted when `#[unsafe(...)]` is omitted, except when the attribute's edition - /// is less than the one stored in `unsafe_since`. This handles attributes that were safe in - /// earlier editions, but become unsafe in later ones. - Unsafe { unsafe_since: Option }, -} - #[derive(Clone, Debug, Copy)] pub enum AttributeGate { /// A gated attribute which requires a feature gate to be enabled. @@ -205,54 +187,15 @@ macro_rules! template { } macro_rules! ungated { - (unsafe($edition:ident) $attr:ident $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - safety: AttributeSafety::Unsafe { unsafe_since: Some(Edition::$edition) }, - gate: Ungated, - } - }; - (unsafe $attr:ident $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - safety: AttributeSafety::Unsafe { unsafe_since: None }, - gate: Ungated, - } - }; ($attr:ident $(,)?) => { - BuiltinAttribute { name: sym::$attr, safety: AttributeSafety::Normal, gate: Ungated } + BuiltinAttribute { name: sym::$attr, gate: Ungated } }; } macro_rules! gated { - (unsafe $attr:ident, $gate:ident, $message:expr $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - safety: AttributeSafety::Unsafe { unsafe_since: None }, - gate: Gated { - feature: sym::$gate, - message: $message, - check: Features::$gate, - notes: &[], - }, - } - }; - (unsafe $attr:ident, $message:expr $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - safety: AttributeSafety::Unsafe { unsafe_since: None }, - gate: Gated { - feature: sym::$attr, - message: $message, - check: Features::$attr, - notes: &[], - }, - } - }; ($attr:ident, $gate:ident, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, - safety: AttributeSafety::Normal, gate: Gated { feature: sym::$gate, message: $message, @@ -264,7 +207,6 @@ macro_rules! gated { ($attr:ident, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, - safety: AttributeSafety::Normal, gate: Gated { feature: sym::$attr, message: $message, @@ -289,7 +231,6 @@ macro_rules! rustc_attr { ($attr:ident $(, $notes:expr)* $(,)?) => { BuiltinAttribute { name: sym::$attr, - safety: AttributeSafety::Normal, gate: Gated { feature: sym::rustc_attrs, message: "use of an internal attribute", @@ -299,7 +240,7 @@ macro_rules! rustc_attr { stringify!($attr), "]` attribute is an internal implementation detail that will never be stable"), $($notes),* - ] + ] }, } }; @@ -313,7 +254,6 @@ macro_rules! experimental { pub struct BuiltinAttribute { pub name: Symbol, - pub safety: AttributeSafety, pub gate: AttributeGate, } @@ -348,10 +288,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ungated!(forbid), ungated!(deny), ungated!(must_use), - gated!( - must_not_suspend, - experimental!(must_not_suspend) - ), + gated!(must_not_suspend, experimental!(must_not_suspend)), ungated!(deprecated), // Crate properties: @@ -366,222 +303,103 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity gated!(rustc_align, fn_align, experimental!(rustc_align)), gated!(rustc_align_static, static_align, experimental!(rustc_align_static)), - ungated!( - unsafe(Edition2024) export_name, - ), - ungated!( - unsafe(Edition2024) link_section, - ), - ungated!( - unsafe(Edition2024) no_mangle, - ), - ungated!( - used, - ), - ungated!( - link_ordinal, - ), - ungated!( - unsafe naked, - ), + ungated!(export_name), + ungated!(link_section), + ungated!(no_mangle), + ungated!(used), + ungated!(link_ordinal), + ungated!(naked), // See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details. - rustc_attr!( - rustc_pass_indirectly_in_non_rustic_abis, - "types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic ABIs" - ), + rustc_attr!(rustc_pass_indirectly_in_non_rustic_abis, "types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic ABIs"), // Limits: - ungated!( - recursion_limit, - ), - ungated!( - type_length_limit, - ), - gated!( - move_size_limit, - large_assignments, experimental!(move_size_limit) - ), + ungated!(recursion_limit), + ungated!(type_length_limit), + gated!(move_size_limit, large_assignments, experimental!(move_size_limit)), // Entry point: - ungated!( - no_main, - ), + ungated!(no_main), // Modules, prelude, and resolution: - ungated!( - path, - ), - ungated!( - no_std, - ), - ungated!( - no_implicit_prelude, - ), - ungated!( - non_exhaustive, - ), + ungated!(path), + ungated!(no_std), + ungated!(no_implicit_prelude), + ungated!(non_exhaustive), // Runtime - ungated!( - windows_subsystem, - ), - ungated!( // RFC 2070 - panic_handler, - ), + ungated!(windows_subsystem), + ungated!(panic_handler), // RFC 2070 // Code generation: - ungated!( - inline, - ), - ungated!( - cold, - ), - ungated!( - no_builtins, - ), - ungated!( - target_feature, - ), - ungated!( - track_caller, - ), - ungated!( - instruction_set, - ), - gated!( - unsafe force_target_feature, - effective_target_features, experimental!(force_target_feature) - ), - gated!( - sanitize, - sanitize, experimental!(sanitize), - ), - gated!( - coverage, - coverage_attribute, experimental!(coverage) - ), - - ungated!( - doc, - ), + ungated!(inline), + ungated!(cold), + ungated!(no_builtins), + ungated!(target_feature), + ungated!(track_caller), + ungated!(instruction_set), + gated!(force_target_feature, effective_target_features, experimental!(force_target_feature)), + gated!(sanitize, sanitize, experimental!(sanitize)), + gated!(coverage, coverage_attribute, experimental!(coverage)), + + ungated!(doc), // Debugging - ungated!( - debugger_visualizer, - ), - ungated!( - collapse_debuginfo, - ), + ungated!(debugger_visualizer), + ungated!(collapse_debuginfo), // ========================================================================== // Unstable attributes: // ========================================================================== // Linking: - gated!( - export_stable, - experimental!(export_stable) - ), + gated!(export_stable, experimental!(export_stable)), // Testing: - gated!( - test_runner, - custom_test_frameworks, - "custom test frameworks are an unstable feature", - ), + gated!(test_runner, custom_test_frameworks, "custom test frameworks are an unstable feature"), - gated!( - reexport_test_harness_main, - custom_test_frameworks, - "custom test frameworks are an unstable feature", - ), + gated!(reexport_test_harness_main, custom_test_frameworks, "custom test frameworks are an unstable feature"), // RFC #1268 - gated!( - marker, - marker_trait_attr, experimental!(marker) - ), - gated!( - thread_local, - "`#[thread_local]` is an experimental feature, and does not currently handle destructors", - ), - gated!( - no_core, - experimental!(no_core) - ), + gated!(marker, marker_trait_attr, experimental!(marker)), + gated!(thread_local, "`#[thread_local]` is an experimental feature, and does not currently handle destructors"), + gated!(no_core, experimental!(no_core)), // RFC 2412 - gated!( - optimize, - optimize_attribute, experimental!(optimize) - ), + gated!(optimize, optimize_attribute, experimental!(optimize)), - gated!( - unsafe ffi_pure, - experimental!(ffi_pure) - ), - gated!( - unsafe ffi_const, - experimental!(ffi_const) - ), - gated!( - register_tool, - experimental!(register_tool), - ), + gated!(ffi_pure, experimental!(ffi_pure)), + gated!(ffi_const, experimental!(ffi_const)), + gated!(register_tool, experimental!(register_tool)), // `#[cfi_encoding = ""]` - gated!( - cfi_encoding, - experimental!(cfi_encoding) - ), + gated!(cfi_encoding, experimental!(cfi_encoding)), // `#[coroutine]` attribute to be applied to closures to make them coroutines instead - gated!( - coroutine, - coroutines, experimental!(coroutine) - ), + gated!(coroutine, coroutines, experimental!(coroutine)), // RFC 3543 // `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` - gated!( - patchable_function_entry, - experimental!(patchable_function_entry) - ), + gated!(patchable_function_entry, experimental!(patchable_function_entry)), // The `#[loop_match]` and `#[const_continue]` attributes are part of the // lang experiment for RFC 3720 tracked in: // // - https://github.com/rust-lang/rust/issues/132306 - gated!( - const_continue, - loop_match, experimental!(const_continue) - ), - gated!( - loop_match, - loop_match, experimental!(loop_match) - ), + gated!(const_continue, loop_match, experimental!(const_continue)), + gated!(loop_match, loop_match, experimental!(loop_match)), // The `#[pin_v2]` attribute is part of the `pin_ergonomics` experiment // that allows structurally pinning, tracked in: // // - https://github.com/rust-lang/rust/issues/130494 - gated!( - pin_v2, - pin_ergonomics, experimental!(pin_v2), - ), + gated!(pin_v2, pin_ergonomics, experimental!(pin_v2)), // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== - ungated!( - feature, - ), + ungated!(feature), // DuplicatesOk since it has its own validation - ungated!( - stable, - ), - ungated!( - unstable, - ), + ungated!(stable), + ungated!(unstable), ungated!(unstable_feature_bound), ungated!(unstable_removed), ungated!(rustc_const_unstable), @@ -636,24 +454,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Internal attributes: Runtime related: // ========================================================================== - rustc_attr!( - rustc_allocator, - ), - rustc_attr!( - rustc_nounwind, - ), - rustc_attr!( - rustc_reallocator, - ), - rustc_attr!( - rustc_deallocator, - ), - rustc_attr!( - rustc_allocator_zeroed, - ), - rustc_attr!( - rustc_allocator_zeroed_variant, - ), + rustc_attr!(rustc_allocator), + rustc_attr!(rustc_nounwind), + rustc_attr!(rustc_reallocator), + rustc_attr!(rustc_deallocator), + rustc_attr!(rustc_allocator_zeroed), + rustc_attr!(rustc_allocator_zeroed_variant), gated!( default_lib_allocator, allocator_internals, experimental!(default_lib_allocator), @@ -720,49 +526,31 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_on_unimplemented, "see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute" ), - rustc_attr!( - rustc_confusables, - ), + rustc_attr!(rustc_confusables), // Enumerates "identity-like" conversion methods to suggest on type mismatch. - rustc_attr!( - rustc_conversion_suggestion, - ), + rustc_attr!(rustc_conversion_suggestion), // Prevents field reads in the marked trait or method to be considered // during dead code analysis. - rustc_attr!( - rustc_trivial_field_reads, - ), + rustc_attr!(rustc_trivial_field_reads), // Used by the `rustc::potential_query_instability` lint to warn methods which // might not be stable during incremental compilation. - rustc_attr!( - rustc_lint_query_instability, - ), + rustc_attr!(rustc_lint_query_instability), // Used by the `rustc::untracked_query_information` lint to warn methods which // might not be stable during incremental compilation. - rustc_attr!( - rustc_lint_untracked_query_information, - ), + rustc_attr!(rustc_lint_untracked_query_information), // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions` // types (as well as any others in future). - rustc_attr!( - rustc_lint_opt_ty, - ), + rustc_attr!(rustc_lint_opt_ty), // Used by the `rustc::bad_opt_access` lint on fields // types (as well as any others in future). - rustc_attr!( - rustc_lint_opt_deny_field_access, - ), + rustc_attr!(rustc_lint_opt_deny_field_access), // ========================================================================== // Internal attributes, Const related: // ========================================================================== - rustc_attr!( - rustc_promotable, - ), - rustc_attr!( - rustc_legacy_const_generics, - ), + rustc_attr!(rustc_promotable), + rustc_attr!(rustc_legacy_const_generics), // Do not const-check this function's body. It will always get replaced during CTFE via `hook_special_const_fn`. rustc_attr!( rustc_do_not_const_check, @@ -873,7 +661,6 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ BuiltinAttribute { name: sym::rustc_diagnostic_item, - safety: AttributeSafety::Normal, gate: Gated { feature: sym::rustc_attrs, message: "use of an internal attribute", @@ -961,99 +748,39 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== rustc_attr!(TEST, rustc_effective_visibility), - rustc_attr!( - TEST, rustc_dump_inferred_outlives, - ), - rustc_attr!( - TEST, rustc_capture_analysis, - ), - rustc_attr!( - TEST, rustc_insignificant_dtor, - ), - rustc_attr!( - TEST, rustc_no_implicit_bounds, - ), - rustc_attr!( - TEST, rustc_strict_coherence, - ), - rustc_attr!( - TEST, rustc_dump_variances, - ), - rustc_attr!( - TEST, rustc_dump_variances_of_opaques, - ), - rustc_attr!( - TEST, rustc_dump_hidden_type_of_opaques, - ), - rustc_attr!( - TEST, rustc_dump_layout, - ), - rustc_attr!( - TEST, rustc_abi, - ), - rustc_attr!( - TEST, rustc_regions, - ), - rustc_attr!( - TEST, rustc_delayed_bug_from_inside_query, - ), - rustc_attr!( - TEST, rustc_dump_user_args, - ), - rustc_attr!( - TEST, rustc_evaluate_where_clauses, - ), - rustc_attr!( - TEST, rustc_if_this_changed, - ), - rustc_attr!( - TEST, rustc_then_this_would_need, - ), - rustc_attr!( - TEST, rustc_clean, - ), - rustc_attr!( - TEST, rustc_partition_reused, - ), - rustc_attr!( - TEST, rustc_partition_codegened, - ), - rustc_attr!( - TEST, rustc_expected_cgu_reuse, - ), - rustc_attr!( - TEST, rustc_dump_symbol_name, - ), - rustc_attr!( - TEST, rustc_dump_def_path, - ), - rustc_attr!( - TEST, rustc_mir, - ), + rustc_attr!(TEST, rustc_dump_inferred_outlives), + rustc_attr!(TEST, rustc_capture_analysis,), + rustc_attr!(TEST, rustc_insignificant_dtor), + rustc_attr!(TEST, rustc_no_implicit_bounds), + rustc_attr!(TEST, rustc_strict_coherence), + rustc_attr!(TEST, rustc_dump_variances), + rustc_attr!(TEST, rustc_dump_variances_of_opaques), + rustc_attr!(TEST, rustc_dump_hidden_type_of_opaques), + rustc_attr!(TEST, rustc_dump_layout), + rustc_attr!(TEST, rustc_abi), + rustc_attr!(TEST, rustc_regions), + rustc_attr!(TEST, rustc_delayed_bug_from_inside_query), + rustc_attr!(TEST, rustc_dump_user_args), + rustc_attr!(TEST, rustc_evaluate_where_clauses), + rustc_attr!(TEST, rustc_if_this_changed), + rustc_attr!(TEST, rustc_then_this_would_need), + rustc_attr!(TEST, rustc_clean), + rustc_attr!(TEST, rustc_partition_reused), + rustc_attr!(TEST, rustc_partition_codegened), + rustc_attr!(TEST, rustc_expected_cgu_reuse), + rustc_attr!(TEST, rustc_dump_symbol_name), + rustc_attr!(TEST, rustc_dump_def_path), + rustc_attr!(TEST, rustc_mir), gated!( custom_mir, "the `#[custom_mir]` attribute is just used for the Rust test suite", ), - rustc_attr!( - TEST, rustc_dump_item_bounds, - ), - rustc_attr!( - TEST, rustc_dump_predicates, - ), - rustc_attr!( - TEST, rustc_dump_def_parents, - ), - rustc_attr!( - TEST, rustc_dump_object_lifetime_defaults, - ), - rustc_attr!( - TEST, rustc_dump_vtable, - ), - rustc_attr!( - TEST, rustc_dummy, - ), - rustc_attr!( - TEST, pattern_complexity_limit, - ), + rustc_attr!(TEST, rustc_dump_item_bounds), + rustc_attr!(TEST, rustc_dump_predicates), + rustc_attr!(TEST, rustc_dump_def_parents), + rustc_attr!(TEST, rustc_dump_object_lifetime_defaults), + rustc_attr!(TEST, rustc_dump_vtable), + rustc_attr!(TEST, rustc_dummy), + rustc_attr!(TEST, pattern_complexity_limit), ]; pub fn is_builtin_attr_name(name: Symbol) -> bool { diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 34ac6b3f9a7c8..ce3ce6fcccee4 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -129,7 +129,7 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option Date: Sat, 24 Jan 2026 13:37:53 +0100 Subject: [PATCH 05/17] ImproperCTypes: Move erasing_region_normalisation into helper function Another interal change that shouldn't impact rustc users. To prepare for the upcoming split of visit_type, we reorganise the instances of `cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty)` into a helper function outside of the main structs. --- .../rustc_lint/src/types/improper_ctypes.rs | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 9f10cba64cd43..865112219cc2d 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -138,6 +138,17 @@ declare_lint_pass!(ImproperCTypesLint => [ USES_POWER_ALIGNMENT ]); +/// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). +#[inline] +fn get_type_from_field<'tcx>( + cx: &LateContext<'tcx>, + field: &ty::FieldDef, + args: GenericArgsRef<'tcx>, +) -> Ty<'tcx> { + let field_ty = field.ty(cx.tcx, args); + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), field_ty).unwrap_or(field_ty) +} + /// Check a variant of a non-exhaustive enum for improper ctypes /// /// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". @@ -365,22 +376,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } } - /// Checks if the given field's type is "ffi-safe". - fn check_field_type_for_ffi( - &mut self, - state: VisitorState, - field: &ty::FieldDef, - args: GenericArgsRef<'tcx>, - ) -> FfiResult<'tcx> { - let field_ty = field.ty(self.cx.tcx, args); - let field_ty = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), field_ty) - .unwrap_or(field_ty); - self.visit_type(state, field_ty) - } - /// Checks if the given `VariantDef`'s field types are "ffi-safe". fn check_variant_for_ffi( &mut self, @@ -394,7 +389,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. - match self.check_field_type_for_ffi(state, field, args) { + let field_ty = get_type_from_field(self.cx, field, args); + match self.visit_type(state, field_ty) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -412,7 +408,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { - all_phantom &= match self.check_field_type_for_ffi(state, field, args) { + let field_ty = get_type_from_field(self.cx, field, args); + all_phantom &= match self.visit_type(state, field_ty) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -721,22 +718,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - if let Some(ty) = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), ty) - .unwrap_or(ty) - .visit_with(&mut ProhibitOpaqueTypes) - .break_value() - { - Some(FfiResult::FfiUnsafe { - ty, - reason: msg!("opaque types have no C equivalent"), - help: None, - }) - } else { - None - } + ty.visit_with(&mut ProhibitOpaqueTypes).break_value().map(|ty| FfiResult::FfiUnsafe { + ty, + reason: msg!("opaque types have no C equivalent"), + help: None, + }) } /// Check if the type is array and emit an unsafe type lint. @@ -754,12 +740,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Determine the FFI-safety of a single (MIR) type, given the context of how it is used. fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); - // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. So, first test that the top level isn't an array, and then // recursively check the types inside. From a9d7027f3986fba29cc329c2cf5038d9c21b7583 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 14 Apr 2026 18:52:50 -0400 Subject: [PATCH 06/17] rustdoc: percent-encode URL fragments --- src/librustdoc/html/markdown.rs | 6 ++++-- tests/rustdoc-html/unicode.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 tests/rustdoc-html/unicode.rs diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 858545bd09847..2034abdfd1566 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -582,6 +582,7 @@ impl<'a, I: Iterator>> Iterator for HeadingLinks<'a, '_, } } let id = self.id_map.derive(id); + let percent_encoded_id = small_url_encode(id.clone()); if let Some(ref mut builder) = self.toc { let mut text_header = String::new(); @@ -596,8 +597,9 @@ impl<'a, I: Iterator>> Iterator for HeadingLinks<'a, '_, std::cmp::min(level as u32 + (self.heading_offset as u32), MAX_HEADER_LEVEL); self.buf.push_back((Event::Html(format!("").into()), 0..0)); - let start_tags = - format!("§"); + let start_tags = format!( + "§" + ); return Some((Event::Html(start_tags.into()), 0..0)); } event diff --git a/tests/rustdoc-html/unicode.rs b/tests/rustdoc-html/unicode.rs new file mode 100644 index 0000000000000..a961f178ec3b1 --- /dev/null +++ b/tests/rustdoc-html/unicode.rs @@ -0,0 +1,10 @@ +#![crate_name = "unicode"] + +pub struct Foo; + +impl Foo { + //@ has unicode/struct.Foo.html //a/@href "#%C3%BA" + //@ !has unicode/struct.Foo.html //a/@href "#ú" + /// # ú + pub fn foo() {} +} From 98242676d9ef57ea6e6224a45229ea65025b7a84 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Thu, 16 Apr 2026 05:57:55 +0200 Subject: [PATCH 07/17] tests/debuginfo/basic-stepping.rs: Remove FIXME related to ZSTs We don't consider it a bug that users can't break on initialization of some non-zero sized types (see comment on `maximally-steppable` at the top of the file), so it does not make sense to consider it a bug that users can't break on initialization of some zero-sized types. --- tests/debuginfo/basic-stepping.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/debuginfo/basic-stepping.rs b/tests/debuginfo/basic-stepping.rs index a4410c70ba38a..f81c5cf7d3565 100644 --- a/tests/debuginfo/basic-stepping.rs +++ b/tests/debuginfo/basic-stepping.rs @@ -142,8 +142,8 @@ fn main () { let a = (); // #break let b : [i32; 0] = []; - // FIXME(#97083): Should we be able to break on initialization of zero-sized types? - // FIXME(#97083): Right now the first breakable line is: + // The above lines initialize zero-sized types. That does not emit machine + // code, so the first breakable line is: let mut c = 27; let d = c = 99; let e = "hi bob"; From 52ad8c071cda74ae9465fb54bb350396db294735 Mon Sep 17 00:00:00 2001 From: Shivendra Sharma Date: Wed, 8 Apr 2026 03:30:00 +0530 Subject: [PATCH 08/17] rustdoc: preserve `doc(cfg)` on locally re-exported type aliases When a type alias is locally re-exported from a private module (an implicit inline), rustdoc drops its `cfg` attributes because it treats it like a standard un-inlined re-export. Since type aliases have no inner fields to carry the `cfg` badge (unlike structs or enums), the portability info is lost entirely. This patch explicitly preserves the target's `cfg` metadata when the generated item is a `TypeAliasItem`, ensuring the portability badge renders correctly without breaking standard cross-crate re-export behavior. --- src/librustdoc/clean/mod.rs | 3 +- .../reexport/type-alias-reexport.rs | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-html/reexport/type-alias-reexport.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 5366a0eca3293..d628889450a0c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -205,7 +205,8 @@ fn generate_item_with_correct_attrs( attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); is_inline = is_inline || import_is_inline; } - add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None); + let keep_target_cfg = is_inline || matches!(kind, ItemKind::TypeAliasItem(..)); + add_without_unwanted_attributes(&mut attrs, target_attrs, keep_target_cfg, None); attrs } else { // We only keep the item's attributes. diff --git a/tests/rustdoc-html/reexport/type-alias-reexport.rs b/tests/rustdoc-html/reexport/type-alias-reexport.rs new file mode 100644 index 0000000000000..1bcdff88e22c5 --- /dev/null +++ b/tests/rustdoc-html/reexport/type-alias-reexport.rs @@ -0,0 +1,34 @@ +// Regression test for . +// This test ensures that auto-generated and explicit `doc(cfg)` attributes are correctly +// preserved for locally re-exported type aliases. + +//@ compile-flags: --cfg feature="foo" + +#![crate_name = "foo"] +#![feature(doc_cfg)] + +mod inner { + #[cfg(feature = "foo")] + pub type One = u32; + + #[doc(cfg(feature = "foo"))] + pub type Two = u32; +} + +//@ has 'foo/index.html' +// There should be two items in the type aliases table. +//@ count - '//*[@class="item-table"]/dt' 2 +// Both of them should have the portability badge in the module index. +//@ count - '//*[@class="item-table"]/dt/*[@class="stab portability"]' 2 + +//@ has 'foo/type.One.html' +// Check that the individual type page has the portability badge. +//@ count - '//*[@id="main-content"]/*[@class="item-info"]/*[@class="stab portability"]' 1 +//@ has - '//*[@id="main-content"]/*[@class="item-info"]/*[@class="stab portability"]' 'foo' + +//@ has 'foo/type.Two.html' +// Check the explicit doc(cfg) type page as well. +//@ count - '//*[@id="main-content"]/*[@class="item-info"]/*[@class="stab portability"]' 1 +//@ has - '//*[@id="main-content"]/*[@class="item-info"]/*[@class="stab portability"]' 'foo' + +pub use self::inner::{One, Two}; From 0529b94578aba5147649516dcf0186eb193f2e40 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 16 Apr 2026 18:16:27 +1000 Subject: [PATCH 09/17] Move `Token` impl block. For no apparent reason it's in a different file to `Token` itself. This commit moves it. --- compiler/rustc_ast_pretty/src/pp.rs | 6 ++++++ compiler/rustc_ast_pretty/src/pp/convenience.rs | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 4108671a3629e..9d0888a15d8f2 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -188,6 +188,12 @@ pub(crate) enum Token { End, } +impl Token { + pub(crate) fn is_hardbreak_tok(&self) -> bool { + *self == Printer::hardbreak_tok_offset(0) + } +} + #[derive(Copy, Clone)] enum PrintFrame { Fits, diff --git a/compiler/rustc_ast_pretty/src/pp/convenience.rs b/compiler/rustc_ast_pretty/src/pp/convenience.rs index 9b902b38122c8..c9589535940a0 100644 --- a/compiler/rustc_ast_pretty/src/pp/convenience.rs +++ b/compiler/rustc_ast_pretty/src/pp/convenience.rs @@ -89,9 +89,3 @@ impl Printer { }); } } - -impl Token { - pub(crate) fn is_hardbreak_tok(&self) -> bool { - *self == Printer::hardbreak_tok_offset(0) - } -} From 9e940e40519573b150c85c76c593d7cd777411e1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 16 Apr 2026 18:16:11 +1000 Subject: [PATCH 10/17] Merge `Printer` impl blocks. `rustc_ast_pretty::pp` defines `Printer` and has a 346 line `impl Printer` block for it. `rustc_ast_pretty::pp::convenience` has another `impl Printer` block with 85 lines. `rustc_ast_pretty::helpers` has another `impl Printer` block with 45 lines. This commit merges the two small `impl Printer` blocks into the bigger one, because there is no good reason for them to be separate. Doing this eliminates the `rustc_ast_pretty::pp::convenience` and `rustc_ast_pretty::helpers` modules; no great loss given that they were small and had extremely generic names. --- compiler/rustc_ast_pretty/src/helpers.rs | 49 ------- compiler/rustc_ast_pretty/src/lib.rs | 1 - compiler/rustc_ast_pretty/src/pp.rs | 129 +++++++++++++++++- .../rustc_ast_pretty/src/pp/convenience.rs | 91 ------------ 4 files changed, 128 insertions(+), 142 deletions(-) delete mode 100644 compiler/rustc_ast_pretty/src/helpers.rs delete mode 100644 compiler/rustc_ast_pretty/src/pp/convenience.rs diff --git a/compiler/rustc_ast_pretty/src/helpers.rs b/compiler/rustc_ast_pretty/src/helpers.rs deleted file mode 100644 index 34641ea2f5ae0..0000000000000 --- a/compiler/rustc_ast_pretty/src/helpers.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::borrow::Cow; - -use crate::pp::Printer; - -impl Printer { - pub fn word_space>>(&mut self, w: W) { - self.word(w); - self.space(); - } - - pub fn popen(&mut self) { - self.word("("); - } - - pub fn pclose(&mut self) { - self.word(")"); - } - - pub fn hardbreak_if_not_bol(&mut self) { - if !self.is_beginning_of_line() { - self.hardbreak() - } - } - - pub fn space_if_not_bol(&mut self) { - if !self.is_beginning_of_line() { - self.space(); - } - } - - pub fn nbsp(&mut self) { - self.word(" ") - } - - pub fn word_nbsp>>(&mut self, w: S) { - self.word(w); - self.nbsp() - } - - /// Synthesizes a comment that was not textually present in the original - /// source file. - pub fn synth_comment(&mut self, text: impl Into>) { - self.word("/*"); - self.space(); - self.word(text); - self.space(); - self.word("*/") - } -} diff --git a/compiler/rustc_ast_pretty/src/lib.rs b/compiler/rustc_ast_pretty/src/lib.rs index a7d9f89fb3df5..bfc1d387b7009 100644 --- a/compiler/rustc_ast_pretty/src/lib.rs +++ b/compiler/rustc_ast_pretty/src/lib.rs @@ -3,6 +3,5 @@ #![feature(negative_impls)] // tidy-alphabetical-end -mod helpers; pub mod pp; pub mod pprust; diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index 9d0888a15d8f2..c7a38d981b893 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -132,7 +132,6 @@ //! methods called `Printer::scan_*`, and the 'PRINT' process is the //! method called `Printer::print`. -mod convenience; mod ring; use std::borrow::Cow; @@ -485,4 +484,132 @@ impl Printer { self.out.push_str(string); self.space -= string.len() as isize; } + + /// Synthesizes a comment that was not textually present in the original + /// source file. + pub fn synth_comment(&mut self, text: impl Into>) { + self.word("/*"); + self.space(); + self.word(text); + self.space(); + self.word("*/") + } + + /// "raw box" + pub fn rbox(&mut self, indent: isize, breaks: Breaks) -> BoxMarker { + self.scan_begin(BeginToken { indent: IndentStyle::Block { offset: indent }, breaks }) + } + + /// Inconsistent breaking box + pub fn ibox(&mut self, indent: isize) -> BoxMarker { + self.rbox(indent, Breaks::Inconsistent) + } + + /// Consistent breaking box + pub fn cbox(&mut self, indent: isize) -> BoxMarker { + self.rbox(indent, Breaks::Consistent) + } + + pub fn visual_align(&mut self) -> BoxMarker { + self.scan_begin(BeginToken { indent: IndentStyle::Visual, breaks: Breaks::Consistent }) + } + + pub fn break_offset(&mut self, n: usize, off: isize) { + self.scan_break(BreakToken { + offset: off, + blank_space: n as isize, + ..BreakToken::default() + }); + } + + pub fn end(&mut self, b: BoxMarker) { + self.scan_end(b) + } + + pub fn eof(mut self) -> String { + self.scan_eof(); + self.out + } + + pub fn word>>(&mut self, wrd: S) { + let string = wrd.into(); + self.scan_string(string) + } + + pub fn word_space>>(&mut self, w: W) { + self.word(w); + self.space(); + } + + pub fn nbsp(&mut self) { + self.word(" ") + } + + pub fn word_nbsp>>(&mut self, w: S) { + self.word(w); + self.nbsp() + } + + fn spaces(&mut self, n: usize) { + self.break_offset(n, 0) + } + + pub fn zerobreak(&mut self) { + self.spaces(0) + } + + pub fn space(&mut self) { + self.spaces(1) + } + + pub fn popen(&mut self) { + self.word("("); + } + + pub fn pclose(&mut self) { + self.word(")"); + } + + pub fn hardbreak(&mut self) { + self.spaces(SIZE_INFINITY as usize) + } + + pub fn is_beginning_of_line(&self) -> bool { + match self.last_token() { + Some(last_token) => last_token.is_hardbreak_tok(), + None => true, + } + } + + pub fn hardbreak_if_not_bol(&mut self) { + if !self.is_beginning_of_line() { + self.hardbreak() + } + } + + pub fn space_if_not_bol(&mut self) { + if !self.is_beginning_of_line() { + self.space(); + } + } + + pub(crate) fn hardbreak_tok_offset(off: isize) -> Token { + Token::Break(BreakToken { + offset: off, + blank_space: SIZE_INFINITY, + ..BreakToken::default() + }) + } + + pub fn trailing_comma(&mut self) { + self.scan_break(BreakToken { pre_break: Some(','), ..BreakToken::default() }); + } + + pub fn trailing_comma_or_space(&mut self) { + self.scan_break(BreakToken { + blank_space: 1, + pre_break: Some(','), + ..BreakToken::default() + }); + } } diff --git a/compiler/rustc_ast_pretty/src/pp/convenience.rs b/compiler/rustc_ast_pretty/src/pp/convenience.rs deleted file mode 100644 index c9589535940a0..0000000000000 --- a/compiler/rustc_ast_pretty/src/pp/convenience.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::borrow::Cow; - -use crate::pp::{ - BeginToken, BoxMarker, BreakToken, Breaks, IndentStyle, Printer, SIZE_INFINITY, Token, -}; - -impl Printer { - /// "raw box" - pub fn rbox(&mut self, indent: isize, breaks: Breaks) -> BoxMarker { - self.scan_begin(BeginToken { indent: IndentStyle::Block { offset: indent }, breaks }) - } - - /// Inconsistent breaking box - pub fn ibox(&mut self, indent: isize) -> BoxMarker { - self.rbox(indent, Breaks::Inconsistent) - } - - /// Consistent breaking box - pub fn cbox(&mut self, indent: isize) -> BoxMarker { - self.rbox(indent, Breaks::Consistent) - } - - pub fn visual_align(&mut self) -> BoxMarker { - self.scan_begin(BeginToken { indent: IndentStyle::Visual, breaks: Breaks::Consistent }) - } - - pub fn break_offset(&mut self, n: usize, off: isize) { - self.scan_break(BreakToken { - offset: off, - blank_space: n as isize, - ..BreakToken::default() - }); - } - - pub fn end(&mut self, b: BoxMarker) { - self.scan_end(b) - } - - pub fn eof(mut self) -> String { - self.scan_eof(); - self.out - } - - pub fn word>>(&mut self, wrd: S) { - let string = wrd.into(); - self.scan_string(string) - } - - fn spaces(&mut self, n: usize) { - self.break_offset(n, 0) - } - - pub fn zerobreak(&mut self) { - self.spaces(0) - } - - pub fn space(&mut self) { - self.spaces(1) - } - - pub fn hardbreak(&mut self) { - self.spaces(SIZE_INFINITY as usize) - } - - pub fn is_beginning_of_line(&self) -> bool { - match self.last_token() { - Some(last_token) => last_token.is_hardbreak_tok(), - None => true, - } - } - - pub(crate) fn hardbreak_tok_offset(off: isize) -> Token { - Token::Break(BreakToken { - offset: off, - blank_space: SIZE_INFINITY, - ..BreakToken::default() - }) - } - - pub fn trailing_comma(&mut self) { - self.scan_break(BreakToken { pre_break: Some(','), ..BreakToken::default() }); - } - - pub fn trailing_comma_or_space(&mut self) { - self.scan_break(BreakToken { - blank_space: 1, - pre_break: Some(','), - ..BreakToken::default() - }); - } -} From b8ba4002f5d3d71be024e4d0ff39913e887ec510 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 22 Feb 2026 17:32:24 +0100 Subject: [PATCH 11/17] c-variadic: handle c_int being i16 and c_double being f32 on avr --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 68 +++++++++++-------- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 10 ++- library/core/src/ffi/va_list.rs | 48 ++++++++++++- .../c-link-to-rust-va-list-fn/checkrust.rs | 12 ++-- .../run-make/c-link-to-rust-va-list-fn/test.c | 2 +- 5 files changed, 99 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 0d3d682ece21f..9742f9fb3e42e 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -285,37 +285,47 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } sym::breakpoint => self.call_intrinsic("llvm.debugtrap", &[], &[]), sym::va_arg => { - match result.layout.backend_repr { - BackendRepr::Scalar(scalar) => { - match scalar.primitive() { - Primitive::Int(..) => { - if self.cx().size_of(result.layout.ty).bytes() < 4 { - // `va_arg` should not be called on an integer type - // less than 4 bytes in length. If it is, promote - // the integer to an `i32` and truncate the result - // back to the smaller type. - let promoted_result = emit_va_arg(self, args[0], tcx.types.i32); - self.trunc(promoted_result, result.layout.llvm_type(self)) - } else { - emit_va_arg(self, args[0], result.layout.ty) - } - } - Primitive::Float(Float::F16) => { - bug!("the va_arg intrinsic does not work with `f16`") - } - Primitive::Float(Float::F64) | Primitive::Pointer(_) => { - emit_va_arg(self, args[0], result.layout.ty) - } - // `va_arg` should never be used with the return type f32. - Primitive::Float(Float::F32) => { - bug!("the va_arg intrinsic does not work with `f32`") - } - Primitive::Float(Float::F128) => { - bug!("the va_arg intrinsic does not work with `f128`") - } + let BackendRepr::Scalar(scalar) = result.layout.backend_repr else { + bug!("the va_arg intrinsic does not support non-scalar types") + }; + + match scalar.primitive() { + Primitive::Pointer(_) => { + // Pointers are always OK. + emit_va_arg(self, args[0], result.layout.ty) + } + Primitive::Int(..) => { + let int_width = self.cx().size_of(result.layout.ty).bits(); + let target_c_int_width = self.cx().sess().target.options.c_int_width; + if int_width < u64::from(target_c_int_width) { + // Smaller integer types are automatically promototed and `va_arg` + // should not be called on them. + bug!( + "va_arg got i{} but needs at least c_int (an i{})", + int_width, + target_c_int_width + ); } + emit_va_arg(self, args[0], result.layout.ty) + } + Primitive::Float(Float::F16) => { + bug!("the va_arg intrinsic does not support `f16`") + } + Primitive::Float(Float::F32) => { + if self.cx().sess().target.arch == Arch::Avr { + // c_double is actually f32 on avr. + emit_va_arg(self, args[0], result.layout.ty) + } else { + bug!("the va_arg intrinsic does not support `f32` on this target") + } + } + Primitive::Float(Float::F64) => { + // 64-bit floats are always OK. + emit_va_arg(self, args[0], result.layout.ty) + } + Primitive::Float(Float::F128) => { + bug!("the va_arg intrinsic does not support `f128`") } - _ => bug!("the va_arg intrinsic does not work with non-scalar types"), } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index bb31bcbf70f1b..966f020686319 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -499,10 +499,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::Float(ty::FloatTy::F32) => { variadic_error(tcx.sess, arg.span, arg_ty, "c_double"); } - ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Bool => { + ty::Int(ty::IntTy::I8) | ty::Bool => { variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); } - ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) => { + ty::Uint(ty::UintTy::U8) => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); + } + ty::Int(ty::IntTy::I16) if tcx.sess.target.options.c_int_width > 16 => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); + } + ty::Uint(ty::UintTy::U16) if tcx.sess.target.options.c_int_width > 16 => { variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); } ty::FnDef(..) => { diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index f0f58a0f83430..45e25fabe3bd2 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -266,14 +266,17 @@ impl<'f> const Drop for VaList<'f> { mod sealed { pub trait Sealed {} + impl Sealed for i16 {} impl Sealed for i32 {} impl Sealed for i64 {} impl Sealed for isize {} + impl Sealed for u16 {} impl Sealed for u32 {} impl Sealed for u64 {} impl Sealed for usize {} + impl Sealed for f32 {} impl Sealed for f64 {} impl Sealed for *mut T {} @@ -299,22 +302,61 @@ mod sealed { // to accept unsupported types in the meantime. pub unsafe trait VaArgSafe: sealed::Sealed {} -// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. +crate::cfg_select! { + any(target_arch = "avr", target_arch = "msp430") => { + // c_int/c_uint are i16/u16 on these targets. + // + // - i8 is implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. + // - u8 is implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`. + unsafe impl VaArgSafe for i16 {} + unsafe impl VaArgSafe for u16 {} + } + _ => { + // c_int/c_uint are i32/u32 on this target. + // + // - i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. + // - u8 and u16 are implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`. + } +} + +crate::cfg_select! { + target_arch = "avr" => { + // c_double is f32 on this target. + unsafe impl VaArgSafe for f32 {} + } + _ => { + // c_double is f64 on this target. + // + // - f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`. + } +} + unsafe impl VaArgSafe for i32 {} unsafe impl VaArgSafe for i64 {} unsafe impl VaArgSafe for isize {} -// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. unsafe impl VaArgSafe for u32 {} unsafe impl VaArgSafe for u64 {} unsafe impl VaArgSafe for usize {} -// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`. unsafe impl VaArgSafe for f64 {} unsafe impl VaArgSafe for *mut T {} unsafe impl VaArgSafe for *const T {} +// Check that relevant `core::ffi` types implement `VaArgSafe`. +const _: () = { + const fn va_arg_safe_check() {} + + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); +}; + impl<'f> VaList<'f> { /// Read an argument from the variable argument list, and advance to the next argument. /// diff --git a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs index c522ac46d918e..109fbb1c62036 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +++ b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs @@ -30,17 +30,17 @@ pub unsafe extern "C" fn check_list_1(mut ap: VaList) -> usize { continue_if!(ap.arg::() == '4' as c_int); continue_if!(ap.arg::() == ';' as c_int); continue_if!(ap.arg::() == 0x32); - continue_if!(ap.arg::() == 0x10000001); + continue_if!(ap.arg::() == 0x10000001); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"Valid!")); 0 } #[unsafe(no_mangle)] pub unsafe extern "C" fn check_list_2(mut ap: VaList) -> usize { - continue_if!(ap.arg::() == 3.14f64); + continue_if!(ap.arg::() == 3.14); continue_if!(ap.arg::() == 12); continue_if!(ap.arg::() == 'a' as c_int); - continue_if!(ap.arg::() == 6.28f64); + continue_if!(ap.arg::() == 6.28); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"Hello")); continue_if!(ap.arg::() == 42); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"World")); @@ -49,7 +49,7 @@ pub unsafe extern "C" fn check_list_2(mut ap: VaList) -> usize { #[unsafe(no_mangle)] pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize { - continue_if!(ap.arg::() == 6.28f64); + continue_if!(ap.arg::() == 6.28); continue_if!(ap.arg::() == 16); continue_if!(ap.arg::() == 'A' as c_int); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"Skip Me!")); @@ -66,7 +66,7 @@ pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize { #[unsafe(no_mangle)] pub unsafe extern "C" fn check_varargs_1(_: c_int, mut ap: ...) -> usize { - continue_if!(ap.arg::() == 3.14f64); + continue_if!(ap.arg::() == 3.14); continue_if!(ap.arg::() == 12); continue_if!(ap.arg::() == 'A' as c_int); continue_if!(ap.arg::() == 1); @@ -156,7 +156,7 @@ extern "C" fn run_test_variadic() -> usize { #[unsafe(no_mangle)] extern "C" fn run_test_va_list_by_value() -> usize { - unsafe extern "C" fn helper(mut ap: ...) -> usize { + unsafe extern "C" fn helper(ap: ...) -> usize { unsafe { test_va_list_by_value(ap) } } diff --git a/tests/run-make/c-link-to-rust-va-list-fn/test.c b/tests/run-make/c-link-to-rust-va-list-fn/test.c index 2bb93c0b5d0ef..b368302326c71 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/test.c +++ b/tests/run-make/c-link-to-rust-va-list-fn/test.c @@ -32,7 +32,7 @@ int test_rust(size_t (*fn)(va_list), ...) { int main(int argc, char* argv[]) { assert(test_rust(check_list_0, 0x01LL, 0x02, 0x03LL) == 0); - assert(test_rust(check_list_1, -1, 'A', '4', ';', 0x32, 0x10000001, "Valid!") == 0); + assert(test_rust(check_list_1, -1, 'A', '4', ';', 0x32, (int32_t)0x10000001, "Valid!") == 0); assert(test_rust(check_list_2, 3.14, 12l, 'a', 6.28, "Hello", 42, "World") == 0); From a875e140b6ff6735f2b93186c1d80c1d4165ae38 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 22 Feb 2026 19:53:54 +0100 Subject: [PATCH 12/17] c-variadic: make `VaArgSafe` a lang item so that we can check whether a type implements the trait --- compiler/rustc_hir/src/lang_items.rs | 1 + .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 24 ++++++++++++------- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/ffi/va_list.rs | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index c144f0b7dbc5b..91a5415039ac0 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -221,6 +221,7 @@ language_item_table! { UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None; UnsafePinned, sym::unsafe_pinned, unsafe_pinned_type, Target::Struct, GenericRequirement::None; + VaArgSafe, sym::va_arg_safe, va_arg_safe, Target::Trait, GenericRequirement::None; VaList, sym::va_list, va_list, Target::Struct, GenericRequirement::None; Deref, sym::deref, deref_trait, Target::Trait, GenericRequirement::Exact(0); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 966f020686319..b084705425a49 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -494,21 +494,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // There are a few types which get autopromoted when passed via varargs // in C but we just error out instead and require explicit casts. + // + // We use implementations of VaArgSafe as the source of truth. On some embedded + // targets, c_double is f32 and c_int/c_uing are i16/u16, and these types implement + // VaArgSafe there. On all other targets, these types do not implement VaArgSafe. + // + // cfg(bootstrap): change the if let to an unwrap. let arg_ty = self.structurally_resolve_type(arg.span, arg_ty); + if let Some(trait_def_id) = tcx.lang_items().va_arg_safe() + && self + .type_implements_trait(trait_def_id, [arg_ty], self.param_env) + .must_apply_modulo_regions() + { + continue; + } + match arg_ty.kind() { ty::Float(ty::FloatTy::F32) => { variadic_error(tcx.sess, arg.span, arg_ty, "c_double"); } - ty::Int(ty::IntTy::I8) | ty::Bool => { - variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); - } - ty::Uint(ty::UintTy::U8) => { - variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); - } - ty::Int(ty::IntTy::I16) if tcx.sess.target.options.c_int_width > 16 => { + ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Bool => { variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); } - ty::Uint(ty::UintTy::U16) if tcx.sess.target.options.c_int_width > 16 => { + ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) => { variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); } ty::FnDef(..) => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 80d1c91c81ddc..718d9e22626eb 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2209,6 +2209,7 @@ symbols! { v1, v8plus, va_arg, + va_arg_safe, va_copy, va_end, va_list, diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 45e25fabe3bd2..034e4ad728b8a 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -300,6 +300,7 @@ mod sealed { // We may unseal this trait in the future, but currently our `va_arg` implementations don't support // types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used // to accept unsupported types in the meantime. +#[lang = "va_arg_safe"] pub unsafe trait VaArgSafe: sealed::Sealed {} crate::cfg_select! { From 78a465a86d7c0239f8190830b1288489caac3a8d Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 16 Apr 2026 18:09:36 +0800 Subject: [PATCH 13/17] Use `box_new` diagnostic item for Box::new suggestions --- compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index a2f4c57bd442c..b5d138f183b99 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -3107,14 +3107,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { let deref_kind = if checked_ty.is_box() { // detect Box::new(..) - // FIXME: use `box_new` diagnostic item instead? if let ExprKind::Call(box_new, [_]) = expr.kind && let ExprKind::Path(qpath) = &box_new.kind && let Res::Def(DefKind::AssocFn, fn_id) = self.typeck_results.borrow().qpath_res(qpath, box_new.hir_id) - && let Some(impl_id) = self.tcx.inherent_impl_of_assoc(fn_id) - && self.tcx.type_of(impl_id).skip_binder().is_box() - && self.tcx.item_name(fn_id) == sym::new + && self.tcx.is_diagnostic_item(sym::box_new, fn_id) { let l_paren = self.tcx.sess.source_map().next_point(box_new.span); let r_paren = self.tcx.sess.source_map().end_point(expr.span); From d0f5b5caa865445bef2f59d1dd0f339a0750434f Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Thu, 16 Apr 2026 15:19:42 +0300 Subject: [PATCH 14/17] Replace redundant unwrap with get_or_insert_with --- compiler/rustc_middle/src/query/job.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_middle/src/query/job.rs b/compiler/rustc_middle/src/query/job.rs index 24c4daf9855d2..8c78bf24287e0 100644 --- a/compiler/rustc_middle/src/query/job.rs +++ b/compiler/rustc_middle/src/query/job.rs @@ -36,10 +36,7 @@ impl<'tcx> QueryJob<'tcx> { } pub fn latch(&mut self) -> QueryLatch<'tcx> { - if self.latch.is_none() { - self.latch = Some(QueryLatch::new()); - } - self.latch.as_ref().unwrap().clone() + self.latch.get_or_insert_with(QueryLatch::new).clone() } /// Signals to waiters that the query is complete. From 4645f036d054cbc083194deb8d69c57cd3cd8317 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:48:06 +0200 Subject: [PATCH 15/17] triagebot: notify on diagnostic attribute changes --- triagebot.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index 7708bdbceffcb..dd5bc55cfe784 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1451,6 +1451,12 @@ code; adding it needs t-lang approval. """ cc = ["@rust-lang/wg-const-eval"] +[mentions."compiler/rustc_attr_parsing/src/attributes/diagnostic"] +message = "Some changes occurred to diagnostic attributes." +cc = ["@mejrs"] +[mentions."compiler/rustc_hir/src/attrs/diagnostic.rs"] +message = "Some changes occurred to diagnostic attributes." +cc = ["@mejrs"] # ------------------------------------------------------------------------------ # PR assignments From b2d24051be4030a80a5f19b64ef3297963ddc793 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Thu, 16 Apr 2026 08:02:57 -0700 Subject: [PATCH 16/17] `as_ref_unchecked` docs link fix --- library/core/src/ptr/mut_ptr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 289dd972f679c..98b70a77fad7b 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -594,7 +594,7 @@ impl *mut T { /// /// [`as_mut`]: #method.as_mut /// [`as_uninit_mut`]: #method.as_uninit_mut - /// [`as_ref_unchecked`]: #method.as_mut_unchecked + /// [`as_ref_unchecked`]: #method.as_ref_unchecked /// /// # Safety /// From 6c4ec59d5f5292bd1c309f612fd2616b1c9b643d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 16 Apr 2026 16:41:37 +0200 Subject: [PATCH 17/17] Tweak how the "copy path" rustdoc button works to allow some accessibility tool to work with rustdoc --- src/librustdoc/html/static/js/main.js | 35 ++++++++++++++++----------- tests/rustdoc-gui/copy-path.goml | 16 ++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 491be052bca2e..8a72382cf90d4 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -2123,20 +2123,27 @@ function preLoadCss(cssUrl) { return; } but.onclick = () => { - // Most page titles are ' in - Rust', except - // modules (which don't have the first part) and keywords/primitives - // (which don't have a module path) - const titleElement = document.querySelector("title"); - const title = titleElement && titleElement.textContent ? - titleElement.textContent.replace(" - Rust", "") : ""; - const [item, module] = title.split(" in "); - const path = [item]; - if (module !== undefined) { - path.unshift(module); - } - - copyContentToClipboard(path.join("::")); - copyButtonAnimation(but); + // We get the path from the "breadcrumbs" and the actual item name. + let path = ""; + // @ts-expect-error + const heading = document.getElementById(MAIN_ID).querySelector(".main-heading"); + + if (heading) { + const breadcrumbs = heading.querySelector(".rustdoc-breadcrumbs"); + if (breadcrumbs) { + // @ts-expect-error + path = breadcrumbs.innerText; + if (path.length > 0) { + path += "::"; + } + } + + // @ts-expect-error + path += heading.querySelector("h1 > span").innerText; + + copyContentToClipboard(path); + copyButtonAnimation(but); + } }; /** diff --git a/tests/rustdoc-gui/copy-path.goml b/tests/rustdoc-gui/copy-path.goml index e8766688f8d5e..61e63d7822c73 100644 --- a/tests/rustdoc-gui/copy-path.goml +++ b/tests/rustdoc-gui/copy-path.goml @@ -18,3 +18,19 @@ assert-size: ("#copy-path.clicked", {"width": |width|, "height": |height|}) wait-for: "#copy-path:not(.clicked)" // We check that the size is still the same. assert-size: ("#copy-path:not(.clicked)", {"width": |width|, "height": |height|}) + +// Check the path for a module. +go-to: "file://" + |DOC_PATH| + "/test_docs/foreign_impl_order/index.html" +click: "#copy-path" +// We wait for the new text to appear. +wait-for: "#copy-path.clicked" +// We check that the clipboard value is the expected one. +assert-clipboard: "test_docs::foreign_impl_order" + +// Check the path for the crate. +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +click: "#copy-path" +// We wait for the new text to appear. +wait-for: "#copy-path.clicked" +// We check that the clipboard value is the expected one. +assert-clipboard: "test_docs"