@@ -1216,8 +1216,6 @@ where
12161216 // Constructor/Initializer traits
12171217 ( "__new__" , "Constructor" ) ,
12181218 ( "__init__" , "Initializer" ) ,
1219- // Representable trait
1220- ( "__repr__" , "Representable" ) ,
12211219 // ("__str__", "???"), // allow __str__
12221220 // Hashable trait
12231221 ( "__hash__" , "Hashable" ) ,
@@ -1342,37 +1340,45 @@ where
13421340 args. attrs . push ( allow_attr) ;
13431341 }
13441342
1345- // __str__ is a single-method protocol : register its implementation as a
1343+ // __str__ and __repr__ are single-method protocols : register its implementation as a
13461344 // slot and let add_operators expose the usual wrapper_descriptor. Other
13471345 // protocols still use their traits. Reuse native argument and result
13481346 // conversion so this keeps the same Rust signatures as a normal method.
1349- if py_name == "__str__" && self . inner . attr_name == AttrName :: Method && !coexist {
1347+ if matches ! ( py_name. as_str( ) , "__str__" | "__repr__" )
1348+ && self . inner . attr_name == AttrName :: Method
1349+ && !coexist
1350+ {
1351+ let slot_name = py_name. trim_matches ( '_' ) ;
1352+ let slot_ident = Ident :: new ( slot_name, ident. span ( ) ) ;
1353+ let return_error = format ! ( "{py_name} returned non-string (type {{}})" ) ;
13501354 if raw {
13511355 return Err ( syn:: Error :: new_spanned (
13521356 item_attr,
1353- "#[pymethod(raw)] cannot define '__str__'; use #[pyslot(str)] instead" ,
1357+ format ! (
1358+ "#[pymethod(raw)] cannot define '{py_name}'; use #[pyslot({slot_name})] instead"
1359+ ) ,
13541360 ) ) ;
13551361 }
13561362 let slot_tokens = quote_spanned ! { ident. span( ) =>
1357- slots. str . store( Some ( |zelf, vm| {
1363+ slots. #slot_ident . store( Some ( |zelf, vm| {
13581364 use :: rustpython_vm:: { AsObject as _, function:: IntoPyNativeFn } ;
13591365 let result = IntoPyNativeFn :: call(
13601366 & Self :: #ident,
13611367 vm,
13621368 vec![ zelf. to_owned( ) ] . into( ) ,
1363- :: rustpython_vm:: function:: Callee :: named( "__str__" ) . with_instance_arg( true ) ,
1369+ :: rustpython_vm:: function:: Callee :: named( #py_name ) . with_instance_arg( true ) ,
13641370 ) ?;
13651371 result. downcast:: <:: rustpython_vm:: builtins:: PyStr >( ) . map_err( |obj| {
13661372 vm. new_type_error( format!(
1367- "__str__ returned non-string (type {})" ,
1373+ #return_error ,
13681374 obj. class( ) . slot_name( )
13691375 ) )
13701376 } )
13711377 } ) ) ;
13721378 } ;
13731379 args. context . extend_slots_items . add_item (
13741380 ident. clone ( ) ,
1375- vec ! [ "(slot str)" . to_string ( ) ] ,
1381+ vec ! [ format! ( "(slot {slot_name})" ) ] ,
13761382 args. cfgs . to_vec ( ) ,
13771383 slot_tokens,
13781384 2 ,
@@ -2883,7 +2889,7 @@ fn parse_vec_ident(
28832889}
28842890
28852891#[ cfg( test) ]
2886- mod str_slot_tests {
2892+ mod string_slot_tests {
28872893 use super :: * ;
28882894
28892895 fn expand ( item : Item ) -> String {
@@ -2892,48 +2898,62 @@ mod str_slot_tests {
28922898 . to_string ( )
28932899 }
28942900
2901+ fn protocols ( ) -> [ ( Ident , & ' static str ) ; 2 ] {
2902+ [ ( "__str__" , "str" ) , ( "__repr__" , "repr" ) ]
2903+ . map ( |( method, slot) | ( Ident :: new ( method, Span :: call_site ( ) ) , slot) )
2904+ }
2905+
28952906 #[ test]
2896- fn str_alias_uses_native_conversion_without_method_entry ( ) {
2897- let tokens = expand ( parse_quote ! {
2898- impl Py <Example > {
2899- #[ pymethod( name = "__str__" ) ]
2900- fn display( & self ) -> String { String :: new( ) }
2901- }
2902- } ) ;
2903- assert ! ( tokens. contains( "slots . str . store" ) ) ;
2904- assert ! ( tokens. contains( "& Self :: display" ) ) ;
2905- assert ! ( tokens. contains( "IntoPyNativeFn" ) ) ;
2906- assert ! ( !tokens. contains( "PyMethodDef :: new_const" ) ) ;
2907- assert ! ( !tokens. contains( "fn slot_str" ) ) ;
2907+ fn alias_uses_native_conversion_without_method_entry ( ) {
2908+ for ( method, slot) in protocols ( ) {
2909+ let method = method. to_string ( ) ;
2910+ let tokens = expand ( parse_quote ! {
2911+ impl Py <Example > {
2912+ #[ pymethod( name = #method) ]
2913+ fn display( & self ) -> String { String :: new( ) }
2914+ }
2915+ } ) ;
2916+ assert ! ( tokens. contains( & format!( "slots . {slot} . store" ) ) ) ;
2917+ assert ! ( tokens. contains( "& Self :: display" ) ) ;
2918+ assert ! ( tokens. contains( "IntoPyNativeFn" ) ) ;
2919+ assert ! ( !tokens. contains( "PyMethodDef :: new_const" ) ) ;
2920+ assert ! ( !tokens. contains( & format!( "fn slot_{slot}" ) ) ) ;
2921+ }
29082922 }
29092923
29102924 #[ test]
2911- fn str_slot_preserves_cfg_on_assignment ( ) {
2912- let tokens = expand ( parse_quote ! {
2913- impl Py <Example > {
2914- #[ cfg( feature = "example" ) ]
2915- #[ pymethod]
2916- fn __str__( & self ) -> String { String :: new( ) }
2917- }
2918- } ) ;
2919- assert ! ( tokens. contains( "# [cfg (feature = \" example\" )] { slots . str . store" ) ) ;
2920- assert_eq ! ( tokens. matches( "cfg (feature = \" example\" )" ) . count( ) , 2 ) ;
2921- assert ! ( !tokens. contains( "fn slot_str" ) ) ;
2925+ fn slot_preserves_cfg_on_assignment ( ) {
2926+ for ( method, slot) in protocols ( ) {
2927+ let tokens = expand ( parse_quote ! {
2928+ impl Py <Example > {
2929+ #[ cfg( feature = "example" ) ]
2930+ #[ pymethod]
2931+ fn #method( & self ) -> String { String :: new( ) }
2932+ }
2933+ } ) ;
2934+ assert ! ( tokens. contains( & format!(
2935+ "# [cfg (feature = \" example\" )] {{ slots . {slot} . store"
2936+ ) ) ) ;
2937+ assert_eq ! ( tokens. matches( "cfg (feature = \" example\" )" ) . count( ) , 2 ) ;
2938+ assert ! ( !tokens. contains( & format!( "fn slot_{slot}" ) ) ) ;
2939+ }
29222940 }
29232941
29242942 #[ test]
2925- fn trait_str_is_registered_inside_extend_slots ( ) {
2926- let tokens = expand ( parse_quote ! {
2927- trait Display : PyPayload {
2928- #[ pymethod]
2929- fn __str__( zelf: & Py <Self >) -> String { String :: new( ) }
2930- }
2931- } ) ;
2932- assert ! ( tokens. contains( "fn __extend_slots" ) ) ;
2933- assert ! ( tokens. contains( "slots . str . store" ) ) ;
2934- assert ! ( tokens. contains( "& Self :: __str__" ) ) ;
2935- assert ! ( !tokens. contains( "fn slot_str" ) ) ;
2936- assert ! ( !tokens. contains( "PyMethodDef :: new_const" ) ) ;
2943+ fn trait_method_is_registered_inside_extend_slots ( ) {
2944+ for ( method, slot) in protocols ( ) {
2945+ let tokens = expand ( parse_quote ! {
2946+ trait Display : PyPayload {
2947+ #[ pymethod]
2948+ fn #method( zelf: & Py <Self >) -> String { String :: new( ) }
2949+ }
2950+ } ) ;
2951+ assert ! ( tokens. contains( "fn __extend_slots" ) ) ;
2952+ assert ! ( tokens. contains( & format!( "slots . {slot} . store" ) ) ) ;
2953+ assert ! ( tokens. contains( & format!( "& Self :: {method}" ) ) ) ;
2954+ assert ! ( !tokens. contains( & format!( "fn slot_{slot}" ) ) ) ;
2955+ assert ! ( !tokens. contains( "PyMethodDef :: new_const" ) ) ;
2956+ }
29372957
29382958 let consumer = impl_pyclass_impl (
29392959 parse_quote ! ( with( Display ) ) ,
@@ -2945,76 +2965,86 @@ mod str_slot_tests {
29452965 }
29462966
29472967 #[ test]
2948- fn repr_still_requires_protocol_trait ( ) {
2949- let tokens = expand ( parse_quote ! {
2950- impl Py <Example > {
2951- #[ pymethod]
2952- fn __repr__( & self ) -> String { String :: new( ) }
2968+ fn method_is_registered_as_slot ( ) {
2969+ for ( method, slot) in protocols ( ) {
2970+ let tokens = expand ( parse_quote ! {
2971+ impl Py <Example > {
2972+ #[ pymethod]
2973+ fn #method( & self ) -> String { String :: new( ) }
2974+ }
2975+ } ) ;
2976+ assert ! ( !tokens. contains( "compile_error" ) ) ;
2977+ assert ! ( tokens. contains( & format!( "slots . {slot} . store" ) ) ) ;
2978+ assert ! ( tokens. contains( & format!( "{method} returned non-string (type {{}})" ) ) ) ;
2979+ assert ! ( !tokens. contains( "PyMethodDef :: new_const" ) ) ;
2980+ }
2981+ }
2982+
2983+ #[ test]
2984+ fn class_and_static_methods_remain_methods ( ) {
2985+ for ( method, slot) in protocols ( ) {
2986+ for attribute in [ quote ! ( pyclassmethod) , quote ! ( pystaticmethod) ] {
2987+ let tokens = expand ( parse_quote ! {
2988+ impl Py <Example > {
2989+ #[ #attribute]
2990+ fn #method( ) -> String { String :: new( ) }
2991+ }
2992+ } ) ;
2993+ assert ! ( tokens. contains( "PyMethodDef :: new_const" ) ) ;
2994+ assert ! ( !tokens. contains( & format!( "slots . {slot} . store" ) ) ) ;
29532995 }
2954- } ) ;
2955- assert ! ( tokens. contains( "compile_error" ) ) ;
2956- assert ! ( tokens. contains( "Representable" ) ) ;
2957- assert ! ( !tokens. contains( "slots . str . store" ) ) ;
2996+ }
29582997 }
29592998
29602999 #[ test]
2961- fn class_and_static_str_remain_methods ( ) {
2962- for attribute in [ quote ! ( pyclassmethod ) , quote ! ( pystaticmethod ) ] {
2963- let item = syn :: parse2 ( quote ! {
3000+ fn coexist_remains_an_ordinary_method ( ) {
3001+ for ( method , slot ) in protocols ( ) {
3002+ let tokens = expand ( parse_quote ! {
29643003 impl Py <Example > {
2965- #[ #attribute ]
2966- fn __str__ ( ) -> String { String :: new( ) }
3004+ #[ pymethod ( coexist ) ]
3005+ fn #method ( & self ) -> String { String :: new( ) }
29673006 }
2968- } )
2969- . unwrap ( ) ;
2970- let tokens = expand ( item) ;
3007+ } ) ;
29713008 assert ! ( tokens. contains( "PyMethodDef :: new_const" ) ) ;
2972- assert ! ( !tokens. contains( "slots . str . store" ) ) ;
3009+ assert ! ( tokens. contains( "COEXIST" ) ) ;
3010+ assert ! ( !tokens. contains( & format!( "slots . {slot} . store" ) ) ) ;
29733011 }
29743012 }
29753013
29763014 #[ test]
2977- fn coexist_str_remains_an_ordinary_method ( ) {
2978- let tokens = expand ( parse_quote ! {
2979- impl Py <Example > {
2980- #[ pymethod( coexist) ]
2981- fn __str__( & self ) -> String { String :: new( ) }
2982- }
2983- } ) ;
2984- assert ! ( tokens. contains( "PyMethodDef :: new_const" ) ) ;
2985- assert ! ( tokens. contains( "COEXIST" ) ) ;
2986- assert ! ( !tokens. contains( "slots . str . store" ) ) ;
3015+ fn explicit_and_method_slots_conflict ( ) {
3016+ for ( method, slot) in protocols ( ) {
3017+ let slot_ident = Ident :: new ( slot, Span :: call_site ( ) ) ;
3018+ let error = impl_pyclass_impl (
3019+ parse_quote ! ( ) ,
3020+ parse_quote ! {
3021+ impl Py <Example > {
3022+ #[ pymethod]
3023+ fn #method( & self ) -> String { String :: new( ) }
3024+ #[ pyslot( #slot_ident) ]
3025+ fn stringify( zelf: & PyObject , vm: & VirtualMachine ) -> PyResult <PyStrRef > {
3026+ todo!( )
3027+ }
3028+ }
3029+ } ,
3030+ )
3031+ . expect_err ( "two implementations of the same slot must be rejected" ) ;
3032+ assert ! ( error. to_string( ) . contains( & format!( "(slot {slot})" ) ) ) ;
3033+ }
29873034 }
29883035
29893036 #[ test]
2990- fn explicit_and_method_str_slots_conflict ( ) {
2991- let error = impl_pyclass_impl (
2992- parse_quote ! ( ) ,
2993- parse_quote ! {
3037+ fn raw_method_reports_an_explicit_error ( ) {
3038+ for ( method, slot) in protocols ( ) {
3039+ let tokens = expand ( parse_quote ! {
29943040 impl Py <Example > {
2995- #[ pymethod]
2996- fn __str__( & self ) -> String { String :: new( ) }
2997- #[ pyslot( str ) ]
2998- fn stringify( zelf: & PyObject , vm: & VirtualMachine ) -> PyResult <PyStrRef > {
2999- todo!( )
3000- }
3041+ #[ pymethod( raw) ]
3042+ fn #method( vm: & VirtualMachine , args: FuncArgs ) -> PyResult { todo!( ) }
30013043 }
3002- } ,
3003- )
3004- . expect_err ( "two str slots must be rejected" ) ;
3005- assert ! ( error. to_string( ) . contains( "(slot str)" ) ) ;
3006- }
3007-
3008- #[ test]
3009- fn raw_str_reports_an_explicit_error ( ) {
3010- let tokens = expand ( parse_quote ! {
3011- impl Py <Example > {
3012- #[ pymethod( raw) ]
3013- fn __str__( vm: & VirtualMachine , args: FuncArgs ) -> PyResult { todo!( ) }
3014- }
3015- } ) ;
3016- assert ! ( tokens. contains( "compile_error" ) ) ;
3017- assert ! ( tokens. contains( "#[pymethod(raw)] cannot define '__str__'" ) ) ;
3018- assert ! ( !tokens. contains( "slots . str . store" ) ) ;
3044+ } ) ;
3045+ assert ! ( tokens. contains( "compile_error" ) ) ;
3046+ assert ! ( tokens. contains( & format!( "#[pymethod(raw)] cannot define '{method}'" ) ) ) ;
3047+ assert ! ( !tokens. contains( & format!( "slots . {slot} . store" ) ) ) ;
3048+ }
30193049 }
30203050}
0 commit comments