@@ -97,6 +97,26 @@ impl PyObjectRef {
9797 if let Some ( complex) = self . downcast_ref_if_exact :: < PyComplex > ( vm) {
9898 return Ok ( Some ( ( complex. as_complex ( ) , true ) ) ) ;
9999 }
100+ if let Some ( value) = self . try_complex_special_method ( vm) ? {
101+ return Ok ( Some ( ( value. as_complex ( ) , true ) ) ) ;
102+ }
103+ // `complex` does not have a `__complex__` by default, so subclasses might not either,
104+ // use the actual stored value in this case
105+ if let Some ( complex) = self . downcast_ref :: < PyComplex > ( ) {
106+ return Ok ( Some ( ( complex. as_complex ( ) , true ) ) ) ;
107+ }
108+
109+ if let Some ( float) = self . try_float_opt ( vm) {
110+ return Ok ( Some ( ( Complex64 :: new ( float?. to_f64 ( ) , 0.0 ) , false ) ) ) ;
111+ }
112+
113+ Ok ( None )
114+ }
115+
116+ fn try_complex_special_method (
117+ & self ,
118+ vm : & VirtualMachine ,
119+ ) -> PyResult < Option < PyRef < PyComplex > > > {
100120 if let Some ( method) = vm. get_method ( self . clone ( ) , identifier ! ( vm, __complex__) ) {
101121 let result = method?. call ( ( ) , vm) ?;
102122
@@ -115,27 +135,17 @@ impl PyObjectRef {
115135 vm,
116136 ) ?;
117137
118- return Ok ( Some ( ( ret. as_complex ( ) , true ) ) ) ;
138+ return Ok ( Some ( ret. to_owned ( ) ) ) ;
119139 }
120140
121141 return match result. downcast_ref :: < PyComplex > ( ) {
122- Some ( complex_obj) => Ok ( Some ( ( complex_obj. as_complex ( ) , true ) ) ) ,
142+ Some ( complex_obj) => Ok ( Some ( complex_obj. to_owned ( ) ) ) ,
123143 None => Err ( vm. new_type_error ( format ! (
124144 "__complex__ returned non-complex (type '{}')" ,
125145 result. class( ) . name( )
126146 ) ) ) ,
127147 } ;
128148 }
129- // `complex` does not have a `__complex__` by default, so subclasses might not either,
130- // use the actual stored value in this case
131- if let Some ( complex) = self . downcast_ref :: < PyComplex > ( ) {
132- return Ok ( Some ( ( complex. as_complex ( ) , true ) ) ) ;
133- }
134-
135- if let Some ( float) = self . try_float_opt ( vm) {
136- return Ok ( Some ( ( Complex64 :: new ( float?. to_f64 ( ) , 0.0 ) , false ) ) ) ;
137- }
138-
139149 Ok ( None )
140150 }
141151}
@@ -395,13 +405,17 @@ impl Constructor for PyComplex {
395405 type Args = ComplexArgs ;
396406
397407 fn slot_new ( cls : PyTypeRef , func_args : FuncArgs , vm : & VirtualMachine ) -> PyResult {
398- // Optimization: return exact complex as-is (only when imag is not provided)
399- if cls. is ( vm. ctx . types . complex_type )
400- && func_args. args . len ( ) == 1
401- && func_args. kwargs . is_empty ( )
402- && func_args. args [ 0 ] . class ( ) . is ( vm. ctx . types . complex_type )
403- {
404- return Ok ( func_args. args [ 0 ] . clone ( ) ) ;
408+ if func_args. kwargs . is_empty ( ) && func_args. args . len ( ) <= 1 {
409+ let payload = if let Some ( value) = func_args. args . first ( ) {
410+ if cls. is ( vm. ctx . types . complex_type ) && value. class ( ) . is ( vm. ctx . types . complex_type )
411+ {
412+ return Ok ( value. clone ( ) ) ;
413+ }
414+ Self :: from_single_argument ( value, vm) ?
415+ } else {
416+ Self :: from ( Complex64 :: new ( 0.0 , 0.0 ) )
417+ } ;
418+ return payload. into_ref_with_type ( vm, cls) . map ( Into :: into) ;
405419 }
406420
407421 let args: Self :: Args = func_args. bind_for ( vm, Self :: NAME ) ?;
@@ -411,51 +425,75 @@ impl Constructor for PyComplex {
411425
412426 fn py_new ( _cls : & Py < PyType > , args : Self :: Args , vm : & VirtualMachine ) -> PyResult < Self > {
413427 let imag_missing = args. imag . is_missing ( ) ;
414- let ( real, real_was_complex) = match args. real {
415- OptionalArg :: Missing => ( Complex64 :: new ( 0.0 , 0.0 ) , false ) ,
416- OptionalArg :: Present ( val) => {
417- if let Some ( c) = val. try_complex ( vm) ? {
418- c
419- } else if let Some ( s) = val. downcast_ref :: < PyStr > ( ) {
420- if args. imag . is_present ( ) {
421- return Err ( vm. new_type_error (
422- "complex() can't take second arg if first is a string" ,
423- ) ) ;
424- }
425- if has_misplaced_underscore ( s. as_wtf8 ( ) . as_bytes ( ) ) {
426- let repr = val. repr ( vm) ?;
427- return Err ( vm. new_value_error ( format ! (
428- "could not convert string to complex: {repr}"
429- ) ) ) ;
430- }
431- let ( re, im) = rustpython_literal:: complex:: parse_str (
432- & crate :: protocol:: numeric_literal_from_str ( s) ,
433- )
434- . ok_or_else ( || vm. new_value_error ( "complex() arg is a malformed string" ) ) ?;
435- return Ok ( Self :: from ( Complex64 { re, im } ) ) ;
436- } else {
437- return Err ( vm. new_type_error ( format ! (
438- "complex() argument must be a string or a number, not {}" ,
439- val. class( ) . slot_name( )
440- ) ) ) ;
441- }
428+ let original_real = match args. real {
429+ OptionalArg :: Missing => vm. ctx . new_int ( 0 ) . into ( ) ,
430+ OptionalArg :: Present ( value) => value,
431+ } ;
432+ let converted_real = original_real. try_complex_special_method ( vm) ?;
433+ if converted_real. is_none ( )
434+ && original_real. downcast_ref :: < Self > ( ) . is_none ( )
435+ && !has_real_number_conversion ( & original_real)
436+ {
437+ return Err ( vm. new_type_error ( format ! (
438+ "complex() argument 'real' must be a real number, not {}" ,
439+ original_real. class( ) . fully_qualified_name( vm) ?
440+ ) ) ) ;
441+ }
442+ if let OptionalArg :: Present ( imag) = & args. imag
443+ && imag. downcast_ref :: < Self > ( ) . is_none ( )
444+ && !has_real_number_conversion ( imag)
445+ {
446+ return Err ( vm. new_type_error ( format ! (
447+ "complex() argument 'imag' must be a real number, not {}" ,
448+ imag. class( ) . fully_qualified_name( vm) ?
449+ ) ) ) ;
450+ }
451+
452+ let complex_real = converted_real
453+ . as_ref ( )
454+ . map ( |value| value. as_complex ( ) )
455+ . or_else ( || {
456+ original_real
457+ . downcast_ref :: < Self > ( )
458+ . map ( |value| value. as_complex ( ) )
459+ } ) ;
460+ let real_was_complex = complex_real. is_some ( ) ;
461+ let real = if let Some ( value) = complex_real {
462+ drop ( converted_real) ;
463+ if !has_real_number_conversion ( & original_real) {
464+ _warnings:: warn (
465+ vm. ctx . exceptions . deprecation_warning ,
466+ format ! (
467+ "complex() argument 'real' must be a real number, not {}" ,
468+ original_real. class( ) . fully_qualified_name( vm) ?
469+ ) ,
470+ 1 ,
471+ vm,
472+ ) ?;
442473 }
474+ value
475+ } else {
476+ Complex64 :: new ( original_real. try_float ( vm) ?. to_f64 ( ) , 0.0 )
443477 } ;
444478
445479 let ( imag, imag_was_complex) = match args. imag {
446480 // Copy the imaginary from the real to the real of the imaginary
447481 // if an imaginary argument is not passed in
448482 OptionalArg :: Missing => ( Complex64 :: new ( real. im , 0.0 ) , false ) ,
449483 OptionalArg :: Present ( obj) => {
450- if let Some ( c) = obj. try_complex ( vm) ? {
451- c
452- } else if obj. class ( ) . fast_issubclass ( vm. ctx . types . str_type ) {
453- return Err ( vm. new_type_error ( "complex() second arg can't be a string" ) ) ;
484+ if let Some ( value) = obj. downcast_ref :: < Self > ( ) {
485+ _warnings:: warn (
486+ vm. ctx . exceptions . deprecation_warning ,
487+ format ! (
488+ "complex() argument 'imag' must be a real number, not {}" ,
489+ obj. class( ) . fully_qualified_name( vm) ?
490+ ) ,
491+ 1 ,
492+ vm,
493+ ) ?;
494+ ( value. as_complex ( ) , true )
454495 } else {
455- return Err ( vm. new_type_error ( format ! (
456- "complex() second argument must be a number, not '{}'" ,
457- obj. class( ) . name( )
458- ) ) ) ;
496+ ( Complex64 :: new ( obj. try_float ( vm) ?. to_f64 ( ) , 0.0 ) , false )
459497 }
460498 }
461499 } ;
@@ -476,7 +514,37 @@ impl Constructor for PyComplex {
476514 }
477515}
478516
517+ fn has_real_number_conversion ( value : & PyObject ) -> bool {
518+ let number = & value. class ( ) . slots ( ) . as_number ;
519+ number. float . load ( ) . is_some ( ) || number. index . load ( ) . is_some ( )
520+ }
521+
479522impl PyComplex {
523+ fn from_single_argument ( value : & PyObjectRef , vm : & VirtualMachine ) -> PyResult < Self > {
524+ if let Some ( string) = value. downcast_ref :: < PyStr > ( ) {
525+ if has_misplaced_underscore ( string. as_wtf8 ( ) . as_bytes ( ) ) {
526+ let repr = value. repr ( vm) ?;
527+ return Err (
528+ vm. new_value_error ( format ! ( "could not convert string to complex: {repr}" ) )
529+ ) ;
530+ }
531+ let ( re, im) = rustpython_literal:: complex:: parse_str (
532+ & crate :: protocol:: numeric_literal_from_str ( string) ,
533+ )
534+ . ok_or_else ( || vm. new_value_error ( "complex() arg is a malformed string" ) ) ?;
535+ return Ok ( Self :: from ( Complex64 { re, im } ) ) ;
536+ }
537+ value
538+ . try_complex ( vm) ?
539+ . map ( |( value, _) | Self :: from ( value) )
540+ . ok_or_else ( || {
541+ vm. new_type_error ( format ! (
542+ "complex() argument must be a string or a number, not {}" ,
543+ value. class( ) . slot_name( )
544+ ) )
545+ } )
546+ }
547+
480548 #[ deprecated( note = "use PyComplex::from(...).into_ref() instead" ) ]
481549 pub fn new_ref ( value : Complex64 , ctx : & Context ) -> PyRef < Self > {
482550 Self :: from ( value) . into_ref ( ctx)
@@ -763,10 +831,10 @@ impl Representable for PyComplex {
763831
764832#[ derive( FromArgs ) ]
765833pub struct ComplexArgs {
766- // Missing real is 0. A string real uses the imag-missing path .
834+ // Component construction defaults the missing real part to 0 .
767835 #[ pyarg( any, default , py_default = "0" ) ]
768836 real : OptionalArg < PyObjectRef > ,
769- // Missing imag is 0 and is distinct from imag=0 when real is a string .
837+ // Missing imag retains the imaginary part of a converted real argument .
770838 #[ pyarg( any, default , py_default = "0" ) ]
771839 imag : OptionalArg < PyObjectRef > ,
772840}
0 commit comments