make matching on NaN a hard error

This commit is contained in:
Ralf Jung 2023-09-30 08:15:34 +02:00
parent 69db514ed9
commit cda3588572
9 changed files with 220 additions and 109 deletions

View File

@ -418,8 +418,8 @@ impl<'tcx, Prov: Provenance> Scalar<Prov> {
#[inline] #[inline]
pub fn to_float<F: Float>(self) -> InterpResult<'tcx, F> { pub fn to_float<F: Float>(self) -> InterpResult<'tcx, F> {
// Going through `to_uint` to check size and truncation. // Going through `to_bits` to check size and truncation.
Ok(F::from_bits(self.to_uint(Size::from_bits(F::BITS))?)) Ok(F::from_bits(self.to_bits(Size::from_bits(F::BITS))?))
} }
#[inline] #[inline]

View File

@ -249,11 +249,6 @@ impl ScalarInt {
} }
} }
#[inline]
pub fn try_to_target_usize(&self, tcx: TyCtxt<'_>) -> Result<u64, Size> {
Ok(self.to_bits(tcx.data_layout.pointer_size)? as u64)
}
/// Tries to convert the `ScalarInt` to an unsigned integer of the given size. /// Tries to convert the `ScalarInt` to an unsigned integer of the given size.
/// Fails if the size of the `ScalarInt` is not equal to `size` and returns the /// Fails if the size of the `ScalarInt` is not equal to `size` and returns the
/// `ScalarInt`s size in that case. /// `ScalarInt`s size in that case.
@ -262,6 +257,51 @@ impl ScalarInt {
self.to_bits(size) self.to_bits(size)
} }
// Tries to convert the `ScalarInt` to `u8`. Fails if the `size` of the `ScalarInt`
// in not equal to `Size { raw: 1 }` and returns the `size` value of the `ScalarInt` in
// that case.
#[inline]
pub fn try_to_u8(self) -> Result<u8, Size> {
self.try_to_uint(Size::from_bits(8)).map(|v| u8::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u16`. Fails if the size of the `ScalarInt`
/// in not equal to `Size { raw: 2 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u16(self) -> Result<u16, Size> {
self.try_to_uint(Size::from_bits(16)).map(|v| u16::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u32`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 4 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u32(self) -> Result<u32, Size> {
self.try_to_uint(Size::from_bits(32)).map(|v| u32::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u64`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 8 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u64(self) -> Result<u64, Size> {
self.try_to_uint(Size::from_bits(64)).map(|v| u64::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u128`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 16 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u128(self) -> Result<u128, Size> {
self.try_to_uint(Size::from_bits(128))
}
#[inline]
pub fn try_to_target_usize(&self, tcx: TyCtxt<'_>) -> Result<u64, Size> {
self.try_to_uint(tcx.data_layout.pointer_size).map(|v| u64::try_from(v).unwrap())
}
// Tries to convert the `ScalarInt` to `bool`. Fails if the `size` of the `ScalarInt` // Tries to convert the `ScalarInt` to `bool`. Fails if the `size` of the `ScalarInt`
// in not equal to `Size { raw: 1 }` or if the value is not 0 or 1 and returns the `size` // in not equal to `Size { raw: 1 }` or if the value is not 0 or 1 and returns the `size`
// value of the `ScalarInt` in that case. // value of the `ScalarInt` in that case.
@ -274,46 +314,6 @@ impl ScalarInt {
} }
} }
// Tries to convert the `ScalarInt` to `u8`. Fails if the `size` of the `ScalarInt`
// in not equal to `Size { raw: 1 }` and returns the `size` value of the `ScalarInt` in
// that case.
#[inline]
pub fn try_to_u8(self) -> Result<u8, Size> {
self.to_bits(Size::from_bits(8)).map(|v| u8::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u16`. Fails if the size of the `ScalarInt`
/// in not equal to `Size { raw: 2 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u16(self) -> Result<u16, Size> {
self.to_bits(Size::from_bits(16)).map(|v| u16::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u32`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 4 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u32(self) -> Result<u32, Size> {
self.to_bits(Size::from_bits(32)).map(|v| u32::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u64`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 8 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u64(self) -> Result<u64, Size> {
self.to_bits(Size::from_bits(64)).map(|v| u64::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to `u128`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 16 }` and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u128(self) -> Result<u128, Size> {
self.to_bits(Size::from_bits(128))
}
/// Tries to convert the `ScalarInt` to a signed integer of the given size. /// Tries to convert the `ScalarInt` to a signed integer of the given size.
/// Fails if the size of the `ScalarInt` is not equal to `size` and returns the /// Fails if the size of the `ScalarInt` is not equal to `size` and returns the
/// `ScalarInt`s size in that case. /// `ScalarInt`s size in that case.
@ -357,6 +357,27 @@ impl ScalarInt {
pub fn try_to_i128(self) -> Result<i128, Size> { pub fn try_to_i128(self) -> Result<i128, Size> {
self.try_to_int(Size::from_bits(128)) self.try_to_int(Size::from_bits(128))
} }
#[inline]
pub fn try_to_target_isize(&self, tcx: TyCtxt<'_>) -> Result<i64, Size> {
self.try_to_int(tcx.data_layout.pointer_size).map(|v| i64::try_from(v).unwrap())
}
#[inline]
pub fn try_to_float<F: Float>(self) -> Result<F, Size> {
// Going through `to_uint` to check size and truncation.
Ok(F::from_bits(self.to_bits(Size::from_bits(F::BITS))?))
}
#[inline]
pub fn try_to_f32(self) -> Result<Single, Size> {
self.try_to_float()
}
#[inline]
pub fn try_to_f64(self) -> Result<Double, Size> {
self.try_to_float()
}
} }
macro_rules! from { macro_rules! from {
@ -399,11 +420,7 @@ impl TryFrom<ScalarInt> for bool {
type Error = Size; type Error = Size;
#[inline] #[inline]
fn try_from(int: ScalarInt) -> Result<Self, Size> { fn try_from(int: ScalarInt) -> Result<Self, Size> {
int.to_bits(Size::from_bytes(1)).and_then(|u| match u { int.try_to_bool()
0 => Ok(false),
1 => Ok(true),
_ => Err(Size::from_bytes(1)),
})
} }
} }

View File

@ -232,6 +232,10 @@ mir_build_mutation_of_layout_constrained_field_requires_unsafe_unsafe_op_in_unsa
.note = mutating layout constrained fields cannot statically be checked for valid values .note = mutating layout constrained fields cannot statically be checked for valid values
.label = mutation of layout constrained field .label = mutation of layout constrained field
mir_build_nan_pattern = cannot use NaN in patterns
.note = NaNs compare inequal to everything, even themselves, so this pattern would never match
.help = try using the `is_nan` method instead
mir_build_non_const_path = runtime values cannot be referenced in patterns mir_build_non_const_path = runtime values cannot be referenced in patterns
mir_build_non_empty_never_pattern = mir_build_non_empty_never_pattern =

View File

@ -780,6 +780,15 @@ pub struct UnsizedPattern<'tcx> {
pub non_sm_ty: Ty<'tcx>, pub non_sm_ty: Ty<'tcx>,
} }
#[derive(Diagnostic)]
#[diag(mir_build_nan_pattern)]
#[note]
#[help]
pub struct NaNPattern {
#[primary_span]
pub span: Span,
}
#[derive(LintDiagnostic)] #[derive(LintDiagnostic)]
#[diag(mir_build_float_pattern)] #[diag(mir_build_float_pattern)]
pub struct FloatPattern; pub struct FloatPattern;

View File

@ -1,3 +1,4 @@
use rustc_apfloat::Float;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_index::Idx; use rustc_index::Idx;
@ -16,7 +17,7 @@ use std::cell::Cell;
use super::PatCtxt; use super::PatCtxt;
use crate::errors::{ use crate::errors::{
FloatPattern, IndirectStructuralMatch, InvalidPattern, NonPartialEqMatch, FloatPattern, IndirectStructuralMatch, InvalidPattern, NaNPattern, NonPartialEqMatch,
NontrivialStructuralMatch, PointerPattern, TypeNotStructural, UnionPattern, UnsizedPattern, NontrivialStructuralMatch, PointerPattern, TypeNotStructural, UnionPattern, UnsizedPattern,
}; };
@ -317,16 +318,6 @@ impl<'tcx> ConstToPat<'tcx> {
let param_env = self.param_env; let param_env = self.param_env;
let kind = match ty.kind() { let kind = match ty.kind() {
ty::Float(_) => {
self.saw_const_match_lint.set(true);
tcx.emit_node_span_lint(
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
id,
span,
FloatPattern,
);
return Err(FallbackToOpaqueConst);
}
// If the type is not structurally comparable, just emit the constant directly, // If the type is not structurally comparable, just emit the constant directly,
// causing the pattern match code to treat it opaquely. // causing the pattern match code to treat it opaquely.
// FIXME: This code doesn't emit errors itself, the caller emits the errors. // FIXME: This code doesn't emit errors itself, the caller emits the errors.
@ -486,6 +477,28 @@ impl<'tcx> ConstToPat<'tcx> {
} }
} }
}, },
ty::Float(flt) => {
let v = cv.unwrap_leaf();
let is_nan = match flt {
ty::FloatTy::F32 => v.try_to_f32().unwrap().is_nan(),
ty::FloatTy::F64 => v.try_to_f64().unwrap().is_nan(),
};
if is_nan {
// NaNs are not ever equal to anything so they make no sense as patterns.
// Also see <https://github.com/rust-lang/rfcs/pull/3535>.
let e = tcx.dcx().emit_err(NaNPattern { span });
self.saw_const_match_error.set(Some(e));
} else {
self.saw_const_match_lint.set(true);
tcx.emit_node_span_lint(
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
id,
span,
FloatPattern,
);
}
return Err(FallbackToOpaqueConst);
}
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::RawPtr(..) => { ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::RawPtr(..) => {
// The raw pointers we see here have been "vetted" by valtree construction to be // The raw pointers we see here have been "vetted" by valtree construction to be
// just integers, so we simply allow them. // just integers, so we simply allow them.

View File

@ -0,0 +1,32 @@
// Matching against NaN should result in an error
#![feature(exclusive_range_pattern)]
#![allow(unused)]
#![allow(illegal_floating_point_literal_pattern)]
const NAN: f64 = f64::NAN;
fn main() {
let x = NAN;
match x {
NAN => {}, //~ ERROR cannot use NaN in patterns
_ => {},
};
match [x, 1.0] {
[NAN, _] => {}, //~ ERROR cannot use NaN in patterns
_ => {},
};
// Also cover range patterns
match x {
NAN..=1.0 => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than or equal to upper
-1.0..=NAN => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than or equal to upper
NAN.. => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than or equal to upper
..NAN => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than upper
_ => {},
};
}

View File

@ -0,0 +1,82 @@
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:11:9
|
LL | NAN => {},
| ^^^
|
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:16:10
|
LL | [NAN, _] => {},
| ^^^
|
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:22:9
|
LL | NAN..=1.0 => {},
| ^^^
|
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error[E0030]: lower range bound must be less than or equal to upper
--> $DIR/issue-6804-nan-match.rs:22:9
|
LL | NAN..=1.0 => {},
| ^^^^^^^^^ lower bound larger than upper bound
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:24:16
|
LL | -1.0..=NAN => {},
| ^^^
|
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error[E0030]: lower range bound must be less than or equal to upper
--> $DIR/issue-6804-nan-match.rs:24:9
|
LL | -1.0..=NAN => {},
| ^^^^^^^^^^ lower bound larger than upper bound
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:26:9
|
LL | NAN.. => {},
| ^^^
|
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error[E0030]: lower range bound must be less than or equal to upper
--> $DIR/issue-6804-nan-match.rs:26:9
|
LL | NAN.. => {},
| ^^^^^ lower bound larger than upper bound
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:28:11
|
LL | ..NAN => {},
| ^^^
|
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error[E0579]: lower range bound must be less than upper
--> $DIR/issue-6804-nan-match.rs:28:9
|
LL | ..NAN => {},
| ^^^^^
error: aborting due to 10 previous errors
Some errors have detailed explanations: E0030, E0579.
For more information about an error, try `rustc --explain E0030`.

View File

@ -1,21 +0,0 @@
// Matching against NaN should result in a warning
#![allow(unused)]
#![deny(illegal_floating_point_literal_pattern)]
const NAN: f64 = f64::NAN;
fn main() {
let x = NAN;
match x {
NAN => {}, //~ ERROR floating-point types cannot be used
//~| WARN this was previously accepted by the compiler but is being phased out
_ => {},
};
match [x, 1.0] {
[NAN, _] => {}, //~ ERROR floating-point types cannot be used
//~| WARN this was previously accepted by the compiler but is being phased out
_ => {},
};
}

View File

@ -1,25 +0,0 @@
error: floating-point types cannot be used in patterns
--> $DIR/issue-6804.rs:11:9
|
LL | NAN => {},
| ^^^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #41620 <https://github.com/rust-lang/rust/issues/41620>
note: the lint level is defined here
--> $DIR/issue-6804.rs:4:9
|
LL | #![deny(illegal_floating_point_literal_pattern)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: floating-point types cannot be used in patterns
--> $DIR/issue-6804.rs:17:10
|
LL | [NAN, _] => {},
| ^^^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #41620 <https://github.com/rust-lang/rust/issues/41620>
error: aborting due to 2 previous errors