Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start using pattern types in libcore #136006

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Prev Previous commit
Start using pattern types in libcore
oli-obk committed Mar 12, 2025
commit e65f76e6c161f770b5bfbcc22d8c1dba4243c885
13 changes: 12 additions & 1 deletion compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
Original file line number Diff line number Diff line change
@@ -455,7 +455,18 @@ pub(crate) fn type_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, t: Ty<'tcx>) ->
AdtKind::Enum => enums::build_enum_type_di_node(cx, unique_type_id),
},
ty::Tuple(_) => build_tuple_type_di_node(cx, unique_type_id),
_ => bug!("debuginfo: unexpected type in type_di_node(): {:?}", t),
ty::Pat(base, _) => return type_di_node(cx, base),
// FIXME(unsafe_binders): impl debug info
ty::UnsafeBinder(_) => unimplemented!(),
ty::Alias(..)
| ty::Param(_)
| ty::Bound(..)
| ty::Infer(_)
| ty::Placeholder(_)
| ty::CoroutineWitness(..)
| ty::Error(_) => {
bug!("debuginfo: unexpected type in type_di_node(): {:?}", t)
}
};

{
3 changes: 3 additions & 0 deletions compiler/rustc_const_eval/src/interpret/call.rs
Original file line number Diff line number Diff line change
@@ -86,6 +86,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let (_, field) = layout.non_1zst_field(self).unwrap();
self.unfold_transparent(field, may_unfold)
}
ty::Pat(base, _) => self.layout_of(*base).expect(
Copy link
Member

@RalfJung RalfJung Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be changed here if we also change the docs to guarantee that a pattern type is ABI-compatible with its inner type. And then the ABI compatibility tests should be extended to cover that (in the rustc test suite and the Miri test suite).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, considering it's the only way to guarantee that NonZeroU8 is still compatible with u8, I think the answer is yes, and that we already have at least tests for that. I'll add additional tests for bare pattern types

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a note on the tracking issue so that before stabilization, we decide whether this is true for all pattern types or only for some.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We unconditionally allow transmuting from pattern types to their base type. For any pattern. I don't see a design that could work differently

Copy link
Member

@RalfJung RalfJung Mar 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A can be transmuted to B" does not imply "A and B are ABI-compatible". For instance, i32 and u32 are not ABI-compatible. The code you changed here is about Miri ensuring ABI compatibility of caller and callee.

"if the layout of a pattern type could be computed, so can the layout of its base",
),
// Not a transparent type, no further unfolding.
_ => layout,
}
2 changes: 2 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -183,6 +183,8 @@
#![feature(no_core)]
#![feature(no_sanitize)]
#![feature(optimize_attribute)]
#![feature(pattern_type_macro)]
#![feature(pattern_types)]
#![feature(prelude_import)]
#![feature(repr_simd)]
#![feature(rustc_allow_const_fn_unstable)]
1 change: 1 addition & 0 deletions library/core/src/num/mod.rs
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ mod wrapping;

/// 100% perma-unstable
#[doc(hidden)]
#[cfg_attr(bootstrap, path = "niche_types_bootstrap.rs")]
pub mod niche_types;

#[stable(feature = "rust1", since = "1.0.0")]
103 changes: 39 additions & 64 deletions library/core/src/num/niche_types.rs
Original file line number Diff line number Diff line change
@@ -5,60 +5,48 @@
)]

use crate::cmp::Ordering;
use crate::fmt;
use crate::hash::{Hash, Hasher};
use crate::marker::StructuralPartialEq;
use crate::{fmt, pattern_type};

