Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
moved default arg converter to its own file; cached converter lookup
  • Loading branch information
lostmsu committed Jan 31, 2020
commit 9c81661fce5d33a587b7e2c94cdfa6bfdd87ad03
111 changes: 111 additions & 0 deletions src/runtime/defaultpyargconverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
namespace Python.Runtime {
using System;

/// <summary>
/// The implementation of <see cref="T:Python.Runtime.IPyArgumentConverter" /> used by default
/// </summary>
public class DefaultPyArgumentConverter: IPyArgumentConverter
{
/// <summary>
/// Gets the singleton instance.
/// </summary>
public static DefaultPyArgumentConverter Instance { get; } = new DefaultPyArgumentConverter();

/// <inheritdoc />
/// <summary>
/// Attempts to convert an argument passed by Python to the specified parameter type.
/// </summary>
/// <param name="pyarg">Unmanaged pointer to the Python argument value</param>
/// <param name="parameterType">The expected type of the parameter</param>
/// <param name="needsResolution"><c>true</c> if the method is overloaded</param>
/// <param name="arg">This parameter will receive the converted value, matching the specified type</param>
/// <param name="isOut">This parameter will be set to <c>true</c>,
/// if the final type needs to be marshaled as an out argument.</param>
/// <returns><c>true</c>, if the object matches requested type,
/// and conversion was successful, otherwise <c>false</c></returns>
public virtual bool TryConvertArgument(
IntPtr pyarg, Type parameterType, bool needsResolution,
out object arg, out bool isOut)
{
arg = null;
isOut = false;
Type clrType = TryComputeClrArgumentType(parameterType, pyarg, needsResolution: needsResolution);
if (clrType == null)
{
return false;
}

if (!Converter.ToManaged(pyarg, clrType, out arg, false))
{
Exceptions.Clear();
return false;
}

isOut = clrType.IsByRef;
return true;
}

static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool needsResolution)
{
// this logic below handles cases when multiple overloading methods
// are ambiguous, hence comparison between Python and CLR types
// is necessary
Type clrType = null;
IntPtr pyArgType;
if (needsResolution)
{
// HACK: each overload should be weighted in some way instead
pyArgType = Runtime.PyObject_Type(argument);
Exceptions.Clear();
if (pyArgType != IntPtr.Zero)
{
clrType = Converter.GetTypeByAlias(pyArgType);
}
Runtime.XDecref(pyArgType);
}

if (clrType != null)
{
if ((parameterType != typeof(object)) && (parameterType != clrType))
{
IntPtr pyParamType = Converter.GetPythonTypeByAlias(parameterType);
pyArgType = Runtime.PyObject_Type(argument);
Exceptions.Clear();

bool typeMatch = false;
if (pyArgType != IntPtr.Zero && pyParamType == pyArgType)
{
typeMatch = true;
clrType = parameterType;
}
if (!typeMatch)
{
// this takes care of enum values
TypeCode argTypeCode = Type.GetTypeCode(parameterType);
TypeCode paramTypeCode = Type.GetTypeCode(clrType);
if (argTypeCode == paramTypeCode)
{
typeMatch = true;
clrType = parameterType;
}
}
Runtime.XDecref(pyArgType);
if (!typeMatch)
{
return null;
}
}
else
{
clrType = parameterType;
}
}
else
{
clrType = parameterType;
}

return clrType;
}
}
}
121 changes: 20 additions & 101 deletions src/runtime/methodbinder.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Collections.Generic;
using System.Linq;

namespace Python.Runtime
{
using System.Linq;

/// <summary>
/// A MethodBinder encapsulates information about a (possibly overloaded)
/// managed method, and is responsible for selecting the right method given
Expand Down Expand Up @@ -180,14 +180,7 @@ IPyArgumentConverter GetArgumentConverter() {
Type converterType = null;
foreach (MethodBase method in this.methods)
{
var attribute = method.DeclaringType?
.GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false)
.OfType<PyArgConverterAttribute>()
.SingleOrDefault()
?? method.DeclaringType?.Assembly
.GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false)
.OfType<PyArgConverterAttribute>()
.SingleOrDefault();
PyArgConverterAttribute attribute = TryGetArgConverter(method.DeclaringType);
if (converterType == null)
{
if (attribute == null) continue;
Expand All @@ -203,6 +196,23 @@ IPyArgumentConverter GetArgumentConverter() {
return converter ?? DefaultPyArgumentConverter.Instance;
}

static readonly ConcurrentDictionary<Type, PyArgConverterAttribute> ArgConverterCache =
new ConcurrentDictionary<Type, PyArgConverterAttribute>();
static PyArgConverterAttribute TryGetArgConverter(Type type) {
if (type == null) return null;

return ArgConverterCache.GetOrAdd(type, declaringType =>
declaringType
.GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false)
.OfType<PyArgConverterAttribute>()
.SingleOrDefault()
?? declaringType.Assembly
.GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false)
.OfType<PyArgConverterAttribute>()
.SingleOrDefault()
);
}

