Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 716c98d

Browse files
committed
Better error messages for method argument mismatch, Python to managed value conversion, and other problems
Converter.ToManaged obeys setError consistently PyObject_Hash and tp_hash return nint MakeGenericType and MakeGenericMethod have try/catch RaiseTypeError sets __cause__ instead of changing the message string
1 parent 32fdc9c commit 716c98d

20 files changed

+299
-83
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ details about the cause of the failure
3131
- `PyObject` now implements `IEnumerable<PyObject>` in addition to `IEnumerable`
3232
- floating point values passed from Python are no longer silently truncated
3333
when .NET expects an integer [#1342][i1342]
34+
- More specific error messages for method argument mismatch
3435

3536
### Fixed
3637

@@ -49,6 +50,7 @@ when .NET expects an integer [#1342][i1342]
4950
- Fixed objects returned by enumerating `PyObject` being disposed too soon
5051
- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException
5152
- `import` may now raise errors with more detail than "No module named X"
53+
- Providing an invalid type parameter to a generic type or method produces a helpful Python error
5254

5355
### Removed
5456

src/clrmodule/ClrModule.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ public static IntPtr PyInit_clr()
6262
pythonRuntime = Assembly.Load(pythonRuntimeName);
6363
DebugPrint("Success loading 'Python.Runtime' using standard binding rules.");
6464
}
65-
catch (IOException)
65+
catch (IOException ex)
6666
{
67-
DebugPrint("'Python.Runtime' not found using standard binding rules.");
67+
DebugPrint($"'Python.Runtime' not found using standard binding rules: {ex}");
6868
try
6969
{
7070
// If the above fails for any reason, we fallback to attempting to load "Python.Runtime.dll"

src/runtime/classbase.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,16 @@ public virtual IntPtr type_subscript(IntPtr idx)
6666

6767
if (target != null)
6868
{
69-
Type t = target.MakeGenericType(types);
69+
Type t;
70+
try
71+
{
72+
// MakeGenericType can throw ArgumentException
73+
t = target.MakeGenericType(types);
74+
}
75+
catch (ArgumentException e)
76+
{
77+
return Exceptions.RaiseTypeError(e.Message);
78+
}
7079
ManagedType c = ClassManager.GetClass(t);
7180
Runtime.XIncref(c.pyHandle);
7281
return c.pyHandle;
@@ -263,14 +272,14 @@ public static IntPtr tp_iter(IntPtr ob)
263272
/// <summary>
264273
/// Standard __hash__ implementation for instances of reflected types.
265274
/// </summary>
266-
public static IntPtr tp_hash(IntPtr ob)
275+
public static nint tp_hash(IntPtr ob)
267276
{
268277
var co = GetManagedObject(ob) as CLRObject;
269278
if (co == null)
270279
{
271280
return Exceptions.RaiseTypeError("unhashable type");
272281
}
273-
return new IntPtr(co.inst.GetHashCode());
282+
return co.inst.GetHashCode();
274283
}
275284

276285

src/runtime/converter.cs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,11 @@ internal static IntPtr ToPythonImplicit(object value)
303303
/// Return a managed object for the given Python object, taking funny
304304
/// byref types into account.
305305
/// </summary>
306+
/// <param name="value">A Python object</param>
307+
/// <param name="type">The desired managed type</param>
308+
/// <param name="result">Receives the managed object</param>
309+
/// <param name="setError">If true, call <c>Exceptions.SetError</c> with the reason for failure.</param>
310+
/// <returns>True on success</returns>
306311
internal static bool ToManaged(IntPtr value, Type type,
307312
out object result, bool setError)
308313
{
@@ -341,7 +346,10 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
341346
result = tmp;
342347
return true;
343348
}
344-
Exceptions.SetError(Exceptions.TypeError, $"value cannot be converted to {obType}");
349+
if (setError)
350+
{
351+
Exceptions.SetError(Exceptions.TypeError, $"value cannot be converted to {obType}");
352+
}
345353
return false;
346354
}
347355
if (mt is ClassBase)
@@ -376,6 +384,15 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
376384
obType = obType.GetGenericArguments()[0];
377385
}
378386

387+
if (obType.ContainsGenericParameters)
388+
{
389+
if (setError)
390+
{
391+
Exceptions.SetError(Exceptions.TypeError, $"Cannot create an instance of the open generic type {obType}");
392+
}
393+
return false;
394+
}
395+
379396
if (obType.IsArray)
380397
{
381398
return ToArray(value, obType, out result, setError);
@@ -777,7 +794,7 @@ private static void SetConversionError(IntPtr value, Type target)
777794
IntPtr ob = Runtime.PyObject_Repr(value);
778795
string src = Runtime.GetManagedString(ob);
779796
Runtime.XDecref(ob);
780-
Exceptions.SetError(Exceptions.TypeError, $"Cannot convert {src} to {target}");
797+
Exceptions.RaiseTypeError($"Cannot convert {src} to {target}");
781798
}
782799

783800

@@ -791,32 +808,58 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s
791808
Type elementType = obType.GetElementType();
792809
result = null;
793810

794-
bool IsSeqObj = Runtime.PySequence_Check(value);
795-
var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1;
796-
797811
IntPtr IterObject = Runtime.PyObject_GetIter(value);
798-
799-
if(IterObject==IntPtr.Zero) {
812+
if (IterObject == IntPtr.Zero)
813+
{
800814
if (setError)
801815
{
802816
SetConversionError(value, obType);
803817
}
818+
else
819+
{
820+
// PyObject_GetIter will have set an error
821+
Exceptions.Clear();
822+
}
804823
return false;
805824
}
806825

807-
Array items;
826+
IList list;
827+
try
828+
{
829+
// MakeGenericType can throw because elementType may not be a valid generic argument even though elementType[] is a valid array type.
830+
// For example, if elementType is a pointer type.
831+
// See https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype#System_Type_MakeGenericType_System_Type
832+
var constructedListType = typeof(List<>).MakeGenericType(elementType);
833+
bool IsSeqObj = Runtime.PySequence_Check(value);
834+
if (IsSeqObj)
835+
{
836+
var len = Runtime.PySequence_Size(value);
837+
list = (IList)Activator.CreateInstance(constructedListType, new Object[] { (int)len });
838+
}
839+
else
840+
{
841+
// CreateInstance can throw even if MakeGenericType succeeded.
842+
// See https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance#System_Activator_CreateInstance_System_Type_
843+
list = (IList)Activator.CreateInstance(constructedListType);
844+
}
845+
}
846+
catch (Exception e)
847+
{
848+
if (setError)
849+
{
850+
Exceptions.SetError(e);
851+
SetConversionError(value, obType);
852+
}
853+
return false;
854+
}
808855

809-
var listType = typeof(List<>);
810-
var constructedListType = listType.MakeGenericType(elementType);
811-
IList list = IsSeqObj ? (IList) Activator.CreateInstance(constructedListType, new Object[] {(int) len}) :
812-
(IList) Activator.CreateInstance(constructedListType);
813856
IntPtr item;
814857

815858
while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero)
816859
{
817-
object obj = null;
860+
object obj;
818861

819-
if (!Converter.ToManaged(item, elementType, out obj, true))
862+
if (!Converter.ToManaged(item, elementType, out obj, setError))
820863
{
821864
Runtime.XDecref(item);
822865
return false;
@@ -827,7 +870,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s
827870
}
828871
Runtime.XDecref(IterObject);
829872

830-
items = Array.CreateInstance(elementType, list.Count);
873+
Array items = Array.CreateInstance(elementType, list.Count);
831874
list.CopyTo(items, 0);
832875

833876
result = items;

src/runtime/eventbinding.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,35 +68,27 @@ public static IntPtr nb_inplace_subtract(IntPtr ob, IntPtr arg)
6868
/// <summary>
6969
/// EventBinding __hash__ implementation.
7070
/// </summary>
71-
public static IntPtr tp_hash(IntPtr ob)
71+
public static nint tp_hash(IntPtr ob)
7272
{
7373
var self = (EventBinding)GetManagedObject(ob);
74-
long x = 0;
75-
long y = 0;
74+
nint x = 0;
7675

7776
if (self.target != IntPtr.Zero)
7877
{
79-
x = Runtime.PyObject_Hash(self.target).ToInt64();
78+
x = Runtime.PyObject_Hash(self.target);
8079
if (x == -1)
8180
{
82-
return new IntPtr(-1);
81+
return x;
8382
}
8483
}
8584

86-
y = Runtime.PyObject_Hash(self.e.pyHandle).ToInt64();
85+
nint y = Runtime.PyObject_Hash(self.e.pyHandle);
8786
if (y == -1)
8887
{
89-
return new IntPtr(-1);
88+
return y;
9089
}
9190

92-
x ^= y;
93-
94-
if (x == -1)
95-
{
96-
x = -1;
97-
}
98-
99-
return new IntPtr(x);
91+
return x ^ y;
10092
}
10193

10294

src/runtime/eventobject.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,22 @@ internal bool AddEventHandler(IntPtr target, IntPtr handler)
7272
/// </summary>
7373
internal bool RemoveEventHandler(IntPtr target, IntPtr handler)
7474
{
75+
if (reg == null)
76+
{
77+
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
78+
return false;
79+
}
80+
7581
object obj = null;
7682
if (target != IntPtr.Zero)
7783
{
7884
var co = (CLRObject)GetManagedObject(target);
7985
obj = co.inst;
8086
}
8187

82-
IntPtr hash = Runtime.PyObject_Hash(handler);
83-
if (Exceptions.ErrorOccurred() || reg == null)
88+
nint hash = Runtime.PyObject_Hash(handler);
89+
if (hash == -1 || Exceptions.ErrorOccurred())
8490
{
85-
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
8691
return false;
8792
}
8893

src/runtime/exceptions.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,20 @@ public static void SetError(Exception e)
290290
Runtime.XDecref(op);
291291
}
292292

293+
/// <summary>
294+
/// When called after SetError, sets the cause of the error.
295+
/// </summary>
296+
/// <param name="cause"></param>
297+
public static void SetCause(PythonException cause)
298+
{
299+
var currentException = new PythonException();
300+
currentException.Normalize();
301+
cause.Normalize();
302+
Runtime.XIncref(cause.PyValue);
303+
Runtime.PyException_SetCause(currentException.PyValue, cause.PyValue);
304+
currentException.Restore();
305+
}
306+
293307
/// <summary>
294308
/// ErrorOccurred Method
295309
/// </summary>
@@ -368,17 +382,31 @@ public static void deprecation(string message)
368382
// Internal helper methods for common error handling scenarios.
369383
//====================================================================
370384

385+
/// <summary>
386+
/// Raises a TypeError exception and attaches any existing exception as its cause.
387+
/// </summary>
388+
/// <param name="message">The exception message</param>
389+
/// <returns><c>IntPtr.Zero</c></returns>
371390
internal static IntPtr RaiseTypeError(string message)
372391
{
392+
PythonException previousException = null;
393+
if (ErrorOccurred())
394+
{
395+
previousException = new PythonException();
396+
}
373397
Exceptions.SetError(Exceptions.TypeError, message);
398+
if (previousException != null)
399+
{
400+
SetCause(previousException);
401+
}
374402
return IntPtr.Zero;
375403
}
376404

377405
// 2010-11-16: Arranged in python (2.6 & 2.7) source header file order
378406
/* Predefined exceptions are
379-
puplic static variables on the Exceptions class filled in from
407+
public static variables on the Exceptions class filled in from
380408
the python class using reflection in Initialize() looked up by
381-
name, not posistion. */
409+
name, not position. */
382410
public static IntPtr BaseException;
383411
public static IntPtr Exception;
384412
public static IntPtr StopIteration;

0 commit comments

Comments
 (0)