Skip to content

Commit b6c253d

Browse files
committed
Register partial vectorcall through pyslot
Use the existing slot macro instead of a class extension hook. Return TypeError for non-string incoming names in the non-vectorcall fallback rather than panicking. Assisted-by: OpenAI Codex:model version unavailable
1 parent 5cd074a commit b6c253d

1 file changed

Lines changed: 98 additions & 102 deletions

File tree

‎crates/vm/src/stdlib/_functools.rs‎

Lines changed: 98 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)