From 94f8b2b77892135d8d6fd8fee8dad5548ea157d7 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Wed, 21 Oct 2026 22:04:06 +0900 Subject: [PATCH] Match canonical static builtin profiler labels and metadata Normalize the raw builtin receiver instead of its call-argument accessor. Snapshot static method module metadata at construction, matching CPython, so explicit module mutation and deletion remain observable. Avoid a profiler-time fallback that would obscure deleted metadata. Correct float.__getformat__ to CPython's classmethod calling convention, including subclass binding, rather than special-casing its profiler label. Assisted-by: dot:GPT-6.1 --- crates/vm/src/builtins/float.rs | 8 ++++++-- crates/vm/src/function/method.rs | 16 +++++++++++++++- crates/vm/src/stdlib/_lsprof.rs | 8 +++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index de38a89fa..0a753446a 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -305,8 +305,12 @@ impl Py { }) } - #[pystaticmethod] - fn __getformat__(typestr: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { + #[pyclassmethod] + fn __getformat__( + _cls: PyTypeRef, + typestr: PyUtf8StrRef, + vm: &VirtualMachine, + ) -> PyResult { if !matches!(typestr.as_str(), "double" | "float") { return Err( vm.new_value_error("__getformat__() argument 1 must be 'double' or 'float'") diff --git a/crates/vm/src/function/method.rs b/crates/vm/src/function/method.rs index d586c099b..8a186ebd6 100644 --- a/crates/vm/src/function/method.rs +++ b/crates/vm/src/function/method.rs @@ -7,6 +7,7 @@ use crate::{ }, class::PyClassDef, function::{FuncArgs, IntoPyNativeFn, PyNativeFn, SigArg}, + types::PyTypeFlags, }; bitflags::bitflags! { @@ -294,8 +295,21 @@ impl PyMethodDef { class: &'static Py, ) -> PyRef { debug_assert!(self.flags.contains(PyMethodFlags::STATIC)); + // Match PyCFunction_NewEx(meth, type, type_module(type)): snapshot + // module metadata here so later mutation/deletion remains observable. + let module = if class.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + class.attributes.get(ctx.intern_str("__module__")) + } else { + let name = class.slot_name(); + let module = name + .rsplit_once('.') + .map_or("builtins", |(module, _)| module); + Some(ctx.intern_str(module).to_owned().into()) + }; // m_self is the type; STATIC skips prepending it on call. - self.build_bound_function(ctx, class.to_owned().into()) + let function = self.build_bound_function(ctx, class.to_owned().into()); + drop(function.module.store(module)); + function } /// Concatenate method groups. A pending body is copied from `docs`, then cleared. diff --git a/crates/vm/src/stdlib/_lsprof.rs b/crates/vm/src/stdlib/_lsprof.rs index 1c77e0fbb..8d1b5173f 100644 --- a/crates/vm/src/stdlib/_lsprof.rs +++ b/crates/vm/src/stdlib/_lsprof.rs @@ -456,8 +456,14 @@ mod _lsprof { Self::Function(function) => function, }; let name = function.value.name; + // CPython normalizes the raw m_self, including STATIC's hidden + // type receiver, rather than its call-argument accessor. + let receiver = function + .zelf + .as_deref() + .or(function.module_object.as_deref()); let module = function.module.load_owned(); - let label = if let Some(receiver) = function.get_self() { + let label = if let Some(receiver) = receiver { if let Some(descriptor) = receiver.class().get_attr(vm.ctx.intern_str(name)) && let Ok(label) = descriptor.repr(vm) {