@@ -6,6 +6,7 @@ use core::str;
66use std:: os:: raw:: c_char;
77
88use objc2:: msg_send;
9+ use objc2:: rc:: { autoreleasepool, AutoreleasePool } ;
910use objc2:: rc:: { Id , Owned , Shared } ;
1011
1112use super :: INSObject ;
@@ -57,16 +58,58 @@ pub trait INSString: INSObject {
5758 self . len ( ) == 0
5859 }
5960
60- fn as_str ( & self ) -> & str {
61- let bytes = unsafe {
62- let bytes: * const c_char = msg_send ! [ self , UTF8String ] ;
63- bytes as * const u8
64- } ;
61+ /// TODO
62+ ///
63+ /// ```compile_fail
64+ /// # use objc2::rc::autoreleasepool;
65+ /// # use objc2_foundation::{INSObject, INSString, NSString};
66+ /// autoreleasepool(|pool| {
67+ /// let ns_string = NSString::new();
68+ /// let s = ns_string.as_str(pool);
69+ /// drop(ns_string);
70+ /// println!("{}", s);
71+ /// });
72+ /// ```
73+ ///
74+ /// ```compile_fail
75+ /// # use objc2::rc::autoreleasepool;
76+ /// # use objc2_foundation::{INSObject, INSString, NSString};
77+ /// let ns_string = NSString::new();
78+ /// let s = autoreleasepool(|pool| ns_string.as_str(pool));
79+ /// ```
80+ fn as_str < ' r , ' s : ' r , ' p : ' r > ( & ' s self , pool : & ' p AutoreleasePool ) -> & ' r str {
81+ // This is necessary until `auto` types stabilizes.
82+ pool. __verify_is_inner ( ) ;
83+
84+ // The documentation on `UTF8String` is a bit sparse, but with
85+ // educated guesses and testing I've determined that NSString stores
86+ // a pointer to the string data, sometimes with an UTF-8 encoding,
87+ // (usual for ascii data), sometimes in other encodings (UTF-16?).
88+ //
89+ // `UTF8String` then checks the internal encoding:
90+ // - If the data is UTF-8 encoded, it returns the internal pointer.
91+ // - If the data is in another encoding, it creates a new allocation,
92+ // writes the UTF-8 representation of the string into it,
93+ // autoreleases the allocation and returns a pointer to it.
94+ //
95+ // So the lifetime of the returned pointer is either the same as the
96+ // NSString OR the lifetime of the innermost @autoreleasepool.
97+ let bytes: * const c_char = unsafe { msg_send ! [ self , UTF8String ] } ;
98+ let bytes = bytes as * const u8 ;
6599 let len = self . len ( ) ;
66- unsafe {
67- let bytes = slice:: from_raw_parts ( bytes, len) ;
68- str:: from_utf8 ( bytes) . unwrap ( )
69- }
100+
101+ // SAFETY:
102+ // The held AutoreleasePool is the innermost, and the reference is
103+ // constrained both by the pool and the NSString.
104+ //
105+ // `len` is the length of the string in the UTF-8 encoding.
106+ //
107+ // `bytes` is a null-terminated C string (with length = len + 1), so
108+ // it is never a NULL pointer.
109+ let bytes: & ' r [ u8 ] = unsafe { slice:: from_raw_parts ( bytes, len) } ;
110+
111+ // TODO: Always UTF-8, so should we use `from_utf8_unchecked`?
112+ str:: from_utf8 ( bytes) . unwrap ( )
70113 }
71114
72115 fn from_str ( string : & str ) -> Id < Self , Self :: Ownership > {
@@ -95,40 +138,98 @@ impl INSCopying for NSString {
95138
96139impl fmt:: Display for NSString {
97140 fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
98- fmt:: Display :: fmt ( self . as_str ( ) , f)
141+ autoreleasepool ( |pool| fmt:: Display :: fmt ( self . as_str ( pool ) , f) )
99142 }
100143}
101144
102145#[ cfg( test) ]
103146mod tests {
104- use super :: { INSCopying , INSString , NSString } ;
147+ use super :: * ;
105148
106- #[ cfg( not ( target_vendor = "apple" ) ) ]
149+ #[ cfg( gnustep ) ]
107150 #[ test]
108151 fn ensure_linkage ( ) {
109152 unsafe { objc2:: __gnustep_hack:: get_class_to_force_linkage ( ) } ;
110153 }
111154
155+ #[ test]
156+ fn test_empty ( ) {
157+ let s1 = NSString :: from_str ( "" ) ;
158+ let s2 = NSString :: new ( ) ;
159+
160+ assert_eq ! ( s1. len( ) , 0 ) ;
161+ assert_eq ! ( s2. len( ) , 0 ) ;
162+
163+ assert_eq ! ( s1, s2) ;
164+
165+ autoreleasepool ( |pool| {
166+ assert_eq ! ( s1. as_str( pool) , "" ) ;
167+ assert_eq ! ( s2. as_str( pool) , "" ) ;
168+ } ) ;
169+ }
170+
112171 #[ test]
113172 fn test_utf8 ( ) {
114173 let expected = "ประเทศไทย中华Việt Nam" ;
115174 let s = NSString :: from_str ( expected) ;
116- assert ! ( s. len( ) == expected. len( ) ) ;
117- assert ! ( s. as_str( ) == expected) ;
175+ assert_eq ! ( s. len( ) , expected. len( ) ) ;
176+ autoreleasepool ( |pool| {
177+ assert_eq ! ( s. as_str( pool) , expected) ;
178+ } ) ;
118179 }
119180
120181 #[ test]
121182 fn test_interior_nul ( ) {
122183 let expected = "Hello\0 World" ;
123184 let s = NSString :: from_str ( expected) ;
124- assert ! ( s. len( ) == expected. len( ) ) ;
125- assert ! ( s. as_str( ) == expected) ;
185+ assert_eq ! ( s. len( ) , expected. len( ) ) ;
186+ autoreleasepool ( |pool| {
187+ assert_eq ! ( s. as_str( pool) , expected) ;
188+ } ) ;
126189 }
127190
128191 #[ test]
129192 fn test_copy ( ) {
130193 let s = NSString :: from_str ( "Hello!" ) ;
131194 let copied = s. copy ( ) ;
132- assert ! ( copied. as_str( ) == s. as_str( ) ) ;
195+ autoreleasepool ( |pool| {
196+ assert_eq ! ( copied. as_str( pool) , s. as_str( pool) ) ;
197+ } ) ;
198+ }
199+
200+ #[ test]
201+ fn test_copy_nsstring_is_same ( ) {
202+ let string1 = NSString :: from_str ( "Hello, world!" ) ;
203+ let string2 = string1. copy ( ) ;
204+
205+ let s1: * const NSString = & * string1;
206+ let s2: * const NSString = & * string2;
207+
208+ assert_eq ! ( s1, s2, "Cloned NSString didn't have the same address" ) ;
209+ }
210+
211+ #[ test]
212+ /// Apparently NSString does this for some reason?
213+ fn test_strips_first_leading_zero_width_no_break_space ( ) {
214+ let ns_string = NSString :: from_str ( "\u{feff} " ) ;
215+ let expected = "" ;
216+ autoreleasepool ( |pool| {
217+ assert_eq ! ( ns_string. as_str( pool) , expected) ;
218+ } ) ;
219+ assert_eq ! ( ns_string. len( ) , 0 ) ;
220+
221+ let s = "\u{feff} \u{feff} a\u{feff} " ;
222+
223+ // Huh, this difference might be a GNUStep bug?
224+ #[ cfg( not( gnustep) ) ]
225+ let expected = "\u{feff} a\u{feff} " ;
226+ #[ cfg( gnustep) ]
227+ let expected = "a\u{feff} " ;
228+
229+ let ns_string = NSString :: from_str ( s) ;
230+ autoreleasepool ( |pool| {
231+ assert_eq ! ( ns_string. as_str( pool) , expected) ;
232+ } ) ;
233+ assert_eq ! ( ns_string. len( ) , expected. len( ) ) ;
133234 }
134235}
0 commit comments