From 095f89b1299c66c82cc821061d7b61fb8d98a937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Bj=C3=B8rnager=20Jensen?= Date: Tue, 14 Oct 2025 20:54:40 +0200 Subject: [PATCH 01/11] Add 'bit' and 'set_bit' methods to integral types; --- library/core/src/num/int_macros.rs | 72 +++++++++++++++++ library/core/src/num/uint_macros.rs | 77 +++++++++++++++++++ library/coretests/tests/lib.rs | 1 + library/coretests/tests/num/int_macros.rs | 53 ++++++++++++- library/coretests/tests/num/uint_macros.rs | 52 +++++++++++++ tests/ui/const-generics/issues/issue-86820.rs | 2 +- 6 files changed, 255 insertions(+), 2 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 058c0229aa530..bc8281d5f442a 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -56,6 +56,78 @@ macro_rules! int_impl { #[stable(feature = "int_bits_const", since = "1.53.0")] pub const BITS: u32 = <$UnsignedT>::BITS; + /// Tests the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal to [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let n = 0b111_1001", stringify!($SelfT), ";")] + /// + /// assert!(n.bit(0)); + /// assert!(!n.bit(1)); + /// assert!(!n.bit(2)); + /// assert!(n.bit(3)); + /// assert!(n.bit(4)); + /// assert!(n.bit(5)); + /// assert!(n.bit(6)); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn bit(self, index: u32) -> bool { + self.cast_unsigned().bit(index) + } + + /// Sets the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal to [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let mut n = 0b101_0101", stringify!($SelfT), ";")] + /// + /// n = n.set_bit(0, false); + /// n = n.set_bit(1, true); + /// n = n.set_bit(2, false); + /// n = n.set_bit(3, true); + /// n = n.set_bit(4, false); + /// n = n.set_bit(5, true); + /// n = n.set_bit(6, false); + /// + /// assert_eq!(n, 0b010_1010); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn set_bit(self, index: u32, value: bool) -> Self { + self.cast_unsigned().set_bit(index, value).cast_signed() + } + /// Returns the number of ones in the binary representation of `self`. /// /// # Examples diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index fcda1edcf4cb2..72f5e023711f1 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -60,6 +60,83 @@ macro_rules! uint_impl { #[stable(feature = "int_bits_const", since = "1.53.0")] pub const BITS: u32 = Self::MAX.count_ones(); + /// Tests the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal to [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let n = 0b0111_1001", stringify!($SelfT), ";")] + /// + /// assert!(n.bit(0)); + /// assert!(!n.bit(1)); + /// assert!(!n.bit(2)); + /// assert!(n.bit(3)); + /// assert!(n.bit(4)); + /// assert!(n.bit(5)); + /// assert!(n.bit(6)); + /// assert!(!n.bit(7)); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn bit(self, index: u32) -> bool { + (self & (1 << index)) != 0 + } + + /// Sets the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let mut n = 0b0101_0101", stringify!($SelfT), ";")] + /// + /// n = n.set_bit(0, false); + /// n = n.set_bit(1, true); + /// n = n.set_bit(2, false); + /// n = n.set_bit(3, true); + /// n = n.set_bit(4, false); + /// n = n.set_bit(5, true); + /// n = n.set_bit(6, false); + /// n = n.set_bit(7, true); + /// + /// assert_eq!(n, 0b1010_1010); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn set_bit(mut self, index: u32, value: bool) -> Self { + self &= !(1 << index); + self |= (value as $SelfT) << index; + + self + } + /// Returns the number of ones in the binary representation of `self`. /// /// # Examples diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 1fdb0c820d662..665e464fd8ff0 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -66,6 +66,7 @@ #![feature(freeze)] #![feature(future_join)] #![feature(generic_assert_internals)] +#![feature(get_set_bit)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] #![feature(int_from_ascii)] diff --git a/library/coretests/tests/num/int_macros.rs b/library/coretests/tests/num/int_macros.rs index f391fe23cd1f5..6165edc6dda1e 100644 --- a/library/coretests/tests/num/int_macros.rs +++ b/library/coretests/tests/num/int_macros.rs @@ -32,7 +32,6 @@ macro_rules! int_module { } test_runtime_and_compiletime! { - fn test_rem_euclid() { assert_eq_const_safe!($T: (-1 as $T).rem_euclid(MIN), MAX); } @@ -73,6 +72,58 @@ macro_rules! int_module { const _1: $T = !0; test_runtime_and_compiletime! { + fn test_bit() { + assert!(!A.bit(0)); + assert!(!A.bit(1)); + assert!(A.bit(2)); + assert!(A.bit(3)); + assert!(!A.bit(4)); + assert!(A.bit(5)); + assert!(!A.bit(6)); + + assert!(B.bit(0)); + assert!(!B.bit(1)); + assert!(!B.bit(2)); + assert!(!B.bit(3)); + assert!(!B.bit(4)); + assert!(B.bit(5)); + assert!(!B.bit(6)); + + assert!(C.bit(0)); + assert!(!C.bit(1)); + assert!(!C.bit(2)); + assert!(C.bit(3)); + assert!(C.bit(4)); + assert!(C.bit(5)); + assert!(C.bit(6)); + } + + fn test_set_bit() { + assert_eq_const_safe!($T: A.set_bit(0, true), 0b0101101); + assert_eq_const_safe!($T: A.set_bit(1, true), 0b0101110); + assert_eq_const_safe!($T: A.set_bit(2, false), 0b0101000); + assert_eq_const_safe!($T: A.set_bit(3, false), 0b0100100); + assert_eq_const_safe!($T: A.set_bit(4, true), 0b0111100); + assert_eq_const_safe!($T: A.set_bit(5, false), 0b0001100); + assert_eq_const_safe!($T: A.set_bit(6, true), 0b1101100); + + assert_eq_const_safe!($T: B.set_bit(0, false), 0b0100000); + assert_eq_const_safe!($T: B.set_bit(1, true), 0b0100011); + assert_eq_const_safe!($T: B.set_bit(2, true), 0b0100101); + assert_eq_const_safe!($T: B.set_bit(3, true), 0b0101001); + assert_eq_const_safe!($T: B.set_bit(4, true), 0b0110001); + assert_eq_const_safe!($T: B.set_bit(5, false), 0b0000001); + assert_eq_const_safe!($T: B.set_bit(6, true), 0b1100001); + + assert_eq_const_safe!($T: C.set_bit(0, false), 0b1111000); + assert_eq_const_safe!($T: C.set_bit(1, true), 0b1111011); + assert_eq_const_safe!($T: C.set_bit(2, true), 0b1111101); + assert_eq_const_safe!($T: C.set_bit(3, false), 0b1110001); + assert_eq_const_safe!($T: C.set_bit(4, false), 0b1101001); + assert_eq_const_safe!($T: C.set_bit(5, false), 0b1011001); + assert_eq_const_safe!($T: C.set_bit(6, false), 0b0111001); + } + fn test_count_ones() { assert_eq_const_safe!(u32: A.count_ones(), 3); assert_eq_const_safe!(u32: B.count_ones(), 2); diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 4dfcb1b8b4688..9d500d1578b64 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -35,6 +35,58 @@ macro_rules! uint_module { const _1: $T = !0; test_runtime_and_compiletime! { + fn test_bit() { + assert!(!A.bit(0)); + assert!(!A.bit(1)); + assert!(A.bit(2)); + assert!(A.bit(3)); + assert!(!A.bit(4)); + assert!(A.bit(5)); + assert!(!A.bit(6)); + + assert!(B.bit(0)); + assert!(!B.bit(1)); + assert!(!B.bit(2)); + assert!(!B.bit(3)); + assert!(!B.bit(4)); + assert!(B.bit(5)); + assert!(!B.bit(6)); + + assert!(C.bit(0)); + assert!(!C.bit(1)); + assert!(!C.bit(2)); + assert!(C.bit(3)); + assert!(C.bit(4)); + assert!(C.bit(5)); + assert!(C.bit(6)); + } + + fn test_set_bit() { + assert_eq_const_safe!($T: A.set_bit(0, true), 0b0101101); + assert_eq_const_safe!($T: A.set_bit(1, true), 0b0101110); + assert_eq_const_safe!($T: A.set_bit(2, false), 0b0101000); + assert_eq_const_safe!($T: A.set_bit(3, false), 0b0100100); + assert_eq_const_safe!($T: A.set_bit(4, true), 0b0111100); + assert_eq_const_safe!($T: A.set_bit(5, false), 0b0001100); + assert_eq_const_safe!($T: A.set_bit(6, true), 0b1101100); + + assert_eq_const_safe!($T: B.set_bit(0, false), 0b0100000); + assert_eq_const_safe!($T: B.set_bit(1, true), 0b0100011); + assert_eq_const_safe!($T: B.set_bit(2, true), 0b0100101); + assert_eq_const_safe!($T: B.set_bit(3, true), 0b0101001); + assert_eq_const_safe!($T: B.set_bit(4, true), 0b0110001); + assert_eq_const_safe!($T: B.set_bit(5, false), 0b0000001); + assert_eq_const_safe!($T: B.set_bit(6, true), 0b1100001); + + assert_eq_const_safe!($T: C.set_bit(0, false), 0b1111000); + assert_eq_const_safe!($T: C.set_bit(1, true), 0b1111011); + assert_eq_const_safe!($T: C.set_bit(2, true), 0b1111101); + assert_eq_const_safe!($T: C.set_bit(3, false), 0b1110001); + assert_eq_const_safe!($T: C.set_bit(4, false), 0b1101001); + assert_eq_const_safe!($T: C.set_bit(5, false), 0b1011001); + assert_eq_const_safe!($T: C.set_bit(6, false), 0b0111001); + } + fn test_count_ones() { assert!(A.count_ones() == 3); assert!(B.count_ones() == 2); diff --git a/tests/ui/const-generics/issues/issue-86820.rs b/tests/ui/const-generics/issues/issue-86820.rs index c2c0c1a8d0390..51a160cb213b3 100644 --- a/tests/ui/const-generics/issues/issue-86820.rs +++ b/tests/ui/const-generics/issues/issue-86820.rs @@ -5,7 +5,7 @@ use std::ops::BitAnd; const C: fn() = || is_set(); fn is_set() { - 0xffu8.bit::<0>(); + Bits::bit::<0>(0xffu8); } trait Bits { From a19d427d0da3f48567a5249473b1b78f1b6ff3be Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 4 Nov 2026 20:49:45 +0100 Subject: [PATCH 02/11] Make notable trait `i` badges `` instead of `` --- src/librustdoc/html/render/mod.rs | 2 +- src/librustdoc/html/static/css/rustdoc.css | 18 ++++---- src/librustdoc/html/templates/print_item.html | 2 +- tests/rustdoc-gui/notable-trait-badges.goml | 42 +++++++++++++++---- tests/rustdoc-gui/tooltips.goml | 2 +- .../where.SWhere_TraitWhere_item-decl.html | 2 +- .../notable-trait/doc-notable_trait.rs | 6 +-- .../notable-trait-badge-negative.rs | 5 ++- .../notable-trait-badge-supertrait.rs | 5 ++- ...able-trait-badge-unlinkable-cross-crate.rs | 6 +-- .../notable-trait/notable-trait-badge.rs | 6 +-- .../notable-trait/notable-trait-generics.rs | 5 ++- .../spotlight-from-dependency.rs | 2 +- 13 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index bee0f61c3a69e..813f21f29ba90 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1722,7 +1722,7 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optionⓘ", + " ⓘ", ty = Escape(&format!("{:#}", print_type(ty, cx))), ) }) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 2634a9a3d8a95..997fb7092a017 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1683,16 +1683,15 @@ so that we can apply CSS-filters to change the arrow color in themes */ position: relative; } -.notable-trait-badge-container > a.tooltip { +.notable-trait-badge-container > .tooltip { position: absolute; width: 16px; line-height: 1.5rem; - vertical-align: center; left: calc(-1 * var(--docblock-indent)); text-decoration: none; } -.notable-trait-badge-container > a:not(.tooltip) { +.notable-trait-badge-container > a { display: flex; align-items: center; width: fit-content; @@ -2026,24 +2025,23 @@ instead, we check that it's not a "finger" cursor. border-right: 3px solid var(--target-border-color); } -a.tooltip { +span.tooltip { font-family: var(--font-family); -} - -.code-header a.tooltip { + cursor: pointer; color: inherit; margin-right: 15px; position: relative; + display: inline; } -.code-header a.tooltip:hover { +span.tooltip:hover { color: var(--link-color); } /* placeholder thunk so that the mouse can easily travel from "(i)" to popover the resulting "hover tunnel" is a stepped triangle, approximating https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */ -a.tooltip:hover::after { +span.tooltip:hover::after { position: absolute; top: calc(100% - 10px); left: -15px; @@ -2990,7 +2988,7 @@ in src-script.js and main.js left: -11px; } - .notable-trait-badge-container > a.tooltip { + .notable-trait-badge-container > span.tooltip { left: calc(-1 * var(--docblock-indent) - 11px); } diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index 4297ddf484561..95e3ea851ffec 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -31,7 +31,7 @@

{% endmatch %} {% if !notable_trait_badges.is_empty() %}
{# #} - ⓘ + ⓘ {% for badge in notable_trait_badges.iter() %} .docblock", {"x": top_docblock_x}) assert-position: (".notable-trait-badge-container", {"x": |top_docblock_x|}) store-position: (".top-doc > summary::before", {"x": top_docblock_toggle_x}) - assert-position: (".notable-trait-badge-container > a.tooltip", {"x": |top_docblock_toggle_x|}) - store-position: (".notable-trait-badge-container > a.tooltip + a", {"x": notable_trait_badge_first_x}) - store-position: (".notable-trait-badge-container > a.tooltip", {"x": notable_trait_tooltip_x}) - store-size: (".notable-trait-badge-container > a.tooltip", {"width": notable_trait_tooltip_width}) + assert-position: ( + ".notable-trait-badge-container > .tooltip", + {"x": |top_docblock_toggle_x|}, + ) + store-position: ( + ".notable-trait-badge-container > .tooltip + a", + {"x": notable_trait_badge_first_x}, + ) + store-position: ( + ".notable-trait-badge-container > .tooltip", + {"x": notable_trait_tooltip_x}, + ) + store-size: ( + ".notable-trait-badge-container > .tooltip", + {"width": notable_trait_tooltip_width}, + ) assert: |notable_trait_tooltip_x| + |notable_trait_tooltip_width| < |notable_trait_badge_first_x| set-window-size: (320, 480) store-position: (".top-doc > .docblock", {"x": top_docblock_x}) assert-position: (".notable-trait-badge-container", {"x": |top_docblock_x|}) store-position: (".top-doc > summary::before", {"x": top_docblock_toggle_x}) - assert-position: (".notable-trait-badge-container > a.tooltip", {"x": |top_docblock_toggle_x|}) - store-position: (".notable-trait-badge-container > a.tooltip + a", {"x": notable_trait_badge_first_x}) - store-position: (".notable-trait-badge-container > a.tooltip", {"x": notable_trait_tooltip_x}) - store-size: (".notable-trait-badge-container > a.tooltip", {"width": notable_trait_tooltip_width}) + assert-position: ( + ".notable-trait-badge-container > .tooltip", + {"x": |top_docblock_toggle_x|}, + ) + store-position: ( + ".notable-trait-badge-container > .tooltip + a", + {"x": notable_trait_badge_first_x}, + ) + store-position: ( + ".notable-trait-badge-container > .tooltip", + {"x": notable_trait_tooltip_x}, + ) + store-size: ( + ".notable-trait-badge-container > .tooltip", + {"width": notable_trait_tooltip_width}, + ) assert: |notable_trait_tooltip_x| + |notable_trait_tooltip_width| < |notable_trait_badge_first_x| } ) @@ -83,7 +107,7 @@ call-function: ("test-badges", { }) assert-false: ".tooltip.popover" -click: ".notable-trait-badge-container > a.tooltip" +click: ".notable-trait-badge-container > .tooltip" wait-for: ".tooltip.popover" assert-text: (".tooltip.popover h3", "Notable traits for NotableTraitColors") assert-count: (".tooltip.popover .where", 8) diff --git a/tests/rustdoc-gui/tooltips.goml b/tests/rustdoc-gui/tooltips.goml index 6e79196be2731..f6310283aacf4 100644 --- a/tests/rustdoc-gui/tooltips.goml +++ b/tests/rustdoc-gui/tooltips.goml @@ -6,7 +6,7 @@ define-function: ( block { go-to: "file://" + |DOC_PATH| + "/test_docs/" + |path| assert-css: ( - "a.tooltip", {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, ALL, + ".tooltip", {"font-family": '"Source Serif 4", NanumBarunGothic, serif'}, ALL, ) } ) diff --git a/tests/rustdoc-html/display/where.SWhere_TraitWhere_item-decl.html b/tests/rustdoc-html/display/where.SWhere_TraitWhere_item-decl.html index c0fea95878005..ca7012b80f7bb 100644 --- a/tests/rustdoc-html/display/where.SWhere_TraitWhere_item-decl.html +++ b/tests/rustdoc-html/display/where.SWhere_TraitWhere_item-decl.html @@ -5,7 +5,7 @@ // Provided methods fn func(self) where Self: Sized { ... } - fn lines(self) -> Lines<Self> ⓘ + fn lines(self) -> Lines<Self> ⓘ where Self: Sized { ... } fn merge<T>(self, a: T) where Self: Sized, diff --git a/tests/rustdoc-html/notable-trait/doc-notable_trait.rs b/tests/rustdoc-html/notable-trait/doc-notable_trait.rs index 83fe6172e9471..0a4eb7e86c874 100644 --- a/tests/rustdoc-html/notable-trait/doc-notable_trait.rs +++ b/tests/rustdoc-html/notable-trait/doc-notable_trait.rs @@ -9,7 +9,7 @@ impl SomeTrait for Wrapper {} #[doc(notable_trait)] pub trait SomeTrait { //@ has doc_notable_trait/trait.SomeTrait.html - //@ has - '//a[@class="tooltip"]/@data-notable-ty' 'Wrapper' + //@ has - '//span[@class="tooltip"]/@data-notable-ty' 'Wrapper' //@ snapshot wrap-me - '//script[@id="notable-traits-data"]' fn wrap_me(self) -> Wrapper where Self: Sized { Wrapper { @@ -23,7 +23,7 @@ impl SomeTrait for SomeStruct {} impl SomeStruct { //@ has doc_notable_trait/struct.SomeStruct.html - //@ has - '//a[@class="tooltip"]/@data-notable-ty' 'SomeStruct' + //@ has - '//span[@class="tooltip"]/@data-notable-ty' 'SomeStruct' //@ snapshot some-struct-new - '//script[@id="notable-traits-data"]' pub fn new() -> SomeStruct { SomeStruct @@ -31,7 +31,7 @@ impl SomeStruct { } //@ has doc_notable_trait/fn.bare_fn.html -//@ has - '//a[@class="tooltip"]/@data-notable-ty' 'SomeStruct' +//@ has - '//span[@class="tooltip"]/@data-notable-ty' 'SomeStruct' //@ snapshot bare-fn - '//script[@id="notable-traits-data"]' pub fn bare_fn() -> SomeStruct { SomeStruct diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs index dc7b349ede2f3..ae18248bd3dd1 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs @@ -8,8 +8,9 @@ pub trait Pos {} // A negative impl must not produce a badge. //@ has 'foo/struct.T.html' -//@ count - '//div[@class="notable-trait-badge-container"]/a' 2 -//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' +//@ count - '//div[@class="notable-trait-badge-container"]/span' 1 +//@ count - '//div[@class="notable-trait-badge-container"]/a' 1 +//@ has - '//div[@class="notable-trait-badge-container"]/span' 'ⓘ' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Pos.html"]' 'Pos' pub struct T; impl !Neg for T {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs index 2d29fbeedf80e..a007ec2388862 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs @@ -9,8 +9,9 @@ pub trait Derived: Base {} //@ has 'foo/struct.S.html' // Implementing `Derived` requires implementing the notable supertrait `Base`, // so its badge shows up. -//@ count - '//div[@class="notable-trait-badge-container"]/a' 2 -//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' +//@ count - '//div[@class="notable-trait-badge-container"]/a' 1 +//@ count - '//div[@class="notable-trait-badge-container"]/span' 1 +//@ has - '//div[@class="notable-trait-badge-container"]/span' 'ⓘ' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Base.html"]' 'Base' pub struct S; impl Base for S {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs index a249a100e6541..87981770c3e0e 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs @@ -9,10 +9,8 @@ extern crate notable_dep; use notable_dep::Spaceship; //@ has 'foo/struct.Rocket.html' -// The badge is present... //@ has - '//div[@class="notable-trait-badge-container"]/a' 'Spaceship' -// ...but unlinked: no badge carries an `href`. -//@ count - '//div[@class="notable-trait-badge-container"]/a[@href]' 1 -//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' +// We ensure that the `tabindex` is set so you can focus the `span`. +//@ has - '//div[@class="notable-trait-badge-container"]/span/@tabindex' '0' pub struct Rocket; impl Spaceship for Rocket {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge.rs index 7692201bc6140..56750ca211505 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge.rs @@ -12,9 +12,9 @@ pub trait Plain {} //@ has 'foo/struct.Tagged.html' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Labeled.html"][@title="foo::Labeled"]' 'Labeled' // Badges are sorted by trait name, so `AlsoLabeled` precedes `Labeled`. -//@ has - '//div[@class="notable-trait-badge-container"]/a[1]' 'ⓘ' -//@ has - '//div[@class="notable-trait-badge-container"]/a[2]' 'AlsoLabeled' -//@ has - '//div[@class="notable-trait-badge-container"]/a[3]' 'Labeled' +//@ has - '//div[@class="notable-trait-badge-container"]/span[1]' 'ⓘ' +//@ has - '//div[@class="notable-trait-badge-container"]/a[1]' 'AlsoLabeled' +//@ has - '//div[@class="notable-trait-badge-container"]/a[2]' 'Labeled' pub struct Tagged; impl Labeled for Tagged {} impl AlsoLabeled for Tagged {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-generics.rs b/tests/rustdoc-html/notable-trait/notable-trait-generics.rs index 60d09661aff52..37dbf4f76c8b1 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-generics.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-generics.rs @@ -11,7 +11,7 @@ pub mod generic_return { impl NotableTrait for Wrapper {} //@ has notable_trait_generics/generic_return/fn.returning.html - //@ !has - '//a[@class="tooltip"]/@data-notable-ty' 'Wrapper' + //@ count - '//span[@class="tooltip"]' 0 pub fn returning() -> Wrapper { loop {} } @@ -28,7 +28,8 @@ pub mod generic_impl { impl NotableTrait for Wrapper {} //@ has notable_trait_generics/generic_impl/fn.returning.html - //@ has - '//a[@class="tooltip"]/@data-notable-ty' 'Wrapper' + //@ count - '//span[@class="tooltip"]' 1 + //@ has - '//span[@class="tooltip"]/@data-notable-ty' 'Wrapper' pub fn returning() -> Wrapper { loop {} } diff --git a/tests/rustdoc-html/notable-trait/spotlight-from-dependency.rs b/tests/rustdoc-html/notable-trait/spotlight-from-dependency.rs index 992102f87c7c6..78dc6652326f3 100644 --- a/tests/rustdoc-html/notable-trait/spotlight-from-dependency.rs +++ b/tests/rustdoc-html/notable-trait/spotlight-from-dependency.rs @@ -3,7 +3,7 @@ use std::iter::Iterator; //@ has foo/struct.Odd.html -//@ has - '//*[@id="method.new"]//a[@class="tooltip"]/@data-notable-ty' 'Odd' +//@ has - '//*[@id="method.new"]//*[@class="tooltip"]/@data-notable-ty' 'Odd' //@ snapshot odd - '//script[@id="notable-traits-data"]' pub struct Odd { current: usize, From 2cab4ca15df3fa9cdb69f190dcca72c1362b7fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beat=20K=C3=BCng?= Date: Wed, 23 Sep 2026 10:23:05 +0200 Subject: [PATCH 03/11] nuttx: use updated pollfd definition Corresponding libc PR: https://github.com/rust-lang/libc/pull/5555 pollfd has some extra internal fields on NuttX, so we use mem::zeroed() to initialze. --- library/std/src/sys/net/connection/socket/unix.rs | 4 +++- library/std/src/sys/pal/unix/mod.rs | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index 0f80646449e1f..9c8b9e6f3a2e6 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -172,7 +172,9 @@ impl Socket { } pub fn poll_connected(&self, timeout: Duration) -> io::Result<()> { - let mut pollfd = libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLOUT, revents: 0 }; + let mut pollfd: libc::pollfd = unsafe { mem::zeroed() }; + pollfd.fd = self.as_raw_fd(); + pollfd.events = libc::POLLOUT; if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { return Err(io::Error::ZERO_TIMEOUT); diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 292c02f3d1f3e..fbf346a9a4950 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -88,13 +88,16 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { )))] 'poll: { use crate::sys::io::errno; - let pfds: &mut [_] = &mut [ - libc::pollfd { fd: 0, events: 0, revents: 0 }, - libc::pollfd { fd: 1, events: 0, revents: 0 }, - libc::pollfd { fd: 2, events: 0, revents: 0 }, - ]; - while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 { + let mut pfds: [libc::pollfd; 3] = crate::mem::zeroed(); + pfds[0].fd = 0; + pfds[0].events = 0; + pfds[1].fd = 1; + pfds[1].events = 0; + pfds[2].fd = 2; + pfds[2].events = 0; + + while libc::poll(pfds.as_mut_ptr(), pfds.len() as libc::nfds_t, 0) == -1 { match errno() { libc::EINTR => continue, #[cfg(target_vendor = "unikraft")] From 5cc2524d712a1a2c03930f4b74300b1b2d3d6ae7 Mon Sep 17 00:00:00 2001 From: panstromek Date: Thu, 5 Nov 2026 18:18:55 +0100 Subject: [PATCH 04/11] Migrate basic block ctors to require Terminator where possible --- compiler/rustc_middle/src/mir/mod.rs | 24 ++++++++++++++++++- compiler/rustc_mir_build/src/builder/cfg.rs | 2 +- .../rustc_mir_build/src/builder/custom/mod.rs | 2 +- .../src/builder/custom/parse.rs | 4 ++-- compiler/rustc_mir_build/src/builder/mod.rs | 2 +- .../rustc_mir_dataflow/src/framework/tests.rs | 2 +- .../src/add_call_guards.rs | 2 +- .../src/add_moves_for_packed_drops.rs | 2 +- .../rustc_mir_transform/src/check_enums.rs | 4 ++-- .../rustc_mir_transform/src/check_pointers.rs | 2 +- .../rustc_mir_transform/src/coroutine/drop.rs | 2 +- .../rustc_mir_transform/src/coroutine/mod.rs | 14 +++++------ .../rustc_mir_transform/src/coverage/tests.rs | 4 ++-- .../src/early_otherwise_branch.rs | 4 ++-- .../rustc_mir_transform/src/elaborate_drop.rs | 4 ++-- compiler/rustc_mir_transform/src/inline.rs | 5 +--- .../src/move_elimination.rs | 2 +- compiler/rustc_mir_transform/src/patch.rs | 16 ++++++------- .../rustc_mir_transform/src/promote_consts.rs | 5 +--- compiler/rustc_mir_transform/src/shim.rs | 16 ++++++------- .../src/shim/async_destructor_ctor.rs | 16 ++++++------- 21 files changed, 74 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 52b584f12213a..20576ce4dd034 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -1346,11 +1346,33 @@ pub struct BasicBlockData<'tcx> { } impl<'tcx> BasicBlockData<'tcx> { - pub fn new(terminator: Option>, is_cleanup: bool) -> BasicBlockData<'tcx> { + pub fn new(terminator: Terminator<'tcx>, is_cleanup: bool) -> BasicBlockData<'tcx> { BasicBlockData::new_stmts(Vec::new(), terminator, is_cleanup) } pub fn new_stmts( + statements: Vec>, + terminator: Terminator<'tcx>, + is_cleanup: bool, + ) -> BasicBlockData<'tcx> { + BasicBlockData { + statements, + after_last_stmt_debuginfos: StmtDebugInfos::default(), + terminator: Some(terminator), + is_cleanup, + } + } + + /// This is being phased out. Use [`BasicBlockData::new`] instead. + pub fn maybe_unterminated( + terminator: Option>, + is_cleanup: bool, + ) -> BasicBlockData<'tcx> { + BasicBlockData::maybe_unterminated_stmts(Vec::new(), terminator, is_cleanup) + } + + /// This is being phased out. Use [`BasicBlockData::new_stmts`] instead. + pub fn maybe_unterminated_stmts( statements: Vec>, terminator: Option>, is_cleanup: bool, diff --git a/compiler/rustc_mir_build/src/builder/cfg.rs b/compiler/rustc_mir_build/src/builder/cfg.rs index 75377f761f401..76bb5009a6ec1 100644 --- a/compiler/rustc_mir_build/src/builder/cfg.rs +++ b/compiler/rustc_mir_build/src/builder/cfg.rs @@ -21,7 +21,7 @@ impl<'tcx> CFG<'tcx> { // it as #[inline(never)] to keep rustc's stack use in check. #[inline(never)] pub(crate) fn start_new_block(&mut self) -> BasicBlock { - self.basic_blocks.push(BasicBlockData::new(None, false)) + self.basic_blocks.push(BasicBlockData::maybe_unterminated(None, false)) } pub(crate) fn start_new_cleanup_block(&mut self) -> BasicBlock { diff --git a/compiler/rustc_mir_build/src/builder/custom/mod.rs b/compiler/rustc_mir_build/src/builder/custom/mod.rs index f6c8658544b43..6d0c415b7358a 100644 --- a/compiler/rustc_mir_build/src/builder/custom/mod.rs +++ b/compiler/rustc_mir_build/src/builder/custom/mod.rs @@ -64,7 +64,7 @@ pub(super) fn build_custom_mir<'tcx>( }; body.local_decls.push(LocalDecl::new(return_ty, return_ty_span)); - body.basic_blocks_mut().push(BasicBlockData::new(None, false)); + body.basic_blocks_mut().push(BasicBlockData::maybe_unterminated(None, false)); body.source_scopes.push(SourceScopeData { span, parent_scope: None, diff --git a/compiler/rustc_mir_build/src/builder/custom/parse.rs b/compiler/rustc_mir_build/src/builder/custom/parse.rs index b227923292779..0cc73e381ea2b 100644 --- a/compiler/rustc_mir_build/src/builder/custom/parse.rs +++ b/compiler/rustc_mir_build/src/builder/custom/parse.rs @@ -199,7 +199,7 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> { match &self.thir[stmt].kind { StmtKind::Let { pattern, initializer: Some(initializer), .. } => { let (var, ..) = self.parse_var(pattern)?; - let data = BasicBlockData::new( + let data = BasicBlockData::maybe_unterminated( None, parse_by_kind!(self, *initializer, _, "basic block declaration", @variant(mir_basic_block, Normal) => false, @@ -303,7 +303,7 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> { ExprKind::Block { block } => &self.thir[*block], ); - let mut data = BasicBlockData::new(None, is_cleanup); + let mut data = BasicBlockData::maybe_unterminated(None, is_cleanup); for stmt_id in &*block.stmts { let stmt = self.statement_as_expr(*stmt_id)?; let span = self.thir[stmt].span; diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 077cca785a2cc..323158a868da5 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -837,7 +837,7 @@ fn mir_for_direct_const<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Body<'tc let rvalue = Rvalue::Use(Operand::Constant(Box::new(value)), WithRetag::Yes); let statement_kind = StatementKind::Assign(Box::new((Place::return_place(), rvalue))); let statement = Statement::new(source_info, statement_kind); - let terminator = Some(Terminator { source_info, kind: TerminatorKind::Return }); + let terminator = Terminator { source_info, kind: TerminatorKind::Return }; let block = BasicBlockData::new_stmts(vec![statement], terminator, false); let basic_blocks = IndexVec::from_elem_n(block, 1); diff --git a/compiler/rustc_mir_dataflow/src/framework/tests.rs b/compiler/rustc_mir_dataflow/src/framework/tests.rs index bfdf96a48f401..5f9545860b6bb 100644 --- a/compiler/rustc_mir_dataflow/src/framework/tests.rs +++ b/compiler/rustc_mir_dataflow/src/framework/tests.rs @@ -21,7 +21,7 @@ fn mock_body<'tcx>() -> mir::Body<'tcx> { blocks.push(mir::BasicBlockData::new_stmts( std::iter::repeat(&nop).cloned().take(n).collect(), - Some(mir::Terminator { source_info, kind }), + mir::Terminator { source_info, kind }, false, )) }; diff --git a/compiler/rustc_mir_transform/src/add_call_guards.rs b/compiler/rustc_mir_transform/src/add_call_guards.rs index 17e99f506e0f2..5bf932a42e43a 100644 --- a/compiler/rustc_mir_transform/src/add_call_guards.rs +++ b/compiler/rustc_mir_transform/src/add_call_guards.rs @@ -88,7 +88,7 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards { let cur_len = body.basic_blocks.len(); let mut new_block = |source_info: SourceInfo, is_cleanup: bool, target: BasicBlock| { let block = BasicBlockData::new( - Some(Terminator { source_info, kind: TerminatorKind::goto(target) }), + Terminator { source_info, kind: TerminatorKind::goto(target) }, is_cleanup, ); let idx = cur_len + new_blocks.len(); diff --git a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs index 41c63d4857df8..1be185bd5b6bd 100644 --- a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs +++ b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs @@ -94,7 +94,7 @@ fn add_move_for_packed_drop<'tcx>( let storage_dead_block = patch.new_block(BasicBlockData::new_stmts( vec![Statement::new(source_info, StatementKind::StorageDead(temp))], - Some(Terminator { source_info, kind: TerminatorKind::goto(target) }), + Terminator { source_info, kind: TerminatorKind::goto(target) }, is_cleanup, )); diff --git a/compiler/rustc_mir_transform/src/check_enums.rs b/compiler/rustc_mir_transform/src/check_enums.rs index 260028c570461..6cc12f48debb8 100644 --- a/compiler/rustc_mir_transform/src/check_enums.rs +++ b/compiler/rustc_mir_transform/src/check_enums.rs @@ -244,7 +244,7 @@ fn split_block( let block_data = &mut basic_blocks[location.block]; // Drain every statement after this one and move the current terminator to a new basic block. - let new_block = BasicBlockData::new_stmts( + let new_block = BasicBlockData::maybe_unterminated_stmts( block_data.statements.split_off(location.statement_index), block_data.terminator.take(), block_data.is_cleanup, @@ -348,7 +348,7 @@ fn insert_direct_enum_check<'tcx>( new_block: BasicBlock, ) { // Insert a new target block that is branched to in case of an invalid discriminant. - let invalid_discr_block_data = BasicBlockData::new(None, false); + let invalid_discr_block_data = BasicBlockData::maybe_unterminated(None, false); let invalid_discr_block = basic_blocks.push(invalid_discr_block_data); let block_data = &mut basic_blocks[current_block]; let discr_place = insert_discr_cast_to_u128( diff --git a/compiler/rustc_mir_transform/src/check_pointers.rs b/compiler/rustc_mir_transform/src/check_pointers.rs index 25336280926fe..0c73e3af0eb00 100644 --- a/compiler/rustc_mir_transform/src/check_pointers.rs +++ b/compiler/rustc_mir_transform/src/check_pointers.rs @@ -235,7 +235,7 @@ fn split_block( let block_data = &mut basic_blocks[location.block]; // Drain every statement after this one and move the current terminator to a new basic block. - let new_block = BasicBlockData::new_stmts( + let new_block = BasicBlockData::maybe_unterminated_stmts( block_data.statements.split_off(location.statement_index), block_data.terminator.take(), block_data.is_cleanup, diff --git a/compiler/rustc_mir_transform/src/coroutine/drop.rs b/compiler/rustc_mir_transform/src/coroutine/drop.rs index c6bd539eb6eee..69f950c6396e0 100644 --- a/compiler/rustc_mir_transform/src/coroutine/drop.rs +++ b/compiler/rustc_mir_transform/src/coroutine/drop.rs @@ -377,7 +377,7 @@ pub(super) fn create_coroutine_drop_shim_proxy_async<'tcx>( body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(poll_enum, source_info); // call coroutine_drop() - let call_bb = body.basic_blocks_mut().push(BasicBlockData::new(None, false)); + let call_bb = body.basic_blocks_mut().push(BasicBlockData::maybe_unterminated(None, false)); // return Poll::Ready() let ret_bb = insert_poll_ready_block(tcx, &mut body); diff --git a/compiler/rustc_mir_transform/src/coroutine/mod.rs b/compiler/rustc_mir_transform/src/coroutine/mod.rs index 9bf85b56a4c66..c62982764f937 100644 --- a/compiler/rustc_mir_transform/src/coroutine/mod.rs +++ b/compiler/rustc_mir_transform/src/coroutine/mod.rs @@ -252,7 +252,7 @@ impl<'tcx> TransformVisitor<'tcx> { body.basic_blocks_mut().push(BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, )); @@ -722,7 +722,7 @@ fn insert_switch<'tcx>( // Add the switch as entry block, and put the former entry block at the end. let former_entry = std::mem::replace( &mut body.basic_blocks_mut()[START_BLOCK], - BasicBlockData::new_stmts(vec![assign], None, false), + BasicBlockData::maybe_unterminated_stmts(vec![assign], None, false), ); let former_entry = body.basic_blocks_mut().push(former_entry); @@ -742,7 +742,7 @@ fn insert_switch<'tcx>( fn insert_term_block<'tcx>(body: &mut Body<'tcx>, kind: TerminatorKind<'tcx>) -> BasicBlock { let source_info = SourceInfo::outermost(body.span); - body.basic_blocks_mut().push(BasicBlockData::new(Some(Terminator { source_info, kind }), false)) + body.basic_blocks_mut().push(BasicBlockData::new(Terminator { source_info, kind }, false)) } fn return_poll_ready_assign<'tcx>(tcx: TyCtxt<'tcx>, source_info: SourceInfo) -> Statement<'tcx> { @@ -761,7 +761,7 @@ fn insert_poll_ready_block<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> Ba let source_info = SourceInfo::outermost(body.span); body.basic_blocks_mut().push(BasicBlockData::new_stmts( [return_poll_ready_assign(tcx, source_info)].to_vec(), - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, )) } @@ -813,7 +813,7 @@ fn insert_poll_pending_block<'tcx>( let source_info = SourceInfo::outermost(body.span); body.basic_blocks_mut().push(BasicBlockData::new_stmts( [return_poll_pending(tcx, source_info, return_type)].to_vec(), - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, )) } @@ -847,7 +847,7 @@ fn generate_poison_block_and_redirect_unwinds_there<'tcx>( let source_info = SourceInfo::outermost(body.span); let poison_block = body.basic_blocks_mut().push(BasicBlockData::new_stmts( vec![transform.set_discr(VariantIdx::new(CoroutineArgs::POISONED), source_info)], - Some(Terminator { source_info, kind: TerminatorKind::UnwindResume }), + Terminator { source_info, kind: TerminatorKind::UnwindResume }, true, )); @@ -1049,7 +1049,7 @@ fn create_cases<'tcx>( // Then jump to the real target let block = body.basic_blocks_mut().push(BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind: TerminatorKind::goto(target) }), + Terminator { source_info, kind: TerminatorKind::goto(target) }, false, )); diff --git a/compiler/rustc_mir_transform/src/coverage/tests.rs b/compiler/rustc_mir_transform/src/coverage/tests.rs index 9fcd2a3028f84..cef8d42fb1539 100644 --- a/compiler/rustc_mir_transform/src/coverage/tests.rs +++ b/compiler/rustc_mir_transform/src/coverage/tests.rs @@ -69,10 +69,10 @@ impl<'tcx> MockBlocks<'tcx> { }; let next_hi = next_lo + BytePos(1); self.blocks.push(BasicBlockData::new( - Some(Terminator { + Terminator { source_info: SourceInfo::outermost(Span::with_root_ctxt(next_lo, next_hi)), kind, - }), + }, false, )) } diff --git a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs index 6a34f0167354b..cc4f3e2b3911b 100644 --- a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs +++ b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs @@ -167,14 +167,14 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch { // Create `bbEq` in example above let eq_switch = BasicBlockData::new( - Some(Terminator { + Terminator { source_info: bbs[parent].terminator().source_info, kind: TerminatorKind::SwitchInt { // switch on the first discriminant, so we can mark the second one as dead discr: parent_op, targets: eq_targets, }, - }), + }, bbs[parent].is_cleanup, ); diff --git a/compiler/rustc_mir_transform/src/elaborate_drop.rs b/compiler/rustc_mir_transform/src/elaborate_drop.rs index 7d05589b6169a..ce5da4fa6084a 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drop.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drop.rs @@ -1619,7 +1619,7 @@ where #[instrument(level = "trace", skip(self), ret)] fn new_block(&mut self, unwind: Unwind, k: TerminatorKind<'tcx>) -> BasicBlock { self.elaborator.patch().new_block(BasicBlockData::new( - Some(Terminator { source_info: self.source_info, kind: k }), + Terminator { source_info: self.source_info, kind: k }, unwind.is_cleanup(), )) } @@ -1633,7 +1633,7 @@ where ) -> BasicBlock { self.elaborator.patch().new_block(BasicBlockData::new_stmts( statements, - Some(Terminator { source_info: self.source_info, kind: k }), + Terminator { source_info: self.source_info, kind: k }, unwind.is_cleanup(), )) } diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 501d2c38df170..bae72f0198a03 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -868,10 +868,7 @@ fn inline_call<'tcx, I: Inliner<'tcx>>( // Prepare a new block for code that should execute when call returns. We don't use // target block directly since it might have other predecessors. let data = BasicBlockData::new( - Some(Terminator { - source_info: terminator.source_info, - kind: TerminatorKind::goto(block), - }), + Terminator { source_info: terminator.source_info, kind: TerminatorKind::goto(block) }, caller_body[block].is_cleanup, ); Some(caller_body.basic_blocks_mut().push(data)) diff --git a/compiler/rustc_mir_transform/src/move_elimination.rs b/compiler/rustc_mir_transform/src/move_elimination.rs index 95a7f8cf237b5..3717dcc864c5b 100644 --- a/compiler/rustc_mir_transform/src/move_elimination.rs +++ b/compiler/rustc_mir_transform/src/move_elimination.rs @@ -742,7 +742,7 @@ fn get_or_split_edge<'tcx>( } let source_info = body.basic_blocks[pred].terminator().source_info; let split_bb = patcher.new_block(BasicBlockData::new( - Some(Terminator { source_info, kind: TerminatorKind::goto(succ) }), + Terminator { source_info, kind: TerminatorKind::goto(succ) }, body.basic_blocks[succ].is_cleanup, )); patcher.mutate_terminator(body, pred, |kind| { diff --git a/compiler/rustc_mir_transform/src/patch.rs b/compiler/rustc_mir_transform/src/patch.rs index 739fa5e37ab13..f1476108557a8 100644 --- a/compiler/rustc_mir_transform/src/patch.rs +++ b/compiler/rustc_mir_transform/src/patch.rs @@ -89,10 +89,10 @@ impl<'tcx> MirPatch<'tcx> { } let bb = self.new_block(BasicBlockData::new( - Some(Terminator { + Terminator { source_info: SourceInfo::outermost(self.body_span), kind: TerminatorKind::UnwindResume, - }), + }, true, )); self.resume_block = Some(bb); @@ -105,10 +105,10 @@ impl<'tcx> MirPatch<'tcx> { } let bb = self.new_block(BasicBlockData::new( - Some(Terminator { + Terminator { source_info: SourceInfo::outermost(self.body_span), kind: TerminatorKind::Unreachable, - }), + }, true, )); self.unreachable_cleanup_block = Some(bb); @@ -121,10 +121,10 @@ impl<'tcx> MirPatch<'tcx> { } let bb = self.new_block(BasicBlockData::new( - Some(Terminator { + Terminator { source_info: SourceInfo::outermost(self.body_span), kind: TerminatorKind::Unreachable, - }), + }, false, )); self.unreachable_no_cleanup_block = Some(bb); @@ -139,10 +139,10 @@ impl<'tcx> MirPatch<'tcx> { } let bb = self.new_block(BasicBlockData::new( - Some(Terminator { + Terminator { source_info: SourceInfo::outermost(self.body_span), kind: TerminatorKind::UnwindTerminate(reason), - }), + }, true, )); self.terminate_block = Some((bb, reason)); diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 7ad164956cbfd..5be8a1b8523c8 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -752,10 +752,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { fn new_block(&mut self) -> BasicBlock { let span = self.promoted.span; self.promoted.basic_blocks_mut().push(BasicBlockData::new( - Some(Terminator { - source_info: SourceInfo::outermost(span), - kind: TerminatorKind::Return, - }), + Terminator { source_info: SourceInfo::outermost(span), kind: TerminatorKind::Return }, false, )) } diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index 170a13ac173ea..abe3490fb7886 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -279,7 +279,7 @@ pub fn build_drop_shim<'tcx>( let return_block = BasicBlock::new(1); let mut blocks = IndexVec::with_capacity(2); let block = |blocks: &mut IndexVec<_, _>, kind| -> BasicBlock { - blocks.push(BasicBlockData::new(Some(Terminator { source_info, kind }), false)) + blocks.push(BasicBlockData::new(Terminator { source_info, kind }, false)) }; if ty.is_some() { block(&mut blocks, TerminatorKind::goto(return_block)); @@ -493,7 +493,7 @@ fn build_thread_local_shim<'tcx>(tcx: TyCtxt<'tcx>, shim: ty::ShimKind<'tcx>) -> Rvalue::ThreadLocalRef(def_id), ))), )], - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, )]); @@ -579,7 +579,7 @@ impl<'tcx> CloneShimBuilder<'tcx> { let source_info = self.source_info(); self.blocks.push(BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind }), + Terminator { source_info, kind }, is_cleanup, )) } @@ -933,7 +933,7 @@ fn build_call_shim<'tcx>( let block = |blocks: &mut IndexVec<_, _>, statements, kind, is_cleanup| { blocks.push(BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind }), + Terminator { source_info, kind }, is_cleanup, )) }; @@ -1060,7 +1060,7 @@ pub(super) fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> Body<'_> { let start_block = BasicBlockData::new_stmts( vec![statement], - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, ); @@ -1143,7 +1143,7 @@ fn build_fn_ptr_as_ptr_shim<'tcx>( let start_block = BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, ); let source = MirSource::from_shim(ty::ShimKind::FnPtrAsPtr(def_id, self_ty)); @@ -1178,7 +1178,7 @@ fn build_fn_ptr_from_ptr_shim<'tcx>( let start_block = BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, ); let source = MirSource::from_shim(ty::ShimKind::FnPtrFromPtr(def_id, self_ty)); @@ -1276,7 +1276,7 @@ fn build_construct_coroutine_by_move_shim<'tcx>( let statements = vec![stmt]; let start_block = BasicBlockData::new_stmts( statements, - Some(Terminator { source_info, kind: TerminatorKind::Return }), + Terminator { source_info, kind: TerminatorKind::Return }, false, ); diff --git a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs index 510da5cbce89b..70de1cc6a2d63 100644 --- a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs +++ b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs @@ -85,7 +85,7 @@ pub(super) fn build_async_drop_shim<'tcx>( let return_block = BasicBlock::new(1); let mut blocks = IndexVec::with_capacity(2); let block = |blocks: &mut IndexVec<_, _>, kind| -> BasicBlock { - blocks.push(BasicBlockData::new(Some(Terminator { source_info, kind }), false)) + blocks.push(BasicBlockData::new(Terminator { source_info, kind }, false)) }; block( &mut blocks, @@ -386,7 +386,7 @@ fn build_adrop_for_adrop_shim<'tcx>( // call Pin::new_unchecked(&mut impl_cor) blocks.push(BasicBlockData::new_stmts( statements, - Some(Terminator { + Terminator { source_info, kind: TerminatorKind::Call { func: Operand::function_handle(tcx, pin_fn, &[cor_ref.into()], span), @@ -397,7 +397,7 @@ fn build_adrop_for_adrop_shim<'tcx>( call_source: CallSource::Misc, fn_span: span, }, - }), + }, false, )); // When dropping async drop coroutine, we continue its execution: @@ -405,7 +405,7 @@ fn build_adrop_for_adrop_shim<'tcx>( let poll_fn = tcx.require_lang_item(LangItem::FuturePoll, span); let resume_ctx = Place::from(Local::new(2)); blocks.push(BasicBlockData::new( - Some(Terminator { + Terminator { source_info, kind: TerminatorKind::Call { func: Operand::function_handle(tcx, poll_fn, &[impl_ty.into()], span), @@ -420,13 +420,11 @@ fn build_adrop_for_adrop_shim<'tcx>( call_source: CallSource::Misc, fn_span: span, }, - }), - false, - )); - blocks.push(BasicBlockData::new( - Some(Terminator { source_info, kind: TerminatorKind::Return }), + }, false, )); + blocks + .push(BasicBlockData::new(Terminator { source_info, kind: TerminatorKind::Return }, false)); let source = MirSource::from_shim(shim); let mut body = new_body(source, blocks, locals, sig.inputs().len(), span); From 3d83ab4f5ca56ad2a8d1e3e52980e90fcf86a176 Mon Sep 17 00:00:00 2001 From: waffle Date: Fri, 6 Nov 2026 14:27:52 +0100 Subject: [PATCH 05/11] use lang item instead of name when assembling builtin `Clone::clone` instances --- compiler/rustc_attr_ir/src/lang_items.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + compiler/rustc_ty_utils/src/instance.rs | 41 ++++++++++++------------ library/core/src/clone.rs | 1 + 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_attr_ir/src/lang_items.rs b/compiler/rustc_attr_ir/src/lang_items.rs index 844daa32a04ac..b576c5038404d 100644 --- a/compiler/rustc_attr_ir/src/lang_items.rs +++ b/compiler/rustc_attr_ir/src/lang_items.rs @@ -169,6 +169,7 @@ language_item_table! { Copy, sym::copy, copy_trait, Target::Trait, GenericRequirement::Exact(0); Clone, sym::clone, clone_trait, Target::Trait, GenericRequirement::None; CloneFn, sym::clone_fn, clone_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + CloneFromFn, sym::clone_from_fn, clone_from_fn, Target::Method(MethodKind::Trait { body: true }), GenericRequirement::None; UseCloned, sym::use_cloned, use_cloned_trait, Target::Trait, GenericRequirement::None; TrivialClone, sym::trivial_clone, trivial_clone_trait, Target::Trait, GenericRequirement::None; Sync, sym::sync, sync_trait, Target::Trait, GenericRequirement::Exact(0); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a7f8e37687b18..2170f610f0939 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -648,6 +648,7 @@ symbols! { clone_closures, clone_fn, clone_from, + clone_from_fn, close_fn, closure, closure_lifetime_binder, diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index 9640f9add604c..2bdffe20f5bcd 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -271,30 +271,29 @@ fn resolve_associated_item<'tcx>( } traits::ImplSource::Builtin(BuiltinImplSource::Misc | BuiltinImplSource::Trivial, _) => { if tcx.is_lang_item(trait_ref.def_id, LangItem::Clone) { - // FIXME(eddyb) use lang items for methods instead of names. - let name = tcx.item_name(trait_item_id); - if name == sym::clone { - let self_ty = trait_ref.self_ty(); - match self_ty.kind() { - ty::FnDef(..) | ty::FnPtr(..) => (), - ty::Coroutine(..) - | ty::CoroutineWitness(..) - | ty::Closure(..) - | ty::CoroutineClosure(..) - | ty::Tuple(..) => {} - _ => return Ok(None), - }; + let self_ty = trait_ref.self_ty(); + match self_ty.kind() { + ty::FnDef(..) + | ty::FnPtr(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Tuple(..) => {} + _ => return Ok(None), + }; - Some(Instance { + match tcx.as_lang_item(trait_item_id) { + Some(LangItem::CloneFn) => Some(Instance { def: ty::InstanceKind::Shim(ty::ShimKind::Clone(trait_item_id, self_ty)), args: rcvr_args, - }) - } else { - assert_eq!(name, sym::clone_from); - - // Use the default `fn clone_from` from `trait Clone`. - let args = tcx.erase_and_anonymize_regions(rcvr_args); - Some(ty::Instance::new_raw(trait_item_id, args)) + }), + Some(LangItem::CloneFromFn) => { + // Use the default `fn clone_from` from `trait Clone`. + let args = tcx.erase_and_anonymize_regions(rcvr_args); + Some(ty::Instance::new_raw(trait_item_id, args)) + } + _ => bug!("unknown `Clone` item"), } } else if tcx.is_lang_item(trait_ref.def_id, LangItem::FnPtrTrait) { let self_ty = trait_ref.self_ty(); diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index 61528873a38c7..a549e12c33995 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -242,6 +242,7 @@ pub const trait Clone: Sized { /// allocations. #[inline] #[stable(feature = "rust1", since = "1.0.0")] + #[lang = "clone_from_fn"] fn clone_from(&mut self, source: &Self) where Self: [const] Destruct, From 4efd16754f76122bfef88dadf4ff22430418f82a Mon Sep 17 00:00:00 2001 From: cezarbbb Date: Mon, 26 Oct 2026 10:34:36 +0800 Subject: [PATCH 06/11] Introduce `OptimizationObjective` and fix the entry shim's size attributes --- compiler/rustc_codegen_llvm/src/attributes.rs | 2 ++ compiler/rustc_codegen_llvm/src/back/write.rs | 19 +++++----- compiler/rustc_codegen_llvm/src/context.rs | 5 +++ compiler/rustc_codegen_llvm/src/intrinsic.rs | 1 + compiler/rustc_codegen_llvm/src/llvm/mod.rs | 9 ----- compiler/rustc_codegen_ssa/src/back/linker.rs | 7 ++-- compiler/rustc_codegen_ssa/src/back/write.rs | 18 ++++------ compiler/rustc_codegen_ssa/src/base.rs | 35 +++++++------------ compiler/rustc_codegen_ssa/src/traits/misc.rs | 3 ++ compiler/rustc_session/src/config.rs | 34 +++++++++++++++--- compiler/rustc_session/src/session.rs | 5 +++ .../codegen-llvm/loop-vectorize-in-opt-sz.rs | 23 ++++++++++++ tests/codegen-llvm/merge-functions.rs | 3 +- tests/codegen-llvm/opt-size-attr-shim.rs | 30 ++++++++++++++++ tests/codegen-llvm/opt-size-attr.rs | 29 +++++++++++++++ tests/codegen-llvm/unroll-in-opt-sz.rs | 31 ++++++++++++++++ 16 files changed, 193 insertions(+), 61 deletions(-) create mode 100644 tests/codegen-llvm/loop-vectorize-in-opt-sz.rs create mode 100644 tests/codegen-llvm/opt-size-attr-shim.rs create mode 100644 tests/codegen-llvm/opt-size-attr.rs create mode 100644 tests/codegen-llvm/unroll-in-opt-sz.rs diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index bdbd43de07a53..d74c6dd68564b 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -478,6 +478,8 @@ pub(crate) fn default_optimisation_attrs<'ll>( sess: &Session, ) -> SmallVec<[&'ll Attribute; 2]> { let mut attrs = SmallVec::new(); + // Either attribute satisfies LLVM's combined size predicate, but the software pipeliner reads + // `optsize` alone, so a `z` build needs both. match sess.opts.optimize { OptLevel::Size => { attrs.push(llvm::AttributeKind::OptimizeForSize.create_attr(cx.llcx)); diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 693d8e6adb1c3..e43b9ba6359ed 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -119,15 +119,15 @@ pub(crate) fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> OwnedTar target_machine_factory(tcx.sess, tcx.backend_optimization_level(()))(tcx.dcx(), config) } -fn to_llvm_opt_settings(cfg: config::OptLevel) -> (llvm::CodeGenOptLevel, llvm::CodeGenOptSize) { +// `s` and `z` are the same codegen level; what tells LLVM that they are size builds is the +// per-function attribute, not this setting. +fn to_llvm_opt_level(cfg: config::OptLevel) -> llvm::CodeGenOptLevel { use self::config::OptLevel::*; match cfg { - No => (llvm::CodeGenOptLevel::None, llvm::CodeGenOptSizeNone), - Less => (llvm::CodeGenOptLevel::Less, llvm::CodeGenOptSizeNone), - More => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeNone), - Aggressive => (llvm::CodeGenOptLevel::Aggressive, llvm::CodeGenOptSizeNone), - Size => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeDefault), - SizeMin => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeAggressive), + No => llvm::CodeGenOptLevel::None, + Less => llvm::CodeGenOptLevel::Less, + More | Size | SizeMin => llvm::CodeGenOptLevel::Default, + Aggressive => llvm::CodeGenOptLevel::Aggressive, } } @@ -184,7 +184,7 @@ pub(crate) fn target_machine_factory( let reloc_model = to_llvm_relocation_model(sess.relocation_model()); - let (opt_level, _) = to_llvm_opt_settings(optlvl); + let opt_level = to_llvm_opt_level(optlvl); let float_abi = to_llvm_float_abi(sess.target.llvm_floatabi); let ffunction_sections = @@ -594,8 +594,7 @@ pub(crate) unsafe fn llvm_optimize<'ll>( vectorize_slp = false; vectorize_loop = false; } else { - unroll_loops = - opt_level != config::OptLevel::Size && opt_level != config::OptLevel::SizeMin; + unroll_loops = opt_level.objective() != config::OptimizationObjective::Size; merge_functions = config.merge_functions; vectorize_slp = config.vectorize_slp; vectorize_loop = config.vectorize_loop; diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 40d5e08578faf..6ba3a82de659f 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -1071,6 +1071,11 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &attrs); } + fn apply_default_optimisation_attrs(&self, llfn: &'ll Value) { + let attrs = attributes::default_optimisation_attrs(self, self.sess()); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &attrs); + } + fn declare_c_main(&self, fn_type: Self::Type) -> Option { let entry_name = self.sess().target.entry_name.as_ref(); if self.get_declared_value(entry_name).is_none() { diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 594f4cbcf759d..38af3011b47e7 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1707,6 +1707,7 @@ fn gen_fn<'a, 'll, 'tcx>( let llfn = cx.declare_fn(name, fn_abi, None); cx.set_frame_pointer_type(llfn); cx.apply_target_cpu_attr(llfn); + cx.apply_default_optimisation_attrs(llfn); // FIXME(eddyb) find a nicer way to do this. llvm::set_linkage(llfn, llvm::Linkage::InternalLinkage); let llbb = Builder::append_block(cx, llfn, "entry-block"); diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index f9863da230dfb..e77fed0913ec2 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -10,7 +10,6 @@ use rustc_abi::{AddressSpace, Align, Size, WrappingRange}; use rustc_llvm::RustString; pub(crate) use self::CallConv::*; -pub(crate) use self::CodeGenOptSize::*; pub(crate) use self::conversions::*; pub(crate) use self::ffi::*; pub(crate) use self::metadata_kind::*; @@ -195,14 +194,6 @@ impl AttributePlace { } } -#[derive(Copy, Clone, PartialEq)] -#[repr(C)] -pub(crate) enum CodeGenOptSize { - CodeGenOptSizeNone = 0, - CodeGenOptSizeDefault = 1, - CodeGenOptSizeAggressive = 2, -} - pub(crate) fn SetInstructionCallConv(instr: &Value, cc: CallConv) { unsafe { LLVMSetInstructionCallConv(instr, cc as c_uint); diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index ffb9130ed1a08..b92e79c8f43ec 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -407,6 +407,7 @@ impl<'a> GccLinker<'a> { self.link_arg(&arg); } + // The plugin has no distinct size setting: `s` and `z` both map to `O2`, as `More` does. let opt_level = match self.sess.opts.optimize { config::OptLevel::No => "O0", config::OptLevel::Less => "O1", @@ -698,9 +699,7 @@ impl<'a> Linker for GccLinker<'a> { // GNU-style linkers support optimization with -O. GNU ld doesn't // need a numeric argument, but other linkers do. - if self.sess.opts.optimize == config::OptLevel::More - || self.sess.opts.optimize == config::OptLevel::Aggressive - { + if self.sess.optimization_objective() == config::OptimizationObjective::Speed { self.link_arg("-O1"); } } @@ -1021,7 +1020,7 @@ impl<'a> Linker for MsvcLinker<'a> { // MSVC's ICF (Identical COMDAT Folding) link optimization is // slow for Rust and thus we disable it by default when not in // optimization build. - if self.sess.opts.optimize != config::OptLevel::No { + if self.sess.optimization_objective() != config::OptimizationObjective::None { self.link_arg("/OPT:REF,ICF"); } else { // It is necessary to specify NOICF here, because /OPT:REF diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 44958c376a46a..406e5a5b04c61 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -23,8 +23,8 @@ use rustc_metadata::fs::copy_to_stdout; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ - self, JobsMode, Lto, OptLevel, OutFileName, OutputFilenames, OutputType, Passes, - SwitchWithOptPath, + self, JobsMode, Lto, OptLevel, OptimizationObjective, OutFileName, OutputFilenames, OutputType, + Passes, SwitchWithOptPath, }; use rustc_session::{BorrowedIncrCompSession, IncrCompSession, Session}; use rustc_span::source_map::SourceMap; @@ -231,8 +231,7 @@ impl ModuleConfig { // Copy what clang does by turning on loop vectorization at O2 and // slp vectorization at O3. vectorize_loop: !sess.opts.cg.no_vectorize_loops - && (sess.opts.optimize == config::OptLevel::More - || sess.opts.optimize == config::OptLevel::Aggressive), + && sess.optimization_objective() == OptimizationObjective::Speed, vectorize_slp: !sess.opts.cg.no_vectorize_slp && sess.opts.optimize == config::OptLevel::Aggressive, @@ -247,13 +246,10 @@ impl ModuleConfig { // O2 and O3) since it can be useful for reducing code size. merge_functions: match sess.merge_functions() { MergeFunctions::Disabled => false, - MergeFunctions::Trampolines | MergeFunctions::Aliases => { - use config::OptLevel::*; - match sess.opts.optimize { - Aggressive | More | SizeMin | Size => true, - Less | No => false, - } - } + MergeFunctions::Trampolines | MergeFunctions::Aliases => matches!( + sess.optimization_objective(), + OptimizationObjective::Speed | OptimizationObjective::Size + ), }, emit_lifetime_markers: sess.emit_lifetime_markers(), diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 1f820a17195bb..ae35dcfe42e60 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -555,6 +555,8 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // `main` should respect same config for frame pointer elimination as rest of code cx.set_frame_pointer_type(llfn); cx.apply_target_cpu_attr(llfn); + // The shim has no item behind it, so it does not go through the per-item attribute path. + cx.apply_default_optimisation_attrs(llfn); let llbb = Bx::append_block(cx, llfn, "top"); let mut bx = Bx::build(cx, llbb); @@ -1217,23 +1219,16 @@ impl CrateInfo { pub(crate) fn provide(providers: &mut Providers) { providers.backend_optimization_level = |tcx, cratenum| { - let for_speed = match tcx.sess.opts.optimize { - // If globally no optimisation is done, #[optimize] has no effect. - // - // This is done because if we ended up "upgrading" to `-O2` here, we’d populate the - // pass manager and it is likely that some module-wide passes (such as inliner or - // cross-function constant propagation) would ignore the `optnone` annotation we put - // on the functions, thus necessarily involving these functions into optimisations. - config::OptLevel::No => return config::OptLevel::No, - // If globally optimise-speed is already specified, just use that level. - config::OptLevel::Less => return config::OptLevel::Less, - config::OptLevel::More => return config::OptLevel::More, - config::OptLevel::Aggressive => return config::OptLevel::Aggressive, - // If globally optimize-for-size has been requested, use -O2 instead (if optimize(size) - // are present). - config::OptLevel::Size => config::OptLevel::More, - config::OptLevel::SizeMin => config::OptLevel::More, - }; + let global = tcx.sess.opts.optimize; + + // Nothing to promote outside a size build. At `-Copt-level=0` the upgrade would populate + // the pass manager, and its module-wide passes ignore the `optnone` annotation. + if global.objective() != config::OptimizationObjective::Size { + return global; + } + + // A speed-marked body in a size build needs a speed level, and `-O2` is the lowest one. + let for_speed = config::OptLevel::More; let defids = tcx.collect_and_partition_mono_items(cratenum).all_mono_items; @@ -1242,11 +1237,7 @@ pub(crate) fn provide(providers: &mut Providers) { matches!(optimize, OptimizeAttr::Speed) }); - if any_for_speed { - return for_speed; - } - - tcx.sess.opts.optimize + if any_for_speed { for_speed } else { global } }; } diff --git a/compiler/rustc_codegen_ssa/src/traits/misc.rs b/compiler/rustc_codegen_ssa/src/traits/misc.rs index add7128a2974b..7e48e86290ee8 100644 --- a/compiler/rustc_codegen_ssa/src/traits/misc.rs +++ b/compiler/rustc_codegen_ssa/src/traits/misc.rs @@ -28,6 +28,9 @@ pub trait MiscCodegenMethods<'tcx>: BackendTypes { fn sess(&self) -> &Session; fn set_frame_pointer_type(&self, llfn: Self::Function); fn apply_target_cpu_attr(&self, llfn: Self::Function); + /// Applies the compilation's optimization attributes to a function that no item produced, + /// such as the C entry shim. + fn apply_default_optimisation_attrs(&self, _llfn: Self::Function) {} /// Declares the extern "C" main function for the entry point. Returns None if the symbol /// already exists. fn declare_c_main(&self, fn_type: Self::FunctionSignature) -> Option; diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index b3d30997aca2a..2665cb92fc8d0 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -126,6 +126,33 @@ impl OptLevel { _ => 2, } } + + /// Maps a general opt-level onto the objective it is trying to achieve. + pub fn objective(&self) -> OptimizationObjective { + match self { + OptLevel::No => OptimizationObjective::None, + OptLevel::Less => OptimizationObjective::Balanced, + OptLevel::More | OptLevel::Aggressive => OptimizationObjective::Speed, + OptLevel::Size | OptLevel::SizeMin => OptimizationObjective::Size, + } + } +} + +/// What a compilation, or a single item, is being asked to optimize *for*. +/// +/// Unlike [`OptLevel`], speed and size are alternatives here rather than degrees of the same +/// thing. Sites that map a level onto an LLVM or linker flag, where `s` and `z` produce different +/// flags, still match on [`OptLevel`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OptimizationObjective { + /// Nothing is optimized, so there is no objective (`-Copt-level=0`). + None, + /// Optimize, without a strong preference for speed over size (`-Copt-level=1`). + Balanced, + /// Favor runtime speed over binary size (`-Copt-level=2`/`3`, `#[optimize(speed)]`). + Speed, + /// Favor binary size over runtime speed (`-Copt-level=s`/`z`, `#[optimize(size)]`). + Size, } /// This is what the `LtoCli` values get mapped to after resolving defaults and @@ -1572,10 +1599,9 @@ impl Options { pub fn share_generics(&self) -> bool { match self.unstable_opts.share_generics { Some(setting) => setting, - None => match self.optimize { - OptLevel::No | OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => true, - OptLevel::More | OptLevel::Aggressive => false, - }, + // Sharing instantiations means fewer copies, at the cost of leaving them opaque to + // speed optimizations. That is a good trade unless speed is what is asked for. + None => self.optimize.objective() != OptimizationObjective::Speed, } } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b69c26639e7e8..9bf2de0466233 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -988,6 +988,11 @@ impl Session { self.opts.unstable_opts.mir_opt_level.unwrap_or_else(|| self.opts.optimize.mir_opt_level()) } + /// What this compilation optimizes for; see [`config::OptimizationObjective`]. + pub fn optimization_objective(&self) -> config::OptimizationObjective { + self.opts.optimize.objective() + } + /// Calculates the flavor of LTO to use for this compilation. pub fn lto(&self) -> config::Lto { // If our target has codegen requirements ignore the command line diff --git a/tests/codegen-llvm/loop-vectorize-in-opt-sz.rs b/tests/codegen-llvm/loop-vectorize-in-opt-sz.rs new file mode 100644 index 0000000000000..f26a558f17ea4 --- /dev/null +++ b/tests/codegen-llvm/loop-vectorize-in-opt-sz.rs @@ -0,0 +1,23 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z +//@ only-x86_64 + +// Loop vectorization is enabled only for the speed objective, so both `-Copt-level=s` and `z` leave +// it off. The loop below is long enough that LLVM at `-O2` does vectorize it. + +#![crate_type = "lib"] + +// CHECK-LABEL: @scale_add( +// O2: fmul <4 x float> +// O2: fadd <4 x float> +// O2: store <4 x float> +// S-NOT: <4 x float> +// Z-NOT: <4 x float> +#[no_mangle] +pub fn scale_add(a: &[f32], b: &[f32], c: &mut [f32]) { + for i in 0..64 { + c[i] = a[i] * b[i] + 3.0; + } +} diff --git a/tests/codegen-llvm/merge-functions.rs b/tests/codegen-llvm/merge-functions.rs index b9d3727ce112e..8c15791c5ae74 100644 --- a/tests/codegen-llvm/merge-functions.rs +++ b/tests/codegen-llvm/merge-functions.rs @@ -1,5 +1,6 @@ -//@ revisions: O Os +//@ revisions: O Os Oz //@[Os] compile-flags: -Copt-level=s +//@[Oz] compile-flags: -Copt-level=z //@[O] compile-flags: -Copt-level=3 #![crate_type = "lib"] diff --git a/tests/codegen-llvm/opt-size-attr-shim.rs b/tests/codegen-llvm/opt-size-attr-shim.rs new file mode 100644 index 0000000000000..a82a3219ca5b1 --- /dev/null +++ b/tests/codegen-llvm/opt-size-attr-shim.rs @@ -0,0 +1,30 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z +//@ ignore-wasi wasi codegens the main symbol differently + +// The C entry shim has no item behind it, so it does not go through the per-item attribute path. +// It still has to carry the size attributes, or `main` is the one definition in a size build that +// LLVM optimizes for speed. + +#![crate_type = "bin"] + +// CHECK-LABEL: define {{.*}} @main( +// CHECK-SAME: [[MAIN_ATTRS:#[0-9]+]] + +// O2: attributes [[MAIN_ATTRS]] = +// O2-NOT: optsize +// O2-NOT: minsize + +// S: attributes [[MAIN_ATTRS]] = +// S-NOT: minsize +// S-SAME: optsize + +// Z: attributes [[MAIN_ATTRS]] = +// Z-SAME: minsize +// Z-SAME: optsize + +fn main() { + std::hint::black_box(0); +} diff --git a/tests/codegen-llvm/opt-size-attr.rs b/tests/codegen-llvm/opt-size-attr.rs new file mode 100644 index 0000000000000..7784e8d1c8e47 --- /dev/null +++ b/tests/codegen-llvm/opt-size-attr.rs @@ -0,0 +1,29 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z + +// `s` gives `optsize` while `z` gives both `minsize` and `optsize`. This is the only coverage of +// the `-Copt-level` path; `optimize-attr-1.rs` reaches these via `#[optimize(size)]`. + +#![crate_type = "lib"] + +// CHECK-LABEL: @f( +// CHECK-SAME: [[F_ATTRS:#[0-9]+]] + +// O2: attributes [[F_ATTRS]] = +// O2-NOT: optsize +// O2-NOT: minsize + +// S: attributes [[F_ATTRS]] = +// S-NOT: minsize +// S-SAME: optsize + +// Z: attributes [[F_ATTRS]] = +// Z-SAME: minsize +// Z-SAME: optsize + +#[no_mangle] +pub fn f(x: u32) -> u32 { + x + 1 +} diff --git a/tests/codegen-llvm/unroll-in-opt-sz.rs b/tests/codegen-llvm/unroll-in-opt-sz.rs new file mode 100644 index 0000000000000..a76c8b3fba51c --- /dev/null +++ b/tests/codegen-llvm/unroll-in-opt-sz.rs @@ -0,0 +1,31 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z + +// `-Copt-level=s`/`z` pass `PTO.LoopUnrolling = false`, so LLVM's unroller runs `OnlyWhenForced`. +// The O2 side is asserted too, so disabling unrolling everywhere cannot pass. Calls are counted +// because this knob emits no `llvm.loop.unroll.*` metadata. + +#![crate_type = "lib"] + +unsafe extern "C" { + fn side_effect(i: u32); +} + +// O2-LABEL: @four_iters( +// O2-COUNT-4: tail call void @side_effect +// S-LABEL: @four_iters( +// S: phi i32 +// S: tail call void @side_effect +// S-NOT: tail call void @side_effect +// Z-LABEL: @four_iters( +// Z: phi i32 +// Z: tail call void @side_effect +// Z-NOT: tail call void @side_effect +#[no_mangle] +pub fn four_iters() { + for i in 0..4u32 { + unsafe { side_effect(i) } + } +} From 45bea6c327a782d35ef87b6ef791df5f946decfd Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Thu, 5 Nov 2026 20:22:19 +0600 Subject: [PATCH 07/11] add tests for -Zcrate-attr ordering rfc 3791 says crate attributes from the command line apply before the attributes in the crate root. ordering-cli checks that several -Zcrate-attr flags apply in command line order. ordering-source records the current behaviour where the command line deny overrides the source warn, which is the wrong way round. --- tests/ui/attributes/z-crate-attr/ordering-cli.rs | 6 ++++++ .../attributes/z-crate-attr/ordering-cli.stderr | 15 +++++++++++++++ .../ui/attributes/z-crate-attr/ordering-source.rs | 8 ++++++++ .../z-crate-attr/ordering-source.stderr | 15 +++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 tests/ui/attributes/z-crate-attr/ordering-cli.rs create mode 100644 tests/ui/attributes/z-crate-attr/ordering-cli.stderr create mode 100644 tests/ui/attributes/z-crate-attr/ordering-source.rs create mode 100644 tests/ui/attributes/z-crate-attr/ordering-source.stderr diff --git a/tests/ui/attributes/z-crate-attr/ordering-cli.rs b/tests/ui/attributes/z-crate-attr/ordering-cli.rs new file mode 100644 index 0000000000000..877a25939b9f3 --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-cli.rs @@ -0,0 +1,6 @@ +// Multiple `-Zcrate-attr` flags apply in command-line order, so the later `deny` wins. +//@ compile-flags: -Zcrate-attr=warn(unused) -Zcrate-attr=deny(unused) + +fn foo() {} //~ ERROR function `foo` is never used + +fn main() {} diff --git a/tests/ui/attributes/z-crate-attr/ordering-cli.stderr b/tests/ui/attributes/z-crate-attr/ordering-cli.stderr new file mode 100644 index 0000000000000..84c47716e62fb --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-cli.stderr @@ -0,0 +1,15 @@ +error: function `foo` is never used + --> $DIR/ordering-cli.rs:4:4 + | +LL | fn foo() {} + | ^^^ + | +note: the lint level is defined here + --> :1:9 + | +LL | #![deny(unused)] + | ^^^^^^ + = note: `#[deny(dead_code)]` implied by `#[deny(unused)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.rs b/tests/ui/attributes/z-crate-attr/ordering-source.rs new file mode 100644 index 0000000000000..d4a248a6be5ca --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-source.rs @@ -0,0 +1,8 @@ +// RFC 3791: `-Zcrate-attr` applies before the crate's own attributes, so `warn` here wins. +//@ compile-flags: -Zcrate-attr=deny(unused) + +#![warn(unused)] + +fn foo() {} //~ ERROR function `foo` is never used + +fn main() {} diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.stderr b/tests/ui/attributes/z-crate-attr/ordering-source.stderr new file mode 100644 index 0000000000000..75af32fc695cd --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-source.stderr @@ -0,0 +1,15 @@ +error: function `foo` is never used + --> $DIR/ordering-source.rs:6:4 + | +LL | fn foo() {} + | ^^^ + | +note: the lint level is defined here + --> :1:9 + | +LL | #![deny(unused)] + | ^^^^^^ + = note: `#[deny(dead_code)]` implied by `#[deny(unused)]` + +error: aborting due to 1 previous error + From 851c9e042bf06381d712e4d79fe6fac826b1d8d3 Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Thu, 5 Nov 2026 07:52:35 +0600 Subject: [PATCH 08/11] apply -Zcrate-attr attributes before the crate attributes cmdline attributes were pushed after the attributes parsed from source, so a -Zcrate-attr lint level overrode the same lint set in the crate root. rfc 3791 says they apply before the source attributes. they are now inserted at the front of the crate attributes in command line order. --- .../rustc_builtin_macros/src/cmdline_attrs.rs | 15 +++++++-------- .../ui/attributes/z-crate-attr/ordering-source.rs | 3 ++- .../z-crate-attr/ordering-source.stderr | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs index b294328b0bb17..b0045c359160d 100644 --- a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -8,7 +8,7 @@ use rustc_session::parse::ParseSess; use rustc_span::FileName; pub fn inject(krate: &mut ast::Crate, psess: &ParseSess, attrs: &[String]) { - for raw_attr in attrs { + let injected = attrs.iter().filter_map(|raw_attr| { let source = format!("#![{raw_attr}]"); let parse = || -> Result>> { let tokens = source_str_to_stream( @@ -22,16 +22,15 @@ pub fn inject(krate: &mut ast::Crate, psess: &ParseSess, attrs: &[String]) { }) .map_err(|e| vec![e]) }; - let meta = match parse() { - Ok(meta) => meta, + match parse() { + Ok(meta) => Some(meta), Err(errs) => { for err in errs { err.emit(); } - continue; + None } - }; - - krate.attrs.push(meta); - } + } + }); + krate.attrs.splice(0..0, injected); } diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.rs b/tests/ui/attributes/z-crate-attr/ordering-source.rs index d4a248a6be5ca..914851d3bb055 100644 --- a/tests/ui/attributes/z-crate-attr/ordering-source.rs +++ b/tests/ui/attributes/z-crate-attr/ordering-source.rs @@ -1,8 +1,9 @@ // RFC 3791: `-Zcrate-attr` applies before the crate's own attributes, so `warn` here wins. +//@ check-pass //@ compile-flags: -Zcrate-attr=deny(unused) #![warn(unused)] -fn foo() {} //~ ERROR function `foo` is never used +fn foo() {} //~ WARN function `foo` is never used fn main() {} diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.stderr b/tests/ui/attributes/z-crate-attr/ordering-source.stderr index 75af32fc695cd..66cf78738bf7c 100644 --- a/tests/ui/attributes/z-crate-attr/ordering-source.stderr +++ b/tests/ui/attributes/z-crate-attr/ordering-source.stderr @@ -1,15 +1,15 @@ -error: function `foo` is never used - --> $DIR/ordering-source.rs:6:4 +warning: function `foo` is never used + --> $DIR/ordering-source.rs:7:4 | LL | fn foo() {} | ^^^ | note: the lint level is defined here - --> :1:9 + --> $DIR/ordering-source.rs:5:9 | -LL | #![deny(unused)] +LL | #![warn(unused)] | ^^^^^^ - = note: `#[deny(dead_code)]` implied by `#[deny(unused)]` + = note: `#[warn(dead_code)]` implied by `#[warn(unused)]` -error: aborting due to 1 previous error +warning: 1 warning emitted From 7aedffb1699a39b10131393b741411cf8e3a3ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 6 Nov 2026 23:18:45 +0100 Subject: [PATCH 09/11] compiletest: Reject lines matching `//\s+@` --- src/etc/htmldocck.py | 12 ----- src/tools/compiletest/src/directives/line.rs | 44 ++++++++++--------- src/tools/compiletest/src/directives/tests.rs | 7 +++ src/tools/jsondocck/src/main.rs | 15 +------ tests/codegen-llvm/terminating-catchpad.rs | 6 +-- tests/ui/consts/const-fn-variant-ctor.rs | 2 +- .../unsupported-abi/rust-call.rs | 2 - .../unsupported-abi/rust-call.stderr | 2 +- .../coherence_different_hidden_ty.rs | 5 +-- .../coherence_different_hidden_ty.stderr | 2 +- 10 files changed, 40 insertions(+), 57 deletions(-) diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py index 6011492ed27c9..23383e32fcf68 100755 --- a/src/etc/htmldocck.py +++ b/src/etc/htmldocck.py @@ -157,22 +157,10 @@ def concat_multi_lines(f): re.X | re.UNICODE, ) -DEPRECATED_LINE_PATTERN = re.compile( - r"//\s+@", - re.X | re.UNICODE, -) - def get_commands(template): with io.open(template, encoding="utf-8") as f: for lineno, line in concat_multi_lines(f): - if DEPRECATED_LINE_PATTERN.search(line): - print_err( - lineno, - line, - "Deprecated command syntax, replace `// @` with `//@ `", - ) - continue m = LINE_PATTERN.search(line) if not m: continue diff --git a/src/tools/compiletest/src/directives/line.rs b/src/tools/compiletest/src/directives/line.rs index 87e803921a853..8eb1865708c34 100644 --- a/src/tools/compiletest/src/directives/line.rs +++ b/src/tools/compiletest/src/directives/line.rs @@ -3,29 +3,38 @@ use std::fmt; use camino::Utf8Path; use crate::directives::LineNumber; - -const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@"; +use crate::{error, fatal, help}; /// If the given line begins with the appropriate comment prefix for a directive, /// returns a struct containing various parts of the directive. pub(crate) fn line_directive<'a>( file_path: &'a Utf8Path, line_number: LineNumber, - original_line: &'a str, + line: &'a str, ) -> Option> { - // Ignore lines that don't start with the comment prefix. - let after_comment = - original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start(); + let comment = line.trim_start().strip_prefix("//")?; + let trimmed_comment = comment.trim_start(); + let mut raw_directive = trimmed_comment.strip_prefix("@")?.trim_start(); + + if trimmed_comment.len() != comment.len() { + error!( + "malformed directive: \ + found whitespace between `//` and `@` at {file_path}:{line_number}" + ); + help!("if you meant to write a directive remove the whitespace to form `//@`"); + help!( + "if you meant to literally write `@` in a code comment \ + add `\\` before `@` or rewrap the contents of the comment so `@` doesn't come first" + ); + panic!("fatal error"); // FIXME: Poison the context and keep going. + } - let revision; - let raw_directive; + let mut revision = None; - if let Some(after_open_bracket) = after_comment.strip_prefix('[') { + if let Some(after_open_bracket) = raw_directive.strip_prefix('[') { // A comment like `//@[foo]` only applies to revision `foo`. let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else { - panic!( - "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`" - ) + fatal!("malformed condition directive: expected `//@[foo]`, found `{line}`") }; revision = Some(line_revision); @@ -34,20 +43,15 @@ pub(crate) fn line_directive<'a>( if line_revision.contains(",") { let suggestion: Vec<_> = line_revision .split(",") - .map(|revision| { - format!("{COMPILETEST_DIRECTIVE_PREFIX} [{revision}]: {raw_directive}") - }) + .map(|revision| format!("//@[{revision}]: {raw_directive}")) .collect(); panic!( "malformed condition directive: multiple revisions aren't supported yet in `{}`, split them like\n{}", - original_line, + line, suggestion.join("\n"), ); } - } else { - revision = None; - raw_directive = after_comment; - }; + } // The directive name ends at the first occurrence of colon, space, or end-of-string. let name = raw_directive.split([':', ' ']).next().expect("split is never empty"); diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index 6facfe362c4eb..179400d97addf 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -668,6 +668,13 @@ fn test_multiple_revisions_in_directive() { parse_early_props(&config, directive); } +#[test] +#[should_panic] +fn test_whitespace_before_at_sign_in_directive() { + let line = "// @ check-pass"; + let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); +} + #[test] fn test_forbidden_revisions_allowed_in_non_filecheck_dir() { let revisions = ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"]; diff --git a/src/tools/jsondocck/src/main.rs b/src/tools/jsondocck/src/main.rs index c3487565c23ed..026004c52d277 100644 --- a/src/tools/jsondocck/src/main.rs +++ b/src/tools/jsondocck/src/main.rs @@ -57,10 +57,6 @@ static LINE_PATTERN: LazyLock = LazyLock::new(|| { .unwrap() }); -static DEPRECATED_LINE_PATTERN: LazyLock = LazyLock::new(|| { - RegexBuilder::new(r"//\s+@").ignore_whitespace(true).unicode(true).build().unwrap() -}); - fn print_err(msg: &str, lineno: usize) { eprintln!("Invalid directive: {} on line {}", msg, lineno) } @@ -72,18 +68,9 @@ fn get_directives(template: &str) -> Result, ()> { let file = fs::read_to_string(template).unwrap(); for (lineno, line) in file.split('\n').enumerate() { + let Some(cap) = LINE_PATTERN.captures(line) else { continue }; let lineno = lineno + 1; - if DEPRECATED_LINE_PATTERN.is_match(line) { - print_err("Deprecated directive syntax, replace `// @` with `//@ `", lineno); - errors = true; - continue; - } - - let Some(cap) = LINE_PATTERN.captures(line) else { - continue; - }; - let negated = &cap["negated"] == "!"; let args_str = cap.name("args").map(|m| m.as_str()).unwrap_or_default(); diff --git a/tests/codegen-llvm/terminating-catchpad.rs b/tests/codegen-llvm/terminating-catchpad.rs index cea5d48ab99e5..4f56a245af6ad 100644 --- a/tests/codegen-llvm/terminating-catchpad.rs +++ b/tests/codegen-llvm/terminating-catchpad.rs @@ -10,9 +10,9 @@ // - `catchpad ... [ptr null]` on Wasm (otherwise LLVM gets confused) // - `catchpad ... [ptr null, i32 64, ptr null]` on Windows (otherwise we catch SEH exceptions) // -// Unlike on windows, on Wasm, we specifically do want to catch foreign -// exceptions. To catch only C++ exceptions we'd need to call -// @llvm.wasm.get.exception and @llvm.wasm.get.ehselector in the catchpad. +// Unlike on windows, on Wasm, we specifically do want to catch foreign exceptions. To catch only +// C++ exceptions we'd need to call @llvm.wasm.get.exception and @llvm.wasm.get.ehselector in the +// catchpad. #![feature(no_core, lang_items, rustc_attrs)] #![crate_type = "lib"] diff --git a/tests/ui/consts/const-fn-variant-ctor.rs b/tests/ui/consts/const-fn-variant-ctor.rs index b56f3145c11c3..17a8b04954c7c 100644 --- a/tests/ui/consts/const-fn-variant-ctor.rs +++ b/tests/ui/consts/const-fn-variant-ctor.rs @@ -1,5 +1,5 @@ //@ check-pass -// @ needs-rustc-debug-assertions +//@ needs-rustc-debug-assertions //@ revisions: current next //@ ignore-compare-mode-next-solver (explicit revisions) //@[next] compile-flags: -Znext-solver diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.rs b/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.rs index 6f3ec783a4bfe..821509a2e8879 100644 --- a/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.rs +++ b/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.rs @@ -2,8 +2,6 @@ // support for tail calls to work correctly. Since there doesn't seem to be a usecase for tail // calling extern "rust-call" functions (extern "rust-call" only shows up in closures, which we // disallow tail-calling anyway), we just disallow that. -// -// @ ignore-backends: gcc #![expect(incomplete_features)] #![feature(explicit_tail_calls)] #![feature(unboxed_closures)] diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.stderr b/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.stderr index 6662836672097..52a97fd30e1fc 100644 --- a/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.stderr +++ b/tests/ui/explicit-tail-calls/unsupported-abi/rust-call.stderr @@ -1,5 +1,5 @@ error: ABI does not support guaranteed tail calls - --> $DIR/rust-call.rs:12:5 + --> $DIR/rust-call.rs:10:5 | LL | become a(()); | ^^^^^^^^^^^^ diff --git a/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.rs b/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.rs index 66442ac874516..35d16fa60e1a6 100644 --- a/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.rs +++ b/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.rs @@ -6,9 +6,8 @@ // // let _ = infcx.take_opaque_types(); // -// @lcnr: Because of this I decided to not bother and cause this to fail instead. -// In the future we can definitely modify the compiler to accept this -// again. +// NOTE(@lcnr): Because of this I decided to not bother and cause this to fail instead. +// In the future we can definitely modify the compiler to accept this again. #![feature(type_alias_impl_trait)] diff --git a/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.stderr b/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.stderr index d7f6606d2099a..ef170101b4455 100644 --- a/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.stderr +++ b/tests/ui/type-alias-impl-trait/coherence/coherence_different_hidden_ty.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `Trait` for type `(TAIT, TAIT)` - --> $DIR/coherence_different_hidden_ty.rs:21:1 + --> $DIR/coherence_different_hidden_ty.rs:20:1 | LL | impl Trait for (TAIT, TAIT) {} | --------------------------- first implementation here From cca4cb8498ceac810f62a2a8292db5f17e0a805c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 6 Nov 2026 23:56:42 +0100 Subject: [PATCH 10/11] compiletest: Reject "leading" revision conditions --- src/tools/compiletest/src/directives/line.rs | 16 ++++++++++++++ src/tools/compiletest/src/directives/tests.rs | 21 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/tools/compiletest/src/directives/line.rs b/src/tools/compiletest/src/directives/line.rs index 8eb1865708c34..8847ce7afd06c 100644 --- a/src/tools/compiletest/src/directives/line.rs +++ b/src/tools/compiletest/src/directives/line.rs @@ -14,8 +14,24 @@ pub(crate) fn line_directive<'a>( ) -> Option> { let comment = line.trim_start().strip_prefix("//")?; let trimmed_comment = comment.trim_start(); + + let (leading_revision, trimmed_comment) = trimmed_comment + .strip_prefix('[') + .and_then(|after_open_bracket| after_open_bracket.split_once(']')) + .map(|(revision, after_close_bracket)| (Some(revision), after_close_bracket.trim_start())) + .unwrap_or((None, trimmed_comment)); + let mut raw_directive = trimmed_comment.strip_prefix("@")?.trim_start(); + if let Some(leading_revision) = leading_revision { + error!( + "malformed directive: found revision condition before `@` instead of after at {file_path}:{line_number}" + ); + let from = &line[..line.substr_range(raw_directive).unwrap().start]; + help!("rewrite the start of the line to `//@[{leading_revision}] ` (from `{from}`)"); + panic!("fatal error"); // FIXME: Poison the context and keep going. + } + if trimmed_comment.len() != comment.len() { error!( "malformed directive: \ diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index 179400d97addf..7a5ac291cb36d 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -675,6 +675,27 @@ fn test_whitespace_before_at_sign_in_directive() { let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); } +#[test] +#[should_panic] +fn test_leading_revision_condition_in_directive_0() { + let line = "//[leading]@ compile-flags: -Aunused"; + let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); +} + +#[test] +#[should_panic] +fn test_leading_revision_condition_in_directive_1() { + let line = "// [leading] @ compile-flags: -Aunused"; + let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); +} + +#[test] +fn test_not_a_leading_revision_condition_in_directive() { + let line = "// [leading @ compile-flags: -Aunused"; + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert!(result.is_none()); +} + #[test] fn test_forbidden_revisions_allowed_in_non_filecheck_dir() { let revisions = ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"]; From 8a55b336c88334a7e0bb9df9432c79b54040ccd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 7 Nov 2026 01:12:23 +0100 Subject: [PATCH 11/11] Make `line_directive` return a `Result` to turn it unit-testable --- src/tools/compiletest/src/diagnostics.rs | 4 +- src/tools/compiletest/src/directives.rs | 9 +- src/tools/compiletest/src/directives/file.rs | 3 +- src/tools/compiletest/src/directives/line.rs | 106 +++++++++++----- .../compiletest/src/directives/line_number.rs | 2 +- src/tools/compiletest/src/directives/tests.rs | 113 ++++++++++++++---- src/tools/compiletest/src/runtest/debugger.rs | 5 +- 7 files changed, 181 insertions(+), 61 deletions(-) diff --git a/src/tools/compiletest/src/diagnostics.rs b/src/tools/compiletest/src/diagnostics.rs index a7598370c2b9b..5243344ec5c0a 100644 --- a/src/tools/compiletest/src/diagnostics.rs +++ b/src/tools/compiletest/src/diagnostics.rs @@ -17,12 +17,12 @@ macro_rules! fatal { #[macro_export] macro_rules! error { - ($($arg:tt)*) => { + ($($arg:tt)*) => {{ let status = ::colored::Colorize::red("ERROR: "); let status = ::colored::Colorize::bold(status); eprint!("{status}"); eprintln!($($arg)*); - }; + }}; } #[macro_export] diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index 33b75f6ab860b..4e99ab99f6059 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -548,9 +548,12 @@ fn iter_directives( }; for directive_str in extra_directives { - let directive_line = line_directive(testfile, LineNumber::ZERO, directive_str) - .unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}")); - it(&directive_line); + if let Some(Ok(directive_line)) = line_directive(testfile, LineNumber::ZERO, directive_str) + { + it(&directive_line); + } else { + panic!("bad extra-directive line: {directive_str:?}") + } } for directive_line in &file_directives.lines { diff --git a/src/tools/compiletest/src/directives/file.rs b/src/tools/compiletest/src/directives/file.rs index bde935729121f..8bd6de5041934 100644 --- a/src/tools/compiletest/src/directives/file.rs +++ b/src/tools/compiletest/src/directives/file.rs @@ -33,7 +33,8 @@ impl<'a> FileDirectives<'a> { } if let Some(directive_line) = line_directive(path, line_number, ln) { - lines.push(directive_line); + let Ok(line) = directive_line.map_err(|e| e.raise_fatal()); + lines.push(line); } } diff --git a/src/tools/compiletest/src/directives/line.rs b/src/tools/compiletest/src/directives/line.rs index 8847ce7afd06c..8c3fe05b6ef7c 100644 --- a/src/tools/compiletest/src/directives/line.rs +++ b/src/tools/compiletest/src/directives/line.rs @@ -3,7 +3,7 @@ use std::fmt; use camino::Utf8Path; use crate::directives::LineNumber; -use crate::{error, fatal, help}; +use crate::{error, help}; /// If the given line begins with the appropriate comment prefix for a directive, /// returns a struct containing various parts of the directive. @@ -11,7 +11,9 @@ pub(crate) fn line_directive<'a>( file_path: &'a Utf8Path, line_number: LineNumber, line: &'a str, -) -> Option> { +) -> Option, Error<'a>>> { + let error = |kind| Some(Err(Error { kind, file_path, line_number })); + let comment = line.trim_start().strip_prefix("//")?; let trimmed_comment = comment.trim_start(); @@ -23,56 +25,37 @@ pub(crate) fn line_directive<'a>( let mut raw_directive = trimmed_comment.strip_prefix("@")?.trim_start(); - if let Some(leading_revision) = leading_revision { - error!( - "malformed directive: found revision condition before `@` instead of after at {file_path}:{line_number}" - ); - let from = &line[..line.substr_range(raw_directive).unwrap().start]; - help!("rewrite the start of the line to `//@[{leading_revision}] ` (from `{from}`)"); - panic!("fatal error"); // FIXME: Poison the context and keep going. + if let Some(revision) = leading_revision { + let context = &line[..line.substr_range(raw_directive).unwrap().start]; + return error(ErrorKind::LeadingRevisionCondition { revision, context }); } if trimmed_comment.len() != comment.len() { - error!( - "malformed directive: \ - found whitespace between `//` and `@` at {file_path}:{line_number}" - ); - help!("if you meant to write a directive remove the whitespace to form `//@`"); - help!( - "if you meant to literally write `@` in a code comment \ - add `\\` before `@` or rewrap the contents of the comment so `@` doesn't come first" - ); - panic!("fatal error"); // FIXME: Poison the context and keep going. + return error(ErrorKind::WhitespaceBeforeAtSign); } let mut revision = None; if let Some(after_open_bracket) = raw_directive.strip_prefix('[') { - // A comment like `//@[foo]` only applies to revision `foo`. let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else { - fatal!("malformed condition directive: expected `//@[foo]`, found `{line}`") + return error(ErrorKind::UnclosedRevisionCondition); }; revision = Some(line_revision); raw_directive = after_close_bracket.trim_start(); if line_revision.contains(",") { - let suggestion: Vec<_> = line_revision - .split(",") - .map(|revision| format!("//@[{revision}]: {raw_directive}")) - .collect(); - panic!( - "malformed condition directive: multiple revisions aren't supported yet in `{}`, split them like\n{}", - line, - suggestion.join("\n"), - ); + return error(ErrorKind::CommaInRevisionCondition { + revision: line_revision, + directive: raw_directive, + }); } } // The directive name ends at the first occurrence of colon, space, or end-of-string. let name = raw_directive.split([':', ' ']).next().expect("split is never empty"); - Some(DirectiveLine { file_path, line_number, revision, raw_directive, name }) + Some(Ok(DirectiveLine { file_path, line_number, revision, raw_directive, name })) } /// The (partly) broken-down contents of a line containing a test directive, @@ -90,6 +73,7 @@ pub(crate) fn line_directive<'a>( /// ^^^^^^^^^^^^^^^^^ raw_directive /// ^^^^^^^^^^^^^ name /// ``` +#[cfg_attr(test, derive(Debug))] pub(crate) struct DirectiveLine<'a> { /// Path of the file containing this line. /// @@ -108,7 +92,10 @@ pub(crate) struct DirectiveLine<'a> { /// /// This is "raw" because the directive's name and colon-separated value /// (if present) have not yet been extracted or checked. + #[cfg(not(test))] raw_directive: &'a str, + #[cfg(test)] + pub(crate) raw_directive: &'a str, /// Name of the directive. /// @@ -150,3 +137,60 @@ impl<'ln> DirectiveLine<'ln> { self.raw_directive } } + +#[cfg_attr(test, derive(Debug))] +pub(crate) struct Error<'a> { + pub(crate) kind: ErrorKind<'a>, + pub(crate) file_path: &'a Utf8Path, + pub(crate) line_number: LineNumber, +} + +#[cfg_attr(test, derive(Debug))] +pub(crate) enum ErrorKind<'a> { + LeadingRevisionCondition { revision: &'a str, context: &'a str }, + WhitespaceBeforeAtSign, + UnclosedRevisionCondition, + CommaInRevisionCondition { revision: &'a str, directive: &'a str }, +} + +impl Error<'_> { + // FIXME: Just report the error, poison the context and keep going. + pub(crate) fn raise_fatal(self) -> ! { + let Self { kind, file_path, line_number } = self; + + let error = |message| error!("malformed directive: {message} at {file_path}:{line_number}"); + + match kind { + ErrorKind::LeadingRevisionCondition { revision, context } => { + error("found revision condition before `@` instead of after"); + help!("rewrite the start of the line to `//@[{revision}] ` (from `{context}`)"); + } + ErrorKind::WhitespaceBeforeAtSign => { + error("found whitespace between `//` and `@`"); + help!("if you meant to write a directive remove the whitespace to form `//@`"); + help!( + "if you meant to literally write `@` in a code comment add `\\` before `@` or \ + rewrap the contents of the comment so `@` doesn't come first" + ); + } + ErrorKind::UnclosedRevisionCondition => { + error("revision condition is missing a closing bracket `]`"); + } + ErrorKind::CommaInRevisionCondition { revision, directive } => { + error( + "found commas in revision condition but multiple revisions aren't supported yet", + ); + + let suggestion = revision + .split(",") + .map(|revision| format!("//@[{revision}]: {directive}")) + .collect::>() + .join("\n"); + + help!("consider splitting the directive like\n{suggestion}"); + } + } + + panic!("fatal error") + } +} diff --git a/src/tools/compiletest/src/directives/line_number.rs b/src/tools/compiletest/src/directives/line_number.rs index ce5f200f470c8..07ae043d1c716 100644 --- a/src/tools/compiletest/src/directives/line_number.rs +++ b/src/tools/compiletest/src/directives/line_number.rs @@ -1,7 +1,7 @@ /// A line number in a file. Internally the first line has index 1. /// If it is 0 it means "no specific line" (used e.g. for implied directives). /// When `Display`:ed, the first line is `1`. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub(crate) struct LineNumber(usize); impl LineNumber { diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index 7a5ac291cb36d..03900706e042f 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -1,9 +1,11 @@ +use std::assert_matches; use std::collections::BTreeSet; use camino::Utf8Path; use semver::Version; use crate::common::{Config, Debugger, TestMode}; +use crate::directives::line::{DirectiveLine, Error, ErrorKind}; use crate::directives::{ self, AuxProps, DIRECTIVE_HANDLERS_MAP, DirectivesCache, EarlyProps, Edition, EditionRange, FileDirectives, KNOWN_DIRECTIVE_NAMES_SET, LineNumber, extract_llvm_version, @@ -654,46 +656,116 @@ fn test_miropt_mode_forbidden_revisions() { } #[test] -#[should_panic(expected = "malformed condition directive: multiple revisions aren't supported yet")] -fn test_multiple_revisions_in_directive() { - let directive = "//@ [foo,bar] compile-flags: -Z hello"; +fn test_ok_directive() { + let line = "//@ aux-build: file.rs"; - // The problem: this is seen as a single revision. - let line_directive = line_directive(Utf8Path::new("foo.txt"), LineNumber::ZERO, directive); - assert!(line_directive.is_some()); - assert_eq!(Some("foo,bar"), line_directive.unwrap().revision); + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!( + result, + Some(Ok(DirectiveLine { + revision: None, + name: "aux-build", + raw_directive: "aux-build: file.rs", + .. + })) + ); +} - // The solution for now: forbid directives from having multiple revisions. - let config: Config = cfg().build(); - parse_early_props(&config, directive); +#[test] +fn indented_directive() { + let line = " //@ key: value"; + + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!( + result, + Some(Ok(DirectiveLine { revision: None, name: "key", raw_directive: "key: value", .. })) + ); +} + +#[test] +fn test_doc_comment_at_sign_not_a_directive() { + let line = "///@ aux-build: file.rs"; + + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!(result, None); +} + +#[test] +fn test_multiple_revisions_in_directive() { + // The problem: If we don't reject this early, this will be seen as a single revision. + let line = "//@ [foo,bar] compile-flags: -Z hello"; + + let result = line_directive(Utf8Path::new("foo.txt"), LineNumber::ZERO, line); + assert_matches!( + result, + Some(Err(Error { + kind: ErrorKind::CommaInRevisionCondition { + revision: "foo,bar", + directive: "compile-flags: -Z hello" + }, + .. + })) + ); } #[test] -#[should_panic] fn test_whitespace_before_at_sign_in_directive() { let line = "// @ check-pass"; - let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!(result, Some(Err(Error { kind: ErrorKind::WhitespaceBeforeAtSign, .. }))); } #[test] -#[should_panic] fn test_leading_revision_condition_in_directive_0() { let line = "//[leading]@ compile-flags: -Aunused"; - let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!( + result, + Some(Err(Error { + kind: ErrorKind::LeadingRevisionCondition { + revision: "leading", + context: "//[leading]@ " + }, + .. + })) + ); } #[test] -#[should_panic] fn test_leading_revision_condition_in_directive_1() { let line = "// [leading] @ compile-flags: -Aunused"; - let _ = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!( + result, + Some(Err(Error { + kind: ErrorKind::LeadingRevisionCondition { + revision: "leading", + context: "// [leading] @ " + }, + .. + })) + ); } #[test] -fn test_not_a_leading_revision_condition_in_directive() { +fn test_not_a_leading_revision_condition_in_directive_0() { let line = "// [leading @ compile-flags: -Aunused"; let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); - assert!(result.is_none()); + assert_matches!(result, None); +} + +#[test] +fn test_not_a_leading_revision_condition_in_directive_1() { + let line = "// [closure@file.rs]"; + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!(result, None); +} + +#[test] +fn test_unclosed_revision_condition_in_directive() { + let line = "//@[revision edition: 2021"; + let result = line_directive(Utf8Path::new("file.rs"), LineNumber::ZERO, line); + assert_matches!(result, Some(Err(Error { kind: ErrorKind::UnclosedRevisionCondition, .. }))); } #[test] @@ -1181,8 +1253,9 @@ fn parse_edition_range(line: &str) -> Option { let config = cfg().build(); let line_with_comment = format!("//@ {line}"); - let line = - line_directive(Utf8Path::new("tmp.rs"), LineNumber::ZERO, &line_with_comment).unwrap(); + let line = line_directive(Utf8Path::new("tmp.rs"), LineNumber::ZERO, &line_with_comment) + .unwrap() + .unwrap(); super::parse_edition_range(&config, &line) } diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs index 7046c981b28fd..d48097bacd556 100644 --- a/src/tools/compiletest/src/runtest/debugger.rs +++ b/src/tools/compiletest/src/runtest/debugger.rs @@ -40,9 +40,8 @@ impl DebuggerCommands { continue; } - let Some(directive) = line_directive(file, line_number, &line) else { - continue; - }; + let Some(directive) = line_directive(file, line_number, &line) else { continue }; + let Ok(directive) = directive.map_err(|e| e.raise_fatal()); if !directive.applies_to_test_revision(test_revision) { continue;