Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ jobs:
- name: Check
run: cargo hack check --manifest-path cold-string/Cargo.toml --verbose --feature-powerset --version-range 1.60..
- name: Test No Exposed Provenance
run: cargo +1.74 hack test --manifest-path cold-string/Cargo.toml --verbose --release --feature-powerset
run: cargo +1.74 hack test --manifest-path cold-string/Cargo.toml --verbose --feature-powerset
- name: Tests
run: cargo hack test --manifest-path cold-string/Cargo.toml --verbose --release --feature-powerset
run: cargo hack test --manifest-path cold-string/Cargo.toml --verbose --feature-powerset
- name: Install nightly + Miri
run: |
rustup toolchain install nightly
rustup component add miri --toolchain nightly
- name: Run Miri
run: |
cargo +nightly miri test --manifest-path cold-string/Cargo.toml
- name: Run Miri Unknown
run: |
cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target mips-unknown-linux-gnu
- name: Miri 64 bit LE
run: cargo +nightly miri test --manifest-path cold-string/Cargo.toml
- name: Miri 64 bit BE
run: cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target powerpc64-unknown-linux-gnu
- name: Miri 32 bit LE
run: cargo +nightly miri test --target i686-unknown-linux-gnu
- name: Miri 32 bit BE
run: cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target mips-unknown-linux-gnu
113 changes: 97 additions & 16 deletions cold-string/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use core::{
mem,
ops::Deref,
ptr, slice, str,
cmp::Ordering,
};

mod vint;
Expand Down Expand Up @@ -346,6 +347,25 @@ impl ColdString {
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
}

/// Returns `true` if this `ColdString` has a length of zero, and `false` otherwise.
///
/// # Examples
///
/// ```
/// let v = cold_string::ColdString::new("");
/// assert!(v.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}

impl Default for ColdString {
fn default() -> Self {
Self::new_inline("")
}
}

impl Deref for ColdString {
Expand Down Expand Up @@ -482,6 +502,18 @@ impl AsRef<[u8]> for ColdString {
}
}

impl Ord for ColdString {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}

impl PartialOrd for ColdString {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}

impl alloc::str::FromStr for ColdString {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<ColdString, Self::Err> {
Expand Down Expand Up @@ -542,6 +574,33 @@ mod tests {
assert_eq!(mem::align_of::<Foo>(), 1);
}

#[test]
fn test_default() {
assert!(ColdString::default().is_empty());
assert_eq!(ColdString::default().len(), 0);
assert_eq!(ColdString::default(), "");
assert_eq!(ColdString::default(), ColdString::new(""));
}

fn assert_correct(s: &str) {
let cs = ColdString::new(s);
assert_eq!(s.len() <= mem::size_of::<usize>(), cs.is_inline());
assert_eq!(cs.len(), s.len());
assert_eq!(cs.as_bytes(), s.as_bytes());
assert_eq!(cs.as_str(), s);
assert_eq!(cs.clone(), cs);
let bh = DefaultHashBuilder::new();
let mut hasher1 = bh.build_hasher();
cs.hash(&mut hasher1);
let mut hasher2 = bh.build_hasher();
cs.clone().hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_eq!(cs, s);
assert_eq!(s, cs);
assert_eq!(cs, *s);
assert_eq!(*s, cs);
}

#[test]
fn it_works() {
for s in [
Expand Down Expand Up @@ -576,22 +635,44 @@ mod tests {
"AaAa0 ® ",
str::from_utf8(&[240, 158, 186, 128, 240, 145, 143, 151]).unwrap(),
] {
let cs = ColdString::new(s);
assert_eq!(s.len() <= mem::size_of::<usize>(), cs.is_inline());
assert_eq!(cs.len(), s.len());
assert_eq!(cs.as_bytes(), s.as_bytes());
assert_eq!(cs.as_str(), s);
assert_eq!(cs.clone(), cs);
let bh = DefaultHashBuilder::new();
let mut hasher1 = bh.build_hasher();
cs.hash(&mut hasher1);
let mut hasher2 = bh.build_hasher();
cs.clone().hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_eq!(cs, s);
assert_eq!(s, cs);
assert_eq!(cs, *s);
assert_eq!(*s, cs);
assert_correct(s);
}
}

fn char_from_leading_byte(b: u8) -> Option<char> {
match b {
0x00..=0x7F => Some(b as char),
0xC2..=0xDF => str::from_utf8(&[b, 0x91]).unwrap().chars().next(),
0xE0 => str::from_utf8(&[b, 0xA0, 0x91]).unwrap().chars().next(),
0xE1..=0xEC | 0xEE..=0xEF => str::from_utf8(&[b, 0x91, 0xA5]).unwrap().chars().next(),
0xED => str::from_utf8(&[b, 0x80, 0x91]).unwrap().chars().next(),
0xF0 => str::from_utf8(&[b, 0x90, 0x91, 0xA5]).unwrap().chars().next(),
0xF1..=0xF3 => str::from_utf8(&[b, 0x91, 0xA5, 0x82]).unwrap().chars().next(),
0xF4 => str::from_utf8(&[b, 0x80, 0x91, 0x82]).unwrap().chars().next(),
_ => None,
}
}

#[test]
fn test_edges() {
let width = mem::size_of::<usize>();
for len in [width - 1, width, width + 1] {
for first_byte in 0u8..=255 {
let first_char = match char_from_leading_byte(first_byte) {
Some(c) => c,
None => continue,
};

let mut s = String::with_capacity(len);
s.push(first_char);

while s.len() < len {
let c = core::char::from_digit((len - s.len()) as u32, 10).unwrap();
s.push(c);
}

assert_correct(&s);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion cold-string/tests/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn proptest_config() -> ProptestConfig {

#[cfg(not(miri))]
fn proptest_config() -> ProptestConfig {
ProptestConfig::with_cases(131072)
ProptestConfig::with_cases(65536)
}

proptest! {
Expand Down