macro_rules! define_valid_range_type {
($(
$(#[$m:meta])*
$vis:vis struct $name:ident($int:ident as $uint:ident in $low:literal..=$high:literal);
$vis:vis struct $name:ident($int:ident is $pat:pat);
)+) => {$(
#[derive(Clone, Copy, Eq)]
#[derive(Clone, Copy)]
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start($low)]
#[rustc_layout_scalar_valid_range_end($high)]
$(#[$m])*
$vis struct $name($int);

const _: () = {
// With the `valid_range` attributes, it's always specified as unsigned
assert!(<$uint>::MIN == 0);
let ulow: $uint = $low;
let uhigh: $uint = $high;
assert!(ulow <= uhigh);

assert!(size_of::<$int>() == size_of::<$uint>());
};

$vis struct $name(pattern_type!($int is $pat));
impl $name {
#[inline]
pub const fn new(val: $int) -> Option<Self> {
if (val as $uint) >= ($low as $uint) && (val as $uint) <= ($high as $uint) {
#[allow(non_contiguous_range_endpoints)]
if let $pat = val {
// SAFETY: just checked the inclusive range
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// SAFETY: just checked the inclusive range
// SAFETY: just checked that the value matches the pattern

Some(unsafe { $name(val) })
Some(unsafe { $name(crate::mem::transmute(val)) })
} else {
None
}
}

/// Constructs an instance of this type from the underlying integer
/// primitive without checking whether its zero.
/// primitive without checking whether its valid.
///
/// # Safety
/// Immediate language UB if `val == 0`, as it violates the validity
/// Immediate language UB if `val` is not in the range of the pattern type,
/// as it violates the validity
/// invariant of this type.
#[inline]
pub const unsafe fn new_unchecked(val: $int) -> Self {
// SAFETY: Caller promised that `val` is non-zero.
unsafe { $name(val) }
// SAFETY: Caller promised that `val` is in the valid range.
unsafe { $name(crate::mem::transmute(val)) }
}

#[inline]
pub const fn as_inner(self) -> $int {
// SAFETY: This is a transparent wrapper, so unwrapping it is sound
// (Not using `.0` due to MCP#807.)
unsafe { crate::mem::transmute(self) }
// SAFETY: pattern types are always legal values of their base type
unsafe { crate::mem::transmute(self.0) }
}
}

@@ -67,6 +55,8 @@ macro_rules! define_valid_range_type {
// by <https://github.com/rust-lang/compiler-team/issues/807>.
impl StructuralPartialEq for $name {}

impl Eq for $name {}

impl PartialEq for $name {
#[inline]
fn eq(&self, other: &Self) -> bool {
@@ -104,7 +94,7 @@ macro_rules! define_valid_range_type {
}

define_valid_range_type! {
pub struct Nanoseconds(u32 as u32 in 0..=999_999_999);
pub struct Nanoseconds(u32 is 0..=999_999_999);
}

impl Nanoseconds {
@@ -119,45 +109,30 @@ impl Default for Nanoseconds {
}
}

define_valid_range_type! {
pub struct NonZeroU8Inner(u8 as u8 in 1..=0xff);
pub struct NonZeroU16Inner(u16 as u16 in 1..=0xff_ff);
pub struct NonZeroU32Inner(u32 as u32 in 1..=0xffff_ffff);
pub struct NonZeroU64Inner(u64 as u64 in 1..=0xffffffff_ffffffff);
pub struct NonZeroU128Inner(u128 as u128 in 1..=0xffffffffffffffff_ffffffffffffffff);

pub struct NonZeroI8Inner(i8 as u8 in 1..=0xff);
pub struct NonZeroI16Inner(i16 as u16 in 1..=0xff_ff);
pub struct NonZeroI32Inner(i32 as u32 in 1..=0xffff_ffff);
pub struct NonZeroI64Inner(i64 as u64 in 1..=0xffffffff_ffffffff);
pub struct NonZeroI128Inner(i128 as u128 in 1..=0xffffffffffffffff_ffffffffffffffff);
}

#[cfg(target_pointer_width = "16")]
define_valid_range_type! {
pub struct UsizeNoHighBit(usize as usize in 0..=0x7fff);
pub struct NonZeroUsizeInner(usize as usize in 1..=0xffff);
pub struct NonZeroIsizeInner(isize as usize in 1..=0xffff);
}
#[cfg(target_pointer_width = "32")]
define_valid_range_type! {
pub struct UsizeNoHighBit(usize as usize in 0..=0x7fff_ffff);
pub struct NonZeroUsizeInner(usize as usize in 1..=0xffff_ffff);
pub struct NonZeroIsizeInner(isize as usize in 1..=0xffff_ffff);
}
#[cfg(target_pointer_width = "64")]
define_valid_range_type! {
pub struct UsizeNoHighBit(usize as usize in 0..=0x7fff_ffff_ffff_ffff);
pub struct NonZeroUsizeInner(usize as usize in 1..=0xffff_ffff_ffff_ffff);
pub struct NonZeroIsizeInner(isize as usize in 1..=0xffff_ffff_ffff_ffff);
}
const HALF_USIZE: usize = usize::MAX >> 1;

define_valid_range_type! {
pub struct U32NotAllOnes(u32 as u32 in 0..=0xffff_fffe);
pub struct I32NotAllOnes(i32 as u32 in 0..=0xffff_fffe);

pub struct U64NotAllOnes(u64 as u64 in 0..=0xffff_ffff_ffff_fffe);
pub struct I64NotAllOnes(i64 as u64 in 0..=0xffff_ffff_ffff_fffe);
pub struct NonZeroU8Inner(u8 is 1..);
pub struct NonZeroU16Inner(u16 is 1..);
pub struct NonZeroU32Inner(u32 is 1..);
pub struct NonZeroU64Inner(u64 is 1..);
pub struct NonZeroU128Inner(u128 is 1..);

pub struct NonZeroI8Inner(i8 is ..0 | 1..);
pub struct NonZeroI16Inner(i16 is ..0 | 1..);
pub struct NonZeroI32Inner(i32 is ..0 | 1..);
pub struct NonZeroI64Inner(i64 is ..0 | 1..);
pub struct NonZeroI128Inner(i128 is ..0 | 1..);

pub struct UsizeNoHighBit(usize is 0..=HALF_USIZE);
pub struct NonZeroUsizeInner(usize is 1..);
pub struct NonZeroIsizeInner(isize is ..0 | 1..);

pub struct U32NotAllOnes(u32 is 0..u32::MAX);
pub struct I32NotAllOnes(i32 is ..-1 | 0..);

pub struct U64NotAllOnes(u64 is 0..u64::MAX);
pub struct I64NotAllOnes(i64 is ..-1 | 0..);
}

pub trait NotAllOnesHelper {
178 changes: 178 additions & 0 deletions library/core/src/num/niche_types_bootstrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#![unstable(
feature = "temporary_niche_types",
issue = "none",
reason = "for core, alloc, and std internals until pattern types are further along"
)]

use crate::cmp::Ordering;
use crate::fmt;
use crate::hash::{Hash, Hasher};
use crate::marker::StructuralPartialEq;

macro_rules! define_valid_range_type {
($(
$(#[$m:meta])*
$vis:vis struct $name:ident($int:ident as $uint:ident in $low:literal..=$high:literal);
)+) => {$(
#[derive(Clone, Copy, Eq)]
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start($low)]
#[rustc_layout_scalar_valid_range_end($high)]
$(#[$m])*
$vis struct $name($int);

const _: () = {
// With the `valid_range` attributes, it's always specified as unsigned
assert!(<$uint>::MIN == 0);
let ulow: $uint = $low;
let uhigh: $uint = $high;
assert!(ulow <= uhigh);

assert!(size_of::<$int>() == size_of::<$uint>());
};

impl $name {
#[inline]
pub const fn new(val: $int) -> Option<Self> {
if (val as $uint) >= ($low as $uint) && (val as $uint) <= ($high as $uint) {
// SAFETY: just checked the inclusive range
Some(unsafe { $name(val) })
} else {
None
}
}

/// Constructs an instance of this type from the underlying integer
/// primitive without checking whether its zero.
///
/// # Safety
/// Immediate language UB if `val == 0`, as it violates the validity
/// invariant of this type.
#[inline]
pub const unsafe fn new_unchecked(val: $int) -> Self {
// SAFETY: Caller promised that `val` is non-zero.
unsafe { $name(val) }
}

#[inline]
pub const fn as_inner(self) -> $int {
// SAFETY: This is a transparent wrapper, so unwrapping it is sound
// (Not using `.0` due to MCP#807.)
unsafe { crate::mem::transmute(self) }
}
}

// This is required to allow matching a constant. We don't get it from a derive
// because the derived `PartialEq` would do a field projection, which is banned
// by <https://github.com/rust-lang/compiler-team/issues/807>.
impl StructuralPartialEq for $name {}

impl PartialEq for $name {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_inner() == other.as_inner()
}
}

impl Ord for $name {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(&self.as_inner(), &other.as_inner())
}
}

impl PartialOrd for $name {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}

impl Hash for $name {
// Required method
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.as_inner(), state);
}
}

impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<$int as fmt::Debug>::fmt(&self.as_inner(), f)
}
}
)+};
}

define_valid_range_type! {
pub struct Nanoseconds(u32 as u32 in 0..=999_999_999);
}

impl Nanoseconds {
// SAFETY: 0 is within the valid range
pub const ZERO: Self = unsafe { Nanoseconds::new_unchecked(0) };
}

impl Default for Nanoseconds {
#[inline]
fn default() -> Self {
Self::ZERO
}
}

define_valid_range_type! {
pub struct NonZeroU8Inner(u8 as u8 in 1..=0xff);
pub struct NonZeroU16Inner(u16 as u16 in 1..=0xff_ff);
pub struct NonZeroU32Inner(u32 as u32 in 1..=0xffff_ffff);
pub struct NonZeroU64Inner(u64 as u64 in 1..=0xffffffff_ffffffff);
pub struct NonZeroU128Inner(u128 as u128 in 1..=0xffffffffffffffff_ffffffffffffffff);

pub struct NonZeroI8Inner(i8 as u8 in 1..=0xff);
pub struct NonZeroI16Inner(i16 as u16 in 1..=0xff_ff);
pub struct NonZeroI32Inner(i32 as u32 in 1..=0xffff_ffff);
pub struct NonZeroI64Inner(i64 as u64 in 1..=0xffffffff_ffffffff);
pub struct NonZeroI128Inner(i128 as u128 in 1..=0xffffffffffffffff_ffffffffffffffff);
}

#[cfg(target_pointer_width = "16")]
define_valid_range_type! {
pub struct UsizeNoHighBit(usize as usize in 0..=0x7fff);
pub struct NonZeroUsizeInner(usize as usize in 1..=0xffff);
pub struct NonZeroIsizeInner(isize as usize in 1..=0xffff);
}
#[cfg(target_pointer_width = "32")]
define_valid_range_type! {
pub struct UsizeNoHighBit(usize as usize in 0..=0x7fff_ffff);
pub struct NonZeroUsizeInner(usize as usize in 1..=0xffff_ffff);
pub struct NonZeroIsizeInner(isize as usize in 1..=0xffff_ffff);
}
#[cfg(target_pointer_width = "64")]
define_valid_range_type! {
pub struct UsizeNoHighBit(usize as usize in 0..=0x7fff_ffff_ffff_ffff);
pub struct NonZeroUsizeInner(usize as usize in 1..=0xffff_ffff_ffff_ffff);
pub struct NonZeroIsizeInner(isize as usize in 1..=0xffff_ffff_ffff_ffff);
}

define_valid_range_type! {
pub struct U32NotAllOnes(u32 as u32 in 0..=0xffff_fffe);
pub struct I32NotAllOnes(i32 as u32 in 0..=0xffff_fffe);

pub struct U64NotAllOnes(u64 as u64 in 0..=0xffff_ffff_ffff_fffe);
pub struct I64NotAllOnes(i64 as u64 in 0..=0xffff_ffff_ffff_fffe);
}

pub trait NotAllOnesHelper {
type Type;
}
pub type NotAllOnes<T> = <T as NotAllOnesHelper>::Type;
impl NotAllOnesHelper for u32 {
type Type = U32NotAllOnes;
}
impl NotAllOnesHelper for i32 {
type Type = I32NotAllOnes;
}
impl NotAllOnesHelper for u64 {
type Type = U64NotAllOnes;
}
impl NotAllOnesHelper for i64 {
type Type = I64NotAllOnes;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
error: Undefined Behavior: constructing invalid value at .0.0: encountered 0, but expected something greater or equal to 1
--> tests/fail/validity/cast_fn_ptr_invalid_callee_ret.rs:LL:CC
|
LL | f();
| ^^^ constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
| ^^^ constructing invalid value at .0.0: encountered 0, but expected something greater or equal to 1
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Loading
Loading