@@ -168,6 +168,36 @@ fn collect_and_sort_map_entries<'py>(
168168 Ok ( entries)
169169}
170170
171+ // `PyUnicode_DecodeUTF8` runs a state machine even on pure-ASCII input. Skip
172+ // it by allocating a compact-ASCII `PyUnicode` and memcpying into its inline
173+ // buffer; non-ASCII falls through to the standard decoder.
174+ #[ cfg( CPython ) ]
175+ #[ inline]
176+ fn pystring_from_bytes_fast < ' py > ( py : Python < ' py > , bytes : & [ u8 ] ) -> PyResult < Bound < ' py , PyString > > {
177+ if !bytes. is_ascii ( ) {
178+ return PyString :: from_bytes ( py, bytes) ;
179+ }
180+
181+ unsafe {
182+ let obj = ffi:: PyUnicode_New ( bytes. len ( ) as ffi:: Py_ssize_t , 127 ) ;
183+ if obj. is_null ( ) {
184+ return Err ( PyErr :: fetch ( py) ) ;
185+ }
186+
187+ let data = obj. cast :: < ffi:: PyASCIIObject > ( ) . offset ( 1 ) . cast :: < u8 > ( ) ;
188+ std:: ptr:: copy_nonoverlapping ( bytes. as_ptr ( ) , data, bytes. len ( ) ) ;
189+ * data. add ( bytes. len ( ) ) = 0 ;
190+
191+ Ok ( Bound :: from_owned_ptr ( py, obj) . cast_into_unchecked :: < PyString > ( ) )
192+ }
193+ }
194+
195+ #[ cfg( not( CPython ) ) ]
196+ #[ inline]
197+ fn pystring_from_bytes_fast < ' py > ( py : Python < ' py > , bytes : & [ u8 ] ) -> PyResult < Bound < ' py , PyString > > {
198+ PyString :: from_bytes ( py, bytes)
199+ }
200+
171201fn get_bytes_from_py_any < ' py > ( obj : & ' py Bound < ' py , PyAny > ) -> PyResult < & ' py [ u8 ] > {
172202 if let Ok ( b) = obj. cast :: < PyBytes > ( ) {
173203 Ok ( b. as_bytes ( ) )
@@ -222,8 +252,9 @@ where
222252 . into_pyobject ( py) ?
223253 . into ( ) ,
224254 major:: STRING => {
225- // The UTF-8 validation is done when it's converted into a Python string
226- PyString :: from_bytes (
255+ // ASCII fast path inside the helper; non-ASCII falls through to
256+ // `PyUnicode_DecodeUTF8`, which is where the spec validation lives.
257+ pystring_from_bytes_fast (
227258 py,
228259 <types:: UncheckedStr < & [ u8 ] > >:: decode ( r)
229260 . map_err ( |_| anyhow ! ( "Cannot decode as bytes" ) ) ?
@@ -277,7 +308,7 @@ where
277308 }
278309 }
279310
280- let key_py = PyString :: from_bytes ( py, key) ?;
311+ let key_py = pystring_from_bytes_fast ( py, key) ?;
281312 prev_key = Some ( key) ;
282313
283314 let value_py = decode_dag_cbor_to_pyobject ( py, r, depth + 1 ) ?;
0 commit comments