From 2d34a531f4ef53f19b8cd512c121dd868328b649 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 12 Jun 2015 10:29:23 +0200 Subject: [PATCH 1/7] Implement DerefMut for String `&mut str` is rarely useful, but it is for e.g. `AsciiExt::make_ascii_lowercase`. --- src/libcollections/string.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libcollections/string.rs b/src/libcollections/string.rs index ef1d9b08b4a9a..491b7a6dd919b 100644 --- a/src/libcollections/string.rs +++ b/src/libcollections/string.rs @@ -989,6 +989,14 @@ impl ops::Deref for String { } } +#[stable(feature = "derefmut_for_string", since = "1.2.0")] +impl ops::DerefMut for String { + #[inline] + fn deref_mut(&mut self) -> &mut str { + unsafe { mem::transmute(&mut self.vec[..]) } + } +} + /// Wrapper type providing a `&String` reference via `Deref`. #[unstable(feature = "collections")] #[deprecated(since = "1.2.0", From 304f5452749b139f518baf6fa6160b66af654223 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 12 Jun 2015 10:30:16 +0200 Subject: [PATCH 2/7] Add tests for AsciiExt::make_ascii_*case, including on String. --- src/libstd/ascii.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/libstd/ascii.rs b/src/libstd/ascii.rs index 9b94b7f7003ed..72c4d4433ef06 100644 --- a/src/libstd/ascii.rs +++ b/src/libstd/ascii.rs @@ -537,6 +537,51 @@ mod tests { } } + #[test] + fn test_make_ascii_lower_case() { + macro_rules! test { + ($from: expr, $to: expr) => { + { + let mut x = $from; + x.make_ascii_lowercase(); + assert_eq!(x, $to); + } + } + } + test!(b'A', b'a'); + test!(b'a', b'a'); + test!(b'!', b'!'); + test!('A', 'a'); + test!('À', 'À'); + test!('a', 'a'); + test!('!', '!'); + test!(b"H\xc3\x89".to_vec(), b"h\xc3\x89"); + test!("HİKß".to_string(), "hİKß"); + } + + + #[test] + fn test_make_ascii_upper_case() { + macro_rules! test { + ($from: expr, $to: expr) => { + { + let mut x = $from; + x.make_ascii_uppercase(); + assert_eq!(x, $to); + } + } + } + test!(b'a', b'A'); + test!(b'A', b'A'); + test!(b'!', b'!'); + test!('a', 'A'); + test!('à', 'à'); + test!('A', 'A'); + test!('!', '!'); + test!(b"h\xc3\xa9".to_vec(), b"H\xc3\xa9"); + test!("hıKß".to_string(), "HıKß"); + } + #[test] fn test_eq_ignore_ascii_case() { assert!("url()URL()uRl()Ürl".eq_ignore_ascii_case("url()url()url()Ürl")); From 90d61d828f82a830b9edc202dd28bb5b4defc7e9 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 12 Jun 2015 10:30:32 +0200 Subject: [PATCH 3/7] Add more tests for AsciiExt::is_ascii --- src/libstd/ascii.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libstd/ascii.rs b/src/libstd/ascii.rs index 72c4d4433ef06..4a664acc32602 100644 --- a/src/libstd/ascii.rs +++ b/src/libstd/ascii.rs @@ -469,16 +469,19 @@ mod tests { use char::from_u32; #[test] - fn test_ascii() { - assert!("banana".chars().all(|c| c.is_ascii())); - assert!(!"ประเทศไทย中华Việt Nam".chars().all(|c| c.is_ascii())); - } + fn test_is_ascii() { + assert!(b"".is_ascii()); + assert!(b"banana\0\x7F".is_ascii()); + assert!(b"banana\0\x7F".iter().all(|b| b.is_ascii())); + assert!(!b"Vi\xe1\xbb\x87t Nam".is_ascii()); + assert!(!b"Vi\xe1\xbb\x87t Nam".iter().all(|b| b.is_ascii())); + assert!(!b"\xe1\xbb\x87".iter().any(|b| b.is_ascii())); - #[test] - fn test_ascii_vec() { assert!("".is_ascii()); - assert!("a".is_ascii()); - assert!(!"\u{2009}".is_ascii()); + assert!("banana\0\u{7F}".is_ascii()); + assert!("banana\0\u{7F}".chars().all(|c| c.is_ascii())); + assert!(!"ประเทศไทย中华Việt Nam".chars().all(|c| c.is_ascii())); + assert!(!"ประเทศไทย中华ệ ".chars().any(|c| c.is_ascii())); } #[test] From f9005512a9d84f469b30f0d469ccc401607ce64c Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 15 Jun 2015 18:33:21 +0200 Subject: [PATCH 4/7] Implement IndexMut for String and str. ... matching the existing Index impls. There is no reason not to if String implement DerefMut. The code removed in `src/librustc/middle/effect.rs` was added in #9750 to prevent things like `s[0] = 0x80` where `s: String`, but I belive became unnecessary when the Index(Mut) traits were introduced. --- src/libcollections/str.rs | 8 +++++ src/libcollections/string.rs | 32 ++++++++++++++++++ src/libcore/str/mod.rs | 64 ++++++++++++++++++++++++++++++++++++ src/libstd/ascii.rs | 4 +++ 4 files changed, 108 insertions(+) diff --git a/src/libcollections/str.rs b/src/libcollections/str.rs index 7f86209bd2ac9..711e2887865d9 100644 --- a/src/libcollections/str.rs +++ b/src/libcollections/str.rs @@ -546,6 +546,14 @@ impl str { core_str::StrExt::slice_unchecked(self, begin, end) } + /// Takes a bytewise mutable slice from a string. + /// + /// Same as `slice_unchecked`, but works with `&mut str` instead of `&str`. + #[stable(feature = "derefmut_for_string", since = "1.2.0")] + pub unsafe fn slice_mut_unchecked(&mut self, begin: usize, end: usize) -> &mut str { + core_str::StrExt::slice_mut_unchecked(self, begin, end) + } + /// Returns a slice of the string from the character range [`begin`..`end`). /// /// That is, start at the `begin`-th code point of the string and continue diff --git a/src/libcollections/string.rs b/src/libcollections/string.rs index 491b7a6dd919b..91142afeda346 100644 --- a/src/libcollections/string.rs +++ b/src/libcollections/string.rs @@ -979,6 +979,38 @@ impl ops::Index for String { } } +#[cfg(not(stage0))] +#[stable(feature = "derefmut_for_string", since = "1.2.0")] +impl ops::IndexMut> for String { + #[inline] + fn index_mut(&mut self, index: ops::Range) -> &mut str { + &mut self[..][index] + } +} +#[cfg(not(stage0))] +#[stable(feature = "derefmut_for_string", since = "1.2.0")] +impl ops::IndexMut> for String { + #[inline] + fn index_mut(&mut self, index: ops::RangeTo) -> &mut str { + &mut self[..][index] + } +} +#[cfg(not(stage0))] +#[stable(feature = "derefmut_for_string", since = "1.2.0")] +impl ops::IndexMut> for String { + #[inline] + fn index_mut(&mut self, index: ops::RangeFrom) -> &mut str { + &mut self[..][index] + } +} +#[stable(feature = "derefmut_for_string", since = "1.2.0")] +impl ops::IndexMut for String { + #[inline] + fn index_mut(&mut self, _index: ops::RangeFull) -> &mut str { + unsafe { mem::transmute(&mut *self.vec) } + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl ops::Deref for String { type Target = str; diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index bd6e1a4063abd..c8237720b0e90 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -1116,6 +1116,23 @@ mod traits { } } + /// Returns a mutable slice of the given string from the byte range + /// [`begin`..`end`). + #[stable(feature = "derefmut_for_string", since = "1.2.0")] + impl ops::IndexMut> for str { + #[inline] + fn index_mut(&mut self, index: ops::Range) -> &mut str { + // is_char_boundary checks that the index is in [0, .len()] + if index.start <= index.end && + self.is_char_boundary(index.start) && + self.is_char_boundary(index.end) { + unsafe { self.slice_mut_unchecked(index.start, index.end) } + } else { + super::slice_error_fail(self, index.start, index.end) + } + } + } + /// Returns a slice of the string from the beginning to byte /// `end`. /// @@ -1138,6 +1155,21 @@ mod traits { } } + /// Returns a mutable slice of the string from the beginning to byte + /// `end`. + #[stable(feature = "derefmut_for_string", since = "1.2.0")] + impl ops::IndexMut> for str { + #[inline] + fn index_mut(&mut self, index: ops::RangeTo) -> &mut str { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(index.end) { + unsafe { self.slice_mut_unchecked(0, index.end) } + } else { + super::slice_error_fail(self, 0, index.end) + } + } + } + /// Returns a slice of the string from `begin` to its end. /// /// Equivalent to `self[begin .. self.len()]`. @@ -1159,6 +1191,21 @@ mod traits { } } + /// Returns a slice of the string from `begin` to its end. + #[stable(feature = "derefmut_for_string", since = "1.2.0")] + impl ops::IndexMut> for str { + #[inline] + fn index_mut(&mut self, index: ops::RangeFrom) -> &mut str { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(index.start) { + let len = self.len(); + unsafe { self.slice_mut_unchecked(index.start, len) } + } else { + super::slice_error_fail(self, index.start, self.len()) + } + } + } + #[stable(feature = "rust1", since = "1.0.0")] impl ops::Index for str { type Output = str; @@ -1168,6 +1215,14 @@ mod traits { self } } + + #[stable(feature = "derefmut_for_string", since = "1.2.0")] + impl ops::IndexMut for str { + #[inline] + fn index_mut(&mut self, _index: ops::RangeFull) -> &mut str { + self + } + } } /// Methods for string slices @@ -1204,6 +1259,7 @@ pub trait StrExt { fn char_len(&self) -> usize; fn slice_chars<'a>(&'a self, begin: usize, end: usize) -> &'a str; unsafe fn slice_unchecked<'a>(&'a self, begin: usize, end: usize) -> &'a str; + unsafe fn slice_mut_unchecked<'a>(&'a mut self, begin: usize, end: usize) -> &'a mut str; fn starts_with<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool; fn ends_with<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool where P::Searcher: ReverseSearcher<'a>; @@ -1379,6 +1435,14 @@ impl StrExt for str { }) } + #[inline] + unsafe fn slice_mut_unchecked(&mut self, begin: usize, end: usize) -> &mut str { + mem::transmute(Slice { + data: self.as_ptr().offset(begin as isize), + len: end - begin, + }) + } + #[inline] fn starts_with<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool { pat.is_prefix_of(self) diff --git a/src/libstd/ascii.rs b/src/libstd/ascii.rs index 4a664acc32602..cf78fa7b69a00 100644 --- a/src/libstd/ascii.rs +++ b/src/libstd/ascii.rs @@ -583,6 +583,10 @@ mod tests { test!('!', '!'); test!(b"h\xc3\xa9".to_vec(), b"H\xc3\xa9"); test!("hıKß".to_string(), "HıKß"); + + let mut x = "Hello".to_string(); + x[..3].make_ascii_uppercase(); // Test IndexMut on String. + assert_eq!(x, "HELlo") } #[test] From 7469914e96a511487e8248d2f8a583befb02149f Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 15 Jun 2015 19:24:52 +0200 Subject: [PATCH 5/7] Add str::split_at_mut --- src/libcollections/str.rs | 6 ++++++ src/libcollectionstest/lib.rs | 1 + src/libcollectionstest/str.rs | 12 ++++++++++++ src/libcore/str/mod.rs | 15 +++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/src/libcollections/str.rs b/src/libcollections/str.rs index 711e2887865d9..306a8ba3e0900 100644 --- a/src/libcollections/str.rs +++ b/src/libcollections/str.rs @@ -793,6 +793,12 @@ impl str { core_str::StrExt::split_at(self, mid) } + /// Divide one mutable string slice into two at an index. + #[inline] + pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str) { + core_str::StrExt::split_at_mut(self, mid) + } + /// An iterator over the codepoints of `self`. /// /// # Examples diff --git a/src/libcollectionstest/lib.rs b/src/libcollectionstest/lib.rs index 6bf7380fdeb50..233623b8a5e4e 100644 --- a/src/libcollectionstest/lib.rs +++ b/src/libcollectionstest/lib.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![feature(ascii)] #![feature(append)] #![feature(bitset)] #![feature(bitvec)] diff --git a/src/libcollectionstest/str.rs b/src/libcollectionstest/str.rs index 4eee99f2bc912..66d9569710259 100644 --- a/src/libcollectionstest/str.rs +++ b/src/libcollectionstest/str.rs @@ -701,6 +701,18 @@ fn test_split_at() { assert_eq!(b, ""); } +#[test] +fn test_split_at_mut() { + use std::ascii::AsciiExt; + let mut s = "Hello World".to_string(); + { + let (a, b) = s.split_at_mut(5); + a.make_ascii_uppercase(); + b.make_ascii_lowercase(); + } + assert_eq!(s, "HELLO world"); +} + #[test] #[should_panic] fn test_split_at_boundscheck() { diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index c8237720b0e90..7e4c2ba3be875 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -1279,6 +1279,7 @@ pub trait StrExt { where P::Searcher: ReverseSearcher<'a>; fn find_str<'a, P: Pattern<'a>>(&'a self, pat: P) -> Option; fn split_at(&self, mid: usize) -> (&str, &str); + fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str); fn slice_shift_char<'a>(&'a self) -> Option<(char, &'a str)>; fn subslice_offset(&self, inner: &str) -> usize; fn as_ptr(&self) -> *const u8; @@ -1591,6 +1592,20 @@ impl StrExt for str { } } + fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str) { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(mid) { + let len = self.len(); + unsafe { + let self2: &mut str = mem::transmute_copy(&self); + (self.slice_mut_unchecked(0, mid), + self2.slice_mut_unchecked(mid, len)) + } + } else { + slice_error_fail(self, 0, mid) + } + } + #[inline] fn slice_shift_char(&self) -> Option<(char, &str)> { if self.is_empty() { From eb99f0e4207915f4cb4246d9d6bc6109fa04400c Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 1 Jul 2015 07:39:47 +0200 Subject: [PATCH 6/7] Mark some new things as unstable. --- src/libcollections/str.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libcollections/str.rs b/src/libcollections/str.rs index 306a8ba3e0900..4bd86bb606955 100644 --- a/src/libcollections/str.rs +++ b/src/libcollections/str.rs @@ -549,7 +549,7 @@ impl str { /// Takes a bytewise mutable slice from a string. /// /// Same as `slice_unchecked`, but works with `&mut str` instead of `&str`. - #[stable(feature = "derefmut_for_string", since = "1.2.0")] + #[unstable(feature = "str_slice_mut", reason = "recently added")] pub unsafe fn slice_mut_unchecked(&mut self, begin: usize, end: usize) -> &mut str { core_str::StrExt::slice_mut_unchecked(self, begin, end) } @@ -789,12 +789,14 @@ impl str { /// assert_eq!(b, " 老虎 Léopard"); /// ``` #[inline] + #[unstable(feature = "str_split_at", reason = "recently added")] pub fn split_at(&self, mid: usize) -> (&str, &str) { core_str::StrExt::split_at(self, mid) } /// Divide one mutable string slice into two at an index. #[inline] + #[unstable(feature = "str_split_at", reason = "recently added")] pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str) { core_str::StrExt::split_at_mut(self, mid) } From 3226858e500fa70b46c18d1accedc60060f2bbc0 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 13 Jul 2015 18:59:06 +0200 Subject: [PATCH 7/7] Fix tests for changes in #26241. --- src/libcollections/str.rs | 2 +- src/libcollectionstest/lib.rs | 1 + src/test/compile-fail/str-mut-idx-2.rs | 16 ---------------- src/test/compile-fail/str-mut-idx.rs | 5 +++-- 4 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 src/test/compile-fail/str-mut-idx-2.rs diff --git a/src/libcollections/str.rs b/src/libcollections/str.rs index 4bd86bb606955..f2d8efdfde5ab 100644 --- a/src/libcollections/str.rs +++ b/src/libcollections/str.rs @@ -780,7 +780,7 @@ impl str { /// /// # Examples /// ``` - /// # #![feature(collections)] + /// # #![feature(str_split_at)] /// let s = "Löwe 老虎 Léopard"; /// let first_space = s.find(' ').unwrap_or(s.len()); /// let (a, b) = s.split_at(first_space); diff --git a/src/libcollectionstest/lib.rs b/src/libcollectionstest/lib.rs index 233623b8a5e4e..d161dc7a287f9 100644 --- a/src/libcollectionstest/lib.rs +++ b/src/libcollectionstest/lib.rs @@ -44,6 +44,7 @@ #![feature(str_char)] #![feature(str_escape)] #![feature(str_match_indices)] +#![feature(str_split_at)] #![feature(str_utf16)] #![feature(box_str)] #![feature(subslice_offset)] diff --git a/src/test/compile-fail/str-mut-idx-2.rs b/src/test/compile-fail/str-mut-idx-2.rs deleted file mode 100644 index aa05d7d5c54d7..0000000000000 --- a/src/test/compile-fail/str-mut-idx-2.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -fn mutate(mut s: &mut str) { - let _s: &mut str = &mut s[1..2]; - //~^ ERROR cannot borrow immutable indexed content as mutable -} - -pub fn main() {} diff --git a/src/test/compile-fail/str-mut-idx.rs b/src/test/compile-fail/str-mut-idx.rs index 88e90c8244d87..73abe6cb59db2 100644 --- a/src/test/compile-fail/str-mut-idx.rs +++ b/src/test/compile-fail/str-mut-idx.rs @@ -13,10 +13,11 @@ fn bot() -> T { loop {} } fn mutate(s: &mut str) { s[1..2] = bot(); //~^ ERROR `core::marker::Sized` is not implemented for the type `str` - //~^^ ERROR `core::marker::Sized` is not implemented for the type `str` + //~| ERROR `core::marker::Sized` is not implemented for the type `str` s[1usize] = bot(); //~^ ERROR `core::ops::Index` is not implemented for the type `str` - //~^^ ERROR `core::ops::Index` is not implemented for the type `str` + //~| ERROR `core::ops::IndexMut` is not implemented for the type `str` + //~| ERROR `core::ops::Index` is not implemented for the type `str` } pub fn main() {}