From 4eb9e661ec4bcaae73d084ea24add520c83a7c5e Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 16 Mar 2024 14:53:41 -0400 Subject: [PATCH 01/25] Extend ASCII fast paths of `char` methods beyond ASCII --- library/core/src/char/methods.rs | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 87b328c912878..284a3eeb75dfb 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -777,8 +777,9 @@ impl char { #[inline] pub fn is_alphabetic(self) -> bool { match self { - 'a'..='z' | 'A'..='Z' => true, - c => c > '\x7f' && unicode::Alphabetic(c), + 'A'..='Z' | 'a'..='z' => true, + '\0'..='\u{A9}' => false, + _ => unicode::Alphabetic(self), } } @@ -819,7 +820,8 @@ impl char { pub const fn is_lowercase(self) -> bool { match self { 'a'..='z' => true, - c => c > '\x7f' && unicode::Lowercase(c), + '\0'..='\u{A9}' => false, + _ => unicode::Lowercase(self), } } @@ -860,7 +862,8 @@ impl char { pub const fn is_uppercase(self) -> bool { match self { 'A'..='Z' => true, - c => c > '\x7f' && unicode::Uppercase(c), + '\0'..='\u{BF}' => false, + _ => unicode::Uppercase(self), } } @@ -893,7 +896,8 @@ impl char { pub const fn is_whitespace(self) -> bool { match self { ' ' | '\x09'..='\x0d' => true, - c => c > '\x7f' && unicode::White_Space(c), + '\0'..='\u{84}' => false, + _ => unicode::White_Space(self), } } @@ -920,10 +924,10 @@ impl char { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn is_alphanumeric(self) -> bool { - if self.is_ascii() { - self.is_ascii_alphanumeric() - } else { - unicode::Alphabetic(self) || unicode::N(self) + match self { + '0'..='9' | 'A'..='Z' | 'a'..='z' => true, + '\0'..='\u{A9}' => false, + _ => unicode::Alphabetic(self) || unicode::N(self), } } @@ -969,7 +973,7 @@ impl char { #[must_use] #[inline] pub(crate) fn is_grapheme_extended(self) -> bool { - !self.is_ascii() && unicode::Grapheme_Extend(self) + self > '\u{02FF}' && unicode::Grapheme_Extend(self) } /// Returns `true` if this `char` has the `Cased` property. @@ -985,7 +989,11 @@ impl char { #[doc(hidden)] #[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")] pub fn is_cased(self) -> bool { - if self.is_ascii() { self.is_ascii_alphabetic() } else { unicode::Cased(self) } + match self { + 'A'..='Z' | 'a'..='z' => true, + '\0'..='\u{A9}' => false, + _ => unicode::Cased(self), + } } /// Returns `true` if this `char` has the `Case_Ignorable` property. @@ -1047,7 +1055,8 @@ impl char { pub fn is_numeric(self) -> bool { match self { '0'..='9' => true, - c => c > '\x7f' && unicode::N(c), + '\0'..='\u{B1}' => false, + _ => unicode::N(self), } } From 5316bab2999f02b42acd831ac007caa77a246893 Mon Sep 17 00:00:00 2001 From: mu001999 Date: Sun, 15 Mar 2026 18:51:45 +0800 Subject: [PATCH 02/25] Avoid choosing a glob import if a single import could define the same with current import --- compiler/rustc_resolve/src/ident.rs | 2 +- .../imports/inconsistent-resolution-153842.rs | 17 ++++++++++++ .../inconsistent-resolution-153842.stderr | 26 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/ui/imports/inconsistent-resolution-153842.rs create mode 100644 tests/ui/imports/inconsistent-resolution-153842.stderr diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 7cfd5b5f861a4..fc9a829423868 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1366,7 +1366,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &single_import.parent_scope, None, ignore_decl, - ignore_import, + None, ) { Err(Determined) => continue, Ok(binding) diff --git a/tests/ui/imports/inconsistent-resolution-153842.rs b/tests/ui/imports/inconsistent-resolution-153842.rs new file mode 100644 index 0000000000000..85fff361e31d8 --- /dev/null +++ b/tests/ui/imports/inconsistent-resolution-153842.rs @@ -0,0 +1,17 @@ +mod a { + pub use crate::s::Trait as s; + //~^ ERROR cannot determine resolution for the import + //~| ERROR cannot determine resolution for the import + //~| ERROR unresolved imports `crate::s::Trait`, `a::s` +} + +mod b { + pub mod s { + pub trait Trait {} + } +} + +use a::s; +use b::*; + +fn main() {} diff --git a/tests/ui/imports/inconsistent-resolution-153842.stderr b/tests/ui/imports/inconsistent-resolution-153842.stderr new file mode 100644 index 0000000000000..2b686ff3f76e0 --- /dev/null +++ b/tests/ui/imports/inconsistent-resolution-153842.stderr @@ -0,0 +1,26 @@ +error: cannot determine resolution for the import + --> $DIR/inconsistent-resolution-153842.rs:2:13 + | +LL | pub use crate::s::Trait as s; + | ^^^^^^^^^^^^^^^^^^^^ + +error: cannot determine resolution for the import + --> $DIR/inconsistent-resolution-153842.rs:2:13 + | +LL | pub use crate::s::Trait as s; + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0432]: unresolved imports `crate::s::Trait`, `a::s` + --> $DIR/inconsistent-resolution-153842.rs:2:13 + | +LL | pub use crate::s::Trait as s; + | ^^^^^^^^^^^^^^^^^^^^ +... +LL | use a::s; + | ^^^^ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0432`. From b3e4ebd9d27b1367016b1ff83e320d953383d3a5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 19 Mar 2026 12:19:59 +0100 Subject: [PATCH 03/25] const validity checking: do not recurse to references inside MaybeDangling --- compiler/rustc_const_eval/src/interpret/validity.rs | 7 +++++-- tests/ui/consts/const-eval/valid-const.rs | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 41a4cb2f23a1c..83c684d5e7d86 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -680,8 +680,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { format!("encountered a {ptr_kind} pointing to uninhabited type {ty}") ) } - // Recursive checking - if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() { + + // Recursive checking (but not inside `MaybeDangling` of course). + if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() + && !self.may_dangle + { // Proceed recursively even for ZST, no reason to skip them! // `!` is a ZST and we want to validate it. if let Some(ctfe_mode) = self.ctfe_mode { diff --git a/tests/ui/consts/const-eval/valid-const.rs b/tests/ui/consts/const-eval/valid-const.rs index 777484c6b0927..6f92997c90d6f 100644 --- a/tests/ui/consts/const-eval/valid-const.rs +++ b/tests/ui/consts/const-eval/valid-const.rs @@ -1,10 +1,12 @@ //@ check-pass // // Some constants that *are* valid +#![feature(maybe_dangling)] use std::mem; use std::ptr::NonNull; use std::num::NonZero; +use std::mem::MaybeDangling; const NON_NULL_PTR1: NonNull = unsafe { mem::transmute(1usize) }; const NON_NULL_PTR2: NonNull = unsafe { mem::transmute(&0) }; @@ -14,4 +16,6 @@ const NON_NULL_USIZE: NonZero = unsafe { mem::transmute(1usize) }; const UNIT: () = (); +const INVALID_INSIDE_MAYBE_DANGLING: MaybeDangling<&bool> = unsafe { std::mem::transmute(&5u8) }; + fn main() {} From aed54f219eccbd4f3cb3ecd89acdc0d9eeb2bceb Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Sat, 21 Mar 2026 16:00:51 +0100 Subject: [PATCH 04/25] tests/ui/async-await/gat-is-send-across-await.rs: New regression test The test began passing in `nightly-2025-10-16`. Probably by c50aebba787 because it fixed two "does not live long enough" errors. The test fails to compile with `nightly-2025-10-15` with such an error: $ rustc +nightly-2025-10-15 --edition 2018 tests/ui/async-await/gat-is-send-across-await.rs error: `impl G` does not live long enough --> tests/ui/async-await/gat-is-send-across-await.rs:16:24 | 16 | let _: &dyn Send = &async move { | ________________________^ 17 | | let _gat = g.as_gat(); 18 | | async{}.await 19 | | }; | |_____^ --- .../async-await/gat-is-send-across-await.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/ui/async-await/gat-is-send-across-await.rs diff --git a/tests/ui/async-await/gat-is-send-across-await.rs b/tests/ui/async-await/gat-is-send-across-await.rs new file mode 100644 index 0000000000000..43b777449edd0 --- /dev/null +++ b/tests/ui/async-await/gat-is-send-across-await.rs @@ -0,0 +1,23 @@ +//! Regression test for . + +//@ edition:2018 +//@ check-pass + +#![allow(unused)] + +trait G: Send { + type Gat<'l>: Send + where + Self: 'l; + + fn as_gat(&self) -> Self::Gat<'_>; +} + +fn a(g: impl G) { + let _: &dyn Send = &async move { + let _gat = g.as_gat(); + async{}.await + }; +} + +fn main() { } From 8d072616a5d0c484e9761b0958ece68e8d235fe3 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 16 Mar 2024 15:56:18 -0400 Subject: [PATCH 05/25] Add APIs for dealing with titlecase - `char::is_cased` - `char::is_titlecase` - `char::case` - `char::to_titlecase` --- library/alloc/src/lib.rs | 1 + library/core/src/char/methods.rs | 240 ++++++++++++++++-- library/core/src/char/mod.rs | 83 ++++-- library/core/src/unicode/mod.rs | 4 +- library/core/src/unicode/unicode_data.rs | 82 +++++- library/coretests/tests/char.rs | 57 ++++- library/coretests/tests/lib.rs | 1 + library/coretests/tests/unicode.rs | 34 ++- library/coretests/tests/unicode/test_data.rs | 78 ++++++ .../src/case_mapping.rs | 45 +++- src/tools/unicode-table-generator/src/main.rs | 58 +++-- 11 files changed, 581 insertions(+), 102 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 7ac9cdc3833d3..bcd9e092a310f 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -148,6 +148,7 @@ #![feature(slice_range)] #![feature(std_internals)] #![feature(temporary_niche_types)] +#![feature(titlecase)] #![feature(transmutability)] #![feature(trivial_clone)] #![feature(trusted_fused)] diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 284a3eeb75dfb..e9c3b040dc50b 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -777,12 +777,76 @@ impl char { #[inline] pub fn is_alphabetic(self) -> bool { match self { - 'A'..='Z' | 'a'..='z' => true, + 'a'..='z' | 'A'..='Z' => true, '\0'..='\u{A9}' => false, _ => unicode::Alphabetic(self), } } + /// Returns `true` if this `char` has the `Cased` property. + /// A character is cased if and only if it is uppercase, lowercase, or titlecase. + /// + /// `Cased` is described in Chapter 4 (Character Properties) of the [Unicode Standard] and + /// specified in the [Unicode Character Database][ucd] [`DerivedCoreProperties.txt`]. + /// + /// [Unicode Standard]: https://www.unicode.org/versions/latest/ + /// [ucd]: https://www.unicode.org/reports/tr44/ + /// [`DerivedCoreProperties.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(titlecase)] + /// assert!('A'.is_cased()); + /// assert!('a'.is_cased()); + /// assert!(!'京'.is_cased()); + /// ``` + #[must_use] + #[unstable(feature = "titlecase", issue = "153892")] + #[inline] + pub fn is_cased(self) -> bool { + match self { + 'a'..='z' | 'A'..='Z' => true, + '\0'..='\u{A9}' => false, + _ => unicode::Cased(self), + } + } + + /// Returns the case of this character: + /// [`Some(CharCase::Upper)`][`CharCase::Upper`] if [`self.is_uppercase()`][`char::is_uppercase`], + /// [`Some(CharCase::Lower)`][`CharCase::Lower`] if [`self.is_lowercase()`][`char::is_lowercase`], + /// [`Some(CharCase::Title)`][`CharCase::Title`] if [`self.is_titlecase()`][`char::is_titlecase`], and + /// `None` if [`!self.is_cased()`][`char::is_cased`]. + /// + /// # Examples + /// + /// ``` + /// #![feature(titlecase)] + /// use core::char::CharCase; + /// assert_eq!('a'.case(), Some(CharCase::Lower)); + /// assert_eq!('δ'.case(), Some(CharCase::Lower)); + /// assert_eq!('A'.case(), Some(CharCase::Upper)); + /// assert_eq!('Δ'.case(), Some(CharCase::Upper)); + /// assert_eq!('Dž'.case(), Some(CharCase::Title)); + /// assert_eq!('中'.case(), None); + /// ``` + #[must_use] + #[unstable(feature = "titlecase", issue = "153892")] + #[inline] + pub fn case(self) -> Option { + match self { + 'a'..='z' => Some(CharCase::Lower), + 'A'..='Z' => Some(CharCase::Upper), + '\0'..='\u{A9}' => None, + _ if !unicode::Cased(self) => None, + _ if unicode::Lowercase(self) => Some(CharCase::Lower), + _ if unicode::Uppercase(self) => Some(CharCase::Upper), + _ => Some(CharCase::Title), + } + } + /// Returns `true` if this `char` has the `Lowercase` property. /// /// `Lowercase` is described in Chapter 4 (Character Properties) of the [Unicode Standard] and @@ -825,6 +889,40 @@ impl char { } } + /// Returns `true` if this `char` has the general category for titlecase letters. + /// Conceptually, these characters consist of an uppercase portion followed by a lowercase portion. + /// + /// Titlecase letters (code points with the general category of `Lt`) are described in Chapter 4 + /// (Character Properties) of the [Unicode Standard] and specified in the [Unicode Character + /// Database][ucd] [`UnicodeData.txt`]. + /// + /// [Unicode Standard]: https://www.unicode.org/versions/latest/ + /// [ucd]: https://www.unicode.org/reports/tr44/ + /// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(titlecase)] + /// assert!('Dž'.is_titlecase()); + /// assert!('ῼ'.is_titlecase()); + /// assert!(!'D'.is_titlecase()); + /// assert!(!'z'.is_titlecase()); + /// assert!(!'中'.is_titlecase()); + /// assert!(!' '.is_titlecase()); + /// ``` + #[must_use] + #[unstable(feature = "titlecase", issue = "153892")] + #[inline] + pub fn is_titlecase(self) -> bool { + match self { + '\0'..='\u{01C4}' => false, + _ => self.is_cased() && !self.is_lowercase() && !self.is_uppercase(), + } + } + /// Returns `true` if this `char` has the `Uppercase` property. /// /// `Uppercase` is described in Chapter 4 (Character Properties) of the [Unicode Standard] and @@ -925,7 +1023,7 @@ impl char { #[inline] pub fn is_alphanumeric(self) -> bool { match self { - '0'..='9' | 'A'..='Z' | 'a'..='z' => true, + 'a'..='z' | 'A'..='Z' | '0'..='9' => true, '\0'..='\u{A9}' => false, _ => unicode::Alphabetic(self) || unicode::N(self), } @@ -976,26 +1074,6 @@ impl char { self > '\u{02FF}' && unicode::Grapheme_Extend(self) } - /// Returns `true` if this `char` has the `Cased` property. - /// - /// `Cased` is described in Chapter 4 (Character Properties) of the [Unicode Standard] and - /// specified in the [Unicode Character Database][ucd] [`DerivedCoreProperties.txt`]. - /// - /// [Unicode Standard]: https://www.unicode.org/versions/latest/ - /// [ucd]: https://www.unicode.org/reports/tr44/ - /// [`DerivedCoreProperties.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt - #[must_use] - #[inline] - #[doc(hidden)] - #[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")] - pub fn is_cased(self) -> bool { - match self { - 'A'..='Z' | 'a'..='z' => true, - '\0'..='\u{A9}' => false, - _ => unicode::Cased(self), - } - } - /// Returns `true` if this `char` has the `Case_Ignorable` property. /// /// `Case_Ignorable` is described in Chapter 4 (Character Properties) of the [Unicode Standard] and @@ -1119,7 +1197,7 @@ impl char { /// // convert into themselves. /// assert_eq!('山'.to_lowercase().to_string(), "山"); /// ``` - #[must_use = "this returns the lowercase character as a new iterator, \ + #[must_use = "this returns the lowercased character as a new iterator, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] #[inline] @@ -1127,9 +1205,115 @@ impl char { ToLowercase(CaseMappingIter::new(conversions::to_lower(self))) } + /// Returns an iterator that yields the titlecase mapping of this `char` as one or more + /// `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. + /// + /// If this `char` does not have an titlecase mapping, the iterator yields the same `char`. + /// + /// If this `char` has a one-to-one titlecase mapping given by the [Unicode Character + /// Database][ucd] [`UnicodeData.txt`], the iterator yields that `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`]. + /// + /// [`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. + /// + /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in + /// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion. + /// + /// [Unicode Standard]: https://www.unicode.org/versions/latest/ + /// + /// # Examples + /// + /// As an iterator: + /// + /// ``` + /// #![feature(titlecase)] + /// for c in 'ß'.to_titlecase() { + /// print!("{c}"); + /// } + /// println!(); + /// ``` + /// + /// Using `println!` directly: + /// + /// ``` + /// #![feature(titlecase)] + /// println!("{}", 'ß'.to_titlecase()); + /// ``` + /// + /// Both are equivalent to: + /// + /// ``` + /// println!("Ss"); + /// ``` + /// + /// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string): + /// + /// ``` + /// #![feature(titlecase)] + /// assert_eq!('c'.to_titlecase().to_string(), "C"); + /// assert_eq!('dž'.to_titlecase().to_string(), "Dž"); + /// assert_eq!('ῼ'.to_titlecase().to_string(), "ῼ"); + /// + /// // Sometimes the result is more than one character: + /// assert_eq!('ß'.to_titlecase().to_string(), "Ss"); + /// + /// // Characters that do not have separate cased forms + /// // convert into themselves. + /// assert_eq!('山'.to_titlecase().to_string(), "山"); + /// ``` + /// + /// # Note on locale + /// + /// 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 lowercase dotted 'i' is the same as the Latin. Therefore: + /// + /// ``` + /// #![feature(titlecase)] + /// let upper_i = 'i'.to_titlecase().to_string(); + /// ``` + /// + /// The value of `upper_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_titlecase()` does not take this into account, and so: + /// + /// ``` + /// #![feature(titlecase)] + /// let upper_i = 'i'.to_titlecase().to_string(); + /// + /// assert_eq!(upper_i, "I"); + /// ``` + /// + /// holds across languages. + #[must_use = "this returns the titlecased character as a new iterator, \ + without modifying the original"] + #[unstable(feature = "titlecase", issue = "153892")] + #[inline] + pub fn to_titlecase(self) -> ToTitlecase { + ToTitlecase(CaseMappingIter::new(conversions::to_title(self))) + } + /// 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. + /// /// If this `char` does not have an uppercase mapping, the iterator yields the same `char`. /// /// If this `char` has a one-to-one uppercase mapping given by the [Unicode Character @@ -1179,9 +1363,11 @@ impl char { /// /// ``` /// assert_eq!('c'.to_uppercase().to_string(), "C"); + /// 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(), "ΩΙ"); /// /// // Characters that do not have both uppercase and lowercase /// // convert into themselves. @@ -1190,7 +1376,7 @@ impl char { /// /// # Note on locale /// - /// In Turkish, the equivalent of 'i' in Latin has five forms instead of two: + /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: /// /// * 'Dotless': I / ı, sometimes written ï /// * 'Dotted': İ / i @@ -1202,7 +1388,7 @@ impl char { /// ``` /// /// The value of `upper_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`, it should + /// in `en-US`, it should be `"I"`, but if we're in `tr-TR` or `az-AZ`, it should /// be `"İ"`. `to_uppercase()` does not take this into account, and so: /// /// ``` @@ -1212,7 +1398,7 @@ impl char { /// ``` /// /// holds across languages. - #[must_use = "this returns the uppercase character as a new iterator, \ + #[must_use = "this returns the uppercased character as a new iterator, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] #[inline] @@ -1455,7 +1641,7 @@ impl char { #[rustc_const_stable(feature = "const_ascii_ctype_on_intrinsics", since = "1.47.0")] #[inline] pub const fn is_ascii_alphabetic(&self) -> bool { - matches!(*self, 'A'..='Z' | 'a'..='z') + matches!(*self, 'a'..='z' | 'A'..='Z') } /// Checks if the value is an ASCII uppercase character: diff --git a/library/core/src/char/mod.rs b/library/core/src/char/mod.rs index 82a3f6f916be3..3231c4193064c 100644 --- a/library/core/src/char/mod.rs +++ b/library/core/src/char/mod.rs @@ -363,13 +363,21 @@ impl fmt::Display for EscapeDebug { } macro_rules! casemappingiter_impls { - ($(#[$attr:meta])* $ITER_NAME:ident) => { + ( + #[$stab:meta] + #[$dendstab:meta] + #[$fusedstab:meta] + #[$exactstab:meta] + #[$displaystab:meta] + $(#[$attr:meta])* + $ITER_NAME:ident + ) => { $(#[$attr])* - #[stable(feature = "rust1", since = "1.0.0")] + #[$stab] #[derive(Debug, Clone)] pub struct $ITER_NAME(CaseMappingIter); - #[stable(feature = "rust1", since = "1.0.0")] + #[$stab] impl Iterator for $ITER_NAME { type Item = char; fn next(&mut self) -> Option { @@ -405,7 +413,7 @@ macro_rules! casemappingiter_impls { } } - #[stable(feature = "case_mapping_double_ended", since = "1.59.0")] + #[$dendstab] impl DoubleEndedIterator for $ITER_NAME { fn next_back(&mut self) -> Option { self.0.next_back() @@ -423,10 +431,10 @@ macro_rules! casemappingiter_impls { } } - #[stable(feature = "fused", since = "1.26.0")] + #[$fusedstab] impl FusedIterator for $ITER_NAME {} - #[stable(feature = "exact_size_case_mapping_iter", since = "1.35.0")] + #[$exactstab] impl ExactSizeIterator for $ITER_NAME { fn len(&self) -> usize { self.0.len() @@ -453,7 +461,7 @@ macro_rules! casemappingiter_impls { #[unstable(feature = "std_internals", issue = "none")] unsafe impl TrustedRandomAccess for $ITER_NAME {} - #[stable(feature = "char_struct_display", since = "1.16.0")] + #[$displaystab] impl fmt::Display for $ITER_NAME { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -464,23 +472,48 @@ macro_rules! casemappingiter_impls { } casemappingiter_impls! { - /// Returns an iterator that yields the lowercase equivalent of a `char`. + #[stable(feature = "rust1", since = "1.0.0")] + #[stable(feature = "case_mapping_double_ended", since = "1.59.0")] + #[stable(feature = "fused", since = "1.26.0")] + #[stable(feature = "exact_size_case_mapping_iter", since = "1.35.0")] + #[stable(feature = "char_struct_display", since = "1.16.0")] + /// Returns an iterator that yields the uppercase equivalent of a `char`. /// - /// This `struct` is created by the [`to_lowercase`] method on [`char`]. See + /// This `struct` is created by the [`to_uppercase`] method on [`char`]. See /// its documentation for more. /// - /// [`to_lowercase`]: char::to_lowercase - ToLowercase + /// [`to_uppercase`]: char::to_uppercase + ToUppercase } casemappingiter_impls! { - /// Returns an iterator that yields the uppercase equivalent of a `char`. + #[unstable(feature = "titlecase", issue = "153892")] + #[unstable(feature = "titlecase", issue = "153892")] + #[unstable(feature = "titlecase", issue = "153892")] + #[unstable(feature = "titlecase", issue = "153892")] + #[unstable(feature = "titlecase", issue = "153892")] + /// Returns an iterator that yields the titlecase equivalent of a `char`. /// - /// This `struct` is created by the [`to_uppercase`] method on [`char`]. See + /// This `struct` is created by the [`to_titlecase`] method on [`char`]. See /// its documentation for more. /// - /// [`to_uppercase`]: char::to_uppercase - ToUppercase + /// [`to_titlecase`]: char::to_titlecase + ToTitlecase +} + +casemappingiter_impls! { + #[stable(feature = "rust1", since = "1.0.0")] + #[stable(feature = "case_mapping_double_ended", since = "1.59.0")] + #[stable(feature = "fused", since = "1.26.0")] + #[stable(feature = "exact_size_case_mapping_iter", since = "1.35.0")] + #[stable(feature = "char_struct_display", since = "1.16.0")] + /// Returns an iterator that yields the lowercase equivalent of a `char`. + /// + /// This `struct` is created by the [`to_lowercase`] method on [`char`]. See + /// its documentation for more. + /// + /// [`to_lowercase`]: char::to_lowercase + ToLowercase } #[derive(Debug, Clone)] @@ -603,3 +636,23 @@ impl fmt::Display for TryFromCharError { #[stable(feature = "u8_from_char", since = "1.59.0")] impl Error for TryFromCharError {} + +/// The case of a cased character, +/// as returned by [`char::case`]. +/// +/// Titlecase characters conceptually are composed of an uppercase portion +/// followed by a lowercase portion. +/// The variant discriminants represent this: +/// the most significant bit represents whether the case +/// conceptually starts as uppercase, while the least significant bit +/// represents whether it conceptually ends as uppercase. +#[unstable(feature = "titlecase", issue = "153892")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum CharCase { + /// Lowercase. Corresponds to the `Lowercase` Unicode property. + Lower = 0b00, + /// Titlecase. Corresponds to the `Titlecase_Letter` Unicode general category. + Title = 0b10, + /// Uppercase. Corresponds to the `Uppercase` Unicode property. + Upper = 0b11, +} diff --git a/library/core/src/unicode/mod.rs b/library/core/src/unicode/mod.rs index 4c220e3ea0129..22a1166fdf168 100644 --- a/library/core/src/unicode/mod.rs +++ b/library/core/src/unicode/mod.rs @@ -4,12 +4,12 @@ // for use in alloc, not re-exported in std. #[rustfmt::skip] -pub use unicode_data::case_ignorable::lookup as Case_Ignorable; -pub use unicode_data::cased::lookup as Cased; pub use unicode_data::conversions; #[rustfmt::skip] pub(crate) use unicode_data::alphabetic::lookup as Alphabetic; +pub(crate) use unicode_data::case_ignorable::lookup as Case_Ignorable; +pub(crate) use unicode_data::cased::lookup as Cased; pub(crate) use unicode_data::grapheme_extend::lookup as Grapheme_Extend; pub(crate) use unicode_data::lowercase::lookup as Lowercase; pub(crate) use unicode_data::n::lookup as N; diff --git a/library/core/src/unicode/unicode_data.rs b/library/core/src/unicode/unicode_data.rs index dd3712669e500..f602cd5c5b6b3 100644 --- a/library/core/src/unicode/unicode_data.rs +++ b/library/core/src/unicode/unicode_data.rs @@ -9,7 +9,8 @@ // White_Space : 256 bytes, 19 codepoints in 8 ranges (U+000085 - U+003001) using cascading // to_lower : 1112 bytes, 1462 codepoints in 185 ranges (U+0000C0 - U+01E921) using 2-level LUT // to_upper : 1998 bytes, 1554 codepoints in 299 ranges (U+0000B5 - U+01E943) using 2-level LUT -// Total : 9657 bytes +// to_title : 340 bytes, 135 codepoints in 49 ranges (U+0000DF - U+00FB17) using 2-level LUT +// Total : 9997 bytes #[inline(always)] const fn bitset_search< @@ -823,14 +824,10 @@ pub mod conversions { unsafe { char::from_u32_unchecked(((plane as u32) << 16) | (low as u32)) } } - fn lookup(input: char, ascii: char, l1_lut: &L1Lut) -> [char; 3] { - if input.is_ascii() { - return [ascii, '\0', '\0']; - } - + fn lookup(input: char, l1_lut: &L1Lut) -> Option<[char; 3]> { let (input_high, input_low) = deconstruct(input); let Some(l2_lut) = l1_lut.l2_luts.get(input_high as usize) else { - return [input, '\0', '\0']; + return None; }; let idx = l2_lut.singles.binary_search_by(|(range, _)| { @@ -844,6 +841,7 @@ pub mod conversions { Ordering::Equal } }); + if let Ok(idx) = idx { // SAFETY: binary search guarantees that the index is in bounds. let &(range, output_delta) = unsafe { l2_lut.singles.get_unchecked(idx) }; @@ -852,7 +850,7 @@ pub mod conversions { let output_low = input_low.wrapping_add_signed(output_delta); // SAFETY: Table data are guaranteed to be valid Unicode. let output = unsafe { reconstruct(input_high, output_low) }; - return [output, '\0', '\0']; + return Some([output, '\0', '\0']); } }; @@ -861,18 +859,37 @@ pub mod conversions { let &(_, output_lows) = unsafe { l2_lut.multis.get_unchecked(idx) }; // SAFETY: Table data are guaranteed to be valid Unicode. let output = output_lows.map(|output_low| unsafe { reconstruct(input_high, output_low) }); - return output; + return Some(output); }; - [input, '\0', '\0'] + None } pub fn to_lower(c: char) -> [char; 3] { - lookup(c, c.to_ascii_lowercase(), &LOWERCASE_LUT) + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Lowercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + if c < '\u{C0}' { + return [c.to_ascii_lowercase(), '\0', '\0']; + } + + lookup(c, &LOWERCASE_LUT).unwrap_or([c, '\0', '\0']) } pub fn to_upper(c: char) -> [char; 3] { - lookup(c, c.to_ascii_uppercase(), &UPPERCASE_LUT) + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Uppercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + if c < '\u{B5}' { + return [c.to_ascii_uppercase(), '\0', '\0']; + } + + lookup(c, &UPPERCASE_LUT).unwrap_or([c, '\0', '\0']) + } + + pub fn to_title(c: char) -> [char; 3] { + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Titlecased%253A%5D-%5B%253AASCII%253A%5D&abb=on + if c < '\u{B5}' { + return [c.to_ascii_uppercase(), '\0', '\0']; + } + + lookup(c, &TITLECASE_LUT).or_else(|| lookup(c, &UPPERCASE_LUT)).unwrap_or([c, '\0', '\0']) } static LOWERCASE_LUT: L1Lut = L1Lut { @@ -1150,4 +1167,45 @@ pub mod conversions { }, ], }; + + static TITLECASE_LUT: L1Lut = L1Lut { + l2_luts: [ + L2Lut { + singles: &[ // 26 entries, 156 bytes + (Range::singleton(0x01c4), 1), (Range::singleton(0x01c5), 0), + (Range::singleton(0x01c6), -1), (Range::singleton(0x01c7), 1), + (Range::singleton(0x01c8), 0), (Range::singleton(0x01c9), -1), + (Range::singleton(0x01ca), 1), (Range::singleton(0x01cb), 0), + (Range::singleton(0x01cc), -1), (Range::singleton(0x01f1), 1), + (Range::singleton(0x01f2), 0), (Range::singleton(0x01f3), -1), + (Range::step_by_1(0x10d0..=0x10fa), 0), (Range::step_by_1(0x10fd..=0x10ff), 0), + (Range::step_by_1(0x1f80..=0x1f87), 8), (Range::step_by_1(0x1f88..=0x1f8f), 0), + (Range::step_by_1(0x1f90..=0x1f97), 8), (Range::step_by_1(0x1f98..=0x1f9f), 0), + (Range::step_by_1(0x1fa0..=0x1fa7), 8), (Range::step_by_1(0x1fa8..=0x1faf), 0), + (Range::singleton(0x1fb3), 9), (Range::singleton(0x1fbc), 0), (Range::singleton(0x1fc3), 9), + (Range::singleton(0x1fcc), 0), (Range::singleton(0x1ff3), 9), (Range::singleton(0x1ffc), 0), + ], + multis: &[ // 23 entries, 184 bytes + (0x00df, [0x0053, 0x0073, 0x0000]), (0x0587, [0x0535, 0x0582, 0x0000]), + (0x1fb2, [0x1fba, 0x0345, 0x0000]), (0x1fb4, [0x0386, 0x0345, 0x0000]), + (0x1fb7, [0x0391, 0x0342, 0x0345]), (0x1fc2, [0x1fca, 0x0345, 0x0000]), + (0x1fc4, [0x0389, 0x0345, 0x0000]), (0x1fc7, [0x0397, 0x0342, 0x0345]), + (0x1ff2, [0x1ffa, 0x0345, 0x0000]), (0x1ff4, [0x038f, 0x0345, 0x0000]), + (0x1ff7, [0x03a9, 0x0342, 0x0345]), (0xfb00, [0x0046, 0x0066, 0x0000]), + (0xfb01, [0x0046, 0x0069, 0x0000]), (0xfb02, [0x0046, 0x006c, 0x0000]), + (0xfb03, [0x0046, 0x0066, 0x0069]), (0xfb04, [0x0046, 0x0066, 0x006c]), + (0xfb05, [0x0053, 0x0074, 0x0000]), (0xfb06, [0x0053, 0x0074, 0x0000]), + (0xfb13, [0x0544, 0x0576, 0x0000]), (0xfb14, [0x0544, 0x0565, 0x0000]), + (0xfb15, [0x0544, 0x056b, 0x0000]), (0xfb16, [0x054e, 0x0576, 0x0000]), + (0xfb17, [0x0544, 0x056d, 0x0000]), + ], + }, + L2Lut { + singles: &[ // 0 entries, 0 bytes + ], + multis: &[ // 0 entries, 0 bytes + ], + }, + ], + }; } diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index f0f6a24429284..aa20585953b7c 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -1,5 +1,6 @@ +use std::char::{self, CharCase}; +use std::str; use std::str::FromStr; -use std::{char, str}; #[test] fn test_convert() { @@ -39,6 +40,29 @@ fn test_from_str() { assert!(char::from_str("abc").is_err()); } +#[test] +fn test_is_cased() { + assert!('a'.is_cased()); + assert!('ö'.is_cased()); + assert!('ß'.is_cased()); + assert!('Ü'.is_cased()); + assert!('P'.is_cased()); + assert!('ª'.is_cased()); + assert!(!'攂'.is_cased()); +} + +#[test] +fn test_char_case() { + for c in '\0'..='\u{10FFFF}' { + match c.case() { + None => assert!(!c.is_cased()), + Some(CharCase::Lower) => assert!(c.is_lowercase()), + Some(CharCase::Upper) => assert!(c.is_uppercase()), + Some(CharCase::Title) => assert!(c.is_titlecase()), + } + } +} + #[test] fn test_is_lowercase() { assert!('a'.is_lowercase()); @@ -48,6 +72,17 @@ fn test_is_lowercase() { assert!(!'P'.is_lowercase()); } +#[test] +fn test_is_titlecase() { + assert!('Dž'.is_titlecase()); + assert!('ᾨ'.is_titlecase()); + assert!(!'h'.is_titlecase()); + assert!(!'ä'.is_titlecase()); + assert!(!'ß'.is_titlecase()); + assert!(!'Ö'.is_titlecase()); + assert!(!'T'.is_titlecase()); +} + #[test] fn test_is_uppercase() { assert!(!'h'.is_uppercase()); @@ -57,6 +92,26 @@ fn test_is_uppercase() { assert!('T'.is_uppercase()); } +#[test] +fn titlecase_fast_path() { + for c in '\0'..='\u{01C4}' { + assert!(!(c.is_cased() && !c.is_lowercase() && !c.is_uppercase())) + } +} + +#[test] +fn at_most_one_case() { + for c in '\0'..='\u{10FFFF}' { + assert_eq!( + !c.is_cased() as u8 + + c.is_lowercase() as u8 + + c.is_uppercase() as u8 + + c.is_titlecase() as u8, + 1 + ); + } +} + #[test] fn test_is_whitespace() { assert!(' '.is_whitespace()); diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 72112f8b01133..5f7039641dae3 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -111,6 +111,7 @@ #![feature(step_trait)] #![feature(str_internals)] #![feature(strict_provenance_lints)] +#![feature(titlecase)] #![feature(trusted_len)] #![feature(trusted_random_access)] #![feature(try_blocks)] diff --git a/library/coretests/tests/unicode.rs b/library/coretests/tests/unicode.rs index 1fae74f0f11ef..a8a221db8f955 100644 --- a/library/coretests/tests/unicode.rs +++ b/library/coretests/tests/unicode.rs @@ -27,17 +27,21 @@ fn test_boolean_property(ranges: &[RangeInclusive], lookup: fn(char) -> bo } #[track_caller] -fn test_case_mapping(ranges: &[(char, [char; 3])], lookup: fn(char) -> [char; 3]) { +fn test_case_mapping( + ranges: &[(char, [char; 3])], + lookup: fn(char) -> [char; 3], + fallback: fn(char) -> [char; 3], +) { let mut start = '\u{80}'; for &(key, val) in ranges { for c in start..key { - assert_eq!(lookup(c), [c, '\0', '\0'], "{c:?}"); + assert_eq!(lookup(c), fallback(c), "{c:?}"); } assert_eq!(lookup(key), val, "{key:?}"); start = char::from_u32(key as u32 + 1).unwrap(); } for c in start..=char::MAX { - assert_eq!(lookup(c), [c, '\0', '\0'], "{c:?}"); + assert_eq!(lookup(c), fallback(c), "{c:?}"); } } @@ -45,6 +49,7 @@ fn test_case_mapping(ranges: &[(char, [char; 3])], lookup: fn(char) -> [char; 3] #[cfg_attr(miri, ignore)] // Miri is too slow fn alphabetic() { test_boolean_property(test_data::ALPHABETIC, unicode_data::alphabetic::lookup); + test_boolean_property(test_data::ALPHABETIC, char::is_alphabetic); } #[test] @@ -57,6 +62,7 @@ fn case_ignorable() { #[cfg_attr(miri, ignore)] // Miri is too slow fn cased() { test_boolean_property(test_data::CASED, unicode_data::cased::lookup); + test_boolean_property(test_data::CASED, char::is_cased); } #[test] @@ -69,34 +75,52 @@ fn grapheme_extend() { #[cfg_attr(miri, ignore)] // Miri is too slow fn lowercase() { test_boolean_property(test_data::LOWERCASE, unicode_data::lowercase::lookup); + test_boolean_property(test_data::LOWERCASE, char::is_lowercase); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn n() { test_boolean_property(test_data::N, unicode_data::n::lookup); + test_boolean_property(test_data::N, char::is_numeric); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn uppercase() { test_boolean_property(test_data::UPPERCASE, unicode_data::uppercase::lookup); + test_boolean_property(test_data::UPPERCASE, char::is_uppercase); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn white_space() { test_boolean_property(test_data::WHITE_SPACE, unicode_data::white_space::lookup); + test_boolean_property(test_data::WHITE_SPACE, char::is_whitespace); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn to_lowercase() { - test_case_mapping(test_data::TO_LOWER, unicode_data::conversions::to_lower); + test_case_mapping(test_data::TO_LOWER, unicode_data::conversions::to_lower, |c| { + [c, '\0', '\0'] + }); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn to_uppercase() { - test_case_mapping(test_data::TO_UPPER, unicode_data::conversions::to_upper); + test_case_mapping(test_data::TO_UPPER, unicode_data::conversions::to_upper, |c| { + [c, '\0', '\0'] + }); +} + +#[test] +#[cfg_attr(miri, ignore)] // Miri is too slow +fn to_titlecase() { + test_case_mapping( + test_data::TO_TITLE, + unicode_data::conversions::to_title, + unicode_data::conversions::to_upper, + ); } diff --git a/library/coretests/tests/unicode/test_data.rs b/library/coretests/tests/unicode/test_data.rs index cfc695475b1f8..3071aedcae07a 100644 --- a/library/coretests/tests/unicode/test_data.rs +++ b/library/coretests/tests/unicode/test_data.rs @@ -2900,3 +2900,81 @@ pub(super) static TO_UPPER: &[(char, [char; 3]); 1554] = &[ ('\u{1e942}', ['\u{1e920}', '\u{0}', '\u{0}']), ('\u{1e943}', ['\u{1e921}', '\u{0}', '\u{0}']), ]; + +#[rustfmt::skip] +pub(super) static TO_TITLE: &[(char, [char; 3]); 135] = &[ + ('\u{df}', ['S', 's', '\u{0}']), ('\u{1c4}', ['\u{1c5}', '\u{0}', '\u{0}']), + ('\u{1c5}', ['\u{1c5}', '\u{0}', '\u{0}']), ('\u{1c6}', ['\u{1c5}', '\u{0}', '\u{0}']), + ('\u{1c7}', ['\u{1c8}', '\u{0}', '\u{0}']), ('\u{1c8}', ['\u{1c8}', '\u{0}', '\u{0}']), + ('\u{1c9}', ['\u{1c8}', '\u{0}', '\u{0}']), ('\u{1ca}', ['\u{1cb}', '\u{0}', '\u{0}']), + ('\u{1cb}', ['\u{1cb}', '\u{0}', '\u{0}']), ('\u{1cc}', ['\u{1cb}', '\u{0}', '\u{0}']), + ('\u{1f1}', ['\u{1f2}', '\u{0}', '\u{0}']), ('\u{1f2}', ['\u{1f2}', '\u{0}', '\u{0}']), + ('\u{1f3}', ['\u{1f2}', '\u{0}', '\u{0}']), ('\u{587}', ['\u{535}', '\u{582}', '\u{0}']), + ('\u{10d0}', ['\u{10d0}', '\u{0}', '\u{0}']), ('\u{10d1}', ['\u{10d1}', '\u{0}', '\u{0}']), + ('\u{10d2}', ['\u{10d2}', '\u{0}', '\u{0}']), ('\u{10d3}', ['\u{10d3}', '\u{0}', '\u{0}']), + ('\u{10d4}', ['\u{10d4}', '\u{0}', '\u{0}']), ('\u{10d5}', ['\u{10d5}', '\u{0}', '\u{0}']), + ('\u{10d6}', ['\u{10d6}', '\u{0}', '\u{0}']), ('\u{10d7}', ['\u{10d7}', '\u{0}', '\u{0}']), + ('\u{10d8}', ['\u{10d8}', '\u{0}', '\u{0}']), ('\u{10d9}', ['\u{10d9}', '\u{0}', '\u{0}']), + ('\u{10da}', ['\u{10da}', '\u{0}', '\u{0}']), ('\u{10db}', ['\u{10db}', '\u{0}', '\u{0}']), + ('\u{10dc}', ['\u{10dc}', '\u{0}', '\u{0}']), ('\u{10dd}', ['\u{10dd}', '\u{0}', '\u{0}']), + ('\u{10de}', ['\u{10de}', '\u{0}', '\u{0}']), ('\u{10df}', ['\u{10df}', '\u{0}', '\u{0}']), + ('\u{10e0}', ['\u{10e0}', '\u{0}', '\u{0}']), ('\u{10e1}', ['\u{10e1}', '\u{0}', '\u{0}']), + ('\u{10e2}', ['\u{10e2}', '\u{0}', '\u{0}']), ('\u{10e3}', ['\u{10e3}', '\u{0}', '\u{0}']), + ('\u{10e4}', ['\u{10e4}', '\u{0}', '\u{0}']), ('\u{10e5}', ['\u{10e5}', '\u{0}', '\u{0}']), + ('\u{10e6}', ['\u{10e6}', '\u{0}', '\u{0}']), ('\u{10e7}', ['\u{10e7}', '\u{0}', '\u{0}']), + ('\u{10e8}', ['\u{10e8}', '\u{0}', '\u{0}']), ('\u{10e9}', ['\u{10e9}', '\u{0}', '\u{0}']), + ('\u{10ea}', ['\u{10ea}', '\u{0}', '\u{0}']), ('\u{10eb}', ['\u{10eb}', '\u{0}', '\u{0}']), + ('\u{10ec}', ['\u{10ec}', '\u{0}', '\u{0}']), ('\u{10ed}', ['\u{10ed}', '\u{0}', '\u{0}']), + ('\u{10ee}', ['\u{10ee}', '\u{0}', '\u{0}']), ('\u{10ef}', ['\u{10ef}', '\u{0}', '\u{0}']), + ('\u{10f0}', ['\u{10f0}', '\u{0}', '\u{0}']), ('\u{10f1}', ['\u{10f1}', '\u{0}', '\u{0}']), + ('\u{10f2}', ['\u{10f2}', '\u{0}', '\u{0}']), ('\u{10f3}', ['\u{10f3}', '\u{0}', '\u{0}']), + ('\u{10f4}', ['\u{10f4}', '\u{0}', '\u{0}']), ('\u{10f5}', ['\u{10f5}', '\u{0}', '\u{0}']), + ('\u{10f6}', ['\u{10f6}', '\u{0}', '\u{0}']), ('\u{10f7}', ['\u{10f7}', '\u{0}', '\u{0}']), + ('\u{10f8}', ['\u{10f8}', '\u{0}', '\u{0}']), ('\u{10f9}', ['\u{10f9}', '\u{0}', '\u{0}']), + ('\u{10fa}', ['\u{10fa}', '\u{0}', '\u{0}']), ('\u{10fd}', ['\u{10fd}', '\u{0}', '\u{0}']), + ('\u{10fe}', ['\u{10fe}', '\u{0}', '\u{0}']), ('\u{10ff}', ['\u{10ff}', '\u{0}', '\u{0}']), + ('\u{1f80}', ['\u{1f88}', '\u{0}', '\u{0}']), ('\u{1f81}', ['\u{1f89}', '\u{0}', '\u{0}']), + ('\u{1f82}', ['\u{1f8a}', '\u{0}', '\u{0}']), ('\u{1f83}', ['\u{1f8b}', '\u{0}', '\u{0}']), + ('\u{1f84}', ['\u{1f8c}', '\u{0}', '\u{0}']), ('\u{1f85}', ['\u{1f8d}', '\u{0}', '\u{0}']), + ('\u{1f86}', ['\u{1f8e}', '\u{0}', '\u{0}']), ('\u{1f87}', ['\u{1f8f}', '\u{0}', '\u{0}']), + ('\u{1f88}', ['\u{1f88}', '\u{0}', '\u{0}']), ('\u{1f89}', ['\u{1f89}', '\u{0}', '\u{0}']), + ('\u{1f8a}', ['\u{1f8a}', '\u{0}', '\u{0}']), ('\u{1f8b}', ['\u{1f8b}', '\u{0}', '\u{0}']), + ('\u{1f8c}', ['\u{1f8c}', '\u{0}', '\u{0}']), ('\u{1f8d}', ['\u{1f8d}', '\u{0}', '\u{0}']), + ('\u{1f8e}', ['\u{1f8e}', '\u{0}', '\u{0}']), ('\u{1f8f}', ['\u{1f8f}', '\u{0}', '\u{0}']), + ('\u{1f90}', ['\u{1f98}', '\u{0}', '\u{0}']), ('\u{1f91}', ['\u{1f99}', '\u{0}', '\u{0}']), + ('\u{1f92}', ['\u{1f9a}', '\u{0}', '\u{0}']), ('\u{1f93}', ['\u{1f9b}', '\u{0}', '\u{0}']), + ('\u{1f94}', ['\u{1f9c}', '\u{0}', '\u{0}']), ('\u{1f95}', ['\u{1f9d}', '\u{0}', '\u{0}']), + ('\u{1f96}', ['\u{1f9e}', '\u{0}', '\u{0}']), ('\u{1f97}', ['\u{1f9f}', '\u{0}', '\u{0}']), + ('\u{1f98}', ['\u{1f98}', '\u{0}', '\u{0}']), ('\u{1f99}', ['\u{1f99}', '\u{0}', '\u{0}']), + ('\u{1f9a}', ['\u{1f9a}', '\u{0}', '\u{0}']), ('\u{1f9b}', ['\u{1f9b}', '\u{0}', '\u{0}']), + ('\u{1f9c}', ['\u{1f9c}', '\u{0}', '\u{0}']), ('\u{1f9d}', ['\u{1f9d}', '\u{0}', '\u{0}']), + ('\u{1f9e}', ['\u{1f9e}', '\u{0}', '\u{0}']), ('\u{1f9f}', ['\u{1f9f}', '\u{0}', '\u{0}']), + ('\u{1fa0}', ['\u{1fa8}', '\u{0}', '\u{0}']), ('\u{1fa1}', ['\u{1fa9}', '\u{0}', '\u{0}']), + ('\u{1fa2}', ['\u{1faa}', '\u{0}', '\u{0}']), ('\u{1fa3}', ['\u{1fab}', '\u{0}', '\u{0}']), + ('\u{1fa4}', ['\u{1fac}', '\u{0}', '\u{0}']), ('\u{1fa5}', ['\u{1fad}', '\u{0}', '\u{0}']), + ('\u{1fa6}', ['\u{1fae}', '\u{0}', '\u{0}']), ('\u{1fa7}', ['\u{1faf}', '\u{0}', '\u{0}']), + ('\u{1fa8}', ['\u{1fa8}', '\u{0}', '\u{0}']), ('\u{1fa9}', ['\u{1fa9}', '\u{0}', '\u{0}']), + ('\u{1faa}', ['\u{1faa}', '\u{0}', '\u{0}']), ('\u{1fab}', ['\u{1fab}', '\u{0}', '\u{0}']), + ('\u{1fac}', ['\u{1fac}', '\u{0}', '\u{0}']), ('\u{1fad}', ['\u{1fad}', '\u{0}', '\u{0}']), + ('\u{1fae}', ['\u{1fae}', '\u{0}', '\u{0}']), ('\u{1faf}', ['\u{1faf}', '\u{0}', '\u{0}']), + ('\u{1fb2}', ['\u{1fba}', '\u{345}', '\u{0}']), + ('\u{1fb3}', ['\u{1fbc}', '\u{0}', '\u{0}']), ('\u{1fb4}', ['\u{386}', '\u{345}', '\u{0}']), + ('\u{1fb7}', ['\u{391}', '\u{342}', '\u{345}']), + ('\u{1fbc}', ['\u{1fbc}', '\u{0}', '\u{0}']), + ('\u{1fc2}', ['\u{1fca}', '\u{345}', '\u{0}']), + ('\u{1fc3}', ['\u{1fcc}', '\u{0}', '\u{0}']), ('\u{1fc4}', ['\u{389}', '\u{345}', '\u{0}']), + ('\u{1fc7}', ['\u{397}', '\u{342}', '\u{345}']), + ('\u{1fcc}', ['\u{1fcc}', '\u{0}', '\u{0}']), + ('\u{1ff2}', ['\u{1ffa}', '\u{345}', '\u{0}']), + ('\u{1ff3}', ['\u{1ffc}', '\u{0}', '\u{0}']), ('\u{1ff4}', ['\u{38f}', '\u{345}', '\u{0}']), + ('\u{1ff7}', ['\u{3a9}', '\u{342}', '\u{345}']), + ('\u{1ffc}', ['\u{1ffc}', '\u{0}', '\u{0}']), ('\u{fb00}', ['F', 'f', '\u{0}']), + ('\u{fb01}', ['F', 'i', '\u{0}']), ('\u{fb02}', ['F', 'l', '\u{0}']), + ('\u{fb03}', ['F', 'f', 'i']), ('\u{fb04}', ['F', 'f', 'l']), + ('\u{fb05}', ['S', 't', '\u{0}']), ('\u{fb06}', ['S', 't', '\u{0}']), + ('\u{fb13}', ['\u{544}', '\u{576}', '\u{0}']), + ('\u{fb14}', ['\u{544}', '\u{565}', '\u{0}']), + ('\u{fb15}', ['\u{544}', '\u{56b}', '\u{0}']), + ('\u{fb16}', ['\u{54e}', '\u{576}', '\u{0}']), + ('\u{fb17}', ['\u{544}', '\u{56d}', '\u{0}']), +]; diff --git a/src/tools/unicode-table-generator/src/case_mapping.rs b/src/tools/unicode-table-generator/src/case_mapping.rs index 6345cb8ef2532..b7b385542ef53 100644 --- a/src/tools/unicode-table-generator/src/case_mapping.rs +++ b/src/tools/unicode-table-generator/src/case_mapping.rs @@ -48,7 +48,7 @@ use std::ops::RangeInclusive; use crate::fmt_helpers::Hex; use crate::{UnicodeData, fmt_list}; -pub(crate) fn generate_case_mapping(data: &UnicodeData) -> (String, [(String, usize); 2]) { +pub(crate) fn generate_case_mapping(data: &UnicodeData) -> (String, [(String, usize); 3]) { let mut file = String::new(); file.push_str("\n\n"); @@ -59,7 +59,10 @@ pub(crate) fn generate_case_mapping(data: &UnicodeData) -> (String, [(String, us file.push_str("\n\n"); let (upper_tables, upper_desc, upper_size) = generate_tables("UPPER", &data.to_upper); file.push_str(&upper_tables); - (file, [(lower_desc, lower_size), (upper_desc, upper_size)]) + file.push_str("\n\n"); + let (title_tables, title_desc, title_size) = generate_tables("TITLE", &data.to_title); + file.push_str(&title_tables); + (file, [(lower_desc, lower_size), (upper_desc, upper_size), (title_desc, title_size)]) } // So far, only planes 0 and 1 (Basic Multilingual Plane and Supplementary @@ -336,14 +339,10 @@ unsafe fn reconstruct(plane: u16, low: u16) -> char { unsafe { char::from_u32_unchecked(((plane as u32) << 16) | (low as u32)) } } -fn lookup(input: char, ascii: char, l1_lut: &L1Lut) -> [char; 3] { - if input.is_ascii() { - return [ascii, '\0', '\0']; - } - +fn lookup(input: char, l1_lut: &L1Lut) -> Option<[char; 3]> { let (input_high, input_low) = deconstruct(input); let Some(l2_lut) = l1_lut.l2_luts.get(input_high as usize) else { - return [input, '\0', '\0']; + return None; }; let idx = l2_lut.singles.binary_search_by(|(range, _)| { @@ -357,6 +356,7 @@ fn lookup(input: char, ascii: char, l1_lut: &L1Lut) -> [char; 3] { Ordering::Equal } }); + if let Ok(idx) = idx { // SAFETY: binary search guarantees that the index is in bounds. let &(range, output_delta) = unsafe { l2_lut.singles.get_unchecked(idx) }; @@ -365,7 +365,7 @@ fn lookup(input: char, ascii: char, l1_lut: &L1Lut) -> [char; 3] { let output_low = input_low.wrapping_add_signed(output_delta); // SAFETY: Table data are guaranteed to be valid Unicode. let output = unsafe { reconstruct(input_high, output_low) }; - return [output, '\0', '\0']; + return Some([output, '\0', '\0']); } }; @@ -374,17 +374,36 @@ fn lookup(input: char, ascii: char, l1_lut: &L1Lut) -> [char; 3] { let &(_, output_lows) = unsafe { l2_lut.multis.get_unchecked(idx) }; // SAFETY: Table data are guaranteed to be valid Unicode. let output = output_lows.map(|output_low| unsafe { reconstruct(input_high, output_low) }); - return output; + return Some(output); }; - [input, '\0', '\0'] + None } pub fn to_lower(c: char) -> [char; 3] { - lookup(c, c.to_ascii_lowercase(), &LOWERCASE_LUT) + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Lowercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + if c < '\u{C0}' { + return [c.to_ascii_lowercase(), '\0', '\0']; + } + + lookup(c, &LOWERCASE_LUT).unwrap_or([c, '\0', '\0']) } pub fn to_upper(c: char) -> [char; 3] { - lookup(c, c.to_ascii_uppercase(), &UPPERCASE_LUT) + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Uppercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + if c < '\u{B5}' { + return [c.to_ascii_uppercase(), '\0', '\0']; + } + + lookup(c, &UPPERCASE_LUT).unwrap_or([c, '\0', '\0']) +} + +pub fn to_title(c: char) -> [char; 3] { + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Titlecased%253A%5D-%5B%253AASCII%253A%5D&abb=on + if c < '\u{B5}' { + return [c.to_ascii_uppercase(), '\0', '\0']; + } + + lookup(c, &TITLECASE_LUT).or_else(|| lookup(c, &UPPERCASE_LUT)).unwrap_or([c, '\0', '\0']) } "; diff --git a/src/tools/unicode-table-generator/src/main.rs b/src/tools/unicode-table-generator/src/main.rs index 4e7c40efc1234..cdd137ff56a52 100644 --- a/src/tools/unicode-table-generator/src/main.rs +++ b/src/tools/unicode-table-generator/src/main.rs @@ -100,32 +100,25 @@ static PROPERTIES: &[&str] = &[ struct UnicodeData { ranges: Vec<(&'static str, Vec>)>, + /// Only stores mappings that are not to self to_upper: BTreeMap, + /// Only stores mappings that differ from `to_upper` + to_title: BTreeMap, + /// Only stores mappings that are not to self to_lower: BTreeMap, } -fn to_mapping(origin: u32, codepoints: Vec) -> Option<[u32; 3]> { - let mut a = None; - let mut b = None; - let mut c = None; - - for codepoint in codepoints { - if origin == codepoint.value() { - return None; - } - - if a.is_none() { - a = Some(codepoint.value()); - } else if b.is_none() { - b = Some(codepoint.value()); - } else if c.is_none() { - c = Some(codepoint.value()); - } else { - panic!("more than 3 mapped codepoints") - } +fn to_mapping( + if_different_from: &[ucd_parse::Codepoint], + codepoints: &[ucd_parse::Codepoint], +) -> Option<[u32; 3]> { + if codepoints == if_different_from { + return None; } - Some([a.unwrap(), b.unwrap_or(0), c.unwrap_or(0)]) + let mut ret = [ucd_parse::Codepoint::default(); 3]; + ret[0..codepoints.len()].copy_from_slice(codepoints); + Some(ret.map(ucd_parse::Codepoint::value)) } static UNICODE_DIRECTORY: &str = "unicode-downloads"; @@ -145,8 +138,7 @@ fn load_data() -> UnicodeData { } } - let mut to_lower = BTreeMap::new(); - let mut to_upper = BTreeMap::new(); + let [mut to_lower, mut to_upper, mut to_title] = [const { BTreeMap::new() }; 3]; for row in ucd_parse::UnicodeDataExpander::new( ucd_parse::parse::<_, ucd_parse::UnicodeData>(&UNICODE_DIRECTORY).unwrap(), ) { @@ -172,6 +164,11 @@ fn load_data() -> UnicodeData { { to_upper.insert(row.codepoint.value(), [mapped.value(), 0, 0]); } + if let Some(mapped) = row.simple_titlecase_mapping + && Some(mapped) != row.simple_uppercase_mapping + { + to_title.insert(row.codepoint.value(), [mapped.value(), 0, 0]); + } } for row in ucd_parse::parse::<_, ucd_parse::SpecialCaseMapping>(&UNICODE_DIRECTORY).unwrap() { @@ -181,12 +178,15 @@ fn load_data() -> UnicodeData { } let key = row.codepoint.value(); - if let Some(lower) = to_mapping(key, row.lowercase) { + if let Some(lower) = to_mapping(&[row.codepoint], &row.lowercase) { to_lower.insert(key, lower); } - if let Some(upper) = to_mapping(key, row.uppercase) { + if let Some(upper) = to_mapping(&[row.codepoint], &row.uppercase) { to_upper.insert(key, upper); } + if let Some(title) = to_mapping(&row.uppercase, &row.titlecase) { + to_title.insert(key, title); + } } // Filter out ASCII codepoints. @@ -207,7 +207,7 @@ fn load_data() -> UnicodeData { .collect(); properties.sort_by_key(|p| p.0); - UnicodeData { ranges: properties, to_lower, to_upper } + UnicodeData { ranges: properties, to_lower, to_title, to_upper } } fn main() { @@ -259,7 +259,7 @@ fn main() { total_bytes += emitter.bytes_used; } let (conversions, sizes) = case_mapping::generate_case_mapping(&unicode_data); - for (name, (desc, size)) in ["to_lower", "to_upper"].iter().zip(sizes) { + for (name, (desc, size)) in ["to_lower", "to_upper", "to_title"].iter().zip(sizes) { table_file.push_str(&format!("// {:16}: {:5} bytes, {desc}\n", name, size,)); total_bytes += size; } @@ -369,7 +369,11 @@ pub(super) static {prop_upper}: &[RangeInclusive; {is_true_len}] = &[{is_t .unwrap(); } - for (name, lut) in ["TO_LOWER", "TO_UPPER"].iter().zip([&data.to_lower, &data.to_upper]) { + for (name, lut) in ["TO_LOWER", "TO_UPPER", "TO_TITLE"].iter().zip([ + &data.to_lower, + &data.to_upper, + &data.to_title, + ]) { let lut = lut .iter() .map(|(key, values)| { From faddfb500118d3f1ad5bd8a87a26613aff490261 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 22 Mar 2026 18:34:43 +0100 Subject: [PATCH 06/25] Add new `LintBuffer::dyn_buffer_lint` method --- compiler/rustc_errors/src/decorate_diag.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/compiler/rustc_errors/src/decorate_diag.rs b/compiler/rustc_errors/src/decorate_diag.rs index a11082e296638..c7e0d508c1732 100644 --- a/compiler/rustc_errors/src/decorate_diag.rs +++ b/compiler/rustc_errors/src/decorate_diag.rs @@ -81,4 +81,21 @@ impl LintBuffer { diagnostic: decorate.into(), }); } + + pub fn dyn_buffer_lint< + F: for<'a> FnOnce(DiagCtxtHandle<'a>, Level) -> Diag<'a, ()> + DynSend + 'static, + >( + &mut self, + lint: &'static Lint, + node_id: NodeId, + span: impl Into, + callback: F, + ) { + self.add_early_lint(BufferedEarlyLint { + lint_id: LintId::of(lint), + node_id, + span: Some(span.into()), + diagnostic: DecorateDiagCompat::Dynamic(Box::new(callback)), + }); + } } From 33fbecc394c54faa8a5fcdd72603ddfe6eb5f1f8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 22 Mar 2026 18:35:02 +0100 Subject: [PATCH 07/25] Remove `BuiltinLintDiag` usage in `rustc_ast_passes` --- .../rustc_ast_passes/src/ast_validation.rs | 39 ++++++++--- compiler/rustc_ast_passes/src/errors.rs | 69 +++++++++++++++++++ compiler/rustc_lint/src/early/diagnostics.rs | 23 ------- compiler/rustc_lint/src/lints.rs | 69 ------------------- compiler/rustc_lint_defs/src/lib.rs | 8 --- 5 files changed, 98 insertions(+), 110 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index bdb0981311248..dd14e91435690 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -27,10 +27,9 @@ use rustc_ast::*; use rustc_ast_pretty::pprust::{self, State}; use rustc_attr_parsing::validate_attr; use rustc_data_structures::fx::FxIndexMap; -use rustc_errors::{DiagCtxtHandle, LintBuffer}; +use rustc_errors::{DiagCtxtHandle, Diagnostic, LintBuffer}; use rustc_feature::Features; use rustc_session::Session; -use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{ DEPRECATED_WHERE_CLAUSE_LOCATION, MISSING_ABI, MISSING_UNSAFE_ON_EXTERN, PATTERNS_IN_FNS_WITHOUT_BODY, UNUSED_VISIBILITIES, @@ -1424,7 +1423,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { UNUSED_VISIBILITIES, item.id, item.vis.span, - BuiltinLintDiag::UnusedVisibility(item.vis.span), + errors::UnusedVisibility { span: item.vis.span }, ) } @@ -1731,14 +1730,19 @@ impl<'a> Visitor<'a> for AstValidator<'a> { Self::check_decl_no_pat(&sig.decl, |span, ident, mut_ident| { if mut_ident && matches!(ctxt, FnCtxt::Assoc(_)) { if let Some(ident) = ident { - self.lint_buffer.buffer_lint( + let is_foreign = matches!(ctxt, FnCtxt::Foreign); + self.lint_buffer.dyn_buffer_lint( PATTERNS_IN_FNS_WITHOUT_BODY, id, span, - BuiltinLintDiag::PatternsInFnsWithoutBody { - span, - ident, - is_foreign: matches!(ctxt, FnCtxt::Foreign), + move |dcx, level| { + let sub = errors::PatternsInFnsWithoutBodySub { ident, span }; + if is_foreign { + errors::PatternsInFnsWithoutBody::Foreign { sub } + } else { + errors::PatternsInFnsWithoutBody::Bodiless { sub } + } + .into_diag(dcx, level) }, ) } @@ -1828,11 +1832,26 @@ impl<'a> Visitor<'a> for AstValidator<'a> { Some((right, snippet)) } }; - self.lint_buffer.buffer_lint( + let left_sp = err.span; + self.lint_buffer.dyn_buffer_lint( DEPRECATED_WHERE_CLAUSE_LOCATION, item.id, err.span, - BuiltinLintDiag::DeprecatedWhereclauseLocation(err.span, sugg), + move |dcx, level| { + let suggestion = match sugg { + Some((right_sp, sugg)) => { + errors::DeprecatedWhereClauseLocationSugg::MoveToEnd { + left: left_sp, + right: right_sp, + sugg, + } + } + None => { + errors::DeprecatedWhereClauseLocationSugg::RemoveWhere { span: left_sp } + } + }; + errors::DeprecatedWhereClauseLocation { suggestion }.into_diag(dcx, level) + }, ); } diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index 390c1556f191c..b3a22c0c99549 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -1150,3 +1150,72 @@ pub(crate) struct RequiresRustAbi { #[label("not using the Rust ABI because of this")] pub extern_abi_span: Span, } + +#[derive(Diagnostic)] +#[diag("visibility qualifiers have no effect on `const _` declarations")] +#[note("`const _` does not declare a name, so there is nothing for the qualifier to apply to")] +pub(crate) struct UnusedVisibility { + #[suggestion( + "remove the qualifier", + style = "short", + code = "", + applicability = "machine-applicable" + )] + pub span: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "remove `mut` from the parameter", + code = "{ident}", + applicability = "machine-applicable" +)] +pub(crate) struct PatternsInFnsWithoutBodySub { + #[primary_span] + pub span: Span, + + pub ident: Ident, +} + +#[derive(Diagnostic)] +pub(crate) enum PatternsInFnsWithoutBody { + #[diag("patterns aren't allowed in foreign function declarations")] + Foreign { + #[subdiagnostic] + sub: PatternsInFnsWithoutBodySub, + }, + #[diag("patterns aren't allowed in functions without bodies")] + Bodiless { + #[subdiagnostic] + sub: PatternsInFnsWithoutBodySub, + }, +} + +#[derive(Diagnostic)] +#[diag("where clause not allowed here")] +#[note("see issue #89122 for more information")] +pub(crate) struct DeprecatedWhereClauseLocation { + #[subdiagnostic] + pub suggestion: DeprecatedWhereClauseLocationSugg, +} + +#[derive(Subdiagnostic)] +pub(crate) enum DeprecatedWhereClauseLocationSugg { + #[multipart_suggestion( + "move it to the end of the type declaration", + applicability = "machine-applicable" + )] + MoveToEnd { + #[suggestion_part(code = "")] + left: Span, + #[suggestion_part(code = "{sugg}")] + right: Span, + + sugg: String, + }, + #[suggestion("remove this `where`", code = "", applicability = "machine-applicable")] + RemoveWhere { + #[primary_span] + span: Span, + }, +} diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index d5cd26dd75e3e..c61bb7307f609 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -116,26 +116,6 @@ impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { stability::Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind } .into_diag(dcx, level) } - BuiltinLintDiag::PatternsInFnsWithoutBody { span: remove_span, ident, is_foreign } => { - let sub = lints::PatternsInFnsWithoutBodySub { ident, span: remove_span }; - if is_foreign { - lints::PatternsInFnsWithoutBody::Foreign { sub } - } else { - lints::PatternsInFnsWithoutBody::Bodiless { sub } - } - .into_diag(dcx, level) - } - BuiltinLintDiag::DeprecatedWhereclauseLocation(left_sp, sugg) => { - let suggestion = match sugg { - Some((right_sp, sugg)) => lints::DeprecatedWhereClauseLocationSugg::MoveToEnd { - left: left_sp, - right: right_sp, - sugg, - }, - None => lints::DeprecatedWhereClauseLocationSugg::RemoveWhere { span: left_sp }, - }; - lints::DeprecatedWhereClauseLocation { suggestion }.into_diag(dcx, level) - } BuiltinLintDiag::SingleUseLifetime { param_span, use_span: Some((use_span, elide)), @@ -257,9 +237,6 @@ impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => { lints::UnusedCrateDependency { extern_crate, local_crate }.into_diag(dcx, level) } - BuiltinLintDiag::UnusedVisibility(span) => { - lints::UnusedVisibility { span }.into_diag(dcx, level) - } BuiltinLintDiag::AttributeLint(kind) => { DecorateAttrLint { sess: self.sess, tcx: self.tcx, diagnostic: &kind } .into_diag(dcx, level) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index d5b5a50d281a0..c66654afc9d1b 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3125,62 +3125,6 @@ pub(crate) enum RedundantImportSub { }, } -#[derive(Diagnostic)] -pub(crate) enum PatternsInFnsWithoutBody { - #[diag("patterns aren't allowed in foreign function declarations")] - Foreign { - #[subdiagnostic] - sub: PatternsInFnsWithoutBodySub, - }, - #[diag("patterns aren't allowed in functions without bodies")] - Bodiless { - #[subdiagnostic] - sub: PatternsInFnsWithoutBodySub, - }, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "remove `mut` from the parameter", - code = "{ident}", - applicability = "machine-applicable" -)] -pub(crate) struct PatternsInFnsWithoutBodySub { - #[primary_span] - pub span: Span, - - pub ident: Ident, -} - -#[derive(Diagnostic)] -#[diag("where clause not allowed here")] -#[note("see issue #89122 for more information")] -pub(crate) struct DeprecatedWhereClauseLocation { - #[subdiagnostic] - pub suggestion: DeprecatedWhereClauseLocationSugg, -} - -#[derive(Subdiagnostic)] -pub(crate) enum DeprecatedWhereClauseLocationSugg { - #[multipart_suggestion( - "move it to the end of the type declaration", - applicability = "machine-applicable" - )] - MoveToEnd { - #[suggestion_part(code = "")] - left: Span, - #[suggestion_part(code = "{sugg}")] - right: Span, - - sugg: String, - }, - #[suggestion("remove this `where`", code = "", applicability = "machine-applicable")] - RemoveWhere { - #[primary_span] - span: Span, - }, -} - #[derive(Diagnostic)] #[diag("lifetime parameter `{$ident}` only used once")] pub(crate) struct SingleUseLifetime { @@ -3644,19 +3588,6 @@ pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion { pub right: Span, } -#[derive(Diagnostic)] -#[diag("visibility qualifiers have no effect on `const _` declarations")] -#[note("`const _` does not declare a name, so there is nothing for the qualifier to apply to")] -pub(crate) struct UnusedVisibility { - #[suggestion( - "remove the qualifier", - style = "short", - code = "", - applicability = "machine-applicable" - )] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("doc alias is duplicated")] pub(crate) struct DocAliasDuplicated { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index e2e4ffc64bea7..f3317017daabc 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -673,13 +673,6 @@ pub enum BuiltinLintDiag { path: String, since_kind: DeprecatedSinceKind, }, - PatternsInFnsWithoutBody { - span: Span, - ident: Ident, - is_foreign: bool, - }, - /// `##` or `#"` in edition < 2024. - DeprecatedWhereclauseLocation(Span, Option<(Span, String)>), SingleUseLifetime { /// Span of the parameter which declares this lifetime. param_span: Span, @@ -737,7 +730,6 @@ pub enum BuiltinLintDiag { extern_crate: Symbol, local_crate: Symbol, }, - UnusedVisibility(Span), AttributeLint(AttributeLintKind), UnreachableCfg { span: Span, From 2fcd8a71f70a64efaf071fbf4f3932899fdb7920 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 22 Mar 2026 20:32:22 +0100 Subject: [PATCH 08/25] Remove more `BuiltinLintDiag` in `rustc_resolve` --- compiler/rustc_lint/src/early/diagnostics.rs | 8 +++----- compiler/rustc_lint/src/lints.rs | 9 --------- compiler/rustc_lint_defs/src/lib.rs | 3 ++- compiler/rustc_resolve/src/errors.rs | 9 +++++++++ compiler/rustc_resolve/src/late/diagnostics.rs | 10 +++------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index c61bb7307f609..43bb3971d7ac4 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -118,13 +118,14 @@ impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { } BuiltinLintDiag::SingleUseLifetime { param_span, - use_span: Some((use_span, elide)), + use_span, + elidable, deletion_span, ident, } => { debug!(?param_span, ?use_span, ?deletion_span); let suggestion = if let Some(deletion_span) = deletion_span { - let (use_span, replace_lt) = if elide { + let (use_span, replace_lt) = if elidable { let use_span = self.sess.source_map().span_extend_while_whitespace(use_span); (use_span, String::new()) @@ -145,9 +146,6 @@ impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { lints::SingleUseLifetime { suggestion, param_span, use_span, ident } .into_diag(dcx, level) } - BuiltinLintDiag::SingleUseLifetime { use_span: None, deletion_span, ident, .. } => { - lints::UnusedLifetime { deletion_span, ident }.into_diag(dcx, level) - } BuiltinLintDiag::NamedArgumentUsedPositionally { position_sp_to_replace, position_sp_for_msg, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c66654afc9d1b..c7c92356c3782 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3149,15 +3149,6 @@ pub(crate) struct SingleUseLifetimeSugg { pub replace_lt: String, } -#[derive(Diagnostic)] -#[diag("lifetime parameter `{$ident}` never used")] -pub(crate) struct UnusedLifetime { - #[suggestion("elide the unused lifetime", code = "", applicability = "machine-applicable")] - pub deletion_span: Option, - - pub ident: Ident, -} - #[derive(Diagnostic)] #[diag("named argument `{$named_arg_name}` is not used by name")] pub(crate) struct NamedArgumentUsedPositionally { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index f3317017daabc..0faa504429614 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -681,7 +681,8 @@ pub enum BuiltinLintDiag { deletion_span: Option, /// Span of the single use, or None if the lifetime is never used. /// If true, the lifetime will be fully elided. - use_span: Option<(Span, bool)>, + use_span: Span, + elidable: bool, ident: Ident, }, NamedArgumentUsedPositionally { diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 24c923de67946..25c7c625ff93b 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1583,3 +1583,12 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for Ambiguity { diag } } + +#[derive(Diagnostic)] +#[diag("lifetime parameter `{$ident}` never used")] +pub(crate) struct UnusedLifetime { + #[suggestion("elide the unused lifetime", code = "", applicability = "machine-applicable")] + pub deletion_span: Option, + + pub ident: Ident, +} diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index a8ca4faf5ab0d..3bc40604ac044 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -3653,7 +3653,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { param.ident.span, lint::BuiltinLintDiag::SingleUseLifetime { param_span: param.ident.span, - use_span: Some((use_span, elidable)), + use_span, + elidable, deletion_span, ident: param.ident, }, @@ -3669,12 +3670,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { lint::builtin::UNUSED_LIFETIMES, param.id, param.ident.span, - lint::BuiltinLintDiag::SingleUseLifetime { - param_span: param.ident.span, - use_span: None, - deletion_span, - ident: param.ident, - }, + errors::UnusedLifetime { deletion_span, ident: param.ident }, ); } } From b357d7ad4c2cee2c599ec1443ddbe2aef210f798 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 17 Mar 2026 11:43:53 +1100 Subject: [PATCH 09/25] Rename various query cycle things. The existing names have bugged me for a while. Changes: - `CycleError` -> `Cycle`. Because we normally use "error" for a `Diag` with `Level::Error`, and this type is just a precursor to that. Also, many existing locals of this type are already named `cycle`. - `CycleError::cycle` -> `Cycle::frames`. Because it is a sequence of frames, and we want to avoid `Cycle::cycle` (and `cycle.cycle`). - `cycle_error` -> `find_and_handle_cycle`. Because that's what it does. The existing name is just a non-descript noun phrase. - `mk_cycle` -> `handle_cycle`. Because it doesn't make the cycle; the cycle is already made. It handles the cycle, which involves (a) creating the error, and (b) handling the error. - `report_cycle` -> `create_cycle_error`. Because that's what it does: creates the cycle error (i.e. the `Diag`). - `value_from_cycle_error` -> `handle_cycle_error_fn`. Because most cases don't produce a value, they just emit an error and quit. And the `_fn` suffix is for consistency with other vtable fns. - `from_cycle_error` -> `handle_cycle_error`. A similar story for this module. --- compiler/rustc_middle/src/queries.rs | 8 +-- compiler/rustc_middle/src/query/job.rs | 6 +- compiler/rustc_middle/src/query/mod.rs | 4 +- compiler/rustc_middle/src/query/plumbing.rs | 15 ++--- compiler/rustc_query_impl/src/execution.rs | 24 ++++---- ...m_cycle_error.rs => handle_cycle_error.rs} | 37 ++++++------- compiler/rustc_query_impl/src/job.rs | 55 +++++++++---------- compiler/rustc_query_impl/src/lib.rs | 4 +- compiler/rustc_query_impl/src/query_impl.rs | 8 +-- .../rustc_ty_utils/src/representability.rs | 2 +- 10 files changed, 77 insertions(+), 86 deletions(-) rename compiler/rustc_query_impl/src/{from_cycle_error.rs => handle_cycle_error.rs} (92%) diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index c5dab806fa192..ff96f5044dc14 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -131,10 +131,10 @@ use crate::{mir, thir}; // `Providers` that the driver creates (using several `rustc_*` crates). // // The result type of each query must implement `Clone`. Additionally -// `ty::query::from_cycle_error::FromCycleError` can be implemented which produces an appropriate +// `QueryVTable::handle_cycle_error_fn` can be used to produce an appropriate // placeholder (error) value if the query resulted in a query cycle. -// Queries without a `FromCycleError` implementation will raise a fatal error on query -// cycles instead. +// Queries without a custom `handle_cycle_error_fn` implementation will raise a +// fatal error on query cycles instead. rustc_queries! { /// Caches the expansion of a derive proc macro, e.g. `#[derive(Serialize)]`. /// The key is: @@ -577,7 +577,7 @@ rustc_queries! { /// Checks whether a type is representable or infinitely sized // - // Infinitely sized types will cause a cycle. The `value_from_cycle_error` impl will print + // Infinitely sized types will cause a cycle. The query's `handle_cycle_error_fn` will print // a custom error about the infinite size and then abort compilation. (In the past we // recovered and continued, but in practice that leads to confusing subsequent error // messages about cycles that then abort.) diff --git a/compiler/rustc_middle/src/query/job.rs b/compiler/rustc_middle/src/query/job.rs index 3bf37a782ee8a..24c4daf9855d2 100644 --- a/compiler/rustc_middle/src/query/job.rs +++ b/compiler/rustc_middle/src/query/job.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use parking_lot::{Condvar, Mutex}; use rustc_span::Span; -use crate::query::CycleError; +use crate::query::Cycle; use crate::ty::TyCtxt; /// A value uniquely identifying an active query job. @@ -59,7 +59,7 @@ pub struct QueryWaiter<'tcx> { pub parent: Option, pub condvar: Condvar, pub span: Span, - pub cycle: Mutex>>, + pub cycle: Mutex>>, } #[derive(Clone, Debug)] @@ -79,7 +79,7 @@ impl<'tcx> QueryLatch<'tcx> { tcx: TyCtxt<'tcx>, query: Option, span: Span, - ) -> Result<(), CycleError<'tcx>> { + ) -> Result<(), Cycle<'tcx>> { let mut waiters_guard = self.waiters.lock(); let Some(waiters) = &mut *waiters_guard else { return Ok(()); // already complete diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 9c012f2da4dfc..b7e5e9bcb5e32 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -5,8 +5,8 @@ pub use self::into_query_key::IntoQueryKey; pub use self::job::{QueryJob, QueryJobId, QueryLatch, QueryWaiter}; pub use self::keys::{AsLocalQueryKey, LocalCrate, QueryKey}; pub use self::plumbing::{ - ActiveKeyStatus, CycleError, EnsureMode, QueryMode, QueryState, QuerySystem, QueryVTable, - TyCtxtAt, TyCtxtEnsureDone, TyCtxtEnsureOk, TyCtxtEnsureResult, + ActiveKeyStatus, Cycle, EnsureMode, QueryMode, QueryState, QuerySystem, QueryVTable, TyCtxtAt, + TyCtxtEnsureDone, TyCtxtEnsureOk, TyCtxtEnsureResult, }; pub use self::stack::QueryStackFrame; pub use crate::queries::Providers; diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index dcf7a952fb970..f66a71412820c 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -47,12 +47,12 @@ pub enum ActiveKeyStatus<'tcx> { } #[derive(Debug)] -pub struct CycleError<'tcx> { +pub struct Cycle<'tcx> { /// The query and related span that uses the cycle. pub usage: Option>, /// The span here corresponds to the reason for which this query was required. - pub cycle: Vec>, + pub frames: Vec>, } #[derive(Debug)] @@ -114,13 +114,10 @@ pub struct QueryVTable<'tcx, C: QueryCache> { /// Function pointer that handles a cycle error. `error` must be consumed, e.g. with `emit` (if /// it should be emitted) or `delay_as_bug` (if it need not be emitted because an alternative - /// error is created and emitted). - pub value_from_cycle_error: fn( - tcx: TyCtxt<'tcx>, - key: C::Key, - cycle_error: CycleError<'tcx>, - error: Diag<'_>, - ) -> C::Value, + /// error is created and emitted). A value may be returned, or (more commonly) the function may + /// just abort after emitting the error. + pub handle_cycle_error_fn: + fn(tcx: TyCtxt<'tcx>, key: C::Key, cycle: Cycle<'tcx>, error: Diag<'_>) -> C::Value, pub format_value: fn(&C::Value) -> String, diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index bbd55e9ead119..1d661a9d9b348 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -8,8 +8,8 @@ use rustc_data_structures::{outline, sharded, sync}; use rustc_errors::FatalError; use rustc_middle::dep_graph::{DepGraphData, DepNodeKey, SerializedDepNodeIndex}; use rustc_middle::query::{ - ActiveKeyStatus, CycleError, EnsureMode, QueryCache, QueryJob, QueryJobId, QueryKey, - QueryLatch, QueryMode, QueryState, QueryVTable, + ActiveKeyStatus, Cycle, EnsureMode, QueryCache, QueryJob, QueryJobId, QueryKey, QueryLatch, + QueryMode, QueryState, QueryVTable, }; use rustc_middle::ty::TyCtxt; use rustc_middle::verify_ich::incremental_verify_ich; @@ -17,7 +17,7 @@ use rustc_span::{DUMMY_SP, Span}; use tracing::warn; use crate::dep_graph::{DepNode, DepNodeIndex}; -use crate::job::{QueryJobInfo, QueryJobMap, find_cycle_in_stack, report_cycle}; +use crate::job::{QueryJobInfo, QueryJobMap, create_cycle_error, find_cycle_in_stack}; use crate::plumbing::{current_query_job, next_job_id, start_query}; use crate::query_impl::for_each_query_vtable; @@ -108,14 +108,14 @@ fn collect_active_query_jobs_inner<'tcx, C>( #[cold] #[inline(never)] -fn mk_cycle<'tcx, C: QueryCache>( +fn handle_cycle<'tcx, C: QueryCache>( query: &'tcx QueryVTable<'tcx, C>, tcx: TyCtxt<'tcx>, key: C::Key, - cycle_error: CycleError<'tcx>, + cycle: Cycle<'tcx>, ) -> C::Value { - let error = report_cycle(tcx, &cycle_error); - (query.value_from_cycle_error)(tcx, key, cycle_error, error) + let error = create_cycle_error(tcx, &cycle); + (query.handle_cycle_error_fn)(tcx, key, cycle, error) } /// Guard object representing the responsibility to execute a query job and @@ -194,7 +194,7 @@ where #[cold] #[inline(never)] -fn cycle_error<'tcx, C: QueryCache>( +fn find_and_handle_cycle<'tcx, C: QueryCache>( query: &'tcx QueryVTable<'tcx, C>, tcx: TyCtxt<'tcx>, key: C::Key, @@ -205,8 +205,8 @@ fn cycle_error<'tcx, C: QueryCache>( // We need the complete map to ensure we find a cycle to break. let job_map = collect_active_query_jobs(tcx, CollectActiveJobsKind::FullNoContention); - let error = find_cycle_in_stack(try_execute, job_map, ¤t_query_job(), span); - (mk_cycle(query, tcx, key, error), None) + let cycle = find_cycle_in_stack(try_execute, job_map, ¤t_query_job(), span); + (handle_cycle(query, tcx, key, cycle), None) } #[inline(always)] @@ -250,7 +250,7 @@ fn wait_for_query<'tcx, C: QueryCache>( (v, Some(index)) } - Err(cycle) => (mk_cycle(query, tcx, key, cycle), None), + Err(cycle) => (handle_cycle(query, tcx, key, cycle), None), } } @@ -334,7 +334,7 @@ fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>( // If we are single-threaded we know that we have cycle error, // so we just return the error. - cycle_error(query, tcx, key, id, span) + find_and_handle_cycle(query, tcx, key, id, span) } } ActiveKeyStatus::Poisoned => FatalError.raise(), diff --git a/compiler/rustc_query_impl/src/from_cycle_error.rs b/compiler/rustc_query_impl/src/handle_cycle_error.rs similarity index 92% rename from compiler/rustc_query_impl/src/from_cycle_error.rs rename to compiler/rustc_query_impl/src/handle_cycle_error.rs index 0c6082e65f624..5676669bf1c0e 100644 --- a/compiler/rustc_query_impl/src/from_cycle_error.rs +++ b/compiler/rustc_query_impl/src/handle_cycle_error.rs @@ -10,33 +10,33 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_middle::bug; use rustc_middle::queries::{QueryVTables, TaggedQueryKey}; -use rustc_middle::query::CycleError; +use rustc_middle::query::Cycle; use rustc_middle::query::erase::erase_val; use rustc_middle::ty::layout::LayoutError; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{ErrorGuaranteed, Span}; -use crate::job::report_cycle; +use crate::job::create_cycle_error; pub(crate) fn specialize_query_vtables<'tcx>(vtables: &mut QueryVTables<'tcx>) { - vtables.fn_sig.value_from_cycle_error = |tcx, key, _, err| { + vtables.fn_sig.handle_cycle_error_fn = |tcx, key, _, err| { let guar = err.delay_as_bug(); erase_val(fn_sig(tcx, key, guar)) }; - vtables.check_representability.value_from_cycle_error = + vtables.check_representability.handle_cycle_error_fn = |tcx, _, cycle, _err| check_representability(tcx, cycle); - vtables.check_representability_adt_ty.value_from_cycle_error = + vtables.check_representability_adt_ty.handle_cycle_error_fn = |tcx, _, cycle, _err| check_representability(tcx, cycle); - vtables.variances_of.value_from_cycle_error = |tcx, key, _, err| { + vtables.variances_of.handle_cycle_error_fn = |tcx, key, _, err| { let _guar = err.delay_as_bug(); erase_val(variances_of(tcx, key)) }; - vtables.layout_of.value_from_cycle_error = |tcx, _, cycle, err| { + vtables.layout_of.handle_cycle_error_fn = |tcx, _, cycle, err| { let _guar = err.delay_as_bug(); erase_val(Err(layout_of(tcx, cycle))) } @@ -72,10 +72,10 @@ fn fn_sig<'tcx>( ))) } -fn check_representability<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError<'tcx>) -> ! { +fn check_representability<'tcx>(tcx: TyCtxt<'tcx>, cycle: Cycle<'tcx>) -> ! { let mut item_and_field_ids = Vec::new(); let mut representable_ids = FxHashSet::default(); - for frame in &cycle_error.cycle { + for frame in &cycle.frames { if let TaggedQueryKey::check_representability(def_id) = frame.tagged_key && tcx.def_kind(def_id) == DefKind::Field { @@ -88,7 +88,7 @@ fn check_representability<'tcx>(tcx: TyCtxt<'tcx>, cycle_error: CycleError<'tcx> item_and_field_ids.push((item_id.expect_local(), field_id)); } } - for frame in &cycle_error.cycle { + for frame in &cycle.frames { if let TaggedQueryKey::check_representability_adt_ty(key) = frame.tagged_key && let Some(adt) = key.ty_adt_def() && let Some(def_id) = adt.did().as_local() @@ -127,14 +127,11 @@ fn search_for_cycle_permutation( otherwise() } -fn layout_of<'tcx>( - tcx: TyCtxt<'tcx>, - cycle_error: CycleError<'tcx>, -) -> &'tcx ty::layout::LayoutError<'tcx> { +fn layout_of<'tcx>(tcx: TyCtxt<'tcx>, cycle: Cycle<'tcx>) -> &'tcx ty::layout::LayoutError<'tcx> { let diag = search_for_cycle_permutation( - &cycle_error.cycle, - |cycle| { - if let TaggedQueryKey::layout_of(key) = cycle[0].tagged_key + &cycle.frames, + |frames| { + if let TaggedQueryKey::layout_of(key) = frames[0].tagged_key && let ty::Coroutine(def_id, _) = key.value.kind() && let Some(def_id) = def_id.as_local() && let def_kind = tcx.def_kind(def_id) @@ -158,7 +155,7 @@ fn layout_of<'tcx>( tcx.def_kind_descr_article(def_kind, def_id.to_def_id()), tcx.def_kind_descr(def_kind, def_id.to_def_id()), ); - for (i, frame) in cycle.iter().enumerate() { + for (i, frame) in frames.iter().enumerate() { let TaggedQueryKey::layout_of(frame_key) = frame.tagged_key else { continue; }; @@ -169,7 +166,7 @@ fn layout_of<'tcx>( continue; }; let frame_span = - frame.tagged_key.default_span(tcx, cycle[(i + 1) % cycle.len()].span); + frame.tagged_key.default_span(tcx, frames[(i + 1) % frames.len()].span); if frame_span.is_dummy() { continue; } @@ -203,7 +200,7 @@ fn layout_of<'tcx>( ControlFlow::Continue(()) } }, - || report_cycle(tcx, &cycle_error), + || create_cycle_error(tcx, &cycle), ); let guar = diag.emit(); diff --git a/compiler/rustc_query_impl/src/job.rs b/compiler/rustc_query_impl/src/job.rs index 213fc79a68434..a27a6f4ea322e 100644 --- a/compiler/rustc_query_impl/src/job.rs +++ b/compiler/rustc_query_impl/src/job.rs @@ -7,9 +7,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::{Diag, DiagCtxtHandle}; use rustc_hir::def::DefKind; use rustc_middle::queries::TaggedQueryKey; -use rustc_middle::query::{ - CycleError, QueryJob, QueryJobId, QueryLatch, QueryStackFrame, QueryWaiter, -}; +use rustc_middle::query::{Cycle, QueryJob, QueryJobId, QueryLatch, QueryStackFrame, QueryWaiter}; use rustc_middle::ty::TyCtxt; use rustc_span::{DUMMY_SP, Span}; @@ -58,29 +56,28 @@ pub(crate) fn find_cycle_in_stack<'tcx>( job_map: QueryJobMap<'tcx>, current_job: &Option, span: Span, -) -> CycleError<'tcx> { - // Find the waitee amongst `current_job` parents - let mut cycle = Vec::new(); +) -> Cycle<'tcx> { + // Find the waitee amongst `current_job` parents. + let mut frames = Vec::new(); let mut current_job = Option::clone(current_job); while let Some(job) = current_job { let info = &job_map.map[&job]; - cycle.push(QueryStackFrame { span: info.job.span, tagged_key: info.tagged_key }); + frames.push(QueryStackFrame { span: info.job.span, tagged_key: info.tagged_key }); if job == id { - cycle.reverse(); - - // This is the end of the cycle - // The span entry we included was for the usage - // of the cycle itself, and not part of the cycle - // Replace it with the span which caused the cycle to form - cycle[0].span = span; - // Find out why the cycle itself was used + frames.reverse(); + + // This is the end of the cycle. The span entry we included was for + // the usage of the cycle itself, and not part of the cycle. + // Replace it with the span which caused the cycle to form. + frames[0].span = span; + // Find out why the cycle itself was used. let usage = try { let parent = info.job.parent?; QueryStackFrame { span: info.job.span, tagged_key: job_map.tagged_key_of(parent) } }; - return CycleError { usage, cycle }; + return Cycle { usage, frames }; } current_job = info.job.parent; @@ -319,9 +316,9 @@ fn remove_cycle<'tcx>( .map(|(span, job)| QueryStackFrame { span, tagged_key: job_map.tagged_key_of(job) }); // Create the cycle error - let error = CycleError { + let error = Cycle { usage, - cycle: stack + frames: stack .iter() .map(|&(span, job)| QueryStackFrame { span, @@ -454,27 +451,27 @@ pub fn print_query_stack<'tcx>( #[inline(never)] #[cold] -pub(crate) fn report_cycle<'tcx>( +pub(crate) fn create_cycle_error<'tcx>( tcx: TyCtxt<'tcx>, - CycleError { usage, cycle: stack }: &CycleError<'tcx>, + Cycle { usage, frames }: &Cycle<'tcx>, ) -> Diag<'tcx> { - assert!(!stack.is_empty()); + assert!(!frames.is_empty()); - let span = stack[0].tagged_key.default_span(tcx, stack[1 % stack.len()].span); + let span = frames[0].tagged_key.default_span(tcx, frames[1 % frames.len()].span); let mut cycle_stack = Vec::new(); use crate::error::StackCount; - let stack_bottom = stack[0].tagged_key.description(tcx); - let stack_count = if stack.len() == 1 { + let stack_bottom = frames[0].tagged_key.description(tcx); + let stack_count = if frames.len() == 1 { StackCount::Single { stack_bottom: stack_bottom.clone() } } else { StackCount::Multiple { stack_bottom: stack_bottom.clone() } }; - for i in 1..stack.len() { - let frame = &stack[i]; - let span = frame.tagged_key.default_span(tcx, stack[(i + 1) % stack.len()].span); + for i in 1..frames.len() { + let frame = &frames[i]; + let span = frame.tagged_key.default_span(tcx, frames[(i + 1) % frames.len()].span); cycle_stack .push(crate::error::CycleStack { span, desc: frame.tagged_key.description(tcx) }); } @@ -484,12 +481,12 @@ pub(crate) fn report_cycle<'tcx>( usage: usage.tagged_key.description(tcx), }); - let alias = if stack + let alias = if frames .iter() .all(|frame| frame.tagged_key.def_kind(tcx) == Some(DefKind::TyAlias)) { Some(crate::error::Alias::Ty) - } else if stack.iter().all(|frame| frame.tagged_key.def_kind(tcx) == Some(DefKind::TraitAlias)) + } else if frames.iter().all(|frame| frame.tagged_key.def_kind(tcx) == Some(DefKind::TraitAlias)) { Some(crate::error::Alias::Trait) } else { diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index 2f69082db66e9..173a7b111f0ad 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -22,7 +22,7 @@ pub use crate::job::{QueryJobMap, break_query_cycles, print_query_stack}; mod dep_kind_vtables; mod error; mod execution; -mod from_cycle_error; +mod handle_cycle_error; mod job; mod plumbing; mod profiling_support; @@ -49,7 +49,7 @@ pub fn query_system<'tcx>( incremental: bool, ) -> QuerySystem<'tcx> { let mut query_vtables = query_impl::make_query_vtables(incremental); - from_cycle_error::specialize_query_vtables(&mut query_vtables); + handle_cycle_error::specialize_query_vtables(&mut query_vtables); QuerySystem { arenas: Default::default(), query_vtables, diff --git a/compiler/rustc_query_impl/src/query_impl.rs b/compiler/rustc_query_impl/src/query_impl.rs index 56b7534da9edf..4a38848dcfa28 100644 --- a/compiler/rustc_query_impl/src/query_impl.rs +++ b/compiler/rustc_query_impl/src/query_impl.rs @@ -174,10 +174,10 @@ macro_rules! define_queries { is_loadable_from_disk_fn: |_tcx, _key, _index| false, // The default just emits `err` and then aborts. - // `from_cycle_error::specialize_query_vtables` overwrites this default for - // certain queries. - value_from_cycle_error: |_tcx, _key, _cycle, err| { - $crate::from_cycle_error::default(err) + // `handle_cycle_error::specialize_query_vtables` overwrites this default + // for certain queries. + handle_cycle_error_fn: |_tcx, _key, _cycle, err| { + $crate::handle_cycle_error::default(err) }, #[cfg($no_hash)] diff --git a/compiler/rustc_ty_utils/src/representability.rs b/compiler/rustc_ty_utils/src/representability.rs index 9fb16af75aafd..c83e0bb77fcfc 100644 --- a/compiler/rustc_ty_utils/src/representability.rs +++ b/compiler/rustc_ty_utils/src/representability.rs @@ -58,7 +58,7 @@ fn check_representability_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) { // -> check_representability_adt_ty(Bar) // -> check_representability(Foo) // -// For the diagnostic output (in `Value::from_cycle_error`), we want to detect +// For the diagnostic output (in `check_representability`), we want to detect // that the `Foo` in the *second* field of the struct is culpable. This // requires traversing the HIR of the struct and calling `params_in_repr(Bar)`. // But we can't call params_in_repr for a given type unless it is known to be From 26c9f7255a0e12e48132ac3dbf0f8519ed3243dc Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Mon, 23 Mar 2026 11:27:06 +0100 Subject: [PATCH 10/25] Allow applying autodiff macros to trait functions. It will use enzyme to generate a default derivative implementation, which can be overwritten by the user. --- .../src/attributes/autodiff.rs | 1 + compiler/rustc_builtin_macros/src/autodiff.rs | 30 +++++++------ compiler/rustc_expand/src/base.rs | 4 +- tests/pretty/autodiff/trait.pp | 43 +++++++++++++++++++ tests/pretty/autodiff/trait.rs | 32 ++++++++++++++ 5 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 tests/pretty/autodiff/trait.pp create mode 100644 tests/pretty/autodiff/trait.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/autodiff.rs b/compiler/rustc_attr_parsing/src/attributes/autodiff.rs index 118a4103b1a96..c72ff224a1502 100644 --- a/compiler/rustc_attr_parsing/src/attributes/autodiff.rs +++ b/compiler/rustc_attr_parsing/src/attributes/autodiff.rs @@ -24,6 +24,7 @@ impl SingleAttributeParser for RustcAutodiffParser { Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::Trait { body: false })), Allow(Target::Method(MethodKind::TraitImpl)), ]); const TEMPLATE: AttributeTemplate = template!( diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 30391e74480fe..afa393a545cd4 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -224,16 +224,18 @@ mod llvm_enzyme { } _ => None, }, - Annotatable::AssocItem(assoc_item, Impl { of_trait: _ }) => match &assoc_item.kind { - ast::AssocItemKind::Fn(box ast::Fn { sig, ident, generics, .. }) => Some(( - assoc_item.vis.clone(), - sig.clone(), - ident.clone(), - generics.clone(), - true, - )), - _ => None, - }, + Annotatable::AssocItem(assoc_item, _ctxt @ (Impl { of_trait: _ } | Trait)) => { + match &assoc_item.kind { + ast::AssocItemKind::Fn(box ast::Fn { sig, ident, generics, .. }) => Some(( + assoc_item.vis.clone(), + sig.clone(), + ident.clone(), + generics.clone(), + true, + )), + _ => None, + } + } _ => None, }) else { dcx.emit_err(errors::AutoDiffInvalidApplication { span: item.span() }); @@ -393,14 +395,14 @@ mod llvm_enzyme { } Annotatable::Item(iitem.clone()) } - Annotatable::AssocItem(ref mut assoc_item, i @ Impl { .. }) => { + Annotatable::AssocItem(ref mut assoc_item, ctxt @ (Impl { .. } | Trait)) => { if !assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) { assoc_item.attrs.push(attr); } if assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) { has_inline_never = true; } - Annotatable::AssocItem(assoc_item.clone(), i) + Annotatable::AssocItem(assoc_item.clone(), ctxt) } Annotatable::Stmt(ref mut stmt) => { match stmt.kind { @@ -441,7 +443,7 @@ mod llvm_enzyme { } let d_annotatable = match &item { - Annotatable::AssocItem(_, _) => { + Annotatable::AssocItem(_, ctxt) => { let assoc_item: AssocItemKind = ast::AssocItemKind::Fn(d_fn); let d_fn = Box::new(ast::AssocItem { attrs: d_attrs, @@ -451,7 +453,7 @@ mod llvm_enzyme { kind: assoc_item, tokens: None, }); - Annotatable::AssocItem(d_fn, Impl { of_trait: false }) + Annotatable::AssocItem(d_fn, *ctxt) } Annotatable::Item(_) => { let mut d_fn = ecx.item(span, d_attrs, ItemKind::Fn(d_fn)); diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 225906dfba2de..44f148f9c15aa 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -149,14 +149,14 @@ impl Annotatable { pub fn expect_trait_item(self) -> Box { match self { Annotatable::AssocItem(i, AssocCtxt::Trait) => i, - _ => panic!("expected Item"), + _ => panic!("expected trait item"), } } pub fn expect_impl_item(self) -> Box { match self { Annotatable::AssocItem(i, AssocCtxt::Impl { .. }) => i, - _ => panic!("expected Item"), + _ => panic!("expected impl item"), } } diff --git a/tests/pretty/autodiff/trait.pp b/tests/pretty/autodiff/trait.pp new file mode 100644 index 0000000000000..cb2fe0c4a4efa --- /dev/null +++ b/tests/pretty/autodiff/trait.pp @@ -0,0 +1,43 @@ +//@ compile-flags: -Zautodiff=Enable -Zautodiff=NoPostopt -C opt-level=3 -Clto=fat +//@ no-prefer-dynamic +//@ needs-enzyme + +// Just check it does not crash for now +// CHECK: ; +#![feature(autodiff)] +#![feature(core_intrinsics)] +#![feature(rustc_attrs)] + +use std::autodiff::autodiff_reverse; + +struct Foo { + a: f64, +} + +trait MyTrait { + #[rustc_autodiff] + fn f(&self, x: f64) -> f64; + #[rustc_autodiff(Reverse, 1, Const, Active, Active)] + fn df(&self, x: f64, seed: f64) -> (f64, f64) { + std::hint::black_box(seed); + std::hint::black_box(x); + ::std::intrinsics::autodiff( + Self::f as for<'a> fn(&'a Self, _: f64) -> f64, + Self::df, + (self, x, seed), + ) + + } +} + +impl MyTrait for Foo { + fn f(&self, x: f64) -> f64 { + x.sin() + } +} + +fn main() { + let foo = Foo { a: 3.0f64 }; + dbg!(foo.df(2.0, 1.0)); + dbg!(2.0_f64.cos()); +} diff --git a/tests/pretty/autodiff/trait.rs b/tests/pretty/autodiff/trait.rs new file mode 100644 index 0000000000000..a308cf3fb2adb --- /dev/null +++ b/tests/pretty/autodiff/trait.rs @@ -0,0 +1,32 @@ +//@ compile-flags: -Zautodiff=Enable -Zautodiff=NoPostopt -C opt-level=3 -Clto=fat +//@ no-prefer-dynamic +//@ needs-enzyme + +// Just check it does not crash for now +// CHECK: ; +#![feature(autodiff)] +#![feature(core_intrinsics)] +#![feature(rustc_attrs)] + +use std::autodiff::autodiff_reverse; + +struct Foo { + a: f64, +} + +trait MyTrait { + #[autodiff_reverse(df, Const, Active, Active)] + fn f(&self, x: f64) -> f64; +} + +impl MyTrait for Foo { + fn f(&self, x: f64) -> f64 { + x.sin() + } +} + +fn main() { + let foo = Foo { a: 3.0f64 }; + dbg!(foo.df(2.0, 1.0)); + dbg!(2.0_f64.cos()); +} From 1fa1611e9fa6315254c9202ab2874e67b65f68b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 11/25] Revert "address review" This reverts commit d665c0b3718dc582189a85dc1d49e641d22e9e21. --- .../src/infer/relate/generalize.rs | 72 +++++-------------- compiler/rustc_middle/src/hooks/mod.rs | 7 +- compiler/rustc_trait_selection/src/solve.rs | 6 +- tests/ui/impl-trait/unsized_coercion.rs | 3 +- 4 files changed, 23 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index b2d591327fea2..f3843c371e2ca 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -355,51 +355,13 @@ impl<'tcx> TypeVisitor> for MaxUniverse { } } -/// This state determines how generalization treats aliases. -/// -/// Based on which state we're in, we treat them either as rigid or normalizable, -/// which might change depending on what types the generalization visitor encounters. -/// See `handle_alias_ty` for the logic of how we change states. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum GeneralizerState { /// Treat aliases as potentially normalizable. - /// - /// This is the default state that generalization starts in, unless we're - /// treating aliases as rigid. It also means we're not currently inside an - /// alias, since then we change the state to `IncompletelyRelateAliasArgs`. Default, - /// We enter this state when we're generalizing the arguments of a - /// potentially normalizeable alias. - /// - /// The behavior here is different between the old and the new solver: - /// - /// In the old solver, the difference between this and `Default` is needed to - /// correctly handle `::Assoc>::Assoc == ?0`. That - /// equality can hold by either normalizing the outer or the inner - /// associated type. In the old solver, we always structurally relate - /// aliases. If we we encounter an occurs check failure, we propagate the - /// failure to the outermost alias, for which we then emit a `Projection` - /// goal instead. - /// - /// In the new solver, we rarely get into this state. - /// When we encounter aliases we instead attempt to normalize them, and treat - /// them as rigid using `ShallowStructurallyRelate`. Only when an alias has - /// escaping bound variables do we continue with similar logic to the old - /// solver, except now we also explicitly relate the type and consts in the - /// arguments of aliases while in this mode. - /// - /// FIXME: Because we relate the type and consts in the arguments of aliases - /// while in this mode, this is incomplete. - IncompletelyRelateAliasArgs, - /// During generalization, when we encounter aliases, we will first attempt - /// to normalize them when we're using the next trait solver. We can now - /// treat the normalized alias as rigid, but only for "one layer", hence - /// shallow. New aliases encountered inside the arguments of the outer alias - /// should once again be related as normal. + IncompletelyRelateHigherRankedAlias, + /// Only one layer ShallowStructurallyRelateAliases, - /// Treat aliases as rigid when relating them. - /// - /// This corresponds to `relation.structurally_relate_aliases()`. StructurallyRelateAliases, } @@ -438,10 +400,11 @@ struct Generalizer<'me, 'tcx> { /// some other type. What will be the variance at this point? ambient_variance: ty::Variance, - /// This field keeps track of how we treat aliases during generalization. + /// This is set once we're generalizing the arguments of an alias. /// - /// Refer to [`GeneralizerState`]'s docs for more information about the - /// all the possible values this can have, and when we use which. + /// This is necessary to correctly handle + /// `::Assoc>::Assoc == ?0`. This equality can + /// hold by either normalizing the outer or the inner associated type. state: GeneralizerState, cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizerState), Ty<'tcx>>, @@ -520,11 +483,11 @@ impl<'tcx> Generalizer<'_, 'tcx> { return res; } - GeneralizerState::Default | GeneralizerState::IncompletelyRelateAliasArgs => {} + GeneralizerState::Default | GeneralizerState::IncompletelyRelateHigherRankedAlias => {} } let previous_state = - mem::replace(&mut self.state, GeneralizerState::IncompletelyRelateAliasArgs); + mem::replace(&mut self.state, GeneralizerState::IncompletelyRelateHigherRankedAlias); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), Err(e) => match previous_state { @@ -541,7 +504,7 @@ impl<'tcx> Generalizer<'_, 'tcx> { debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - GeneralizerState::IncompletelyRelateAliasArgs => return Err(e), + GeneralizerState::IncompletelyRelateHigherRankedAlias => return Err(e), // Early return. GeneralizerState::ShallowStructurallyRelateAliases @@ -650,7 +613,6 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { // of each other. This is currently only used for diagnostics. // To see why, see the docs in the `type_variables` module. inner.type_variables().sub_unify(vid, new_var_id); - // If we're in the new solver and create a new inference // variable inside of an alias we eagerly constrain that // inference variable to prevent unexpected ambiguity errors. @@ -670,13 +632,13 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizerState::IncompletelyRelateAliasArgs => { + GeneralizerState::IncompletelyRelateHigherRankedAlias => { inner.type_variables().equate(vid, new_var_id); } - GeneralizerState::Default - | GeneralizerState::ShallowStructurallyRelateAliases - | GeneralizerState::StructurallyRelateAliases => {} } + GeneralizerState::Default + | GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => {} } debug!("replacing original vid={:?} with new={:?}", vid, new_var_id); @@ -803,13 +765,13 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizerState::IncompletelyRelateAliasArgs => { + GeneralizerState::IncompletelyRelateHigherRankedAlias => { variable_table.union(vid, new_var_id); } - GeneralizerState::Default - | GeneralizerState::ShallowStructurallyRelateAliases - | GeneralizerState::StructurallyRelateAliases => {} } + GeneralizerState::Default + | GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => {} } Ok(ty::Const::new_var(self.cx(), new_var_id)) } diff --git a/compiler/rustc_middle/src/hooks/mod.rs b/compiler/rustc_middle/src/hooks/mod.rs index 1f339ea0cabf6..0caa8dcbc9c64 100644 --- a/compiler/rustc_middle/src/hooks/mod.rs +++ b/compiler/rustc_middle/src/hooks/mod.rs @@ -130,12 +130,7 @@ declare_hooks! { ) -> Ty<'tcx>; } -/// The `try_eagerly_normalize_alias` hook passes an `Infcx` from where it's called (in `rustc_infer`) -/// to where it's provided (in `rustc_trait_selection`). -/// Both of those crates have that type available, but `rustc_middle` does not. -/// Instead we pass this type-erased `Infcx` and transmute on both sides. -/// -/// Has to be `repr(transparent)` so we can transmute a `&'a Infcx<'tcx>` to this struct. +// `repr(transparent)` so we can transmute a `&'a Infcx<'tcx>` to this struct. #[repr(transparent)] pub struct TypeErasedInfcx<'a, 'tcx> { _infcx: *const (), diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index c7699f31a0f95..118bd8c81b1e7 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -56,7 +56,8 @@ fn try_eagerly_normalize_alias<'a, 'tcx>( let cause = ObligationCause::dummy_with_span(span); let obligation = Obligation::new( tcx, - cause, + // we ignore the error anyway + ObligationCause::dummy_with_span(span), param_env, ty::PredicateKind::AliasRelate( alias.to_ty(tcx).into(), @@ -67,8 +68,7 @@ fn try_eagerly_normalize_alias<'a, 'tcx>( ocx.register_obligation(obligation); - // We only use this to constrain inference variables. - // We don't care if it errors. + // This only tries to eagerly resolve, if it errors we don't care. let _ = ocx.try_evaluate_obligations(); infcx.resolve_vars_if_possible(infer_term) diff --git a/tests/ui/impl-trait/unsized_coercion.rs b/tests/ui/impl-trait/unsized_coercion.rs index f77f2198be0ef..6a9a53903fed3 100644 --- a/tests/ui/impl-trait/unsized_coercion.rs +++ b/tests/ui/impl-trait/unsized_coercion.rs @@ -3,7 +3,8 @@ //@ revisions: next old //@[next] compile-flags: -Znext-solver -//@ check-pass +//@[old] check-pass +//@[next] check-pass trait Trait {} From bd781549f2583d6ae7e141cc6adeff443ec9b02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 12/25] Revert "inline into" This reverts commit 9aa065bc49e5c9a0e07dbfc4bb0823821e5f130d. --- .../src/infer/relate/generalize.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index f3843c371e2ca..e1d3f4e6bd489 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -99,10 +99,7 @@ impl<'tcx> InferCtxt<'tcx> { // `?1 <: ?3`. let Generalization { value_may_be_infer: generalized_ty } = self.generalize( relation.span(), - match relation.structurally_relate_aliases() { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - }, + relation.structurally_relate_aliases().into(), target_vid, instantiation_variance, source_ty, @@ -239,10 +236,7 @@ impl<'tcx> InferCtxt<'tcx> { // constants and generic expressions are not yet handled correctly. let Generalization { value_may_be_infer: generalized_ct } = self.generalize( relation.span(), - match relation.structurally_relate_aliases() { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - }, + relation.structurally_relate_aliases().into(), target_vid, ty::Invariant, source_ct, @@ -365,6 +359,15 @@ enum GeneralizerState { StructurallyRelateAliases, } +impl From for GeneralizerState { + fn from(structurally_relate_aliases: StructurallyRelateAliases) -> Self { + match structurally_relate_aliases { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + } + } +} + /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to From e613b82e4eaed11edddfc90024a5e51c807ece42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 13/25] Revert "bless some tests" This reverts commit 6edf58fab80d713b5e017d98a57c4075c12b833e. --- .../impl-trait/unsized_coercion.next.stderr | 11 ++++++ tests/ui/impl-trait/unsized_coercion.rs | 2 +- .../impl-trait/unsized_coercion3.next.stderr | 36 ++----------------- .../impl-trait/unsized_coercion3.old.stderr | 2 +- tests/ui/impl-trait/unsized_coercion3.rs | 2 -- ...id-alias-bound-is-not-inherent.next.stderr | 2 +- ...rg-type-mismatch-issue-45727.current.fixed | 3 +- ...-arg-type-mismatch-issue-45727.next.stderr | 11 ++++-- .../closure-arg-type-mismatch-issue-45727.rs | 3 +- 9 files changed, 30 insertions(+), 42 deletions(-) create mode 100644 tests/ui/impl-trait/unsized_coercion.next.stderr diff --git a/tests/ui/impl-trait/unsized_coercion.next.stderr b/tests/ui/impl-trait/unsized_coercion.next.stderr new file mode 100644 index 0000000000000..bea5ddb0aefcc --- /dev/null +++ b/tests/ui/impl-trait/unsized_coercion.next.stderr @@ -0,0 +1,11 @@ +error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time + --> $DIR/unsized_coercion.rs:14:17 + | +LL | let x = hello(); + | ^^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `dyn Trait` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/unsized_coercion.rs b/tests/ui/impl-trait/unsized_coercion.rs index 6a9a53903fed3..2cbf0d25d7ec6 100644 --- a/tests/ui/impl-trait/unsized_coercion.rs +++ b/tests/ui/impl-trait/unsized_coercion.rs @@ -4,7 +4,6 @@ //@ revisions: next old //@[next] compile-flags: -Znext-solver //@[old] check-pass -//@[next] check-pass trait Trait {} @@ -13,6 +12,7 @@ impl Trait for u32 {} fn hello() -> Box { if true { let x = hello(); + //[next]~^ ERROR: the size for values of type `dyn Trait` cannot be known at compilation time let y: Box = x; } Box::new(1u32) diff --git a/tests/ui/impl-trait/unsized_coercion3.next.stderr b/tests/ui/impl-trait/unsized_coercion3.next.stderr index db758761d7954..a480a69a38641 100644 --- a/tests/ui/impl-trait/unsized_coercion3.next.stderr +++ b/tests/ui/impl-trait/unsized_coercion3.next.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `dyn Send: Trait` is not satisfied - --> $DIR/unsized_coercion3.rs:14:17 + --> $DIR/unsized_coercion3.rs:13:17 | LL | let x = hello(); | ^^^^^^^ the trait `Trait` is not implemented for `dyn Send` @@ -9,37 +9,7 @@ help: the trait `Trait` is implemented for `u32` | LL | impl Trait for u32 {} | ^^^^^^^^^^^^^^^^^^ -note: required by a bound in `Box` - --> $SRC_DIR/alloc/src/boxed.rs:LL:COL -error[E0308]: mismatched types - --> $DIR/unsized_coercion3.rs:19:5 - | -LL | fn hello() -> Box { - | ------------------------ - | | | - | | the expected opaque type - | expected `Box` because of return type -... -LL | Box::new(1u32) - | ^^^^^^^^^^^^^^ types differ - | - = note: expected struct `Box` - found struct `Box` - -error[E0277]: the trait bound `dyn Send: Trait` is not satisfied - --> $DIR/unsized_coercion3.rs:11:1 - | -LL | fn hello() -> Box { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Trait` is not implemented for `dyn Send` - | -help: the trait `Trait` is implemented for `u32` - --> $DIR/unsized_coercion3.rs:9:1 - | -LL | impl Trait for u32 {} - | ^^^^^^^^^^^^^^^^^^ - -error: aborting due to 3 previous errors +error: aborting due to 1 previous error -Some errors have detailed explanations: E0277, E0308. -For more information about an error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/unsized_coercion3.old.stderr b/tests/ui/impl-trait/unsized_coercion3.old.stderr index 3bb9f9c209510..52a72b84a8dd6 100644 --- a/tests/ui/impl-trait/unsized_coercion3.old.stderr +++ b/tests/ui/impl-trait/unsized_coercion3.old.stderr @@ -1,5 +1,5 @@ error[E0277]: the size for values of type `impl Trait + ?Sized` cannot be known at compilation time - --> $DIR/unsized_coercion3.rs:16:32 + --> $DIR/unsized_coercion3.rs:15:32 | LL | let y: Box = x; | ^ doesn't have a size known at compile-time diff --git a/tests/ui/impl-trait/unsized_coercion3.rs b/tests/ui/impl-trait/unsized_coercion3.rs index c1dd5350e229a..ebfbb2955de55 100644 --- a/tests/ui/impl-trait/unsized_coercion3.rs +++ b/tests/ui/impl-trait/unsized_coercion3.rs @@ -9,7 +9,6 @@ trait Trait {} impl Trait for u32 {} fn hello() -> Box { - //[next]~^ ERROR: the trait bound `dyn Send: Trait` is not satisfied if true { let x = hello(); //[next]~^ ERROR: the trait bound `dyn Send: Trait` is not satisfied @@ -17,7 +16,6 @@ fn hello() -> Box { //[old]~^ ERROR: the size for values of type `impl Trait + ?Sized` cannot be know } Box::new(1u32) - //[next]~^ ERROR: mismatched types } fn main() {} diff --git a/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr b/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr index 4652bf5e3c586..afacb3a7d5213 100644 --- a/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr +++ b/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr @@ -9,7 +9,7 @@ note: candidate #1 is defined in the trait `Trait1` | LL | fn method(&self) { | ^^^^^^^^^^^^^^^^ -note: candidate #2 is defined in an impl of the trait `Trait2` for the type `T` +note: candidate #2 is defined in the trait `Trait2` --> $DIR/rigid-alias-bound-is-not-inherent.rs:27:5 | LL | fn method(&self) { diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed index 1c45a2c0adb3e..ba46a447802c8 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed @@ -8,5 +8,6 @@ fn main() { //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found let _ = (-10..=10).find(|x: &i32| x.signum() == 0); //[current]~^ ERROR type mismatch in closure arguments - //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found + //[next]~^^ ERROR expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` + //[next]~| ERROR expected a `FnMut(& as Iterator>::Item)` closure, found } diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr index 36e49c20c4331..7912ed4d7071a 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr @@ -12,6 +12,12 @@ LL | let _ = (-10..=10).find(|x: i32| x.signum() == 0); note: required by a bound in `find` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL +error[E0271]: expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` + --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:24 + | +LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); + | ^^^^ expected `&&i32`, found integer + error[E0277]: expected a `FnMut(& as Iterator>::Item)` closure, found `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:9:29: 9:40}` --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:29 | @@ -26,6 +32,7 @@ LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); note: required by a bound in `find` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0271, E0277. +For more information about an error, try `rustc --explain E0271`. diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs index 20d6fed3b35b8..0fd56707763e9 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs @@ -8,5 +8,6 @@ fn main() { //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); //[current]~^ ERROR type mismatch in closure arguments - //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found + //[next]~^^ ERROR expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` + //[next]~| ERROR expected a `FnMut(& as Iterator>::Item)` closure, found } From 304a197ba5d18bf837f822f8f9d7a7a3f3e3c897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 14/25] Revert "fixup span in obligation cause" This reverts commit 2d411a0faad447b5bfc968b954fd3e9c10596325. --- compiler/rustc_trait_selection/src/solve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index 118bd8c81b1e7..cb02885139038 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -57,7 +57,7 @@ fn try_eagerly_normalize_alias<'a, 'tcx>( let obligation = Obligation::new( tcx, // we ignore the error anyway - ObligationCause::dummy_with_span(span), + ObligationCause::dummy(), param_env, ty::PredicateKind::AliasRelate( alias.to_ty(tcx).into(), From 38cb40091c08bf821745151ab65b3d51e02609b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 15/25] Revert "try generalizing if normalization isn't a tyvar" This reverts commit e8e3544a71f23606c85e8586e37bd98389bc1ebe. --- .../src/infer/relate/generalize.rs | 115 +++++++++--------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index e1d3f4e6bd489..1d4dab0016168 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -38,6 +38,31 @@ impl From for TermVid { } impl<'tcx> InferCtxt<'tcx> { + fn check_generalized_alias_normalizes_to_tyvar>( + &self, + relation: &mut R, + source_ty: Ty<'tcx>, + ) -> Option> { + if !self.next_trait_solver() + || matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::Yes) + { + return None; + } + + // If we get an alias + let ty::Alias(_, alias) = source_ty.kind() else { + return None; + }; + + if alias.has_escaping_bound_vars() { + return None; + } + + let normalized_alias = relation.try_eagerly_normalize_alias(*alias); + + normalized_alias.is_ty_var().then_some(normalized_alias) + } + /// The idea is that we should ensure that the type variable `target_vid` /// is equal to, a subtype of, or a supertype of `source_ty`. /// @@ -61,53 +86,31 @@ impl<'tcx> InferCtxt<'tcx> { ) -> RelateResult<'tcx, ()> { debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown()); - let generalized_ty = if self.next_trait_solver() - && matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::No) - && let ty::Alias(_, alias) = source_ty.kind() - { - let normalized_alias = relation.try_eagerly_normalize_alias(*alias); - - if normalized_alias.is_ty_var() { - normalized_alias - } else { - let Generalization { value_may_be_infer: generalized_ty } = self.generalize( - relation.span(), - GeneralizerState::ShallowStructurallyRelateAliases, - target_vid, - instantiation_variance, - normalized_alias, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; - - // The only way to get a tyvar back is if the outermost type is an alias. - // However, here, though we know it *is* an alias, we initialize the generalizer - // with `ShallowStructurallyRelateAliases` so we treat the outermost alias as rigid, - // ensuring this is never a tyvar. - assert!(!generalized_ty.is_ty_var()); - - generalized_ty - } - } else { - // Generalize `source_ty` depending on the current variance. As an example, assume - // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference - // variable. - // - // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh - // region/type inference variables. - // - // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and - // `?1 <: ?3`. - let Generalization { value_may_be_infer: generalized_ty } = self.generalize( - relation.span(), - relation.structurally_relate_aliases().into(), - target_vid, - instantiation_variance, - source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; - - generalized_ty - }; + let generalized_ty = + match self.check_generalized_alias_normalizes_to_tyvar(relation, source_ty) { + Some(tyvar) => tyvar, + None => { + // Generalize `source_ty` depending on the current variance. As an example, assume + // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference + // variable. + // + // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh + // region/type inference variables. + // + // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and + // `?1 <: ?3`. + let generalizer = self.generalize( + relation.span(), + relation.structurally_relate_aliases(), + target_vid, + instantiation_variance, + source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; + + generalizer.value_may_be_infer + } + }; // Finally, relate `generalized_ty` to `source_ty`, as described in previous comment. // @@ -236,7 +239,7 @@ impl<'tcx> InferCtxt<'tcx> { // constants and generic expressions are not yet handled correctly. let Generalization { value_may_be_infer: generalized_ct } = self.generalize( relation.span(), - relation.structurally_relate_aliases().into(), + relation.structurally_relate_aliases(), target_vid, ty::Invariant, source_ct, @@ -276,7 +279,7 @@ impl<'tcx> InferCtxt<'tcx> { fn generalize> + Relate>>( &self, span: Span, - initial_state: GeneralizerState, + structurally_relate_aliases: StructurallyRelateAliases, target_vid: impl Into, ambient_variance: ty::Variance, source_term: T, @@ -300,7 +303,10 @@ impl<'tcx> InferCtxt<'tcx> { for_universe, root_term: source_term.into(), ambient_variance, - state: initial_state, + state: match structurally_relate_aliases { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, cache: Default::default(), normalize, }; @@ -359,15 +365,6 @@ enum GeneralizerState { StructurallyRelateAliases, } -impl From for GeneralizerState { - fn from(structurally_relate_aliases: StructurallyRelateAliases) -> Self { - match structurally_relate_aliases { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - } - } -} - /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to From c838deca23cca696e00dd999beca5dedccd41180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 16/25] Revert "normalize at the start of generalize if we can" This reverts commit f50591fc1059d98ba57c79094f6e2d46db4b25b6. --- .../src/infer/relate/generalize.rs | 84 ++++++------------- .../src/canonical/canonicalizer.rs | 6 +- 2 files changed, 27 insertions(+), 63 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 1d4dab0016168..d5c0726ed9a88 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -38,31 +38,6 @@ impl From for TermVid { } impl<'tcx> InferCtxt<'tcx> { - fn check_generalized_alias_normalizes_to_tyvar>( - &self, - relation: &mut R, - source_ty: Ty<'tcx>, - ) -> Option> { - if !self.next_trait_solver() - || matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::Yes) - { - return None; - } - - // If we get an alias - let ty::Alias(_, alias) = source_ty.kind() else { - return None; - }; - - if alias.has_escaping_bound_vars() { - return None; - } - - let normalized_alias = relation.try_eagerly_normalize_alias(*alias); - - normalized_alias.is_ty_var().then_some(normalized_alias) - } - /// The idea is that we should ensure that the type variable `target_vid` /// is equal to, a subtype of, or a supertype of `source_ty`. /// @@ -76,7 +51,7 @@ impl<'tcx> InferCtxt<'tcx> { /// `TypeRelation`. Do not use this, and instead please use `At::eq`, for all /// other usecases (i.e. setting the value of a type var). #[instrument(level = "debug", skip(self, relation))] - pub fn instantiate_ty_var>( + pub fn instantiate_ty_var>>( &self, relation: &mut R, target_is_expected: bool, @@ -86,31 +61,30 @@ impl<'tcx> InferCtxt<'tcx> { ) -> RelateResult<'tcx, ()> { debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown()); - let generalized_ty = - match self.check_generalized_alias_normalizes_to_tyvar(relation, source_ty) { - Some(tyvar) => tyvar, - None => { - // Generalize `source_ty` depending on the current variance. As an example, assume - // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference - // variable. - // - // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh - // region/type inference variables. - // - // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and - // `?1 <: ?3`. - let generalizer = self.generalize( - relation.span(), - relation.structurally_relate_aliases(), - target_vid, - instantiation_variance, - source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; - - generalizer.value_may_be_infer - } - }; + // Generalize `source_ty` depending on the current variance. As an example, assume + // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference + // variable. + // + // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh + // region/type inference variables. + // + // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and + // `?1 <: ?3`. + let Generalization { value_may_be_infer: generalized_ty } = self.generalize( + relation.span(), + relation.structurally_relate_aliases(), + target_vid, + instantiation_variance, + source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; + + // Constrain `b_vid` to the generalized type `generalized_ty`. + if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() { + self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid); + } else { + self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty); + } // Finally, relate `generalized_ty` to `source_ty`, as described in previous comment. // @@ -118,10 +92,7 @@ impl<'tcx> InferCtxt<'tcx> { // relations wind up attributed to the same spans. We need // to associate causes/spans with each of the relations in // the stack to get this right. - if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() { - // Constrain `b_vid` to the generalized type variable. - self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid); - + if generalized_ty.is_ty_var() { // This happens for cases like `::Assoc == ?0`. // We can't instantiate `?0` here as that would result in a // cyclic type. We instead delay the unification in case @@ -162,9 +133,6 @@ impl<'tcx> InferCtxt<'tcx> { } } } else { - // Constrain `b_vid` to the generalized type `generalized_ty`. - self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty); - // NOTE: The `instantiation_variance` is not the same variance as // used by the relation. When instantiating `b`, `target_is_expected` // is flipped and the `instantiation_variance` is also flipped. To diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index e469451da993e..ce2be24adc586 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -177,11 +177,7 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { cache: Default::default(), }; let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!( - env_canonicalizer.sub_root_lookup_table.is_empty(), - "{:?}", - env_canonicalizer.sub_root_lookup_table - ); + debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); ( param_env, env_canonicalizer.variables, From df0f627bfc983e79a491ea6bf25d138eed668906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 17/25] Revert "explicitly provide type in transmute" This reverts commit 7a123e87b21cd95fef62b80571ee6c644ac3a1c3. --- compiler/rustc_infer/src/infer/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index e7082f961d0b1..26b094ff8eccb 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1535,8 +1535,7 @@ impl<'tcx> InferCtxt<'tcx> { span: Span, alias: ty::AliasTy<'tcx>, ) -> Ty<'tcx> { - let erased = - unsafe { mem::transmute::<&'a InferCtxt<'tcx>, TypeErasedInfcx<'a, 'tcx>>(self) }; + let erased = unsafe { mem::transmute::<_, TypeErasedInfcx<'a, 'tcx>>(self) }; self.tcx.try_eagerly_normalize_alias(erased, param_env, span, alias) } From e190973e4fae59ad8a851f022be45dcf3a72cce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 18/25] Revert "merge generalizer state and structurally relate aliases" This reverts commit 917713b4db3c627f2a35174c09d3ca715dd814b2. --- .../src/infer/relate/generalize.rs | 123 ++++++++++-------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index d5c0726ed9a88..34fd32c45bd27 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -267,14 +267,12 @@ impl<'tcx> InferCtxt<'tcx> { let mut generalizer = Generalizer { infcx: self, span, + structurally_relate_aliases, root_vid, for_universe, root_term: source_term.into(), ambient_variance, - state: match structurally_relate_aliases { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - }, + state: GeneralizationState::Default, cache: Default::default(), normalize, }; @@ -324,13 +322,10 @@ impl<'tcx> TypeVisitor> for MaxUniverse { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum GeneralizerState { - /// Treat aliases as potentially normalizable. +enum GeneralizationState { Default, - IncompletelyRelateHigherRankedAlias, - /// Only one layer - ShallowStructurallyRelateAliases, - StructurallyRelateAliases, + InAlias, + InNormalizedAlias, } /// The "generalizer" is used when handling inference variables. @@ -351,6 +346,10 @@ struct Generalizer<'me, 'tcx> { span: Span, + /// Whether aliases should be related structurally. If not, we have to + /// be careful when generalizing aliases. + structurally_relate_aliases: StructurallyRelateAliases, + /// The vid of the type variable that is in the process of being /// instantiated. If we find this within the value we are folding, /// that means we would have created a cyclic value. @@ -373,9 +372,10 @@ struct Generalizer<'me, 'tcx> { /// This is necessary to correctly handle /// `::Assoc>::Assoc == ?0`. This equality can /// hold by either normalizing the outer or the inner associated type. - state: GeneralizerState, + // TODO: update doc comment + state: GeneralizationState, - cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizerState), Ty<'tcx>>, + cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizationState), Ty<'tcx>>, /// Normalize an alias in the trait solver. /// If normalization fails, a fresh infer var is returned. @@ -415,51 +415,44 @@ impl<'tcx> Generalizer<'_, 'tcx> { /// continue generalizing the alias. This ends up pulling down the universe of the /// inference variable and is incomplete in case the alias would normalize to a type /// which does not mention that inference variable. - fn handle_alias_ty( + fn generalize_alias_ty( &mut self, - alias_ty: Ty<'tcx>, alias: ty::AliasTy<'tcx>, ) -> Result, TypeError<'tcx>> { - match self.state { - GeneralizerState::ShallowStructurallyRelateAliases => { - // We can switch back to default, we've treated one layer as rigid by doing this operation. - self.state = GeneralizerState::Default; - let res = relate::structurally_relate_tys(self, alias_ty, alias_ty); - self.state = GeneralizerState::ShallowStructurallyRelateAliases; - return res; - } - GeneralizerState::StructurallyRelateAliases => { - return relate::structurally_relate_tys(self, alias_ty, alias_ty); - } - GeneralizerState::Default - if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() => - { - // We do not eagerly replace aliases with inference variables if they have - // escaping bound vars, see the method comment for details. However, when we - // are inside of an alias with escaping bound vars replacing nested aliases - // with inference variables can cause incorrect ambiguity. - // - // cc trait-system-refactor-initiative#110 - let normalized_alias = (self.normalize)(alias); - - self.state = GeneralizerState::ShallowStructurallyRelateAliases; - // recursively generalize, treat the outer alias as rigid to avoid infinite recursion - let res = self.relate(normalized_alias, normalized_alias); - - // only one way to get here - self.state = GeneralizerState::Default; - - return res; + // We do not eagerly replace aliases with inference variables if they have + // escaping bound vars, see the method comment for details. However, when we + // are inside of an alias with escaping bound vars replacing nested aliases + // with inference variables can cause incorrect ambiguity. + // + // cc trait-system-refactor-initiative#110 + if self.infcx.next_trait_solver() + && !alias.has_escaping_bound_vars() + && match self.state { + GeneralizationState::Default => true, + GeneralizationState::InAlias => false, + // When generalizing an alias after normalizing, + // the outer alias should be treated as rigid and we shouldn't try generalizing it again. + // If we recursively find more aliases, the state should have been set back to InAlias. + GeneralizationState::InNormalizedAlias => unreachable!(), } - GeneralizerState::Default | GeneralizerState::IncompletelyRelateHigherRankedAlias => {} + { + let normalized_alias = (self.normalize)(alias); + + self.state = GeneralizationState::InNormalizedAlias; + // recursively generalize, treat the outer alias as rigid to avoid infinite recursion + let res = self.relate(normalized_alias, normalized_alias); + + // only one way to get here + self.state = GeneralizationState::Default; + + return res; } - let previous_state = - mem::replace(&mut self.state, GeneralizerState::IncompletelyRelateHigherRankedAlias); + let previous_state = mem::replace(&mut self.state, GeneralizationState::InAlias); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), Err(e) => match previous_state { - GeneralizerState::Default => { + GeneralizationState::Default => { let mut visitor = MaxUniverse::new(); alias.visit_with(&mut visitor); let infer_replacement_is_complete = @@ -472,11 +465,11 @@ impl<'tcx> Generalizer<'_, 'tcx> { debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - GeneralizerState::IncompletelyRelateHigherRankedAlias => return Err(e), - - // Early return. - GeneralizerState::ShallowStructurallyRelateAliases - | GeneralizerState::StructurallyRelateAliases => unreachable!(), + GeneralizationState::InAlias => return Err(e), + // When generalizing an alias after normalizing, + // the outer alias should be treated as rigid and we shouldn't try generalizing it again. + // If we recursively find more aliases, the state should have been set back to InAlias. + GeneralizationState::InNormalizedAlias => unreachable!(), }, }; self.state = previous_state; @@ -600,9 +593,11 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizerState::IncompletelyRelateHigherRankedAlias => { + GeneralizationState::InAlias => { inner.type_variables().equate(vid, new_var_id); } + GeneralizationState::Default + | GeneralizationState::InNormalizedAlias => {} } GeneralizerState::Default | GeneralizerState::ShallowStructurallyRelateAliases @@ -635,7 +630,21 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { } } - ty::Alias(_, data) => self.handle_alias_ty(t, data), + ty::Alias(_, data) => match self.structurally_relate_aliases { + StructurallyRelateAliases::No => match self.state { + GeneralizationState::Default | GeneralizationState::InAlias => { + self.generalize_alias_ty(data) + } + GeneralizationState::InNormalizedAlias => { + // We can switch back to default, we've treated one layer as rigid by doing this operation. + self.state = GeneralizationState::Default; + let res = relate::structurally_relate_tys(self, t, t); + self.state = GeneralizationState::InNormalizedAlias; + res + } + }, + StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t), + }, _ => relate::structurally_relate_tys(self, t, t), }?; @@ -733,9 +742,11 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizerState::IncompletelyRelateHigherRankedAlias => { + GeneralizationState::InAlias => { variable_table.union(vid, new_var_id); } + GeneralizationState::Default + | GeneralizationState::InNormalizedAlias => {} } GeneralizerState::Default | GeneralizerState::ShallowStructurallyRelateAliases From 4af62bdbac8e27f03c217175aefdd38c1d424e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 19/25] Revert "implement eager normalization in a fresh context during typeck" This reverts commit 04f2c0191ebe7a1018e00cf6db5f8394352c2da6. --- compiler/rustc_infer/src/infer/at.rs | 15 +++--- compiler/rustc_infer/src/infer/mod.rs | 13 +---- .../rustc_infer/src/infer/relate/lattice.rs | 5 +- compiler/rustc_interface/src/passes.rs | 2 +- compiler/rustc_middle/src/hooks/mod.rs | 24 +-------- compiler/rustc_trait_selection/src/solve.rs | 54 ++----------------- 6 files changed, 16 insertions(+), 97 deletions(-) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 3487286d58830..84ad05fa8ea23 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -140,9 +140,8 @@ impl<'a, 'tcx> At<'a, 'tcx> { ty::Contravariant, actual, self.cause.span, - &mut |alias| { - self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) - }, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -176,9 +175,8 @@ impl<'a, 'tcx> At<'a, 'tcx> { ty::Covariant, actual, self.cause.span, - &mut |alias| { - self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) - }, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -231,9 +229,8 @@ impl<'a, 'tcx> At<'a, 'tcx> { ty::Invariant, actual, self.cause.span, - &mut |alias| { - self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) - }, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 26b094ff8eccb..10e7b1e72f446 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1,5 +1,5 @@ use std::cell::{Cell, RefCell}; -use std::{fmt, mem}; +use std::fmt; pub use at::DefineOpaqueTypes; use free_regions::RegionRelations; @@ -21,7 +21,6 @@ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_macros::extension; pub use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::bug; -use rustc_middle::hooks::TypeErasedInfcx; use rustc_middle::infer::canonical::{CanonicalQueryInput, CanonicalVarValues}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::select; @@ -1529,16 +1528,6 @@ impl<'tcx> InferCtxt<'tcx> { } } - pub fn try_eagerly_normalize_alias<'a>( - &'a self, - param_env: ty::ParamEnv<'tcx>, - span: Span, - alias: ty::AliasTy<'tcx>, - ) -> Ty<'tcx> { - let erased = unsafe { mem::transmute::<_, TypeErasedInfcx<'a, 'tcx>>(self) }; - self.tcx.try_eagerly_normalize_alias(erased, param_env, span, alias) - } - /// Attach a callback to be invoked on each root obligation evaluated in the new trait solver. pub fn attach_obligation_inspector(&self, inspector: ObligationInspector<'tcx>) { debug_assert!( diff --git a/compiler/rustc_infer/src/infer/relate/lattice.rs b/compiler/rustc_infer/src/infer/relate/lattice.rs index 7e480df7dda63..2cb256b1c63c4 100644 --- a/compiler/rustc_infer/src/infer/relate/lattice.rs +++ b/compiler/rustc_infer/src/infer/relate/lattice.rs @@ -300,7 +300,8 @@ impl<'tcx> PredicateEmittingRelation> for LatticeOp<'_, 'tcx> { ))]); } - fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { - self.infcx.try_eagerly_normalize_alias(self.param_env(), self.span(), alias) + fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + // TODO: this should actually normalize + self.infcx.next_ty_var(self.span()) } } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 28f51095966d5..6b7f31521d441 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -904,7 +904,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock = LazyLock::new(|| { rustc_hir_typeck::provide(&mut providers.queries); ty::provide(&mut providers.queries); traits::provide(&mut providers.queries); - solve::provide(providers); + solve::provide(&mut providers.queries); rustc_passes::provide(&mut providers.queries); rustc_traits::provide(&mut providers.queries); rustc_ty_utils::provide(&mut providers.queries); diff --git a/compiler/rustc_middle/src/hooks/mod.rs b/compiler/rustc_middle/src/hooks/mod.rs index 0caa8dcbc9c64..691096f4e47c2 100644 --- a/compiler/rustc_middle/src/hooks/mod.rs +++ b/compiler/rustc_middle/src/hooks/mod.rs @@ -3,16 +3,14 @@ //! similar to queries, but queries come with a lot of machinery for caching and incremental //! compilation, whereas hooks are just plain function pointers without any of the query magic. -use std::marker::PhantomData; - use rustc_hir::def_id::{DefId, DefPathHash}; use rustc_session::StableCrateId; use rustc_span::def_id::{CrateNum, LocalDefId}; -use rustc_span::{ExpnHash, ExpnId, Span}; +use rustc_span::{ExpnHash, ExpnId}; +use crate::mir; use crate::query::on_disk_cache::{CacheEncoder, EncodedDepNodeIndex}; use crate::ty::{Ty, TyCtxt}; -use crate::{mir, ty}; macro_rules! declare_hooks { ($($(#[$attr:meta])*hook $name:ident($($arg:ident: $K:ty),*) -> $V:ty;)*) => { @@ -117,24 +115,6 @@ declare_hooks! { encoder: &mut CacheEncoder<'_, 'tcx>, query_result_index: &mut EncodedDepNodeIndex ) -> (); - - /// Tries to normalize an alias, ignoring any errors. - /// - /// Generalization with the new trait solver calls into this, - /// when generalizing outside of the trait solver in `hir_typeck`. - hook try_eagerly_normalize_alias( - type_erased_infcx: TypeErasedInfcx<'_, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - span: Span, - alias: ty::AliasTy<'tcx> - ) -> Ty<'tcx>; -} - -// `repr(transparent)` so we can transmute a `&'a Infcx<'tcx>` to this struct. -#[repr(transparent)] -pub struct TypeErasedInfcx<'a, 'tcx> { - _infcx: *const (), - phantom: PhantomData<&'a mut &'tcx ()>, } #[cold] diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index cb02885139038..5d200c4d340ba 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -1,8 +1,3 @@ -use std::mem; - -use rustc_infer::infer::InferCtxt; -use rustc_infer::traits::{Obligation, ObligationCause}; -use rustc_middle::hooks::TypeErasedInfcx; pub use rustc_next_trait_solver::solve::*; mod delegate; @@ -18,13 +13,10 @@ pub use normalize::{ deeply_normalize, deeply_normalize_with_skipped_universes, deeply_normalize_with_skipped_universes_and_ambiguous_coroutine_goals, }; -use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_middle::util::Providers; -use rustc_span::Span; +use rustc_middle::query::Providers; +use rustc_middle::ty::TyCtxt; pub use select::InferCtxtSelectExt; -use crate::traits::ObligationCtxt; - fn evaluate_root_goal_for_proof_tree_raw<'tcx>( tcx: TyCtxt<'tcx>, canonical_input: CanonicalInput>, @@ -35,46 +27,6 @@ fn evaluate_root_goal_for_proof_tree_raw<'tcx>( ) } -fn try_eagerly_normalize_alias<'a, 'tcx>( - tcx: TyCtxt<'tcx>, - type_erased_infcx: TypeErasedInfcx<'a, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - span: Span, - alias: ty::AliasTy<'tcx>, -) -> Ty<'tcx> { - let infcx = unsafe { - mem::transmute::, &'a InferCtxt<'tcx>>(type_erased_infcx) - }; - - let ocx = ObligationCtxt::new(infcx); - - let infer_term = infcx.next_ty_var(span); - - // Dummy because we ignore the error anyway. - // We do provide a span, because this span is used when registering opaque types. - // For example, if we don't provide a span here, some diagnostics talking about TAIT will refer to a dummy span. - let cause = ObligationCause::dummy_with_span(span); - let obligation = Obligation::new( - tcx, - // we ignore the error anyway - ObligationCause::dummy(), - param_env, - ty::PredicateKind::AliasRelate( - alias.to_ty(tcx).into(), - infer_term.into(), - ty::AliasRelationDirection::Equate, - ), - ); - - ocx.register_obligation(obligation); - - // This only tries to eagerly resolve, if it errors we don't care. - let _ = ocx.try_evaluate_obligations(); - - infcx.resolve_vars_if_possible(infer_term) -} - pub fn provide(providers: &mut Providers) { - providers.hooks.try_eagerly_normalize_alias = try_eagerly_normalize_alias; - providers.queries.evaluate_root_goal_for_proof_tree_raw = evaluate_root_goal_for_proof_tree_raw; + *providers = Providers { evaluate_root_goal_for_proof_tree_raw, ..*providers }; } From 632ed108384bff71b7a59883891f39b90aa7d5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 20/25] Revert "eagerly normalize during generalization" This reverts commit 5d0863b147c0a0084dfb15e6435004909363865f. --- .../src/type_check/relate_tys.rs | 5 - compiler/rustc_infer/src/infer/at.rs | 6 - .../src/infer/relate/generalize.rs | 103 ++++-------------- .../rustc_infer/src/infer/relate/lattice.rs | 5 - .../src/infer/relate/type_relating.rs | 9 -- .../src/solve/assembly/structural_traits.rs | 7 +- .../src/solve/eval_ctxt/mod.rs | 35 +----- compiler/rustc_type_ir/src/relate/combine.rs | 2 - .../src/relate/solver_relating.rs | 49 +++------ 9 files changed, 42 insertions(+), 179 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index 152a7674490c7..e2d684e12a816 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -616,9 +616,4 @@ impl<'b, 'tcx> PredicateEmittingRelation> for NllTypeRelating<'_ } })]); } - - fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { - // Past hir typeck, so we don't have to worry about type inference anymore. - self.type_checker.infcx.next_ty_var(self.span()) - } } diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 84ad05fa8ea23..70e3d7dc9fef0 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -140,8 +140,6 @@ impl<'a, 'tcx> At<'a, 'tcx> { ty::Contravariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -175,8 +173,6 @@ impl<'a, 'tcx> At<'a, 'tcx> { ty::Covariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -229,8 +225,6 @@ impl<'a, 'tcx> At<'a, 'tcx> { ty::Invariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 34fd32c45bd27..69c090b662e54 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -76,7 +76,6 @@ impl<'tcx> InferCtxt<'tcx> { target_vid, instantiation_variance, source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), )?; // Constrain `b_vid` to the generalized type `generalized_ty`. @@ -211,7 +210,6 @@ impl<'tcx> InferCtxt<'tcx> { target_vid, ty::Invariant, source_ct, - &mut |alias| relation.try_eagerly_normalize_alias(alias), )?; debug_assert!(!generalized_ct.is_ct_infer()); @@ -251,7 +249,6 @@ impl<'tcx> InferCtxt<'tcx> { target_vid: impl Into, ambient_variance: ty::Variance, source_term: T, - normalize: &mut dyn FnMut(ty::AliasTy<'tcx>) -> Ty<'tcx>, ) -> RelateResult<'tcx, Generalization> { assert!(!source_term.has_escaping_bound_vars()); let (for_universe, root_vid) = match target_vid.into() { @@ -272,9 +269,8 @@ impl<'tcx> InferCtxt<'tcx> { for_universe, root_term: source_term.into(), ambient_variance, - state: GeneralizationState::Default, + in_alias: false, cache: Default::default(), - normalize, }; let value_may_be_infer = generalizer.relate(source_term, source_term)?; @@ -321,13 +317,6 @@ impl<'tcx> TypeVisitor> for MaxUniverse { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum GeneralizationState { - Default, - InAlias, - InNormalizedAlias, -} - /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to @@ -372,14 +361,9 @@ struct Generalizer<'me, 'tcx> { /// This is necessary to correctly handle /// `::Assoc>::Assoc == ?0`. This equality can /// hold by either normalizing the outer or the inner associated type. - // TODO: update doc comment - state: GeneralizationState, + in_alias: bool, - cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizationState), Ty<'tcx>>, - - /// Normalize an alias in the trait solver. - /// If normalization fails, a fresh infer var is returned. - normalize: &'me mut dyn FnMut(ty::AliasTy<'tcx>) -> Ty<'tcx>, + cache: SsoHashMap<(Ty<'tcx>, ty::Variance, bool), Ty<'tcx>>, } impl<'tcx> Generalizer<'_, 'tcx> { @@ -425,34 +409,17 @@ impl<'tcx> Generalizer<'_, 'tcx> { // with inference variables can cause incorrect ambiguity. // // cc trait-system-refactor-initiative#110 - if self.infcx.next_trait_solver() - && !alias.has_escaping_bound_vars() - && match self.state { - GeneralizationState::Default => true, - GeneralizationState::InAlias => false, - // When generalizing an alias after normalizing, - // the outer alias should be treated as rigid and we shouldn't try generalizing it again. - // If we recursively find more aliases, the state should have been set back to InAlias. - GeneralizationState::InNormalizedAlias => unreachable!(), - } - { - let normalized_alias = (self.normalize)(alias); - - self.state = GeneralizationState::InNormalizedAlias; - // recursively generalize, treat the outer alias as rigid to avoid infinite recursion - let res = self.relate(normalized_alias, normalized_alias); - - // only one way to get here - self.state = GeneralizationState::Default; - - return res; + if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() && !self.in_alias { + return Ok(self.next_ty_var_for_alias()); } - let previous_state = mem::replace(&mut self.state, GeneralizationState::InAlias); + let is_nested_alias = mem::replace(&mut self.in_alias, true); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), - Err(e) => match previous_state { - GeneralizationState::Default => { + Err(e) => { + if is_nested_alias { + return Err(e); + } else { let mut visitor = MaxUniverse::new(); alias.visit_with(&mut visitor); let infer_replacement_is_complete = @@ -465,14 +432,9 @@ impl<'tcx> Generalizer<'_, 'tcx> { debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - GeneralizationState::InAlias => return Err(e), - // When generalizing an alias after normalizing, - // the outer alias should be treated as rigid and we shouldn't try generalizing it again. - // If we recursively find more aliases, the state should have been set back to InAlias. - GeneralizationState::InNormalizedAlias => unreachable!(), - }, + } }; - self.state = previous_state; + self.in_alias = is_nested_alias; result } } @@ -526,7 +488,7 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { assert_eq!(t, t2); // we are misusing TypeRelation here; both LHS and RHS ought to be == - if let Some(&result) = self.cache.get(&(t, self.ambient_variance, self.state)) { + if let Some(&result) = self.cache.get(&(t, self.ambient_variance, self.in_alias)) { return Ok(result); } @@ -591,17 +553,9 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { // cc trait-system-refactor-initiative#108 if self.infcx.next_trait_solver() && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) + && self.in_alias { - match self.state { - GeneralizationState::InAlias => { - inner.type_variables().equate(vid, new_var_id); - } - GeneralizationState::Default - | GeneralizationState::InNormalizedAlias => {} - } - GeneralizerState::Default - | GeneralizerState::ShallowStructurallyRelateAliases - | GeneralizerState::StructurallyRelateAliases => {} + inner.type_variables().equate(vid, new_var_id); } debug!("replacing original vid={:?} with new={:?}", vid, new_var_id); @@ -631,25 +585,14 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { } ty::Alias(_, data) => match self.structurally_relate_aliases { - StructurallyRelateAliases::No => match self.state { - GeneralizationState::Default | GeneralizationState::InAlias => { - self.generalize_alias_ty(data) - } - GeneralizationState::InNormalizedAlias => { - // We can switch back to default, we've treated one layer as rigid by doing this operation. - self.state = GeneralizationState::Default; - let res = relate::structurally_relate_tys(self, t, t); - self.state = GeneralizationState::InNormalizedAlias; - res - } - }, + StructurallyRelateAliases::No => self.generalize_alias_ty(data), StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t), }, _ => relate::structurally_relate_tys(self, t, t), }?; - self.cache.insert((t, self.ambient_variance, self.state), g); + self.cache.insert((t, self.ambient_variance, self.in_alias), g); Ok(g) } @@ -740,17 +683,9 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { // for more details. if self.infcx.next_trait_solver() && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) + && self.in_alias { - match self.state { - GeneralizationState::InAlias => { - variable_table.union(vid, new_var_id); - } - GeneralizationState::Default - | GeneralizationState::InNormalizedAlias => {} - } - GeneralizerState::Default - | GeneralizerState::ShallowStructurallyRelateAliases - | GeneralizerState::StructurallyRelateAliases => {} + variable_table.union(vid, new_var_id); } Ok(ty::Const::new_var(self.cx(), new_var_id)) } diff --git a/compiler/rustc_infer/src/infer/relate/lattice.rs b/compiler/rustc_infer/src/infer/relate/lattice.rs index 2cb256b1c63c4..a05e2d40e829f 100644 --- a/compiler/rustc_infer/src/infer/relate/lattice.rs +++ b/compiler/rustc_infer/src/infer/relate/lattice.rs @@ -299,9 +299,4 @@ impl<'tcx> PredicateEmittingRelation> for LatticeOp<'_, 'tcx> { ty::AliasRelationDirection::Equate, ))]); } - - fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { - // TODO: this should actually normalize - self.infcx.next_ty_var(self.span()) - } } diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index 67f9dc69a4a65..96a0375f5fba6 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -396,13 +396,4 @@ impl<'tcx> PredicateEmittingRelation> for TypeRelating<'_, 'tcx> } })]); } - - fn try_eagerly_normalize_alias( - &mut self, - _alias: rustc_type_ir::AliasTy< as rustc_type_ir::InferCtxtLike>::Interner>, - ) -> < as rustc_type_ir::InferCtxtLike>::Interner as rustc_type_ir::Interner>::Ty - { - // We only try to eagerly normalize aliases if we're using the new solver. - unreachable!() - } } diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 456fe5246d801..cd74e87b670f1 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -987,12 +987,7 @@ where let replacement = self.ecx.instantiate_binder_with_infer(*replacement); self.nested.extend( self.ecx - .relate_and_get_goals( - self.param_env, - alias_term, - ty::Invariant, - replacement.projection_term, - ) + .eq_and_get_goals(self.param_env, alias_term, replacement.projection_term) .expect("expected to be able to unify goal projection with dyn's projection"), ); diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index fedb6390d9588..6841fe1c5124e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -408,7 +408,7 @@ where /// Recursively evaluates `goal`, returning whether any inference vars have /// been constrained and the certainty of the result. - pub(super) fn evaluate_goal( + fn evaluate_goal( &mut self, source: GoalSource, goal: Goal, @@ -1018,8 +1018,7 @@ where variance: ty::Variance, rhs: T, ) -> Result<(), NoSolution> { - let goals = self.relate_and_get_goals(param_env, lhs, variance, rhs)?; - + let goals = self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)?; for &goal in goals.iter() { let source = match goal.predicate.kind().skip_binder() { ty::PredicateKind::Subtype { .. } | ty::PredicateKind::AliasRelate(..) => { @@ -1040,37 +1039,13 @@ where /// If possible, try using `eq` instead which automatically handles nested /// goals correctly. #[instrument(level = "trace", skip(self, param_env), ret)] - pub(super) fn relate_and_get_goals>( - &mut self, + pub(super) fn eq_and_get_goals>( + &self, param_env: I::ParamEnv, lhs: T, - variance: ty::Variance, rhs: T, ) -> Result>, NoSolution> { - let cx = self.cx(); - let delegate = self.delegate; - let origin_span = self.origin_span; - - let mut normalize = |alias: ty::AliasTy| { - let inference_var = self.next_ty_infer(); - - let goal = Goal::new( - cx, - param_env, - ty::PredicateKind::AliasRelate( - alias.to_ty(cx).into(), - inference_var.into(), - ty::AliasRelationDirection::Equate, - ), - ); - - // Ignore the result. If we can't eagerly normalize, returning the inference variable is enough. - let _ = self.evaluate_goal(GoalSource::TypeRelating, goal, None); - - self.resolve_vars_if_possible(inference_var) - }; - - Ok(delegate.relate(param_env, lhs, variance, rhs, origin_span, &mut normalize)?) + Ok(self.delegate.relate(param_env, lhs, ty::Variance::Invariant, rhs, self.origin_span)?) } pub(super) fn instantiate_binder_with_infer + Copy>( diff --git a/compiler/rustc_type_ir/src/relate/combine.rs b/compiler/rustc_type_ir/src/relate/combine.rs index 72d54c23733ee..64b87fac77f94 100644 --- a/compiler/rustc_type_ir/src/relate/combine.rs +++ b/compiler/rustc_type_ir/src/relate/combine.rs @@ -40,8 +40,6 @@ where /// Register `AliasRelate` obligation(s) that both types must be related to each other. fn register_alias_relate_predicate(&mut self, a: I::Ty, b: I::Ty); - - fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy) -> I::Ty; } pub fn super_combine_tys( diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 541b2531fe749..82ee4f75fcb0a 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -15,7 +15,6 @@ pub trait RelateExt: InferCtxtLike { variance: ty::Variance, rhs: T, span: ::Span, - normalize: &mut dyn FnMut(ty::AliasTy) -> ::Ty, ) -> Result< Vec::Predicate>>, TypeError, @@ -33,46 +32,40 @@ pub trait RelateExt: InferCtxtLike { >; } -impl> RelateExt for Infcx { - fn relate>( +impl RelateExt for Infcx { + fn relate>( &self, - param_env: I::ParamEnv, + param_env: ::ParamEnv, lhs: T, variance: ty::Variance, rhs: T, - span: I::Span, - normalize: &mut dyn FnMut(ty::AliasTy) -> I::Ty, - ) -> Result>, TypeError> { - let mut relate = SolverRelating::new( - self, - StructurallyRelateAliases::No, - variance, - param_env, - span, - normalize, - ); + span: ::Span, + ) -> Result< + Vec::Predicate>>, + TypeError, + > { + let mut relate = + SolverRelating::new(self, StructurallyRelateAliases::No, variance, param_env, span); relate.relate(lhs, rhs)?; Ok(relate.goals) } - fn eq_structurally_relating_aliases>( + fn eq_structurally_relating_aliases>( &self, - param_env: I::ParamEnv, + param_env: ::ParamEnv, lhs: T, rhs: T, - span: I::Span, - ) -> Result>, TypeError> { - // Structurally relating, we treat aliases as rigid, - // so we shouldn't ever try to normalize them. - let mut normalize_unreachable = |_alias| unreachable!(); - + span: ::Span, + ) -> Result< + Vec::Predicate>>, + TypeError, + > { let mut relate = SolverRelating::new( self, StructurallyRelateAliases::Yes, ty::Invariant, param_env, span, - &mut normalize_unreachable, ); relate.relate(lhs, rhs)?; Ok(relate.goals) @@ -82,14 +75,12 @@ impl> RelateExt for Infcx { /// Enforce that `a` is equal to or a subtype of `b`. pub struct SolverRelating<'infcx, Infcx, I: Interner> { infcx: &'infcx Infcx, - // Immutable fields. structurally_relate_aliases: StructurallyRelateAliases, param_env: I::ParamEnv, span: I::Span, // Mutable fields. ambient_variance: ty::Variance, - normalize: &'infcx mut dyn FnMut(ty::AliasTy) -> I::Ty, goals: Vec>, /// The cache only tracks the `ambient_variance` as it's the /// only field which is mutable and which meaningfully changes @@ -127,14 +118,12 @@ where ambient_variance: ty::Variance, param_env: I::ParamEnv, span: I::Span, - normalize: &'infcx mut dyn FnMut(ty::AliasTy) -> I::Ty, ) -> Self { SolverRelating { infcx, structurally_relate_aliases, span, ambient_variance, - normalize, param_env, goals: vec![], cache: Default::default(), @@ -417,8 +406,4 @@ where } })]); } - - fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy) -> I::Ty { - (self.normalize)(alias) - } } From 34a876657558c348388bc20066d32b40a393bc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:09:34 +0100 Subject: [PATCH 21/25] Revert "Regression test for trait-system-refactor#262" This reverts commit 244b6d5ca8fd6257df414c8b8d48c9c8d26269de. --- .../generalize/eagely-normalizing-aliases.rs | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs diff --git a/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs b/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs deleted file mode 100644 index 463fe49e55315..0000000000000 --- a/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs +++ /dev/null @@ -1,26 +0,0 @@ -//@ revisions: old next -//@[next] compile-flags: -Znext-solver -//@ ignore-compare-mode-next-solver (explicit revisions) -//@ check-pass -// Regression test for trait-system-refactor-initiative#262 - -trait View {} -trait HasAssoc { - type Assoc; -} - -struct StableVec(T); -impl View for StableVec {} - -fn assert_view(f: F) -> F { f } - - -fn store(x: StableVec) -where - T: HasAssoc, - StableVec: View, -{ - let _: StableVec = assert_view(x); -} - -fn main() {} From e28b48565fcccc43eb6abdf93da1af449815c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 23 Mar 2026 12:11:08 +0100 Subject: [PATCH 22/25] add regression test --- .../constrain-inference-during-normalize.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/ui/traits/next-solver/generalize/constrain-inference-during-normalize.rs diff --git a/tests/ui/traits/next-solver/generalize/constrain-inference-during-normalize.rs b/tests/ui/traits/next-solver/generalize/constrain-inference-during-normalize.rs new file mode 100644 index 0000000000000..c3942b008ccbc --- /dev/null +++ b/tests/ui/traits/next-solver/generalize/constrain-inference-during-normalize.rs @@ -0,0 +1,25 @@ +// revisions: next old +//[next] compile-flags: -Znext-solver +//@ check-pass +// Regression test for https://github.com/rust-lang/rust/issues/154173. +// The ICE there was caused by a (flawed) attempt to eagerly normalize during generalization. +// The normalize would constrain other inference variables, which we couldn't deal with. + +trait Trait { + type Assoc; +} + +impl Trait for () { + type Assoc = u32; +} + +trait Eq {} +impl, T> Eq for (C, T, >::Assoc) {} +fn foo() +where + ((), A, A): Eq +{} + +fn main() { + foo::<_>(); +} From e1e903c7b73b3cd7304ab3ae0e34dbb22bbb75e9 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 23 Mar 2026 08:14:09 -0400 Subject: [PATCH 23/25] Exclude slow tests from miri --- library/coretests/tests/char.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index aa20585953b7c..877017f682c97 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -52,6 +52,7 @@ fn test_is_cased() { } #[test] +#[cfg_attr(miri, ignore)] // Miri is too slow fn test_char_case() { for c in '\0'..='\u{10FFFF}' { match c.case() { @@ -100,6 +101,7 @@ fn titlecase_fast_path() { } #[test] +#[cfg_attr(miri, ignore)] // Miri is too slow fn at_most_one_case() { for c in '\0'..='\u{10FFFF}' { assert_eq!( From 34ef9a6146a26d3c3cfef42202b0f1d6dd2fe053 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 24 Feb 2026 09:08:12 +0100 Subject: [PATCH 24/25] Remove `ATTRIBUTE_ORDER` --- .../src/attributes/autodiff.rs | 3 +- .../src/attributes/cfi_encoding.rs | 1 - .../src/attributes/codegen_attrs.rs | 7 --- .../src/attributes/crate_level.rs | 6 --- .../src/attributes/deprecation.rs | 1 - .../attributes/diagnostic/do_not_recommend.rs | 3 +- .../src/attributes/dummy.rs | 3 +- .../src/attributes/inline.rs | 2 - .../src/attributes/instruction_set.rs | 1 - .../src/attributes/link_attrs.rs | 5 -- .../src/attributes/macro_attrs.rs | 2 - .../rustc_attr_parsing/src/attributes/mod.rs | 54 ++----------------- .../src/attributes/must_not_suspend.rs | 1 - .../src/attributes/must_use.rs | 1 - .../rustc_attr_parsing/src/attributes/path.rs | 1 - .../src/attributes/prelude.rs | 4 +- .../src/attributes/proc_macro_attrs.rs | 2 - .../src/attributes/prototype.rs | 4 +- .../src/attributes/rustc_allocator.rs | 1 - .../src/attributes/rustc_internal.rs | 17 ------ .../src/attributes/test_attrs.rs | 6 --- .../src/attributes/traits.rs | 5 +- .../src/attributes/transparency.rs | 1 - tests/ui/coverage-attr/bad-syntax.rs | 6 +-- tests/ui/coverage-attr/bad-syntax.stderr | 16 +++--- tests/ui/lint/unused/unused-attr-duplicate.rs | 12 ++--- .../lint/unused/unused-attr-duplicate.stderr | 36 ++++++------- tests/ui/sanitize-attr/invalid-sanitize.rs | 6 +-- .../ui/sanitize-attr/invalid-sanitize.stderr | 16 +++--- 29 files changed, 58 insertions(+), 165 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/autodiff.rs b/compiler/rustc_attr_parsing/src/attributes/autodiff.rs index 118a4103b1a96..c512250a83fa6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/autodiff.rs +++ b/compiler/rustc_attr_parsing/src/attributes/autodiff.rs @@ -9,7 +9,7 @@ use rustc_span::{Symbol, sym}; use thin_vec::ThinVec; use crate::attributes::prelude::Allow; -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; +use crate::attributes::{OnDuplicate, SingleAttributeParser}; use crate::context::{AcceptContext, Stage}; use crate::parser::{ArgParser, MetaItemOrLitParser}; use crate::target_checking::AllowedTargets; @@ -18,7 +18,6 @@ pub(crate) struct RustcAutodiffParser; impl SingleAttributeParser for RustcAutodiffParser { const PATH: &[Symbol] = &[sym::rustc_autodiff]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), diff --git a/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs b/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs index df1e569743c04..339697649164e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs @@ -8,7 +8,6 @@ impl SingleAttributeParser for CfiEncodingParser { Allow(Target::Enum), Allow(Target::Union), ]); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "encoding"); diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 652a783de3227..7a748d7627cb2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -12,7 +12,6 @@ pub(crate) struct OptimizeParser; impl SingleAttributeParser for OptimizeParser { const PATH: &[Symbol] = &[sym::optimize]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), @@ -68,7 +67,6 @@ pub(crate) struct CoverageParser; impl SingleAttributeParser for CoverageParser { const PATH: &[Symbol] = &[sym::coverage]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), @@ -119,7 +117,6 @@ pub(crate) struct ExportNameParser; impl SingleAttributeParser for ExportNameParser { const PATH: &[rustc_span::Symbol] = &[sym::export_name]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Static), @@ -157,7 +154,6 @@ pub(crate) struct RustcObjcClassParser; impl SingleAttributeParser for RustcObjcClassParser { const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_class]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]); @@ -189,7 +185,6 @@ pub(crate) struct RustcObjcSelectorParser; impl SingleAttributeParser for RustcObjcSelectorParser { const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_selector]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]); @@ -595,7 +590,6 @@ impl SingleAttributeParser for SanitizeParser { r#"realtime = "nonblocking|blocking|caller""#, ]); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { @@ -724,7 +718,6 @@ pub(crate) struct PatchableFunctionEntryParser; impl SingleAttributeParser for PatchableFunctionEntryParser { const PATH: &[Symbol] = &[sym::patchable_function_entry]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const TEMPLATE: AttributeTemplate = template!(List: &["prefix_nops = m, entry_nops = n"]); diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 2d2994c02cd61..72a0945a4199d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -10,7 +10,6 @@ pub(crate) struct CrateNameParser; impl SingleAttributeParser for CrateNameParser { const PATH: &[Symbol] = &[sym::crate_name]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); @@ -84,7 +83,6 @@ pub(crate) struct RecursionLimitParser; impl SingleAttributeParser for RecursionLimitParser { const PATH: &[Symbol] = &[sym::recursion_limit]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); @@ -107,7 +105,6 @@ pub(crate) struct MoveSizeLimitParser; impl SingleAttributeParser for MoveSizeLimitParser { const PATH: &[Symbol] = &[sym::move_size_limit]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); @@ -130,7 +127,6 @@ pub(crate) struct TypeLengthLimitParser; impl SingleAttributeParser for TypeLengthLimitParser { const PATH: &[Symbol] = &[sym::type_length_limit]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); @@ -153,7 +149,6 @@ pub(crate) struct PatternComplexityLimitParser; impl SingleAttributeParser for PatternComplexityLimitParser { const PATH: &[Symbol] = &[sym::pattern_complexity_limit]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); @@ -213,7 +208,6 @@ pub(crate) struct WindowsSubsystemParser; impl SingleAttributeParser for WindowsSubsystemParser { const PATH: &[Symbol] = &[sym::windows_subsystem]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: ["windows", "console"], "https://doc.rust-lang.org/reference/runtime.html#the-windows_subsystem-attribute"); diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index a2c7e459e0df8..804b54e9ee256 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -34,7 +34,6 @@ fn get( pub(crate) struct DeprecatedParser; impl SingleAttributeParser for DeprecatedParser { const PATH: &[Symbol] = &[sym::deprecated]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::Fn), diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs index 4a89cf6515ce0..9f3d1e29b4dec 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs @@ -4,7 +4,7 @@ use rustc_hir::lints::AttributeLintKind; use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::{Symbol, sym}; -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; +use crate::attributes::{OnDuplicate, SingleAttributeParser}; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; use crate::target_checking::{ALL_TARGETS, AllowedTargets}; @@ -12,7 +12,6 @@ use crate::target_checking::{ALL_TARGETS, AllowedTargets}; pub(crate) struct DoNotRecommendParser; impl SingleAttributeParser for DoNotRecommendParser { const PATH: &[Symbol] = &[sym::diagnostic, sym::do_not_recommend]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); // Checked in check_attr. const TEMPLATE: AttributeTemplate = template!(Word /*doesn't matter */); diff --git a/compiler/rustc_attr_parsing/src/attributes/dummy.rs b/compiler/rustc_attr_parsing/src/attributes/dummy.rs index 71d10b23a37f6..ee5c507b62920 100644 --- a/compiler/rustc_attr_parsing/src/attributes/dummy.rs +++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs @@ -2,7 +2,7 @@ use rustc_feature::{AttributeTemplate, template}; use rustc_hir::attrs::AttributeKind; use rustc_span::{Symbol, sym}; -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; +use crate::attributes::{OnDuplicate, SingleAttributeParser}; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; use crate::target_checking::{ALL_TARGETS, AllowedTargets}; @@ -10,7 +10,6 @@ use crate::target_checking::{ALL_TARGETS, AllowedTargets}; pub(crate) struct RustcDummyParser; impl SingleAttributeParser for RustcDummyParser { const PATH: &[Symbol] = &[sym::rustc_dummy]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index 767200bfa9bf1..82cec25c997ca 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -11,7 +11,6 @@ pub(crate) struct InlineParser; impl SingleAttributeParser for InlineParser { const PATH: &[Symbol] = &[sym::inline]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), @@ -68,7 +67,6 @@ pub(crate) struct RustcForceInlineParser; impl SingleAttributeParser for RustcForceInlineParser { const PATH: &[Symbol] = &[sym::rustc_force_inline]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), diff --git a/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs b/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs index 3be9b9ded9c1f..5f6108108a774 100644 --- a/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs +++ b/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs @@ -16,7 +16,6 @@ impl SingleAttributeParser for InstructionSetParser { ]); const TEMPLATE: AttributeTemplate = template!(List: &["set"], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-instruction_set-attribute"); const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { const POSSIBLE_SYMBOLS: &[Symbol] = &[sym::arm_a32, sym::arm_t32]; diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index c4a483157a19d..52ab4ac8a4494 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -22,7 +22,6 @@ pub(crate) struct LinkNameParser; impl SingleAttributeParser for LinkNameParser { const PATH: &[Symbol] = &[sym::link_name]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::ForeignFn), @@ -466,7 +465,6 @@ pub(crate) struct LinkSectionParser; impl SingleAttributeParser for LinkSectionParser { const PATH: &[Symbol] = &[sym::link_section]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::Static), @@ -541,7 +539,6 @@ pub(crate) struct LinkOrdinalParser; impl SingleAttributeParser for LinkOrdinalParser { const PATH: &[Symbol] = &[sym::link_ordinal]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::ForeignFn), @@ -583,8 +580,6 @@ pub(crate) struct LinkageParser; impl SingleAttributeParser for LinkageParser { const PATH: &[Symbol] = &[sym::linkage]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; - const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs index 00d40687fc85e..86dde5b108ff2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs @@ -130,7 +130,6 @@ pub(crate) struct MacroExportParser; impl SingleAttributeParser for MacroExportParser { const PATH: &[Symbol] = &[sym::macro_export]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const TEMPLATE: AttributeTemplate = template!(Word, List: &["local_inner_macros"]); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ @@ -168,7 +167,6 @@ pub(crate) struct CollapseDebugInfoParser; impl SingleAttributeParser for CollapseDebugInfoParser { const PATH: &[Symbol] = &[sym::collapse_debuginfo]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const TEMPLATE: AttributeTemplate = template!( List: &["no", "external", "yes"], diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 223c88972d75e..67147642921c2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -124,14 +124,8 @@ pub(crate) trait SingleAttributeParser: 'static { /// If you need the parser to accept more than one path, use [`AttributeParser`] instead const PATH: &[Symbol]; - /// Configures the precedence of attributes with the same `PATH` on a syntax node. - const ATTRIBUTE_ORDER: AttributeOrder; - /// Configures what to do when when the same attribute is /// applied more than once on the same syntax node. - /// - /// [`ATTRIBUTE_ORDER`](Self::ATTRIBUTE_ORDER) specified which one is assumed to be correct, - /// and this specified whether to, for example, warn or error on the other one. const ON_DUPLICATE: OnDuplicate; const ALLOWED_TARGETS: AllowedTargets; @@ -162,21 +156,8 @@ impl, S: Stage> AttributeParser for Single >::TEMPLATE, |group: &mut Single, cx, args| { if let Some(pa) = T::convert(cx, args) { - match T::ATTRIBUTE_ORDER { - // keep the first and report immediately. ignore this attribute - AttributeOrder::KeepInnermost => { - if let Some((_, unused)) = group.1 { - T::ON_DUPLICATE.exec::(cx, cx.attr_span, unused); - return; - } - } - // keep the new one and warn about the previous, - // then replace - AttributeOrder::KeepOutermost => { - if let Some((_, used)) = group.1 { - T::ON_DUPLICATE.exec::(cx, used, cx.attr_span); - } - } + if let Some((_, used)) = group.1 { + T::ON_DUPLICATE.exec::(cx, used, cx.attr_span); } group.1 = Some((pa, cx.attr_span)); @@ -206,7 +187,7 @@ pub(crate) enum OnDuplicate { /// Custom function called when a duplicate attribute is found. /// /// - `unused` is the span of the attribute that was unused or bad because of some - /// duplicate reason (see [`AttributeOrder`]) + /// duplicate reason /// - `used` is the span of the attribute that was used in favor of the unused attribute Custom(fn(cx: &AcceptContext<'_, '_, S>, used: Span, unused: Span)), } @@ -223,8 +204,8 @@ impl OnDuplicate { OnDuplicate::WarnButFutureError => cx.warn_unused_duplicate_future_error(used, unused), OnDuplicate::Error => { cx.emit_err(UnusedMultiple { - this: used, - other: unused, + this: unused, + other: used, name: Symbol::intern( &P::PATH.into_iter().map(|i| i.to_string()).collect::>().join(".."), ), @@ -236,30 +217,6 @@ impl OnDuplicate { } } -pub(crate) enum AttributeOrder { - /// Duplicates after the innermost instance of the attribute will be an error/warning. - /// Only keep the lowest attribute. - /// - /// Attributes are processed from bottom to top, so this raises a warning/error on all the attributes - /// further above the lowest one: - /// ``` - /// #[stable(since="1.0")] //~ WARNING duplicated attribute - /// #[stable(since="2.0")] - /// ``` - KeepInnermost, - - /// Duplicates before the outermost instance of the attribute will be an error/warning. - /// Only keep the highest attribute. - /// - /// Attributes are processed from bottom to top, so this raises a warning/error on all the attributes - /// below the highest one: - /// ``` - /// #[path="foo.rs"] - /// #[path="bar.rs"] //~ WARNING duplicated attribute - /// ``` - KeepOutermost, -} - /// An even simpler version of [`SingleAttributeParser`]: /// now automatically check that there are no arguments provided to the attribute. /// @@ -284,7 +241,6 @@ impl, S: Stage> Default for WithoutArgs { impl, S: Stage> SingleAttributeParser for WithoutArgs { const PATH: &[Symbol] = T::PATH; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = T::ON_DUPLICATE; const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const TEMPLATE: AttributeTemplate = template!(Word); diff --git a/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs b/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs index 8456ce7977587..7f37210b8c8af 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs @@ -4,7 +4,6 @@ pub(crate) struct MustNotSuspendParser; impl SingleAttributeParser for MustNotSuspendParser { const PATH: &[rustc_span::Symbol] = &[sym::must_not_suspend]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Struct), diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs index 673e2c902da0b..f1ce810f4eaea 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs @@ -4,7 +4,6 @@ pub(crate) struct MustUseParser; impl SingleAttributeParser for MustUseParser { const PATH: &[Symbol] = &[sym::must_use]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::Fn), diff --git a/compiler/rustc_attr_parsing/src/attributes/path.rs b/compiler/rustc_attr_parsing/src/attributes/path.rs index b60f8e315e5ef..6b5eee7f31bbd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/path.rs +++ b/compiler/rustc_attr_parsing/src/attributes/path.rs @@ -4,7 +4,6 @@ pub(crate) struct PathParser; impl SingleAttributeParser for PathParser { const PATH: &[Symbol] = &[sym::path]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[Allow(Target::Mod), Error(Target::Crate)]); diff --git a/compiler/rustc_attr_parsing/src/attributes/prelude.rs b/compiler/rustc_attr_parsing/src/attributes/prelude.rs index 65c408fa6358c..53adf7e31eb73 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prelude.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs @@ -12,8 +12,8 @@ pub(super) use thin_vec::ThinVec; #[doc(hidden)] pub(super) use crate::attributes::{ - AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn, - NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, + AcceptMapping, AttributeParser, CombineAttributeParser, ConvertFn, NoArgsAttributeParser, + OnDuplicate, SingleAttributeParser, }; // contexts #[doc(hidden)] diff --git a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs index f9ace7e25d1b3..aca0e94cff06c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs @@ -25,7 +25,6 @@ impl NoArgsAttributeParser for ProcMacroAttributeParser { pub(crate) struct ProcMacroDeriveParser; impl SingleAttributeParser for ProcMacroDeriveParser { const PATH: &[Symbol] = &[sym::proc_macro_derive]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS; const TEMPLATE: AttributeTemplate = template!( @@ -46,7 +45,6 @@ impl SingleAttributeParser for ProcMacroDeriveParser { pub(crate) struct RustcBuiltinMacroParser; impl SingleAttributeParser for RustcBuiltinMacroParser { const PATH: &[Symbol] = &[sym::rustc_builtin_macro]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]); const TEMPLATE: AttributeTemplate = diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs index 8eb902be34e26..15d9d3453738b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prototype.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs @@ -5,7 +5,7 @@ use rustc_hir::Target; use rustc_hir::attrs::{AttributeKind, MirDialect, MirPhase}; use rustc_span::{Span, Symbol, sym}; -use super::{AttributeOrder, OnDuplicate}; +use super::OnDuplicate; use crate::attributes::SingleAttributeParser; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; @@ -18,8 +18,6 @@ pub(crate) struct CustomMirParser; impl SingleAttributeParser for CustomMirParser { const PATH: &[rustc_span::Symbol] = &[sym::custom_mir]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; - const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs index 5782f9473a994..e809ad9ed83b7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs @@ -28,7 +28,6 @@ impl SingleAttributeParser for RustcAllocatorZeroedVariantParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::ForeignFn)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "function"); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { let Some(name) = args.name_value().and_then(NameValueParser::value_as_str) else { cx.expected_name_value(cx.attr_span, None); diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index e8b4cb343794c..15772d874507f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -31,7 +31,6 @@ impl SingleAttributeParser for RustcMustImplementOneOfParser { const PATH: &[Symbol] = &[sym::rustc_must_implement_one_of]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(List: &["function1, function2, ..."]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { let Some(list) = args.list() else { @@ -105,7 +104,6 @@ pub(crate) struct RustcLayoutScalarValidRangeStartParser; impl SingleAttributeParser for RustcLayoutScalarValidRangeStartParser { const PATH: &[Symbol] = &[sym::rustc_layout_scalar_valid_range_start]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(List: &["start"]); @@ -120,7 +118,6 @@ pub(crate) struct RustcLayoutScalarValidRangeEndParser; impl SingleAttributeParser for RustcLayoutScalarValidRangeEndParser { const PATH: &[Symbol] = &[sym::rustc_layout_scalar_valid_range_end]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(List: &["end"]); @@ -135,7 +132,6 @@ pub(crate) struct RustcLegacyConstGenericsParser; impl SingleAttributeParser for RustcLegacyConstGenericsParser { const PATH: &[Symbol] = &[sym::rustc_legacy_const_generics]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const TEMPLATE: AttributeTemplate = template!(List: &["N"]); @@ -193,7 +189,6 @@ pub(crate) struct RustcLintOptDenyFieldAccessParser; impl SingleAttributeParser for RustcLintOptDenyFieldAccessParser { const PATH: &[Symbol] = &[sym::rustc_lint_opt_deny_field_access]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Field)]); const TEMPLATE: AttributeTemplate = template!(Word); @@ -373,7 +368,6 @@ impl SingleAttributeParser for RustcDeprecatedSafe2024Parser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(List: &[r#"audit_that = "...""#]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { @@ -441,7 +435,6 @@ impl SingleAttributeParser for RustcNeverTypeOptionsParser { const PATH: &[Symbol] = &[sym::rustc_never_type_options]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(List: &[ r#"fallback = "unit", "never", "no""#, r#"diverging_block_default = "unit", "never""#, @@ -592,7 +585,6 @@ pub(crate) struct RustcSimdMonomorphizeLaneLimitParser; impl SingleAttributeParser for RustcSimdMonomorphizeLaneLimitParser { const PATH: &[Symbol] = &[sym::rustc_simd_monomorphize_lane_limit]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); @@ -610,7 +602,6 @@ pub(crate) struct RustcScalableVectorParser; impl SingleAttributeParser for RustcScalableVectorParser { const PATH: &[Symbol] = &[sym::rustc_scalable_vector]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(Word, List: &["count"]); @@ -636,7 +627,6 @@ pub(crate) struct LangParser; impl SingleAttributeParser for LangParser { const PATH: &[Symbol] = &[sym::lang]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); // Targets are checked per lang item in `rustc_passes` const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); @@ -981,8 +971,6 @@ pub(crate) struct RustcIfThisChangedParser; impl SingleAttributeParser for RustcIfThisChangedParser { const PATH: &[Symbol] = &[sym::rustc_if_this_changed]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; - const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ @@ -1147,7 +1135,6 @@ pub(crate) struct RustcDiagnosticItemParser; impl SingleAttributeParser for RustcDiagnosticItemParser { const PATH: &[Symbol] = &[sym::rustc_diagnostic_item]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Trait), @@ -1220,7 +1207,6 @@ impl SingleAttributeParser for RustcSymbolNameParser { Allow(Target::Impl { of_trait: false }), ]); const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { if let Err(span) = args.no_args() { @@ -1245,7 +1231,6 @@ impl SingleAttributeParser for RustcDefPathParser { Allow(Target::Impl { of_trait: false }), ]); const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { if let Err(span) = args.no_args() { @@ -1275,7 +1260,6 @@ pub(crate) struct RustcReservationImplParser; impl SingleAttributeParser for RustcReservationImplParser { const PATH: &[Symbol] = &[sym::rustc_reservation_impl]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Impl { of_trait: true })]); @@ -1311,7 +1295,6 @@ pub(crate) struct RustcDocPrimitiveParser; impl SingleAttributeParser for RustcDocPrimitiveParser { const PATH: &[Symbol] = &[sym::rustc_doc_primitive]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Mod)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "primitive name"); diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index 2775eab1b5d97..db0350f23246e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -7,7 +7,6 @@ pub(crate) struct IgnoreParser; impl SingleAttributeParser for IgnoreParser { const PATH: &[Symbol] = &[sym::ignore]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[Allow(Target::Fn), Error(Target::WherePredicate)]); @@ -41,7 +40,6 @@ pub(crate) struct ShouldPanicParser; impl SingleAttributeParser for ShouldPanicParser { const PATH: &[Symbol] = &[sym::should_panic]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[Allow(Target::Fn), Error(Target::WherePredicate)]); @@ -98,7 +96,6 @@ pub(crate) struct ReexportTestHarnessMainParser; impl SingleAttributeParser for ReexportTestHarnessMainParser { const PATH: &[Symbol] = &[sym::reexport_test_harness_main]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); @@ -126,7 +123,6 @@ impl SingleAttributeParser for RustcAbiParser { const PATH: &[Symbol] = &[sym::rustc_abi]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::debug, sym::assert_eq]); - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::TyAlias), Allow(Target::Fn), @@ -197,7 +193,6 @@ pub(crate) struct TestRunnerParser; impl SingleAttributeParser for TestRunnerParser { const PATH: &[Symbol] = &[sym::test_runner]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(List: &["path"]); @@ -226,7 +221,6 @@ pub(crate) struct RustcTestMarkerParser; impl SingleAttributeParser for RustcTestMarkerParser { const PATH: &[Symbol] = &[sym::rustc_test_marker]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Const), diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index c09e151fc70c1..c418d1d032c48 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -1,9 +1,7 @@ use std::mem; use super::prelude::*; -use crate::attributes::{ - AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, -}; +use crate::attributes::{NoArgsAttributeParser, OnDuplicate, SingleAttributeParser}; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; use crate::target_checking::Policy::{Allow, Warn}; @@ -12,7 +10,6 @@ use crate::target_checking::{ALL_TARGETS, AllowedTargets}; pub(crate) struct RustcSkipDuringMethodDispatchParser; impl SingleAttributeParser for RustcSkipDuringMethodDispatchParser { const PATH: &[Symbol] = &[sym::rustc_skip_during_method_dispatch]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs index 58b4a0b2fb1ad..c3817406c9803 100644 --- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs +++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs @@ -6,7 +6,6 @@ pub(crate) struct RustcMacroTransparencyParser; impl SingleAttributeParser for RustcMacroTransparencyParser { const PATH: &[Symbol] = &[sym::rustc_macro_transparency]; - const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Custom(|cx, used, unused| { cx.dcx().span_err(vec![used, unused], "multiple macro transparency attributes"); }); diff --git a/tests/ui/coverage-attr/bad-syntax.rs b/tests/ui/coverage-attr/bad-syntax.rs index 062e82ee4b61d..ef7b41db3440e 100644 --- a/tests/ui/coverage-attr/bad-syntax.rs +++ b/tests/ui/coverage-attr/bad-syntax.rs @@ -6,12 +6,12 @@ // Tests the error messages produced (or not produced) by various unusual // uses of the `#[coverage(..)]` attribute. -#[coverage(off)] //~ ERROR multiple `coverage` attributes #[coverage(off)] +#[coverage(off)] //~ ERROR multiple `coverage` attributes fn multiple_consistent() {} -#[coverage(off)] //~ ERROR multiple `coverage` attributes -#[coverage(on)] +#[coverage(off)] +#[coverage(on)] //~ ERROR multiple `coverage` attributes fn multiple_inconsistent() {} #[coverage] //~ ERROR malformed `coverage` attribute input diff --git a/tests/ui/coverage-attr/bad-syntax.stderr b/tests/ui/coverage-attr/bad-syntax.stderr index ecf3ed83bca7b..4a356221ff602 100644 --- a/tests/ui/coverage-attr/bad-syntax.stderr +++ b/tests/ui/coverage-attr/bad-syntax.stderr @@ -1,26 +1,26 @@ error: multiple `coverage` attributes - --> $DIR/bad-syntax.rs:9:1 + --> $DIR/bad-syntax.rs:10:1 | LL | #[coverage(off)] | ^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/bad-syntax.rs:10:1 + --> $DIR/bad-syntax.rs:9:1 | LL | #[coverage(off)] | ^^^^^^^^^^^^^^^^ error: multiple `coverage` attributes - --> $DIR/bad-syntax.rs:13:1 + --> $DIR/bad-syntax.rs:14:1 | -LL | #[coverage(off)] - | ^^^^^^^^^^^^^^^^ help: remove this attribute +LL | #[coverage(on)] + | ^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/bad-syntax.rs:14:1 + --> $DIR/bad-syntax.rs:13:1 | -LL | #[coverage(on)] - | ^^^^^^^^^^^^^^^ +LL | #[coverage(off)] + | ^^^^^^^^^^^^^^^^ error[E0539]: malformed `coverage` attribute input --> $DIR/bad-syntax.rs:17:1 diff --git a/tests/ui/lint/unused/unused-attr-duplicate.rs b/tests/ui/lint/unused/unused-attr-duplicate.rs index a334c49788cb2..2b29fde128e4f 100644 --- a/tests/ui/lint/unused/unused-attr-duplicate.rs +++ b/tests/ui/lint/unused/unused-attr-duplicate.rs @@ -61,7 +61,7 @@ pub mod from_path; fn t1() {} #[must_use] -#[must_use = "some message"] //~ ERROR unused attribute +#[must_use = "some message"] //~ ERROR unused attribute //~^ WARN this was previously accepted // No warnings for #[repr], would require more logic. #[repr(C)] @@ -89,15 +89,15 @@ pub fn xyz() {} #[link(name = "rust_test_helpers", kind = "static")] #[link(name = "rust_test_helpers", kind = "static")] extern "C" { - #[link_name = "this_does_not_exist"] //~ ERROR unused attribute + #[link_name = "this_does_not_exist"] + #[link_name = "rust_dbg_extern_identity_u32"] //~ ERROR unused attribute //~^ WARN this was previously accepted - #[link_name = "rust_dbg_extern_identity_u32"] pub fn name_in_rust(v: u32) -> u32; } -#[export_name = "exported_symbol_name"] //~ ERROR unused attribute +#[export_name = "exported_symbol_name"] +#[export_name = "exported_symbol_name2"] //~ ERROR unused attribute //~^ WARN this was previously accepted -#[export_name = "exported_symbol_name2"] pub fn export_test() {} #[no_mangle] @@ -109,9 +109,9 @@ pub fn no_mangle_test() {} static FOO: u32 = 0; #[link_section = ".text"] +#[link_section = ".bss"] //~^ ERROR unused attribute //~| WARN this was previously accepted -#[link_section = ".bss"] pub extern "C" fn example() {} fn main() {} diff --git a/tests/ui/lint/unused/unused-attr-duplicate.stderr b/tests/ui/lint/unused/unused-attr-duplicate.stderr index 351645f4a783f..1942249d1f8e1 100644 --- a/tests/ui/lint/unused/unused-attr-duplicate.stderr +++ b/tests/ui/lint/unused/unused-attr-duplicate.stderr @@ -165,29 +165,29 @@ LL | #[track_caller] | ^^^^^^^^^^^^^^^ error: unused attribute - --> $DIR/unused-attr-duplicate.rs:92:5 + --> $DIR/unused-attr-duplicate.rs:93:5 | -LL | #[link_name = "this_does_not_exist"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute +LL | #[link_name = "rust_dbg_extern_identity_u32"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/unused-attr-duplicate.rs:94:5 + --> $DIR/unused-attr-duplicate.rs:92:5 | -LL | #[link_name = "rust_dbg_extern_identity_u32"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[link_name = "this_does_not_exist"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! error: unused attribute - --> $DIR/unused-attr-duplicate.rs:98:1 + --> $DIR/unused-attr-duplicate.rs:99:1 | -LL | #[export_name = "exported_symbol_name"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute +LL | #[export_name = "exported_symbol_name2"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/unused-attr-duplicate.rs:100:1 + --> $DIR/unused-attr-duplicate.rs:98:1 | -LL | #[export_name = "exported_symbol_name2"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[export_name = "exported_symbol_name"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! error: unused attribute @@ -215,16 +215,16 @@ LL | #[used] | ^^^^^^^ error: unused attribute - --> $DIR/unused-attr-duplicate.rs:111:1 + --> $DIR/unused-attr-duplicate.rs:112:1 | -LL | #[link_section = ".text"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute +LL | #[link_section = ".bss"] + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/unused-attr-duplicate.rs:114:1 + --> $DIR/unused-attr-duplicate.rs:111:1 | -LL | #[link_section = ".bss"] - | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[link_section = ".text"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! error: unused attribute diff --git a/tests/ui/sanitize-attr/invalid-sanitize.rs b/tests/ui/sanitize-attr/invalid-sanitize.rs index 6846b6ff228b2..5574661fcf5dc 100644 --- a/tests/ui/sanitize-attr/invalid-sanitize.rs +++ b/tests/ui/sanitize-attr/invalid-sanitize.rs @@ -4,12 +4,12 @@ #[sanitize(brontosaurus = "off")] //~ ERROR malformed `sanitize` attribute input fn main() {} -#[sanitize(address = "off")] //~ ERROR multiple `sanitize` attributes #[sanitize(address = "off")] +#[sanitize(address = "off")] //~ ERROR multiple `sanitize` attributes fn multiple_consistent() {} -#[sanitize(address = "on")] //~ ERROR multiple `sanitize` attributes -#[sanitize(address = "off")] +#[sanitize(address = "on")] +#[sanitize(address = "off")] //~ ERROR multiple `sanitize` attributes fn multiple_inconsistent() {} #[sanitize(address = "bogus")] //~ ERROR malformed `sanitize` attribute input diff --git a/tests/ui/sanitize-attr/invalid-sanitize.stderr b/tests/ui/sanitize-attr/invalid-sanitize.stderr index 00bb7746d05f3..259627a355054 100644 --- a/tests/ui/sanitize-attr/invalid-sanitize.stderr +++ b/tests/ui/sanitize-attr/invalid-sanitize.stderr @@ -7,28 +7,28 @@ LL | #[sanitize(brontosaurus = "off")] | valid arguments are "address", "kernel_address", "cfi", "kcfi", "memory", "memtag", "shadow_call_stack", "thread", "hwaddress" or "realtime" error: multiple `sanitize` attributes - --> $DIR/invalid-sanitize.rs:7:1 + --> $DIR/invalid-sanitize.rs:8:1 | LL | #[sanitize(address = "off")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/invalid-sanitize.rs:8:1 + --> $DIR/invalid-sanitize.rs:7:1 | LL | #[sanitize(address = "off")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: multiple `sanitize` attributes - --> $DIR/invalid-sanitize.rs:11:1 + --> $DIR/invalid-sanitize.rs:12:1 | -LL | #[sanitize(address = "on")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute +LL | #[sanitize(address = "off")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/invalid-sanitize.rs:12:1 + --> $DIR/invalid-sanitize.rs:11:1 | -LL | #[sanitize(address = "off")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[sanitize(address = "on")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0539]: malformed `sanitize` attribute input --> $DIR/invalid-sanitize.rs:15:1 From 0e4a7595012bc71ccbb28a117e2fcf35e833cedb Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 23 Mar 2026 14:00:22 +0100 Subject: [PATCH 25/25] Fix `link_ordinal` test --- .../raw-dylib/windows/link-ordinal-multiple.rs | 8 ++++---- .../windows/link-ordinal-multiple.stderr | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs index f5fb1649cdc43..d9251f6950df6 100644 --- a/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs +++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs @@ -1,11 +1,11 @@ //@ only-windows #[link(name = "foo", kind = "raw-dylib")] extern "C" { - #[link_ordinal(1)] //~ ERROR multiple `link_ordinal` attributes - #[link_ordinal(2)] + #[link_ordinal(1)] + #[link_ordinal(2)] //~ ERROR multiple `link_ordinal` attributes fn foo(); - #[link_ordinal(1)] //~ ERROR multiple `link_ordinal` attributes - #[link_ordinal(2)] + #[link_ordinal(1)] + #[link_ordinal(2)] //~ ERROR multiple `link_ordinal` attributes static mut imported_variable: i32; } diff --git a/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr index 2e6cf3761c2f0..26d735154fccc 100644 --- a/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr +++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr @@ -1,25 +1,25 @@ error: multiple `link_ordinal` attributes - --> $DIR/link-ordinal-multiple.rs:4:5 + --> $DIR/link-ordinal-multiple.rs:5:5 | -LL | #[link_ordinal(1)] +LL | #[link_ordinal(2)] | ^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/link-ordinal-multiple.rs:5:5 + --> $DIR/link-ordinal-multiple.rs:4:5 | -LL | #[link_ordinal(2)] +LL | #[link_ordinal(1)] | ^^^^^^^^^^^^^^^^^^ error: multiple `link_ordinal` attributes - --> $DIR/link-ordinal-multiple.rs:7:5 + --> $DIR/link-ordinal-multiple.rs:8:5 | -LL | #[link_ordinal(1)] +LL | #[link_ordinal(2)] | ^^^^^^^^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/link-ordinal-multiple.rs:8:5 + --> $DIR/link-ordinal-multiple.rs:7:5 | -LL | #[link_ordinal(2)] +LL | #[link_ordinal(1)] | ^^^^^^^^^^^^^^^^^^ error: aborting due to 2 previous errors