Skip to content

Commit a1a130b

Browse files
authored
Improve Handling of NotNaN Types (#851)
In Rust 1.81 it has become essential to handle `NaN` properly when sorting. An incorrect implementation of `Ord` now leads to panics when sorting. In order to prevent this, we now try to prove that the values are never `NaN` to begin with.
1 parent d848fb7 commit a1a130b

File tree

2 files changed

+114
-26
lines changed

2 files changed

+114
-26
lines changed

src/run/editor/fuzzy_list.rs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{platform::prelude::*, util::not_nan::NotNaN};
1+
use crate::{
2+
platform::prelude::*,
3+
util::not_nan::{NotNaN, PositiveNotNaN, PositiveNotNaNNotZero},
4+
};
25
use alloc::collections::BinaryHeap;
36

47
/// With a Fuzzy List, you can implement a fuzzy searching algorithm. The list
@@ -50,7 +53,7 @@ impl FuzzyList {
5053
if heap.len() >= max {
5154
heap.pop();
5255
}
53-
heap.push((NotNaN(-score), element));
56+
heap.push((-score, element));
5457
}
5558
}
5659

@@ -60,7 +63,7 @@ impl FuzzyList {
6063
if heap.len() >= max {
6164
heap.pop();
6265
}
63-
heap.push((NotNaN(-score), element));
66+
heap.push((-score, element));
6467
}
6568
}
6669
} else {
@@ -69,7 +72,7 @@ impl FuzzyList {
6972
if heap.len() >= max {
7073
heap.pop();
7174
}
72-
heap.push((NotNaN(-score), element));
75+
heap.push((-score, element));
7376
}
7477
}
7578
}
@@ -78,53 +81,53 @@ impl FuzzyList {
7881
}
7982
}
8083

