Rollup merge of #143925 - oli-obk:slice-const-partialeq, r=fee1-dead

Make slice comparisons const

This needed a fix for `derive_const`, too, as it wasn't usable in libcore anymore as trait impls need const stability attributes. I think we can't use the same system as normal trait impls while `const_trait_impl` is still unstable.

r? ```@fee1-dead```

cc rust-lang/rust#143800
This commit is contained in:
Matthias Krüger 2025-07-18 14:49:19 +02:00 committed by GitHub
commit 82fbbddf63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 134 additions and 49 deletions

View file

@ -23,6 +23,7 @@ pub(crate) fn expand_deriving_copy(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push);
@ -46,6 +47,7 @@ pub(crate) fn expand_deriving_const_param_ty(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push);
@ -60,6 +62,7 @@ pub(crate) fn expand_deriving_const_param_ty(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push);
@ -83,6 +86,7 @@ pub(crate) fn expand_deriving_unsized_const_param_ty(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push);

View file

@ -87,6 +87,7 @@ pub(crate) fn expand_deriving_clone(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand_ext(cx, mitem, item, push, is_simple)

View file

@ -43,6 +43,7 @@ pub(crate) fn expand_deriving_eq(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand_ext(cx, mitem, item, push, true)
}

View file

@ -34,6 +34,7 @@ pub(crate) fn expand_deriving_ord(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)

View file

@ -30,6 +30,7 @@ pub(crate) fn expand_deriving_partial_eq(
methods: Vec::new(),
associated_types: Vec::new(),
is_const: false,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
structural_trait_def.expand(cx, mitem, item, push);
@ -58,6 +59,7 @@ pub(crate) fn expand_deriving_partial_eq(
methods,
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}

View file

@ -64,6 +64,7 @@ pub(crate) fn expand_deriving_partial_ord(
methods: vec![partial_cmp_def],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}

View file

@ -41,6 +41,7 @@ pub(crate) fn expand_deriving_debug(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}

View file

@ -51,6 +51,7 @@ pub(crate) fn expand_deriving_default(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}

View file

@ -181,9 +181,11 @@ use std::{iter, vec};
pub(crate) use StaticFields::*;
pub(crate) use SubstructureFields::*;
use rustc_ast::ptr::P;
use rustc_ast::token::{IdentIsRaw, LitKind, Token, TokenKind};
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenTree};
use rustc_ast::{
self as ast, AnonConst, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind,
Generics, Mutability, PatKind, VariantData,
self as ast, AnonConst, AttrArgs, BindingMode, ByRef, DelimArgs, EnumDef, Expr, GenericArg,
GenericParamKind, Generics, Mutability, PatKind, Safety, VariantData,
};
use rustc_attr_data_structures::{AttributeKind, ReprPacked};
use rustc_attr_parsing::AttributeParser;
@ -222,6 +224,8 @@ pub(crate) struct TraitDef<'a> {
pub associated_types: Vec<(Ident, Ty)>,
pub is_const: bool,
pub is_staged_api_crate: bool,
}
pub(crate) struct MethodDef<'a> {
@ -784,8 +788,45 @@ impl<'a> TraitDef<'a> {
// Create the type of `self`.
let path = cx.path_all(self.span, false, vec![type_ident], self_params);
let self_type = cx.ty_path(path);
let rustc_const_unstable =
cx.path_ident(self.span, Ident::new(sym::rustc_const_unstable, self.span));
let mut attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),];
// Only add `rustc_const_unstable` attributes if `derive_const` is used within libcore/libstd,
// Other crates don't need stability attributes, so adding them is not useful, but libcore needs them
// on all const trait impls.
if self.is_const && self.is_staged_api_crate {
attrs.push(
cx.attr_nested(
rustc_ast::AttrItem {
unsafety: Safety::Default,
path: rustc_const_unstable,
args: AttrArgs::Delimited(DelimArgs {
dspan: DelimSpan::from_single(self.span),
delim: rustc_ast::token::Delimiter::Parenthesis,
tokens: [
TokenKind::Ident(sym::feature, IdentIsRaw::No),
TokenKind::Eq,
TokenKind::lit(LitKind::Str, sym::derive_const, None),
TokenKind::Comma,
TokenKind::Ident(sym::issue, IdentIsRaw::No),
TokenKind::Eq,
TokenKind::lit(LitKind::Str, sym::derive_const_issue, None),
]
.into_iter()
.map(|kind| {
TokenTree::Token(Token { kind, span: self.span }, Spacing::Alone)
})
.collect(),
}),
tokens: None,
},
self.span,
),
)
}
let attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),];
let opt_trait_ref = Some(trait_ref);
cx.item(

View file

@ -41,6 +41,7 @@ pub(crate) fn expand_deriving_hash(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
hash_trait_def.expand(cx, mitem, item, push);

View file

@ -3,8 +3,8 @@ use rustc_ast::token::Delimiter;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::util::literal;
use rustc_ast::{
self as ast, AnonConst, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, UnOp,
attr, token, tokenstream,
self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind,
UnOp, attr, token, tokenstream,
};
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
@ -766,4 +766,10 @@ impl<'a> ExtCtxt<'a> {
span,
)
}
// Builds an attribute fully manually.
pub fn attr_nested(&self, inner: AttrItem, span: Span) -> ast::Attribute {
let g = &self.sess.psess.attr_id_generator;
attr::mk_attr_from_item(g, inner, None, ast::AttrStyle::Outer, span)
}
}

