@@ -195,6 +195,29 @@ impl PySetInner {
195195 Ok ( set)
196196 }
197197
198+ /// Build a set from an arbitrary object, reusing the source's stored
199+ /// hashes when it is a set/frozenset/dict.
200+ pub ( super ) fn from_object ( iterable : PyObjectRef , vm : & VirtualMachine ) -> PyResult < Self > {
201+ let set = Self :: default ( ) ;
202+ set. update_internal ( iterable, vm) ?;
203+ Ok ( set)
204+ }
205+
206+ /// Elements of `obj` paired with the hash already stored alongside them,
207+ /// or `None` if `obj` keeps no such hashes and has to be iterated
208+ /// generically (which calls `__hash__` on every element again).
209+ ///
210+ /// Mirrors the `PyAnySet_Check` / `PyDict_CheckExact` fast paths in
211+ /// CPython's `set_update_internal`.
212+ fn cached_hashes ( obj : & PyObject , vm : & VirtualMachine ) -> Option < Vec < ( PyObjectRef , PyHash ) > > {
213+ if let Some ( set) = extract_set ( obj) {
214+ Some ( set. content . keys_with_hashes ( ) )
215+ } else {
216+ obj. downcast_ref_if_exact :: < PyDict > ( vm)
217+ . map ( |dict| dict. _as_dict_inner ( ) . keys_with_hashes ( ) )
218+ }
219+ }
220+
198221 fn fold_op < O > (
199222 & self ,
200223 others : impl core:: iter:: Iterator < Item = O > ,
@@ -228,6 +251,18 @@ impl PySetInner {
228251 Self :: wrap_unhashable_error ( result, needle, vm)
229252 }
230253
254+ /// [`Self::contains`] for a needle whose hash was read from another
255+ /// set/dict. Such a needle is hashable by construction, so there is no
256+ /// set-to-frozenset retry to do.
257+ fn contains_with_hash (
258+ & self ,
259+ needle : & PyObject ,
260+ hash : PyHash ,
261+ vm : & VirtualMachine ,
262+ ) -> PyResult < bool > {
263+ self . content . contains_with_hash ( vm, needle, hash)
264+ }
265+
231266 fn compare ( & self , other : & Self , op : PyComparisonOp , vm : & VirtualMachine ) -> PyResult < bool > {
232267 if op == PyComparisonOp :: Ne {
233268 return self . compare ( other, PyComparisonOp :: Eq , vm) . map ( |eq| !eq) ;
@@ -251,6 +286,12 @@ impl PySetInner {
251286
252287 pub ( super ) fn union ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
253288 let set = self . clone ( ) ;
289+ if let Some ( elements) = Self :: cached_hashes ( other. as_object ( ) , vm) {
290+ for ( item, hash) in elements {
291+ set. add_with_hash ( item, hash, vm) ?;
292+ }
293+ return Ok ( set) ;
294+ }
254295 for item in other. iter ( vm) ? {
255296 set. add ( item?, vm) ?;
256297 }
@@ -260,6 +301,14 @@ impl PySetInner {
260301
261302 pub ( super ) fn intersection ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
262303 let set = Self :: default ( ) ;
304+ if let Some ( elements) = Self :: cached_hashes ( other. as_object ( ) , vm) {
305+ for ( obj, hash) in elements {
306+ if self . contains_with_hash ( & obj, hash, vm) ? {
307+ set. add_with_hash ( obj, hash, vm) ?;
308+ }
309+ }
310+ return Ok ( set) ;
311+ }
263312 for item in other. iter ( vm) ? {
264313 let obj = item?;
265314 if self . contains ( & obj, vm) ? {
@@ -271,6 +320,12 @@ impl PySetInner {
271320
272321 pub ( super ) fn difference ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
273322 let set = self . copy ( ) ;
323+ if let Some ( elements) = Self :: cached_hashes ( other. as_object ( ) , vm) {
324+ for ( item, hash) in elements {
325+ set. content . delete_if_exists_with_hash ( vm, & * item, hash) ?;
326+ }
327+ return Ok ( set) ;
328+ }
274329 for item in other. iter ( vm) ? {
275330 set. content . delete_if_exists ( vm, & * item?) ?;
276331 }
@@ -284,6 +339,16 @@ impl PySetInner {
284339 ) -> PyResult < Self > {
285340 let new_inner = self . clone ( ) ;
286341
342+ if let Some ( elements) = Self :: cached_hashes ( other. as_object ( ) , vm) {
343+ // the source is already duplicate-free
344+ for ( item, hash) in elements {
345+ new_inner
346+ . content
347+ . delete_or_insert_with_hash ( vm, & item, hash, ( ) ) ?;
348+ }
349+ return Ok ( new_inner) ;
350+ }
351+
287352 // We want to remove duplicates in other
288353 let other_set = Self :: from_iter ( other. iter ( vm) ?, vm) ?;
289354
@@ -333,6 +398,12 @@ impl PySetInner {
333398 Self :: wrap_unhashable_error ( result, & item, vm)
334399 }
335400
401+ /// [`Self::add`] for an item whose hash was read from another set/dict.
402+ fn add_with_hash ( & self , item : PyObjectRef , hash : PyHash , vm : & VirtualMachine ) -> PyResult < ( ) > {
403+ let result = self . content . insert_with_hash ( vm, & * item, hash, ( ) ) ;
404+ Self :: wrap_unhashable_error ( result, & item, vm)
405+ }
406+
336407 fn remove ( & self , item : PyObjectRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
337408 let result =
338409 self . retry_op_with_frozenset ( & item, vm, |item, vm| self . content . delete ( vm, item) ) ;
@@ -393,15 +464,15 @@ impl PySetInner {
393464 }
394465
395466 fn merge_set ( & self , any_set : AnySet , vm : & VirtualMachine ) -> PyResult < ( ) > {
396- for item in any_set. as_inner ( ) . elements ( ) {
397- self . add ( item, vm) ?;
467+ for ( item, hash ) in any_set. as_inner ( ) . content . keys_with_hashes ( ) {
468+ self . add_with_hash ( item, hash , vm) ?;
398469 }
399470 Ok ( ( ) )
400471 }
401472
402473 fn merge_dict ( & self , dict : PyDictRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
403- for ( key, _value ) in dict {
404- self . add ( key, vm) ?;
474+ for ( key, hash ) in dict. _as_dict_inner ( ) . keys_with_hashes ( ) {
475+ self . add_with_hash ( key, hash , vm) ?;
405476 }
406477 Ok ( ( ) )
407478 }
@@ -413,8 +484,8 @@ impl PySetInner {
413484 ) -> PyResult < ( ) > {
414485 let temp_inner = self . fold_op ( others, Self :: intersection, vm) ?;
415486 self . clear ( ) ;
416- for obj in temp_inner. elements ( ) {
417- self . add ( obj, vm) ?;
487+ for ( obj, hash ) in temp_inner. content . keys_with_hashes ( ) {
488+ self . add_with_hash ( obj, hash , vm) ?;
418489 }
419490 Ok ( ( ) )
420491 }
@@ -425,6 +496,12 @@ impl PySetInner {
425496 vm : & VirtualMachine ,
426497 ) -> PyResult < ( ) > {
427498 for iterable in others {
499+ if let Some ( elements) = Self :: cached_hashes ( iterable. as_object ( ) , vm) {
500+ for ( item, hash) in elements {
501+ self . content . delete_if_exists_with_hash ( vm, & * item, hash) ?;
502+ }
503+ continue ;
504+ }
428505 let items = iterable. iter ( vm) ?. collect :: < Result < Vec < _ > , _ > > ( ) ?;
429506 for item in items {
430507 self . content . delete_if_exists ( vm, & * item) ?;
@@ -439,6 +516,14 @@ impl PySetInner {
439516 vm : & VirtualMachine ,
440517 ) -> PyResult < ( ) > {
441518 for iterable in others {
519+ if let Some ( elements) = Self :: cached_hashes ( iterable. as_object ( ) , vm) {
520+ // the source is already duplicate-free
521+ for ( item, hash) in elements {
522+ self . content
523+ . delete_or_insert_with_hash ( vm, & item, hash, ( ) ) ?;
524+ }
525+ continue ;
526+ }
442527 // We want to remove duplicates in iterable
443528 let iterable_set = Self :: from_iter ( iterable. iter ( vm) ?, vm) ?;
444529 for item in iterable_set. elements ( ) {
@@ -955,7 +1040,7 @@ impl Representable for PySet {
9551040}
9561041
9571042impl Constructor for PyFrozenSet {
958- type Args = Vec < PyObjectRef > ;
1043+ type Args = OptionalArg < PyObjectRef > ;
9591044
9601045 fn slot_new ( cls : PyTypeRef , args : FuncArgs , vm : & VirtualMachine ) -> PyResult {
9611046 let is_exact_frozenset = cls. is ( vm. ctx . types . frozenset_type ) ;
@@ -988,11 +1073,11 @@ impl Constructor for PyFrozenSet {
9881073 return Ok ( input. clone ( ) ) ;
9891074 }
9901075
991- iterable. into_option ( )
1076+ iterable
9921077 } else {
9931078 match & args. args [ ..] {
994- [ ] => None ,
995- [ iterable] => Some ( iterable. clone ( ) ) ,
1079+ [ ] => OptionalArg :: Missing ,
1080+ [ iterable] => OptionalArg :: Present ( iterable. clone ( ) ) ,
9961081 slice => {
9971082 return Err ( vm. new_type_error ( format ! (
9981083 "frozenset expected at most 1 argument, got {}" ,
@@ -1002,23 +1087,27 @@ impl Constructor for PyFrozenSet {
10021087 }
10031088 } ;
10041089
1005- let elements = if let Some ( iterable) = iterable_opt {
1006- iterable. try_to_value ( vm) ?
1007- } else {
1008- vec ! [ ]
1009- } ;
1090+ let payload = Self :: py_new ( & cls, iterable_opt, vm) ?;
10101091
10111092 // Return empty frozenset singleton
1012- if is_exact_frozenset && elements . is_empty ( ) {
1093+ if is_exact_frozenset && payload . inner . len ( ) == 0 {
10131094 return Ok ( vm. ctx . empty_frozenset . clone ( ) . into ( ) ) ;
10141095 }
10151096
1016- let payload = Self :: py_new ( & cls, elements, vm) ?;
10171097 payload. into_ref_with_type ( vm, cls) . map ( Into :: into)
10181098 }
10191099
1020- fn py_new ( _cls : & Py < PyType > , elements : Self :: Args , vm : & VirtualMachine ) -> PyResult < Self > {
1021- Self :: from_iter ( vm, elements)
1100+ fn py_new ( _cls : & Py < PyType > , iterable : Self :: Args , vm : & VirtualMachine ) -> PyResult < Self > {
1101+ // built from the object itself, so a set/frozenset/dict source can
1102+ // hand over its stored hashes instead of being re-hashed
1103+ let inner = match iterable {
1104+ OptionalArg :: Present ( iterable) => PySetInner :: from_object ( iterable, vm) ?,
1105+ OptionalArg :: Missing => PySetInner :: default ( ) ,
1106+ } ;
1107+ Ok ( Self {
1108+ inner,
1109+ ..Default :: default ( )
1110+ } )
10221111 }
10231112}
10241113
0 commit comments