@@ -233,16 +233,20 @@ impl PyNativeFunction {
233233 }
234234}
235235
236- // PyCMethodObject in CPython
237- #[ pyclass( name = "builtin_function_or_method" , module = false , base = PyNativeFunction , ctx = "builtin_function_or_method_type" ) ]
236+ // Bound METH_METHOD object. The payload starts with PyNativeFunction so it can
237+ // be read as a builtin function. The Python type is `builtin_method`.
238+ #[ repr( C ) ]
239+ #[ pyclass(
240+ name = "builtin_method" ,
241+ module = false ,
242+ base = PyNativeFunction ,
243+ ctx = "builtin_method_type"
244+ ) ]
238245pub struct PyNativeMethod {
239246 pub ( crate ) func : PyNativeFunction ,
240- pub ( crate ) class : & ' static Py < PyType > , // TODO: the actual life is &'self
247+ pub ( crate ) class : & ' static Py < PyType > ,
241248}
242249
243- // All Python-visible behavior (getters, slots) is registered by PyNativeFunction::extend_class.
244- // PyNativeMethod only extends the Rust-side struct with the defining class reference.
245- // The func field at offset 0 (#[repr(C)]) allows NativeFunctionOrMethod to read it safely.
246250#[ pyclass( flags( HAS_WEAKREF , DISALLOW_INSTANTIATION ) ) ]
247251impl PyNativeMethod { }
248252
@@ -296,6 +300,7 @@ pub(crate) fn init(context: &'static Context) {
296300 . slots
297301 . vectorcall
298302 . store ( Some ( vectorcall_native_function) ) ;
303+ PyNativeMethod :: extend_class ( context, context. types . builtin_method_type ) ;
299304}
300305
301306/// Wrapper that provides access to the common PyNativeFunction data
@@ -306,12 +311,59 @@ impl TryFromObject for NativeFunctionOrMethod {
306311 fn try_from_object ( vm : & VirtualMachine , obj : PyObjectRef ) -> PyResult < Self > {
307312 let class = vm. ctx . types . builtin_function_or_method_type ;
308313 if obj. fast_isinstance ( class) {
309- // Both PyNativeFunction and PyNativeMethod share the same type now.
310- // PyNativeMethod has `func: PyNativeFunction` as its first field,
311- // so we can safely treat the data pointer as PyNativeFunction for reading.
314+ // `builtin_method` is a subclass; the payload starts with PyNativeFunction.
312315 Ok ( Self ( unsafe { obj. downcast_unchecked ( ) } ) )
313316 } else {
314317 Err ( vm. new_downcast_type_error ( class, & obj) )
315318 }
316319 }
317320}
321+
322+ #[ cfg( test) ]
323+ mod tests {
324+ use super :: * ;
325+ use crate :: {
326+ Interpreter , PyObjectRef ,
327+ function:: { PyMethodDef , PyMethodFlags } ,
328+ } ;
329+
330+ #[ test]
331+ fn builtin_method_is_subclass_of_builtin_function ( ) {
332+ Interpreter :: without_stdlib ( Default :: default ( ) ) . enter ( |vm| {
333+ let function_type = vm. ctx . types . builtin_function_or_method_type ;
334+ let method_type = vm. ctx . types . builtin_method_type ;
335+ assert ! ( !function_type. is( method_type) ) ;
336+ assert_eq ! ( & * function_type. name( ) , "builtin_function_or_method" ) ;
337+ assert_eq ! ( & * method_type. name( ) , "builtin_method" ) ;
338+ assert ! ( method_type. fast_issubclass( function_type) ) ;
339+ } ) ;
340+ }
341+
342+ #[ test]
343+ fn bound_instance_method_uses_function_type ( ) {
344+ fn identity ( value : PyObjectRef ) -> PyObjectRef {
345+ value
346+ }
347+ const DEF : PyMethodDef = PyMethodDef :: new_const (
348+ "identity" ,
349+ identity,
350+ PyMethodFlags :: METHOD ,
351+ crate :: function:: ItemDoc :: NONE ,
352+ ) ;
353+
354+ Interpreter :: without_stdlib ( Default :: default ( ) ) . enter ( |vm| {
355+ let bound = DEF . build_bound_function ( & vm. ctx , vm. ctx . none ( ) ) ;
356+ assert ! (
357+ bound
358+ . class( )
359+ . is( vm. ctx. types. builtin_function_or_method_type)
360+ ) ;
361+ assert ! ( !bound. as_object( ) . downcastable:: <PyNativeMethod >( ) ) ;
362+
363+ let method = DEF . build_bound_method ( & vm. ctx , vm. ctx . none ( ) , vm. ctx . types . object_type ) ;
364+ assert ! ( method. class( ) . is( vm. ctx. types. builtin_method_type) ) ;
365+ assert ! ( method. as_object( ) . downcastable:: <PyNativeFunction >( ) ) ;
366+ assert ! ( method. as_object( ) . downcastable:: <PyNativeMethod >( ) ) ;
367+ } ) ;
368+ }
369+ }
0 commit comments