Skip to content

Commit

Permalink
Auto merge of rust-lang#116284 - RalfJung:no-nan-match, r=<try>
Browse files Browse the repository at this point in the history
make matching on NaN a hard error

These arms would never be hit anyway, so the pattern makes little sense. We have had a future-compat lint against float matches in general for a *long* time, so I hope we can get away with immediately making this a hard error.
  • Loading branch information
bors committed Sep 30, 2023
2 parents 9136560 + 92ddac2 commit a3e4be4
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 85 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/interpret/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ impl<'tcx, Prov: Provenance> Scalar<Prov> {
#[inline]
pub fn to_float<F: Float>(self) -> InterpResult<'tcx, F> {
// Going through `to_uint` 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]
Expand Down
71 changes: 44 additions & 27 deletions compiler/rustc_middle/src/ty/consts/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// Fails if the size of the `ScalarInt` is not equal to `size` and returns the
/// `ScalarInt`s size in that case.
Expand All @@ -262,56 +257,61 @@ impl ScalarInt {
self.to_bits(size)
}

// 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`
// value of the `ScalarInt` in that case.
#[inline]
pub fn try_to_bool(self) -> Result<bool, Size> {
match self.try_to_u8()? {
0 => Ok(false),
1 => Ok(true),
_ => Err(self.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.to_bits(Size::from_bits(8)).map(|v| u8::try_from(v).unwrap())
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.to_bits(Size::from_bits(16)).map(|v| u16::try_from(v).unwrap())
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.to_bits(Size::from_bits(32)).map(|v| u32::try_from(v).unwrap())
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.to_bits(Size::from_bits(64)).map(|v| u64::try_from(v).unwrap())
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.to_bits(Size::from_bits(128))
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`
// 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.
#[inline]
pub fn try_to_bool(self) -> Result<bool, Size> {
match self.try_to_u8()? {
0 => Ok(false),
1 => Ok(true),
_ => Err(self.size()),
}
}

/// Tries to convert the `ScalarInt` to a signed integer of the given size.
Expand Down Expand Up @@ -357,6 +357,27 @@ impl ScalarInt {
pub fn try_to_i128(self) -> Result<i128, Size> {
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 {
Expand Down Expand Up @@ -399,11 +420,7 @@ impl TryFrom<ScalarInt> for bool {
type Error = Size;
#[inline]
fn try_from(int: ScalarInt) -> Result<Self, Size> {
int.to_bits(Size::from_bytes(1)).and_then(|u| match u {
0 => Ok(false),
1 => Ok(true),
_ => Err(Size::from_bytes(1)),
})
int.try_to_bool()
}
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ 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
.label = mutation of layout constrained field
mir_build_nan_pattern = cannot use NaN in patterns
mir_build_non_const_path = runtime values cannot be referenced in patterns
mir_build_non_exhaustive_match_all_arms_guarded =
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_mir_build/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,13 @@ pub struct UnsizedPattern<'tcx> {
pub non_sm_ty: Ty<'tcx>,
}

#[derive(Diagnostic)]
#[diag(mir_build_nan_pattern)]
pub struct NaNPattern {
#[primary_span]
pub span: Span,
}

#[derive(LintDiagnostic)]
#[diag(mir_build_float_pattern)]
pub struct FloatPattern;
Expand Down
33 changes: 22 additions & 11 deletions compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rustc_apfloat::Float;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_index::Idx;
Expand All @@ -16,7 +17,7 @@ use std::cell::Cell;

use super::PatCtxt;
use crate::errors::{
FloatPattern, IndirectStructuralMatch, InvalidPattern, NonPartialEqMatch,
FloatPattern, IndirectStructuralMatch, InvalidPattern, NaNPattern, NonPartialEqMatch,
NontrivialStructuralMatch, PointerPattern, TypeNotStructural, UnionPattern, UnsizedPattern,
};

Expand Down Expand Up @@ -310,16 +311,6 @@ impl<'tcx> ConstToPat<'tcx> {
let param_env = self.param_env;

let kind = match ty.kind() {
ty::Float(_) => {
self.saw_const_match_lint.set(true);
tcx.emit_spanned_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,
// causing the pattern match code to treat it opaquely.
// FIXME: This code doesn't emit errors itself, the caller emits the errors.
Expand Down Expand Up @@ -469,6 +460,26 @@ 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 {
self.saw_const_match_error.set(true);
tcx.sess.emit_err(NaNPattern { span });
} else {
self.saw_const_match_lint.set(true);
tcx.emit_spanned_lint(
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
id,
span,
FloatPattern,
);
}
return Err(FallbackToOpaqueConst);
}
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) => {
PatKind::Constant { value: mir::Const::Ty(ty::Const::new_value(tcx, cv, ty)) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
_ => {},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:11:9
|
LL | NAN => {},
| ^^^

error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:16:10
|
LL | [NAN, _] => {},
| ^^^

error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:22:9
|
LL | NAN..=1.0 => {},
| ^^^

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 => {},
| ^^^

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.. => {},
| ^^^

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 => {},
| ^^^

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`.

This file was deleted.

This file was deleted.

0 comments on commit a3e4be4

Please sign in to comment.