/// <summary>
/// Precedence algorithm largely lifted from Jython - the concerns are
/// generally the same so we'll start with this and tweak as necessary.
Expand Down Expand Up @@ -481,97 +491,6 @@ object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
return margs;
}

internal static bool TryConvertArgument(IntPtr op, Type parameterType, bool needsResolution,
out object arg, out bool isOut)
{
arg = null;
isOut = false;
var clrtype = TryComputeClrArgumentType(parameterType, op, needsResolution: needsResolution);
if (clrtype == null)
{
return false;
}

if (!Converter.ToManaged(op, clrtype, out arg, false))
{
Exceptions.Clear();
return false;
}

isOut = clrtype.IsByRef;
return true;
}

static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool needsResolution)
{
// this logic below handles cases when multiple overloading methods
// are ambiguous, hence comparison between Python and CLR types
// is necessary
Type clrtype = null;
IntPtr pyoptype;
if (needsResolution)
{
// HACK: each overload should be weighted in some way instead
pyoptype = Runtime.PyObject_Type(argument);
Exceptions.Clear();
if (pyoptype != IntPtr.Zero)
{
clrtype = Converter.GetTypeByAlias(pyoptype);
}
Runtime.XDecref(pyoptype);
}

if (clrtype != null)
{
var typematch = false;
if ((parameterType != typeof(object)) && (parameterType != clrtype))
{
IntPtr pytype = Converter.GetPythonTypeByAlias(parameterType);
pyoptype = Runtime.PyObject_Type(argument);
Exceptions.Clear();
if (pyoptype != IntPtr.Zero)
{
if (pytype != pyoptype)
{
typematch = false;
}
else
{
typematch = true;
clrtype = parameterType;
}
}
if (!typematch)
{
// this takes care of enum values
TypeCode argtypecode = Type.GetTypeCode(parameterType);
TypeCode paramtypecode = Type.GetTypeCode(clrtype);
if (argtypecode == paramtypecode)
{
typematch = true;
clrtype = parameterType;
}
}
Runtime.XDecref(pyoptype);
if (!typematch)
{
return null;
}
}
else
{
typematch = true;
clrtype = parameterType;
}
}
else
{
clrtype = parameterType;
}

return clrtype;
}

static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
Dictionary<string, IntPtr> kwargDict,
out bool paramsArray,
Expand Down
30 changes: 0 additions & 30 deletions src/runtime/pyargconverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,6 @@ bool TryConvertArgument(IntPtr pyarg, Type parameterType,
bool needsResolution, out object arg, out bool isOut);
}

/// <summary>
/// The implementation of <see cref="IPyArgumentConverter"/> used by default
/// </summary>
public class DefaultPyArgumentConverter: IPyArgumentConverter {
/// <summary>
/// Gets the singleton instance.
/// </summary>
public static DefaultPyArgumentConverter Instance { get; } = new DefaultPyArgumentConverter();

/// <summary>
/// Attempts to convert an argument passed by Python to the specified parameter type.
/// </summary>
/// <param name="pyarg">Unmanaged pointer to the Python argument value</param>
/// <param name="parameterType">The expected type of the parameter</param>
/// <param name="needsResolution"><c>true</c> if the method is overloaded</param>
/// <param name="arg">This parameter will receive the converted value, matching the specified type</param>
/// <param name="isOut">This parameter will be set to <c>true</c>,
/// if the final type needs to be marshaled as an out argument.</param>
/// <returns><c>true</c>, if the object matches requested type,
/// and conversion was successful, otherwise <c>false</c></returns>
public virtual bool TryConvertArgument(
IntPtr pyarg, Type parameterType, bool needsResolution,
out object arg, out bool isOut)
{
return MethodBinder.TryConvertArgument(
pyarg, parameterType, needsResolution,
out arg, out isOut);
}
}

/// <summary>
/// Specifies an argument converter to be used, when methods in this class/assembly are called from Python.
/// </summary>
Expand Down