81-
fn match_against(pattern: &str, text: &str) -> Option<f64> {
82-
let (mut current_score, mut total_score) = (0.0, 0.0);
84+
fn match_against(pattern: &str, text: &str) -> Option<NotNaN> {
85+
let [mut current_score, mut total_score] = [PositiveNotNaN::ZERO; 2];
8386
let mut pattern_chars = pattern.chars();
8487
let mut pattern_char = pattern_chars.next();
8588

8689
for c in text.chars() {
8790
if pattern_char == Some(c) {
8891
pattern_char = pattern_chars.next();
89-
current_score = 1.0 + 2.0 * current_score;
92+
current_score = current_score * PositiveNotNaNNotZero::TWO + PositiveNotNaN::ONE;
9093
} else {
91-
current_score = 0.0;
94+
current_score = PositiveNotNaN::ZERO;
9295
}
93-
total_score += current_score;
96+
total_score = total_score + current_score;
9497
}
9598

9699
if pattern_char.is_none() {
97-
if pattern == text {
98-
Some(f64::INFINITY)
100+
Some(if pattern == text {
101+
NotNaN::INFINITY
99102
} else {
100-
Some(total_score)
101-
}
103+
total_score.into()
104+
})
102105
} else {
103106
None
104107
}
105108
}
106109

107-
fn match_against_ascii(pattern: &str, text: &str) -> Option<f64> {
108-
let (mut current_score, mut total_score) = (0.0, 0.0);
110+
fn match_against_ascii(pattern: &str, text: &str) -> Option<NotNaN> {
111+
let [mut current_score, mut total_score] = [PositiveNotNaN::ZERO; 2];
109112
let mut pattern_chars = pattern.bytes();
110113
let mut pattern_char = pattern_chars.next();
111114

112115
for c in text.bytes() {
113116
if pattern_char == Some(c) {
114117
pattern_char = pattern_chars.next();
115-
current_score = 1.0 + 2.0 * current_score;
118+
current_score = current_score * PositiveNotNaNNotZero::TWO + PositiveNotNaN::ONE;
116119
} else {
117-
current_score = 0.0;
120+
current_score = PositiveNotNaN::ZERO;
118121
}
119-
total_score += current_score;
122+
total_score = total_score + current_score;
120123
}
121124

122125
if pattern_char.is_none() {
123-
if pattern == text {
124-
Some(f64::INFINITY)
126+
Some(if pattern == text {
127+
NotNaN::INFINITY
125128
} else {
126-
Some(total_score)
127-
}
129+
total_score.into()
130+
})
128131
} else {
129132
None
130133
}

src/util/not_nan.rs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,95 @@
1-
use core::cmp::Ordering;
1+
use core::{
2+
cmp::Ordering,
3+
ops::{Add, Mul, Neg},
4+
};
25

6+
/// Never `NaN`, but can be any other [`f64`].
7+
#[derive(Copy, Clone)]
38
#[repr(transparent)]
4-
pub struct NotNaN(pub f64);
9+
pub struct NotNaN(f64);
10+
11+
impl NotNaN {
12+
pub const INFINITY: Self = Self(f64::INFINITY);
13+
}
14+
15+
/// Never `NaN` and always has a positive sign. May be positive infinity.
16+
#[derive(Copy, Clone)]
17+
#[repr(transparent)]
18+
pub struct PositiveNotNaN(f64);
19+
20+
/// Never `NaN` and is always larger than (and not equal to) 0. May be positive
21+
/// infinity.
22+
#[derive(Copy, Clone)]
23+
#[repr(transparent)]
24+
pub struct PositiveNotNaNNotZero(f64);
25+
26+
impl From<PositiveNotNaNNotZero> for PositiveNotNaN {
27+
fn from(value: PositiveNotNaNNotZero) -> Self {
28+
Self(value.0)
29+
}
30+
}
31+
32+
impl From<PositiveNotNaN> for NotNaN {
33+
fn from(value: PositiveNotNaN) -> Self {
34+
Self(value.0)
35+
}
36+
}
37+
38+
impl PositiveNotNaN {
39+
pub const ZERO: Self = Self(0.0);
40+
pub const ONE: Self = Self(1.0);
41+
}
42+
43+
impl PositiveNotNaNNotZero {
44+
pub const TWO: Self = Self(2.0);
45+
}
46+
47+
// The following cases result in NaN:
48+
// - `NaN * x`: We ensure neither input is NaN.
49+
// - `0 * Infinity`: We handle this by ensuring at least one side is not 0.
50+
//
51+
// IEEE Std 754-2008 7.2:
52+
// > a) any general-computational or signaling-computational operation on a
53+
// > signaling NaN (see 6.2), except for some conversions (see 5.12)
54+
// > b) multiplication: multiplication(0, ∞) or multiplication(∞, 0)
55+
impl Mul<PositiveNotNaNNotZero> for PositiveNotNaN {
56+
type Output = Self;
57+
58+
fn mul(self, rhs: PositiveNotNaNNotZero) -> Self {
59+
Self(self.0 * rhs.0)
60+
}
61+
}
62+
63+
// The following cases result in NaN:
64+
// - `NaN + x`: We ensure neither input is NaN.
65+
// - `Infinity + -Infinity`: We handle this by ensuring the inputs are
66+
// positive.
67+
//
68+
// IEEE Std 754-2008 7.2:
69+
// > a) any general-computational or signaling-computational operation on a
70+
// > signaling NaN (see 6.2), except for some conversions (see 5.12)
71+
// > b) addition or subtraction or fusedMultiplyAdd: magnitude subtraction of infinities, such as:
72+
// > addition(+∞, −∞)
73+
impl Add for PositiveNotNaN {
74+
type Output = Self;
75+
76+
fn add(self, rhs: Self) -> Self {
77+
Self(self.0 + rhs.0)
78+
}
79+
}
80+
81+
// Negating a non-NaN value results in a non-NaN value.
82+
impl Neg for NotNaN {
83+
type Output = Self;
84+
85+
fn neg(self) -> Self {
86+
Self(-self.0)
87+
}
88+
}
589

690
impl PartialEq for NotNaN {
791
fn eq(&self, other: &Self) -> bool {
8-
self.cmp(other) == Ordering::Equal
92+
self.0 == other.0
993
}
1094
}
1195

@@ -19,6 +103,7 @@ impl PartialOrd for NotNaN {
19103

20104
impl Ord for NotNaN {
21105
fn cmp(&self, other: &Self) -> Ordering {
22-
self.0.partial_cmp(&other.0).unwrap_or(Ordering::Equal)
106+
// SAFETY: The value is guaranteed to not be NaN. See above.
107+
unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }
23108
}
24109
}

0 commit comments

Comments
 (0)