From 44ef5166c8c2c7ea6254c86051a1a99b919e5dca Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Sun, 12 Apr 2026 15:04:52 -0400 Subject: [PATCH 1/2] fix: Handle char expansion in islower, isupper Closes: #7526 `py_islower` and `py_isupper` need to handle expansions for letter casing. Comparing chars directly can miss edge cases in certain languages. Unfortunately, like the last PR, this allocates to handle potential expansions. I also had to add `icu_casemap` as a dependency. RustPython is already using parts of icu4x so this doesn't add many transitive dependencies. --- Cargo.lock | 108 +++++++++++++++++++++------- Cargo.toml | 3 + crates/vm/Cargo.toml | 3 + crates/vm/src/anystr.rs | 31 +++++--- extra_tests/snippets/builtin_str.py | 6 ++ 5 files changed, 117 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20336689a32..895336f0b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1503,37 +1503,83 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_casemap" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "070f98b5b82798fcb93654bf96ed9f40064fc44c86f51a09ea711092cd5cc5be" +dependencies = [ + "icu_casemap_data", + "icu_collections", + "icu_locale_core", + "icu_properties", + "icu_provider", + "potential_utf", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_casemap_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "846b0857ca091204be3c874bc93daaf89d4777e8d2d20b0d3ffe8f671d98014b" + [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "serde", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] +[[package]] +name = "icu_locale" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a396343c7208121dc86e35623d3dfe19814a7613cfd14964994cdc9c9a2e26" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_locale_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fdcc9ac77c6d74ff5cf6e65ef3181d6af32003b16fce3a77fb451d2f695993" + [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1548,15 +1594,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1568,18 +1614,20 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -2569,6 +2617,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -3458,6 +3508,8 @@ dependencies = [ "glob", "half", "hex", + "icu_casemap", + "icu_locale", "icu_properties", "indexmap", "is-macro", @@ -3507,6 +3559,7 @@ dependencies = [ "which", "widestring", "windows-sys 0.61.2", + "writeable", ] [[package]] @@ -4054,11 +4107,12 @@ checksum = "639ce8ef6d2ba56be0383a94dd13b92138d58de44c62618303bb798fa92bdc00" [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -4905,9 +4959,9 @@ checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4916,9 +4970,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4989,21 +5043,23 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -5011,9 +5067,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 7bd8b8f3374..831b6a22629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -222,6 +222,8 @@ strum = "0.28" strum_macros = "0.28" syn = "2" thiserror = "2.0" +icu_casemap = "2" +icu_locale = "2" icu_properties = "2" icu_normalizer = "2" unicode-casing = "0.1.1" @@ -229,6 +231,7 @@ unic-ucd-age = "0.9.0" unicode_names2 = "2.0.0" widestring = "1.2.0" windows-sys = "0.61.2" +writeable = "0.6.1" wasm-bindgen = "0.2.106" # Lints diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index b721418a4cc..704516cee7b 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -86,7 +86,10 @@ timsort = "0.1.2" # TODO: use unic for this; needed for title case: # https://github.com/RustPython/RustPython/pull/832#discussion_r275428939 unicode-casing = { workspace = true } +icu_casemap = { workspace = true } +icu_locale = { workspace = true } icu_properties = { workspace = true } +writeable = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true } diff --git a/crates/vm/src/anystr.rs b/crates/vm/src/anystr.rs index a9369bf068e..af07b01e583 100644 --- a/crates/vm/src/anystr.rs +++ b/crates/vm/src/anystr.rs @@ -4,7 +4,10 @@ use crate::{ convert::TryFromBorrowedObject, function::OptionalOption, }; +use icu_casemap::CaseMapper; +use icu_locale::LanguageIdentifier; use num_traits::{cast::ToPrimitive, sign::Signed}; +use writeable::Writeable; use core::ops::Range; @@ -408,12 +411,18 @@ pub trait AnyStr { // unicode_islower_impl fn py_islower(&self) -> bool { let mut lower = false; - for c in self.elements() { - if c.is_uppercase() { + let mut lowercased = String::with_capacity(self.bytes_len()); + let cm = CaseMapper::new(); + for chunk in self.as_bytes().utf8_chunks().map(|c| c.valid()) { + let writer = cm.lowercase(chunk, &LanguageIdentifier::UNKNOWN); + lowercased.clear(); + writer + .write_to(&mut lowercased) + .expect("Writing to a buffer is infallible"); + if chunk != lowercased { return false; - } else if !lower && c.is_lowercase() { - lower = true } + lower = true; } lower } @@ -423,12 +432,18 @@ pub trait AnyStr { // unicode_isupper_impl fn py_isupper(&self) -> bool { let mut upper = false; - for c in self.elements() { - if c.is_lowercase() { + let mut uppercased = String::with_capacity(self.bytes_len()); + let cm = CaseMapper::new(); + for chunk in self.as_bytes().utf8_chunks().map(|c| c.valid()) { + let writer = cm.uppercase(chunk, &LanguageIdentifier::UNKNOWN); + uppercased.clear(); + writer + .write_to(&mut uppercased) + .expect("Writing to a buffer is infallible"); + if chunk != uppercased { return false; - } else if !upper && c.is_uppercase() { - upper = true } + upper = true; } upper } diff --git a/extra_tests/snippets/builtin_str.py b/extra_tests/snippets/builtin_str.py index b852e678ace..d0b030d5ed5 100644 --- a/extra_tests/snippets/builtin_str.py +++ b/extra_tests/snippets/builtin_str.py @@ -69,6 +69,8 @@ assert not a.isdecimal() assert not a.isnumeric() assert a.istitle() +assert "\u1c89".istitle() +# assert "DZ".title() == "Dz" assert a.isalpha() s = "1 2 3" @@ -220,6 +222,8 @@ assert "abc\t12345\txyz".expandtabs() == "abc 12345 xyz" assert "-".join(["1", "2", "3"]) == "1-2-3" assert "HALLO".isupper() +assert "\u0295".islower() +assert "\u1c89".isupper() assert "hello, my name is".partition("my ") == ("hello, ", "my ", "name is") assert "hello".partition("is") == ("hello", "", "") assert "hello, my name is".rpartition("is") == ("hello, my name ", "is", "") @@ -236,6 +240,8 @@ assert not "123".isidentifier() assert "Σίσυφος".swapcase() == "σΊΣΥΦΟΣ" +assert "\u0295".swapcase() == "\u0295" +assert "\u1c89".swapcase() == "\u1c8a" # String Formatting assert "{} {}".format(1, 2) == "1 2" From 712bd75d7512e32da3f67b779f2a01eb014ff965 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Sun, 12 Apr 2026 21:28:27 -0400 Subject: [PATCH 2/2] Ensure islower/isupper handles strs without chars This fixes a regression mentioned by CodeRabbit. I also figured out how to check a string's case without allocation using Unicode properties. Thus, this commit removes `icu_casemap` again. `icu_casemap` and my old solution is required for a robust case check, but it seems like the current code is fine for Python. --- Cargo.lock | 55 ----------------------------- Cargo.toml | 3 -- crates/vm/Cargo.toml | 3 -- crates/vm/src/anystr.rs | 41 ++++++++++----------- crates/vm/src/builtins/str.rs | 22 ------------ crates/vm/src/bytes_inner.rs | 8 ----- extra_tests/snippets/builtin_str.py | 6 ++++ 7 files changed, 24 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 895336f0b1d..ffa4d89ec3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1503,28 +1503,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_casemap" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "070f98b5b82798fcb93654bf96ed9f40064fc44c86f51a09ea711092cd5cc5be" -dependencies = [ - "icu_casemap_data", - "icu_collections", - "icu_locale_core", - "icu_properties", - "icu_provider", - "potential_utf", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_casemap_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "846b0857ca091204be3c874bc93daaf89d4777e8d2d20b0d3ffe8f671d98014b" - [[package]] name = "icu_collections" version = "2.2.0" @@ -1533,28 +1511,12 @@ checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", - "serde", "utf8_iter", "yoke", "zerofrom", "zerovec", ] -[[package]] -name = "icu_locale" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a396343c7208121dc86e35623d3dfe19814a7613cfd14964994cdc9c9a2e26" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_locale_data", - "icu_provider", - "potential_utf", - "tinystr", - "zerovec", -] - [[package]] name = "icu_locale_core" version = "2.2.0" @@ -1563,18 +1525,11 @@ checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", - "serde", "tinystr", "writeable", "zerovec", ] -[[package]] -name = "icu_locale_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fdcc9ac77c6d74ff5cf6e65ef3181d6af32003b16fce3a77fb451d2f695993" - [[package]] name = "icu_normalizer" version = "2.2.0" @@ -1626,8 +1581,6 @@ checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "serde", - "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -2617,8 +2570,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "serde_core", - "writeable", "zerovec", ] @@ -3508,8 +3459,6 @@ dependencies = [ "glob", "half", "hex", - "icu_casemap", - "icu_locale", "icu_properties", "indexmap", "is-macro", @@ -3559,7 +3508,6 @@ dependencies = [ "which", "widestring", "windows-sys 0.61.2", - "writeable", ] [[package]] @@ -4112,7 +4060,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", - "serde_core", "zerovec", ] @@ -5050,7 +4997,6 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", - "zerovec", ] [[package]] @@ -5059,7 +5005,6 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "serde", "yoke", "zerofrom", "zerovec-derive", diff --git a/Cargo.toml b/Cargo.toml index 831b6a22629..7bd8b8f3374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -222,8 +222,6 @@ strum = "0.28" strum_macros = "0.28" syn = "2" thiserror = "2.0" -icu_casemap = "2" -icu_locale = "2" icu_properties = "2" icu_normalizer = "2" unicode-casing = "0.1.1" @@ -231,7 +229,6 @@ unic-ucd-age = "0.9.0" unicode_names2 = "2.0.0" widestring = "1.2.0" windows-sys = "0.61.2" -writeable = "0.6.1" wasm-bindgen = "0.2.106" # Lints diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 704516cee7b..b721418a4cc 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -86,10 +86,7 @@ timsort = "0.1.2" # TODO: use unic for this; needed for title case: # https://github.com/RustPython/RustPython/pull/832#discussion_r275428939 unicode-casing = { workspace = true } -icu_casemap = { workspace = true } -icu_locale = { workspace = true } icu_properties = { workspace = true } -writeable = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true } diff --git a/crates/vm/src/anystr.rs b/crates/vm/src/anystr.rs index af07b01e583..5abb34ed6f9 100644 --- a/crates/vm/src/anystr.rs +++ b/crates/vm/src/anystr.rs @@ -4,10 +4,11 @@ use crate::{ convert::TryFromBorrowedObject, function::OptionalOption, }; -use icu_casemap::CaseMapper; -use icu_locale::LanguageIdentifier; +use icu_properties::{ + CodePointSetData, + props::{Alphabetic, ChangesWhenLowercased, ChangesWhenUppercased}, +}; use num_traits::{cast::ToPrimitive, sign::Signed}; -use writeable::Writeable; use core::ops::Range; @@ -133,8 +134,6 @@ where } pub trait AnyChar: Copy { - fn is_lowercase(self) -> bool; - fn is_uppercase(self) -> bool; fn bytes_len(self) -> usize; } @@ -410,19 +409,17 @@ pub trait AnyStr { // _Py_bytes_islower // unicode_islower_impl fn py_islower(&self) -> bool { + let case_change = CodePointSetData::new::(); + let alphabetic = CodePointSetData::new::(); let mut lower = false; - let mut lowercased = String::with_capacity(self.bytes_len()); - let cm = CaseMapper::new(); for chunk in self.as_bytes().utf8_chunks().map(|c| c.valid()) { - let writer = cm.lowercase(chunk, &LanguageIdentifier::UNKNOWN); - lowercased.clear(); - writer - .write_to(&mut lowercased) - .expect("Writing to a buffer is infallible"); - if chunk != lowercased { + if chunk.chars().any(|c| case_change.contains(c)) { return false; } - lower = true; + + if !lower && chunk.chars().any(|c| alphabetic.contains(c)) { + lower = true; + } } lower } @@ -431,19 +428,17 @@ pub trait AnyStr { // Py_bytes_isupper // unicode_isupper_impl fn py_isupper(&self) -> bool { + let case_change = CodePointSetData::new::(); + let alphabetic = CodePointSetData::new::(); let mut upper = false; - let mut uppercased = String::with_capacity(self.bytes_len()); - let cm = CaseMapper::new(); for chunk in self.as_bytes().utf8_chunks().map(|c| c.valid()) { - let writer = cm.uppercase(chunk, &LanguageIdentifier::UNKNOWN); - uppercased.clear(); - writer - .write_to(&mut uppercased) - .expect("Writing to a buffer is infallible"); - if chunk != uppercased { + if chunk.chars().any(|c| case_change.contains(c)) { return false; } - upper = true; + + if !upper && chunk.chars().any(|c| alphabetic.contains(c)) { + upper = true; + } } upper } diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 74564278925..870d3b72a74 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -2228,14 +2228,6 @@ impl AnyStrContainer for String { } impl anystr::AnyChar for char { - fn is_lowercase(self) -> bool { - self.is_lowercase() - } - - fn is_uppercase(self) -> bool { - self.is_uppercase() - } - fn bytes_len(self) -> usize { self.len_utf8() } @@ -2341,12 +2333,6 @@ impl AnyStrContainer for Wtf8Buf { } impl anystr::AnyChar for CodePoint { - fn is_lowercase(self) -> bool { - self.is_char_and(char::is_lowercase) - } - fn is_uppercase(self) -> bool { - self.is_char_and(char::is_uppercase) - } fn bytes_len(self) -> usize { self.len_wtf8() } @@ -2459,14 +2445,6 @@ impl AnyStrContainer for AsciiString { } impl anystr::AnyChar for ascii::AsciiChar { - fn is_lowercase(self) -> bool { - self.is_lowercase() - } - - fn is_uppercase(self) -> bool { - self.is_uppercase() - } - fn bytes_len(self) -> usize { 1 } diff --git a/crates/vm/src/bytes_inner.rs b/crates/vm/src/bytes_inner.rs index 6a1aa73c6b8..52fda9e7f2b 100644 --- a/crates/vm/src/bytes_inner.rs +++ b/crates/vm/src/bytes_inner.rs @@ -1031,14 +1031,6 @@ impl AnyStrContainer<[u8]> for Vec { const ASCII_WHITESPACES: [u8; 6] = [0x20, 0x09, 0x0a, 0x0c, 0x0d, 0x0b]; impl anystr::AnyChar for u8 { - fn is_lowercase(self) -> bool { - self.is_ascii_lowercase() - } - - fn is_uppercase(self) -> bool { - self.is_ascii_uppercase() - } - fn bytes_len(self) -> usize { 1 } diff --git a/extra_tests/snippets/builtin_str.py b/extra_tests/snippets/builtin_str.py index d0b030d5ed5..3d54643b3ce 100644 --- a/extra_tests/snippets/builtin_str.py +++ b/extra_tests/snippets/builtin_str.py @@ -222,6 +222,12 @@ assert "abc\t12345\txyz".expandtabs() == "abc 12345 xyz" assert "-".join(["1", "2", "3"]) == "1-2-3" assert "HALLO".isupper() +assert not "123".isupper() +assert not "123".islower() +assert not "\U0001f431".isupper() +assert not "\U0001f431".islower() +assert "\U0001f431 CAT".isupper() +assert "\U0001f431 cat".islower() assert "\u0295".islower() assert "\u1c89".isupper() assert "hello, my name is".partition("my ") == ("hello, ", "my ", "name is")