View file

@ -840,6 +840,7 @@ symbols! {
derive,
derive_coerce_pointee,
derive_const,
derive_const_issue: "118304",
derive_default_enum,
derive_smart_pointer,
destruct,

View file

@ -381,7 +381,8 @@ pub struct AssertParamIsEq<T: Eq + PointeeSized> {
///
/// assert_eq!(2.cmp(&1), Ordering::Greater);
/// ```
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[derive(Clone, Copy, Eq, PartialOrd, Ord, Debug, Hash)]
#[derive_const(PartialEq)]
#[stable(feature = "rust1", since = "1.0.0")]
// This is a lang item only so that `BinOp::Cmp` in MIR can return it.
// It has no special behavior, but does require that the three variants

View file

@ -17,11 +17,15 @@ use crate::num::NonZero;
/// - Neither `Self` nor `Rhs` have provenance, so integer comparisons are correct.
/// - `<Self as PartialEq<Rhs>>::{eq,ne}` are equivalent to comparing the bytes.
#[rustc_specialization_trait]
pub(crate) unsafe trait BytewiseEq<Rhs = Self>: PartialEq<Rhs> + Sized {}
#[const_trait]
pub(crate) unsafe trait BytewiseEq<Rhs = Self>:
~const PartialEq<Rhs> + Sized
{
}
macro_rules! is_bytewise_comparable {
($($t:ty),+ $(,)?) => {$(
unsafe impl BytewiseEq for $t {}
unsafe impl const BytewiseEq for $t {}
)+};
}

View file

@ -2208,6 +2208,7 @@ pub const unsafe fn raw_eq<T>(a: &T, b: &T) -> bool;
/// [valid]: crate::ptr#safety
#[rustc_nounwind]
#[rustc_intrinsic]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
pub const unsafe fn compare_bytes(left: *const u8, right: *const u8, bytes: usize) -> i32;
/// See documentation of [`std::hint::black_box`] for details.

View file

@ -103,6 +103,7 @@
#![feature(cfg_select)]
#![feature(cfg_target_has_reliable_f16_f128)]
#![feature(const_carrying_mul_add)]
#![feature(const_cmp)]
#![feature(const_destruct)]
#![feature(const_eval_select)]
#![feature(core_intrinsics)]
@ -146,6 +147,7 @@
#![feature(const_trait_impl)]
#![feature(decl_macro)]
#![feature(deprecated_suggestion)]
#![feature(derive_const)]
#![feature(doc_cfg)]
#![feature(doc_cfg_hide)]
#![feature(doc_notable_trait)]

View file

@ -1615,7 +1615,7 @@ pub(crate) mod builtin {
/// See [the reference] for more info.
///
/// [the reference]: ../../../reference/attributes/derive.html
#[unstable(feature = "derive_const", issue = "none")]
#[unstable(feature = "derive_const", issue = "118304")]
#[rustc_builtin_macro]
pub macro derive_const($item:item) {
/* compiler built-in */

View file

@ -200,9 +200,10 @@ impl<T> UseCloned for NonZero<T> where T: ZeroablePrimitive {}
impl<T> Copy for NonZero<T> where T: ZeroablePrimitive {}
#[stable(feature = "nonzero", since = "1.28.0")]
impl<T> PartialEq for NonZero<T>
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<T> const PartialEq for NonZero<T>
where
T: ZeroablePrimitive + PartialEq,
T: ZeroablePrimitive + ~const PartialEq,
{
#[inline]
fn eq(&self, other: &Self) -> bool {

View file

@ -2300,7 +2300,8 @@ impl<'a, T> From<&'a mut Option<T>> for Option<&'a mut T> {
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> crate::marker::StructuralPartialEq for Option<T> {}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: PartialEq> PartialEq for Option<T> {
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<T: ~const PartialEq> const PartialEq for Option<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
// Spelling out the cases explicitly optimizes better than

View file

@ -80,7 +80,7 @@ pub use crate::macros::builtin::{
alloc_error_handler, bench, derive, global_allocator, test, test_case,
};
#[unstable(feature = "derive_const", issue = "none")]
#[unstable(feature = "derive_const", issue = "118304")]
pub use crate::macros::builtin::derive_const;
#[unstable(

View file

@ -8,9 +8,10 @@ use crate::num::NonZero;
use crate::ops::ControlFlow;
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> PartialEq<[U]> for [T]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<T, U> const PartialEq<[U]> for [T]
where
T: PartialEq<U>,
T: ~const PartialEq<U>,
{
fn eq(&self, other: &[U]) -> bool {
SlicePartialEq::equal(self, other)
@ -94,6 +95,8 @@ impl<T: PartialOrd> PartialOrd for [T] {
#[doc(hidden)]
// intermediate trait for specialization of slice's PartialEq
#[const_trait]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
trait SlicePartialEq<B> {
fn equal(&self, other: &[B]) -> bool;
@ -103,9 +106,10 @@ trait SlicePartialEq<B> {
}
// Generic slice equality
impl<A, B> SlicePartialEq<B> for [A]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<A, B> const SlicePartialEq<B> for [A]
where
A: PartialEq<B>,
A: ~const PartialEq<B>,
{
default fn equal(&self, other: &[B]) -> bool {
if self.len() != other.len() {
@ -115,11 +119,14 @@ where
// Implemented as explicit indexing rather
// than zipped iterators for performance reasons.
// See PR https://github.com/rust-lang/rust/pull/116846
for idx in 0..self.len() {
// FIXME(const_hack): make this a `for idx in 0..self.len()` loop.
let mut idx = 0;
while idx < self.len() {
// bound checks are optimized away
if self[idx] != other[idx] {
return false;
}
idx += 1;
}
true
@ -128,9 +135,10 @@ where
// When each element can be compared byte-wise, we can compare all the bytes
// from the whole size in one call to the intrinsics.
impl<A, B> SlicePartialEq<B> for [A]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<A, B> const SlicePartialEq<B> for [A]
where
A: BytewiseEq<B>,
A: ~const BytewiseEq<B>,
{
fn equal(&self, other: &[B]) -> bool {
if self.len() != other.len() {

View file

@ -23,7 +23,8 @@ impl Ord for str {
}
#[stable(feature = "rust1", since = "1.0.0")]
impl PartialEq for str {
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl const PartialEq for str {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()

View file

@ -67,7 +67,7 @@ pub use core::prelude::v1::{
alloc_error_handler, bench, derive, global_allocator, test, test_case,
};
#[unstable(feature = "derive_const", issue = "none")]
#[unstable(feature = "derive_const", issue = "118304")]
pub use core::prelude::v1::derive_const;
// Do not `doc(no_inline)` either.

View file

@ -1,6 +1,6 @@
//@ check-fail
#![feature(core_intrinsics)]
#![feature(core_intrinsics, const_cmp)]
use std::intrinsics::compare_bytes;
use std::mem::MaybeUninit;

View file

@ -1,6 +1,6 @@
//@ run-pass
#![feature(core_intrinsics)]
#![feature(core_intrinsics, const_cmp)]
use std::intrinsics::compare_bytes;
fn main() {

View file

@ -1,10 +1,11 @@
// Crate that exports a const fn. Used for testing cross-crate.
#![crate_type="rlib"]
#![crate_type = "rlib"]
#![stable(feature = "rust1", since = "1.0.0")]
#![feature(staged_api)]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature="foo", issue = "none")]
pub const fn foo() -> u32 { 42 }
#[rustc_const_unstable(feature = "foo", issue = "none")]
pub const fn foo() -> u32 {
42
}

View file

@ -1,7 +1,7 @@
//@ compile-flags: -Znext-solver
//@ known-bug: #110395
// Broken until we have `const PartialEq` impl in stdlib
// Broken until `(): const PartialEq`
#![allow(incomplete_features)]
#![feature(const_trait_impl, const_cmp, const_destruct)]

View file

@ -4,6 +4,7 @@ error[E0658]: use of unstable library feature `derive_const`
LL | #[derive_const(Debug)]
| ^^^^^^^^^^^^
|
= note: see issue #118304 <https://github.com/rust-lang/rust/issues/118304> for more information
= help: add `#![feature(derive_const)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

View file

@ -1,12 +0,0 @@
error[E0015]: cannot match on `str` in constant functions
--> $DIR/match-non-const-eq.rs:7:9
|
LL | "a" => (), //FIXME [gated]~ ERROR can't compare `str` with `str` in const contexts
| ^^^
|
= note: `str` cannot be compared in compile-time, and therefore cannot be used in `match`es
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0015`.

View file

@ -1,11 +1,12 @@
//@ known-bug: #110395
//@ revisions: stock gated
#![cfg_attr(gated, feature(const_trait_impl))]
#![cfg_attr(gated, feature(const_trait_impl, const_cmp))]
//@[gated] check-pass
const fn foo(input: &'static str) {
match input {
"a" => (), //FIXME [gated]~ ERROR can't compare `str` with `str` in const contexts
//FIXME ~^ ERROR cannot match on `str` in constant functions
"a" => (),
//[stock]~^ ERROR cannot match on `str` in constant functions
//[stock]~| ERROR `PartialEq` is not yet stable as a const trait
_ => (),
}
}

View file

@ -1,12 +1,26 @@
error[E0015]: cannot match on `str` in constant functions
error[E0658]: cannot match on `str` in constant functions
--> $DIR/match-non-const-eq.rs:7:9
|
LL | "a" => (), //FIXME [gated]~ ERROR can't compare `str` with `str` in const contexts
LL | "a" => (),
| ^^^
|
= note: `str` cannot be compared in compile-time, and therefore cannot be used in `match`es
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: see issue #143874 <https://github.com/rust-lang/rust/issues/143874> for more information
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 1 previous error
error: `PartialEq` is not yet stable as a const trait
--> $DIR/match-non-const-eq.rs:7:9
|
LL | "a" => (),
| ^^^
|
help: add `#![feature(const_cmp)]` to the crate attributes to enable
|
LL + #![feature(const_cmp)]
|
For more information about this error, try `rustc --explain E0015`.
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.