Skip to content

Commit 4eee36e

Browse files
authored
Fix complex.__new__ (#1385)
* Fix complex.__new__ * Fix BigInteger to Double conversion * Use Python-specific overflow message for int to float conversion * Make test_complex pass under CPython 3.4 * Preserve negative zero in complex constructor * Add extra complex constructor tests
1 parent c98e0d5 commit 4eee36e

File tree

7 files changed

+332
-87
lines changed

7 files changed

+332
-87
lines changed

Src/IronPython/Runtime/Binding/ConversionBinder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,19 +491,19 @@ public IEnumerator ObjectToIEnumeratorConversion(CallSite site, object value) {
491491
}
492492

493493
public Complex BigIntegerToComplexConversion(CallSite site, BigInteger value) {
494-
return BigIntegerOps.ConvertToComplex(value);
494+
return BigIntegerOps.ConvertToDouble(value);
495495
}
496496

497497
public Complex BigIntegerObjectToComplexConversion(CallSite site, object value) {
498498
if (value is BigInteger) {
499-
return BigIntegerOps.ConvertToComplex((BigInteger)value);
499+
return BigIntegerOps.ConvertToDouble((BigInteger)value);
500500
}
501501

502502
return ((CallSite<Func<CallSite, object, Complex>>)site).Update(site, value);
503503
}
504504

505505
public object BigIntegerToComplexObjectConversion(CallSite site, BigInteger value) {
506-
return (object)BigIntegerOps.ConvertToComplex((BigInteger)value);
506+
return (Complex)BigIntegerOps.ConvertToDouble(value);
507507
}
508508

509509
private class IdentityConversion {

Src/IronPython/Runtime/Operations/BigIntegerOps.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -328,15 +328,15 @@ public static object Power(BigInteger x, BigInteger y, BigInteger z) {
328328
[SpecialName]
329329
public static object Power(BigInteger x, int y) {
330330
if (y < 0) {
331-
return DoubleOps.Power(x.ToFloat64(), y);
331+
return DoubleOps.Power(ToDouble(x), y);
332332
}
333333
return x.Power(y);
334334
}
335335

336336
[SpecialName]
337337
public static object Power(BigInteger x, long y) {
338-
if(y < 0) {
339-
return DoubleOps.Power(x.ToFloat64(), y);
338+
if (y < 0) {
339+
return DoubleOps.Power(ToDouble(x), y);
340340
}
341341
return x.Power(y);
342342
}
@@ -512,17 +512,14 @@ public static object __pos__(BigInteger x) {
512512
}
513513

514514
public static object __int__(BigInteger x) {
515-
// The python spec says __int__ should return a long if needed, rather than overflow.
516-
int i32;
517-
if (x.AsInt32(out i32)) {
515+
if (x.AsInt32(out int i32)) {
518516
return Microsoft.Scripting.Runtime.ScriptingRuntimeHelpers.Int32ToObject(i32);
519517
}
520-
521518
return x;
522519
}
523520

524521
public static object __float__(BigInteger self) {
525-
return self.ToFloat64();
522+
return ToDouble(self);
526523
}
527524

528525
public static object __getnewargs__(CodeContext context, BigInteger self) {
@@ -585,17 +582,12 @@ public static double ConvertToDouble(BigInteger self) {
585582

586583
[SpecialName, ExplicitConversionMethod]
587584
public static int ConvertToInt32(BigInteger self) {
588-
int res;
589-
if (self.AsInt32(out res)) return res;
590-
585+
if (self.AsInt32(out int res)) {
586+
return res;
587+
}
591588
throw Converter.CannotConvertOverflow("int", self);
592589
}
593590

594-
[SpecialName, ExplicitConversionMethod]
595-
public static Complex ConvertToComplex(BigInteger self) {
596-
return MathUtils.MakeReal(ConvertToDouble(self));
597-
}
598-
599591
[SpecialName, ImplicitConversionMethod]
600592
public static BigInteger ConvertToBigInteger(bool self) {
601593
return self ? BigInteger.One : BigInteger.Zero;
@@ -713,7 +705,7 @@ public static double ToDouble(BigInteger self, IFormatProvider? provider) {
713705

714706
[PythonHidden]
715707
public static float ToSingle(BigInteger self, IFormatProvider? provider) {
716-
return checked((float)self.ToFloat64());
708+
return checked((float)ConvertToDouble(self));
717709
}
718710

719711
[PythonHidden]
@@ -1033,6 +1025,14 @@ internal static string ToBinary(BigInteger val) {
10331025
return res;
10341026
}
10351027

1028+
internal static double ToDouble(BigInteger self) {
1029+
// Unlike ConvertToDouble, this method produces a Python-specific overflow error messge.
1030+
if (MathUtils.TryToFloat64(self, out double res)) {
1031+
return res;
1032+
}
1033+
throw new OverflowException("int too large to convert to float");
1034+
}
1035+
10361036
private static string ToBinary(BigInteger val, bool includeType, bool lowercase) {
10371037
Debug.Assert(!val.IsNegative());
10381038

Src/IronPython/Runtime/Operations/ComplexOps.cs

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Text;
1313

1414
using IronPython.Modules;
15+
using IronPython.Runtime.Exceptions;
1516
using IronPython.Runtime.Types;
1617

1718
using Microsoft.Scripting.Runtime;
@@ -32,59 +33,86 @@ public static object __new__(CodeContext context, PythonType cls) {
3233
}
3334

3435
[StaticExtensionMethod]
35-
public static object __new__(CodeContext context, PythonType cls, [Optional] object? real, [Optional] object? imag) {
36-
if (real == null) throw PythonOps.TypeError($"complex() first argument must be a string or a number, not '{PythonOps.GetPythonTypeName(real)}'");
37-
if (imag == null) throw PythonOps.TypeError($"complex() second argument must be a number, not '{PythonOps.GetPythonTypeName(real)}'");
36+
public static object __new__(CodeContext context, PythonType cls, object? real, [Optional] object? imag) {
3837

39-
Complex imag2;
38+
// Fast-track for a single argument when type(arg) is complex and no subclasses are involved.
39+
if (real is Complex && imag is Missing && cls == TypeCache.Complex) return real;
40+
41+
if (real is string s) return ParseComplex(s, imag);
42+
if (real is Extensible<string> es) return ParseComplex(es.Value, imag);
43+
44+
if (imag is string || imag is Extensible<string>) throw PythonOps.TypeError("complex() second arg can't be a string");
45+
46+
Complex real2;
47+
bool real_is_entirely_real = false;
48+
if (real is Complex z) {
49+
real2 = z;
50+
} else if (!TryInvokeComplex(context, real, out real2)) {
51+
if (real is Extensible<Complex> ez) {
52+
real2 = ez.Value;
53+
} else if (DoubleOps.TryToFloat(context, real, out double res)) {
54+
real2 = res;
55+
real_is_entirely_real = true; // to preserve zero-sign of imag
56+
} else {
57+
throw PythonOps.TypeErrorForBadInstance("complex() first argument must be a string or a number, not '{0}'", real);
58+
}
59+
}
60+
61+
double real3, imag3;
4062
if (imag is Missing) {
41-
imag2 = Complex.Zero;
63+
real3 = real2.Real;
64+
imag3 = real2.Imaginary;
4265
} else {
43-
if (real is string) throw PythonOps.TypeError("complex() can't take second arg if first is a string");
44-
if (imag is string) throw PythonOps.TypeError("complex() second arg can't be a string");
45-
if (!Converter.TryConvertToComplex(imag, out imag2)) {
46-
throw PythonOps.TypeError($"complex() second argument must be a number, not '{PythonOps.GetPythonTypeName(real)}'");
66+
Complex imag2;
67+
if (imag is Complex z2) {
68+
imag2 = z2;
69+
// surprisingly, no TryInvokeComplex here
70+
} else if (imag is Extensible<Complex> ez2) {
71+
imag2 = ez2.Value;
72+
} else if (DoubleOps.TryToFloat(context, imag, out double res)) {
73+
imag2 = res;
74+
} else {
75+
throw PythonOps.TypeErrorForBadInstance("complex() second argument must be a number, not '{0}'", imag);
4776
}
48-
}
4977

50-
Complex real2;
51-
if (real is Missing) {
52-
real2 = Complex.Zero;
53-
} else if (real is string) {
54-
real2 = LiteralParser.ParseComplex((string)real);
55-
} else if (real is Extensible<string>) {
56-
real2 = LiteralParser.ParseComplex(((Extensible<string>)real).Value);
57-
} else if (real is Complex) {
58-
if (imag is Missing && cls == TypeCache.Complex) return real;
59-
else real2 = (Complex)real;
60-
} else if (!Converter.TryConvertToComplex(real, out real2)) {
61-
throw PythonOps.TypeError($"complex() first argument must be a string or a number, not '{PythonOps.GetPythonTypeName(real)}'");
78+
real3 = real2.Real - imag2.Imaginary;
79+
imag3 = real_is_entirely_real ? imag2.Real : real2.Imaginary + imag2.Real;
6280
}
6381

64-
double real3 = real2.Real - imag2.Imaginary;
65-
double imag3 = real2.Imaginary + imag2.Real;
6682
if (cls == TypeCache.Complex) {
6783
return new Complex(real3, imag3);
6884
} else {
6985
return cls.CreateInstance(context, real3, imag3);
7086
}
71-
}
7287

73-
[StaticExtensionMethod]
74-
public static object __new__(CodeContext context, PythonType cls, double real) {
75-
if (cls == TypeCache.Complex) {
76-
return new Complex(real, 0.0);
77-
} else {
78-
return cls.CreateInstance(context, real, 0.0);
88+
static Complex ParseComplex(string s, object? imag) {
89+
if (imag is Missing) {
90+
return LiteralParser.ParseComplex(s);
91+
} else {
92+
throw PythonOps.TypeError($"complex() can't take second arg if first is a string");
93+
}
7994
}
80-
}
8195

82-
[StaticExtensionMethod]
83-
public static object __new__(CodeContext context, PythonType cls, double real, double imag) {
84-
if (cls == TypeCache.Complex) {
85-
return new Complex(real, imag);
86-
} else {
87-
return cls.CreateInstance(context, real, imag);
96+
static bool TryInvokeComplex(CodeContext context, object? o, out Complex complex) {
97+
if (PythonTypeOps.TryInvokeUnaryOperator(context, o, "__complex__", out object? result)) {
98+
switch (result) {
99+
case Complex z:
100+
complex = z;
101+
return true;
102+
case Extensible<Complex> ez:
103+
Warn(context, result);
104+
complex = ez.Value;
105+
return true;
106+
default:
107+
throw PythonOps.TypeErrorForBadInstance("__complex__ returned non-complex (type {0})", result);
108+
}
109+
110+
static void Warn(CodeContext context, object result) {
111+
PythonOps.Warn(context, PythonExceptions.DeprecationWarning, $"__complex__ returned non-complex (type {PythonOps.GetPythonTypeName(result)}). The ability to return an instance of a strict subclass of complex is deprecated, and may be removed in a future version of Python.");
112+
}
113+
}
114+
complex = default;
115+
return false;
88116
}
89117
}
90118

@@ -275,12 +303,13 @@ public static string __format__(CodeContext/*!*/ context, Complex self, string f
275303
}
276304

277305
// report the same errors as CPython for these invalid conversions
306+
// these operators disappear on complex in Python 3.10
278307
public static double __float__(Complex self) {
279-
throw PythonOps.TypeError("can't convert complex to float; use abs(z)");
308+
throw PythonOps.TypeError("can't convert complex to float");
280309
}
281310

282311
public static int __int__(Complex self) {
283-
throw PythonOps.TypeError("can't convert complex to int; use int(abs(z))");
312+
throw PythonOps.TypeError("can't convert complex to int");
284313
}
285314

286315
private static string FormatComplexValue(CodeContext/*!*/ context, double x)

Src/IronPython/Runtime/Operations/FloatOps.cs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal static object NewFloat(CodeContext/*!*/ context, PythonType type, objec
3636
str = s;
3737
} else if (x is char c) {
3838
str = ScriptingRuntimeHelpers.CharToString(c);
39-
} else if (TryInvokeFloat(context, x, out var d)) {
39+
} else if (TryToFloat(context, x, out var d)) {
4040
return d;
4141
} else if (x is Extensible<string> es) {
4242
str = es.Value;
@@ -53,39 +53,66 @@ internal static object NewFloat(CodeContext/*!*/ context, PythonType type, objec
5353
return res;
5454

5555
throw PythonOps.ValueError($"could not convert string to {type.Name}: {PythonOps.Repr(context, x)}");
56+
}
57+
58+
[StaticExtensionMethod]
59+
public static object __new__(CodeContext/*!*/ context, PythonType cls, object x) {
60+
object value = NewFloat(context, TypeCache.Double, x);
61+
62+
if (cls == TypeCache.Double) {
63+
return value;
64+
} else {
65+
return cls.CreateInstance(context, value);
66+
}
67+
}
68+
69+
internal static bool TryToFloat(CodeContext context, object/*?*/ value, out double result) {
70+
if (value is double d) {
71+
result = d;
72+
} else if (value is int i) {
73+
result = i;
74+
} else if (value is BigInteger bi) {
75+
result = BigIntegerOps.ToDouble(bi);
76+
} else if (TryInvokeFloat(context, value, out result)) {
77+
// pass
78+
} else if (value is Extensible<double> ed) {
79+
result = ed.Value;
80+
} else if (value is Extensible<BigInteger> ebi) {
81+
result = BigIntegerOps.ToDouble(ebi.Value);
82+
} else if (PythonOps.TryToIndex(value, out object ireal)) { // Python 3.8: fall back on __index__
83+
result = ireal switch {
84+
int ii => ii,
85+
BigInteger bii => BigIntegerOps.ToDouble(bii),
86+
_ => throw new InvalidOperationException("Unreachable code")
87+
};
88+
} else {
89+
return false;
90+
}
91+
return true;
5692

57-
static bool TryInvokeFloat(CodeContext context, object o, out object result) {
58-
if (PythonTypeOps.TryInvokeUnaryOperator(context, o, "__float__", out result)) {
59-
switch (result) {
60-
case double _:
93+
static bool TryInvokeFloat(CodeContext context, object/*?*/ o, out double result) {
94+
if (PythonTypeOps.TryInvokeUnaryOperator(context, o, "__float__", out object retobj)) {
95+
switch (retobj) {
96+
case double d:
97+
result = d;
6198
return true;
6299
case Extensible<double> ed:
63-
Warn(context, result);
100+
Warn(context, retobj);
64101
result = ed.Value; // Python 3.6: return the int value
65102
return true;
66103
default:
67-
throw PythonOps.TypeError("__float__ returned non-float (type {0})", PythonOps.GetPythonTypeName(result));
104+
throw PythonOps.TypeError("__float__ returned non-float (type {0})", PythonOps.GetPythonTypeName(retobj));
68105
}
69106

70107
static void Warn(CodeContext context, object result) {
71108
PythonOps.Warn(context, PythonExceptions.DeprecationWarning, $"__float__ returned non-float (type {PythonOps.GetPythonTypeName(result)}). The ability to return an instance of a strict subclass of float is deprecated, and may be removed in a future version of Python.");
72109
}
73110
}
111+
result = default;
74112
return false;
75113
}
76114
}
77115

78-
[StaticExtensionMethod]
79-
public static object __new__(CodeContext/*!*/ context, PythonType cls, object x) {
80-
object value = NewFloat(context, TypeCache.Double, x);
81-
82-
if (cls == TypeCache.Double) {
83-
return value;
84-
} else {
85-
return cls.CreateInstance(context, value);
86-
}
87-
}
88-
89116
public static PythonTuple as_integer_ratio(double self) {
90117
if (Double.IsInfinity(self)) {
91118
throw PythonOps.OverflowError("Cannot pass infinity to float.as_integer_ratio.");

Src/IronPython/Runtime/Types/PythonTypeInfo.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -722,8 +722,7 @@ private class ProtectedMemberResolver : MemberResolver {
722722
get {
723723
if (_ComplexResolver != null) return _ComplexResolver;
724724
_ComplexResolver = MakeConversionResolver(new List<Type> {
725-
typeof(Complex), typeof(ExtensibleComplex), typeof(Extensible<Complex>),
726-
typeof(double), typeof(Extensible<double>)
725+
typeof(Complex), typeof(ExtensibleComplex), typeof(Extensible<Complex>)
727726
});
728727
return _ComplexResolver;
729728
}
@@ -759,6 +758,8 @@ private class ProtectedMemberResolver : MemberResolver {
759758
}
760759
private static Func<MemberBinder/*!*/, Type/*!*/, MemberGroup/*!*/> _BigIntegerResolver;
761760

761+
#endregion
762+
762763
/// <summary>
763764
/// Provides a resolution for __getitem__
764765
/// </summary>
@@ -785,8 +786,6 @@ private class ProtectedMemberResolver : MemberResolver {
785786
}
786787
private static Func<MemberBinder/*!*/, Type/*!*/, MemberGroup/*!*/> _SetItemResolver;
787788

788-
#endregion
789-
790789
/// <summary>
791790
/// Provides a resolution for __str__.
792791
/// </summary>

0 commit comments

Comments
 (0)