@@ -6,6 +6,7 @@ use core::str;
6
6
use std:: os:: raw:: c_char;
7
7
8
8
use objc2:: msg_send;
9
+ use objc2:: rc:: { autoreleasepool, AutoreleasePool } ;
9
10
use objc2:: rc:: { Id , Owned , Shared } ;
10
11
11
12
use super :: INSObject ;
@@ -57,16 +58,58 @@ pub trait INSString: INSObject {
57
58
self . len ( ) == 0
58
59
}
59
60
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 ;
65
99
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 ( )
70
113
}
71
114
72
115
fn from_str ( string : & str ) -> Id < Self , Self :: Ownership > {
@@ -95,40 +138,98 @@ impl INSCopying for NSString {
95
138
96
139
impl fmt:: Display for NSString {
97
140
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) )
99
142
}
100
143
}
101
144
102
145
#[ cfg( test) ]
103
146
mod tests {
104
- use super :: { INSCopying , INSString , NSString } ;
147
+ use super :: * ;
105
148
106
- #[ cfg( not ( target_vendor = "apple" ) ) ]
149
+ #[ cfg( gnustep ) ]
107
150
#[ test]
108
151
fn ensure_linkage ( ) {
109
152
unsafe { objc2:: __gnustep_hack:: get_class_to_force_linkage ( ) } ;
110
153
}
111
154
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
+
112
171
#[ test]
113
172
fn test_utf8 ( ) {
114
173
let expected = "ประเทศไทย中华Việt Nam" ;
115
174
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
+ } ) ;
118
179
}
119
180
120
181
#[ test]
121
182
fn test_interior_nul ( ) {
122
183
let expected = "Hello\0 World" ;
123
184
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
+ } ) ;
126
189
}
127
190
128
191
#[ test]
129
192
fn test_copy ( ) {
130
193
let s = NSString :: from_str ( "Hello!" ) ;
131
194
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( ) ) ;
133
234
}
134
235
}
0 commit comments