@@ -278,9 +278,104 @@ mod _functools {
278278 flags( BASETYPE , HAS_DICT , HAS_WEAKREF )
279279 ) ]
280280 impl PyPartial {
281- #[ extend_class]
282- fn extend_pyclass ( _ctx : & Context , class : & ' static Py < PyType > ) {
283- class. slots . vectorcall . store ( Some ( Self :: vectorcall) ) ;
281+ #[ pyslot]
282+ fn vectorcall (
283+ zelf : & PyObject ,
284+ args : Vec < PyObjectRef > ,
285+ nargs : usize ,
286+ kwnames : Option < & [ PyObjectRef ] > ,
287+ vm : & VirtualMachine ,
288+ ) -> PyResult {
289+ let zelf = zelf. downcast_ref :: < Self > ( ) . unwrap ( ) ;
290+ // Keep the call's state alive before keyword lookups can execute
291+ // __hash__/__eq__ and replace it through __setstate__.
292+ let func = zelf. func . load_owned ( ) ;
293+ let stored_args = zelf. args . load_owned ( ) ;
294+ let keywords = zelf. keywords . load_owned ( ) ;
295+ let phcount = zelf. phcount . load ( Ordering :: Relaxed ) ;
296+
297+ // Check if we have enough args to fill placeholders
298+ if nargs < phcount {
299+ return Err ( vm. new_type_error ( format ! (
300+ "missing positional arguments in 'partial' call; expected at least {phcount}, got {nargs}"
301+ ) ) ) ;
302+ }
303+
304+ // Build combined args, replacing placeholders
305+ let mut combined_args = Vec :: with_capacity ( stored_args. as_slice ( ) . len ( ) + args. len ( ) ) ;
306+ let mut new_args_iter = args[ ..nargs] . iter ( ) ;
307+
308+ for stored_arg in stored_args. as_slice ( ) {
309+ if is_placeholder ( stored_arg) {
310+ // Replace placeholder with next new arg
311+ if let Some ( new_arg) = new_args_iter. next ( ) {
312+ combined_args. push ( new_arg. clone ( ) ) ;
313+ } else {
314+ // This shouldn't happen if phcount check passed
315+ combined_args. push ( stored_arg. clone ( ) ) ;
316+ }
317+ } else {
318+ combined_args. push ( stored_arg. clone ( ) ) ;
319+ }
320+ }
321+ // Append remaining new args
322+ combined_args. extend ( new_args_iter. cloned ( ) ) ;
323+ let combined_nargs = combined_args. len ( ) ;
324+
325+ let kwnames = kwnames. unwrap_or_default ( ) ;
326+ if func. class ( ) . slots . vectorcall . load ( ) . is_none ( ) {
327+ // FuncArgs callables retain their existing keyword binding path.
328+ // In particular, do not introduce vectorcall's hash callbacks
329+ // when the wrapped callable does not support vectorcall.
330+ let mut kwargs = KwArgs :: default ( ) ;
331+ for ( key, value) in keywords. items_vec ( ) {
332+ let key = key
333+ . downcast_ref :: < PyStr > ( )
334+ . ok_or_else ( || vm. new_type_error ( "keywords must be strings" ) ) ?;
335+ kwargs. insert ( key. as_wtf8 ( ) . to_owned ( ) , value) ;
336+ }
337+ for ( key, value) in kwnames. iter ( ) . zip ( & args[ nargs..] ) {
338+ let key = key
339+ . downcast_ref :: < PyStr > ( )
340+ . ok_or_else ( || vm. new_type_error ( "keywords must be strings" ) ) ?;
341+ kwargs. insert ( key. as_wtf8 ( ) . to_owned ( ) , value. clone ( ) ) ;
342+ }
343+ return func. call ( FuncArgs :: new ( combined_args, kwargs) , vm) ;
344+ }
345+ if keywords. is_empty ( ) {
346+ combined_args. extend_from_slice ( & args[ nargs..] ) ;
347+ return func. vectorcall ( combined_args, combined_nargs, Some ( kwnames) , vm) ;
348+ }
349+
350+ // Like partial_vectorcall, copy stored keywords only when an incoming
351+ // key replaces one. New keys remain a tail, preserving their identity
352+ // and avoiding extra hash callbacks from an intermediate dictionary.
353+ let mut merged_keywords = None ;
354+ let mut tail_names = Vec :: new ( ) ;
355+ let mut tail_values = Vec :: new ( ) ;
356+ for ( key, value) in kwnames. iter ( ) . zip ( & args[ nargs..] ) {
357+ let hash = PyDict :: hash_or_unhashable ( & * * key, vm) ?;
358+ if keywords. entries . contains ( vm, & * * key, hash) ? {
359+ let merged = merged_keywords. get_or_insert_with ( || keywords. copy ( ) ) ;
360+ merged. inner_setitem ( & * * key, value. clone ( ) , vm) ?;
361+ } else {
362+ tail_names. push ( key. clone ( ) ) ;
363+ tail_values. push ( value. clone ( ) ) ;
364+ }
365+ }
366+
367+ let merged = merged_keywords. as_ref ( ) . unwrap_or ( & keywords) ;
368+ let mut combined_names = Vec :: with_capacity ( merged. __len__ ( ) + tail_names. len ( ) ) ;
369+ for ( key, value) in merged. items_vec ( ) {
370+ key. downcast_ref :: < PyStr > ( )
371+ . ok_or_else ( || vm. new_type_error ( "keywords must be strings" ) ) ?;
372+ combined_names. push ( key) ;
373+ combined_args. push ( value) ;
374+ }
375+ combined_names. extend ( tail_names) ;
376+ combined_args. extend ( tail_values) ;
377+
378+ func. vectorcall ( combined_args, combined_nargs, Some ( & combined_names) , vm)
284379 }
285380
286381 #[ pygetset]
@@ -551,105 +646,6 @@ mod _functools {
551646 }
552647 }
553648
554- impl PyPartial {
555- fn vectorcall (
556- zelf : & PyObject ,
557- args : Vec < PyObjectRef > ,
558- nargs : usize ,
559- kwnames : Option < & [ PyObjectRef ] > ,
560- vm : & VirtualMachine ,
561- ) -> PyResult {
562- let zelf = zelf. downcast_ref :: < Self > ( ) . unwrap ( ) ;
563- // Keep the call's state alive before keyword lookups can execute
564- // __hash__/__eq__ and replace it through __setstate__.
565- let func = zelf. func . load_owned ( ) ;
566- let stored_args = zelf. args . load_owned ( ) ;
567- let keywords = zelf. keywords . load_owned ( ) ;
568- let phcount = zelf. phcount . load ( Ordering :: Relaxed ) ;
569-
570- // Check if we have enough args to fill placeholders
571- if nargs < phcount {
572- return Err ( vm. new_type_error ( format ! (
573- "missing positional arguments in 'partial' call; expected at least {phcount}, got {nargs}"
574- ) ) ) ;
575- }
576-
577- // Build combined args, replacing placeholders
578- let mut combined_args = Vec :: with_capacity ( stored_args. as_slice ( ) . len ( ) + args. len ( ) ) ;
579- let mut new_args_iter = args[ ..nargs] . iter ( ) ;
580-
581- for stored_arg in stored_args. as_slice ( ) {
582- if is_placeholder ( stored_arg) {
583- // Replace placeholder with next new arg
584- if let Some ( new_arg) = new_args_iter. next ( ) {
585- combined_args. push ( new_arg. clone ( ) ) ;
586- } else {
587- // This shouldn't happen if phcount check passed
588- combined_args. push ( stored_arg. clone ( ) ) ;
589- }
590- } else {
591- combined_args. push ( stored_arg. clone ( ) ) ;
592- }
593- }
594- // Append remaining new args
595- combined_args. extend ( new_args_iter. cloned ( ) ) ;
596- let combined_nargs = combined_args. len ( ) ;
597-
598- let kwnames = kwnames. unwrap_or_default ( ) ;
599- if func. class ( ) . slots . vectorcall . load ( ) . is_none ( ) {
600- // FuncArgs callables retain their existing keyword binding path.
601- // In particular, do not introduce vectorcall's hash callbacks
602- // when the wrapped callable does not support vectorcall.
603- let mut kwargs = KwArgs :: default ( ) ;
604- for ( key, value) in keywords. items_vec ( ) {
605- let key = key
606- . downcast_ref :: < PyStr > ( )
607- . ok_or_else ( || vm. new_type_error ( "keywords must be strings" ) ) ?;
608- kwargs. insert ( key. as_wtf8 ( ) . to_owned ( ) , value) ;
609- }
610- for ( key, value) in kwnames. iter ( ) . zip ( & args[ nargs..] ) {
611- let key = key. downcast_ref :: < PyStr > ( ) . unwrap ( ) ;
612- kwargs. insert ( key. as_wtf8 ( ) . to_owned ( ) , value. clone ( ) ) ;
613- }
614- return func. call ( FuncArgs :: new ( combined_args, kwargs) , vm) ;
615- }
616- if keywords. is_empty ( ) {
617- combined_args. extend_from_slice ( & args[ nargs..] ) ;
618- return func. vectorcall ( combined_args, combined_nargs, Some ( kwnames) , vm) ;
619- }
620-
621- // Like partial_vectorcall, copy stored keywords only when an incoming
622- // key replaces one. New keys remain a tail, preserving their identity
623- // and avoiding extra hash callbacks from an intermediate dictionary.
624- let mut merged_keywords = None ;
625- let mut tail_names = Vec :: new ( ) ;
626- let mut tail_values = Vec :: new ( ) ;
627- for ( key, value) in kwnames. iter ( ) . zip ( & args[ nargs..] ) {
628- let hash = PyDict :: hash_or_unhashable ( & * * key, vm) ?;
629- if keywords. entries . contains ( vm, & * * key, hash) ? {
630- let merged = merged_keywords. get_or_insert_with ( || keywords. copy ( ) ) ;
631- merged. inner_setitem ( & * * key, value. clone ( ) , vm) ?;
632- } else {
633- tail_names. push ( key. clone ( ) ) ;
634- tail_values. push ( value. clone ( ) ) ;
635- }
636- }
637-
638- let merged = merged_keywords. as_ref ( ) . unwrap_or ( & keywords) ;
639- let mut combined_names = Vec :: with_capacity ( merged. __len__ ( ) + tail_names. len ( ) ) ;
640- for ( key, value) in merged. items_vec ( ) {
641- key. downcast_ref :: < PyStr > ( )
642- . ok_or_else ( || vm. new_type_error ( "keywords must be strings" ) ) ?;
643- combined_names. push ( key) ;
644- combined_args. push ( value) ;
645- }
646- combined_names. extend ( tail_names) ;
647- combined_args. extend ( tail_values) ;
648-
649- func. vectorcall ( combined_args, combined_nargs, Some ( & combined_names) , vm)
650- }
651- }
652-
653649 impl GetDescriptor for PyPartial {
654650 fn descr_get (
655651 zelf : & PyObject ,
0 commit comments