Add StrExt, to_lowercase_smolstr & friends
diff --git a/src/lib.rs b/src/lib.rs
index e403233..79a22b6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -545,6 +545,61 @@
     fn to_smolstr(&self) -> SmolStr;
 }
 
+/// [`str`] methods producing [`SmolStr`]s.
+pub trait StrExt: private::Sealed {
+    /// Returns the lowercase equivalent of this string slice as a new [`SmolStr`],
+    /// potentially without allocating.
+    ///
+    /// See [`str::to_lowercase`].
+    fn to_lowercase_smolstr(&self) -> SmolStr;
+
+    /// Returns the uppercase equivalent of this string slice as a new [`SmolStr`],
+    /// potentially without allocating.
+    ///
+    /// See [`str::to_uppercase`].
+    fn to_uppercase_smolstr(&self) -> SmolStr;
+
+    /// Returns the ASCII lowercase equivalent of this string slice as a new [`SmolStr`],
+    /// potentially without allocating.
+    ///
+    /// See [`str::to_ascii_lowercase`].
+    fn to_ascii_lowercase_smolstr(&self) -> SmolStr;
+
+    /// Returns the ASCII uppercase equivalent of this string slice as a new [`SmolStr`],
+    /// potentially without allocating.
+    ///
+    /// See [`str::to_ascii_uppercase`].
+    fn to_ascii_uppercase_smolstr(&self) -> SmolStr;
+}
+
+impl StrExt for str {
+    #[inline]
+    fn to_lowercase_smolstr(&self) -> SmolStr {
+        SmolStr::from_char_iter(self.chars().flat_map(|c| c.to_lowercase()))
+    }
+
+    #[inline]
+    fn to_uppercase_smolstr(&self) -> SmolStr {
+        SmolStr::from_char_iter(self.chars().flat_map(|c| c.to_uppercase()))
+    }
+
+    #[inline]
+    fn to_ascii_lowercase_smolstr(&self) -> SmolStr {
+        SmolStr::from_char_iter(self.chars().map(|c| c.to_ascii_lowercase()))
+    }
+
+    #[inline]
+    fn to_ascii_uppercase_smolstr(&self) -> SmolStr {
+        SmolStr::from_char_iter(self.chars().map(|c| c.to_ascii_uppercase()))
+    }
+}
+
+mod private {
+    /// No downstream impls allowed.
+    pub trait Sealed {}
+    impl Sealed for str {}
+}
+
 /// Formats arguments to a [`SmolStr`], potentially without allocating.
 ///
 /// See [`alloc::format!`] or [`format_args!`] for syntax documentation.
diff --git a/tests/test.rs b/tests/test.rs
index ef5749a..11b7df7 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -224,7 +224,7 @@
     // String which has too many characters to even consider inlining: Chars::size_hint uses
     // (`len` + 3) / 4. With `len` = 89, this results in 23, so `from_iter` will immediately
     // heap allocate
-    let raw: String = std::iter::repeat('a').take(23 * 4 + 1).collect();
+    let raw = "a".repeat(23 * 4 + 1);
     let s: SmolStr = raw.chars().collect();
     assert_eq!(s.as_str(), raw);
     assert!(s.is_heap_allocated());
@@ -270,3 +270,46 @@
         assert_eq!(a, smol_str::format_smolstr!("{}", a));
     }
 }
+
+#[cfg(test)]
+mod test_str_ext {
+    use smol_str::StrExt;
+
+    #[test]
+    fn large() {
+        let lowercase = "aaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaAAAAaaaaaaaaaaaaaa".to_lowercase_smolstr();
+        assert_eq!(
+            lowercase,
+            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        );
+        assert!(lowercase.is_heap_allocated());
+    }
+
+    #[test]
+    fn to_lowercase() {
+        let lowercase = "aßΔC".to_lowercase_smolstr();
+        assert_eq!(lowercase, "aßδc");
+        assert!(!lowercase.is_heap_allocated());
+    }
+
+    #[test]
+    fn to_uppercase() {
+        let uppercase = "aßΔC".to_uppercase_smolstr();
+        assert_eq!(uppercase, "ASSΔC");
+        assert!(!uppercase.is_heap_allocated());
+    }
+
+    #[test]
+    fn to_ascii_lowercase() {
+        let uppercase = "aßΔC".to_ascii_lowercase_smolstr();
+        assert_eq!(uppercase, "aßΔc");
+        assert!(!uppercase.is_heap_allocated());
+    }
+
+    #[test]
+    fn to_ascii_uppercase() {
+        let uppercase = "aßΔC".to_ascii_uppercase_smolstr();
+        assert_eq!(uppercase, "AßΔC");
+        assert!(!uppercase.is_heap_allocated());
+    }
+}