using System;
using System.Reflection;
namespace Python.Runtime
{
///
/// Implements a Python type that wraps a CLR ctor call. Constructor objects
/// support a .Overloads[] syntax to allow explicit ctor overload selection.
///
///
/// ClassManager stores a ConstructorBinding instance in the class's __dict__['Overloads']
/// SomeType.Overloads[Type, ...] works like this:
/// 1) Python retrieves the Overloads attribute from this ClassObject's dictionary normally
/// and finds a non-null tp_descr_get slot which is called by the interpreter
/// and returns an IncRef()ed pyHandle to itself.
/// 2) The ConstructorBinding object handles the [] syntax in its mp_subscript by matching
/// the Type object parameters to a constructor overload using Type.GetConstructor()
/// [NOTE: I don't know why method overloads are not searched the same way.]
/// and creating the BoundContructor object which contains ContructorInfo object.
/// 3) In tp_call, if ctorInfo is not null, ctorBinder.InvokeRaw() is called.
///
[Serializable]
internal class ConstructorBinding : ExtensionType
{
private Type type; // The managed Type being wrapped in a ClassObject
private IntPtr pyTypeHndl; // The python type tells GetInstHandle which Type to create.
private ConstructorBinder ctorBinder;
[NonSerialized]
private IntPtr repr;
public ConstructorBinding(Type type, IntPtr pyTypeHndl, ConstructorBinder ctorBinder)
{
this.type = type;
this.pyTypeHndl = pyTypeHndl; // steal a type reference
this.ctorBinder = ctorBinder;
repr = IntPtr.Zero;
}
///
/// Descriptor __get__ implementation.
/// Implements a Python type that wraps a CLR ctor call that requires the use
/// of a .Overloads[pyTypeOrType...] syntax to allow explicit ctor overload
/// selection.
///
/// PyObject* to a Constructors wrapper
///
/// the instance that the attribute was accessed through,
/// or None when the attribute is accessed through the owner
///
/// always the owner class
///
/// a CtorMapper (that borrows a reference to this python type and the
/// ClassObject's ConstructorBinder) wrapper.
///
///
/// Python 2.6.5 docs:
/// object.__get__(self, instance, owner)
/// Called to get the attribute of the owner class (class attribute access)
/// or of an instance of that class (instance attribute access).
/// owner is always the owner class, while instance is the instance that
/// the attribute was accessed through, or None when the attribute is accessed through the owner.
/// This method should return the (computed) attribute value or raise an AttributeError exception.
///
public static IntPtr tp_descr_get(IntPtr op, IntPtr instance, IntPtr owner)
{
var self = (ConstructorBinding)GetManagedObject(op);
if (self == null)
{
return IntPtr.Zero;
}
// It doesn't seem to matter if it's accessed through an instance (rather than via the type).
/*if (instance != IntPtr.Zero) {
// This is ugly! PyObject_IsInstance() returns 1 for true, 0 for false, -1 for error...
if (Runtime.PyObject_IsInstance(instance, owner) < 1) {
return Exceptions.RaiseTypeError("How in the world could that happen!");
}
}*/
Runtime.XIncref(self.pyHandle);
return self.pyHandle;
}
///
/// Implement explicit overload selection using subscript syntax ([]).
///
///
/// ConstructorBinding.GetItem(PyObject *o, PyObject *key)
/// Return element of o corresponding to the object key or NULL on failure.
/// This is the equivalent of the Python expression o[key].
///
public static IntPtr mp_subscript(IntPtr op, IntPtr key)
{
var self = (ConstructorBinding)GetManagedObject(op);
Type[] types = Runtime.PythonArgsToTypeArray(key);
if (types == null)
{
return Exceptions.RaiseTypeError("type(s) expected");
}
//MethodBase[] methBaseArray = self.ctorBinder.GetMethods();
//MethodBase ci = MatchSignature(methBaseArray, types);
ConstructorInfo ci = self.type.GetConstructor(types);
if (ci == null)
{
return Exceptions.RaiseTypeError("No match found for constructor signature");
}
var boundCtor = new BoundContructor(self.type, self.pyTypeHndl, self.ctorBinder, ci);
return boundCtor.pyHandle;
}
///
/// ConstructorBinding __repr__ implementation [borrowed from MethodObject].
///
public static IntPtr tp_repr(IntPtr ob)
{
var self = (ConstructorBinding)GetManagedObject(ob);
if (self.repr != IntPtr.Zero)
{
Runtime.XIncref(self.repr);
return self.repr;
}
MethodBase[] methods = self.ctorBinder.GetMethods();
string name = self.type.FullName;
var doc = "";
foreach (MethodBase t in methods)
{
if (doc.Length > 0)
{
doc += "\n";
}
string str = t.ToString();
int idx = str.IndexOf("(");
doc += string.Format("{0}{1}", name, str.Substring(idx));
}
self.repr = Runtime.PyString_FromString(doc);
Runtime.XIncref(self.repr);
return self.repr;
}
///
/// ConstructorBinding dealloc implementation.
///
public new static void tp_dealloc(IntPtr ob)
{
var self = (ConstructorBinding)GetManagedObject(ob);
Runtime.XDecref(self.repr);
self.Dealloc();
}
public static int tp_clear(IntPtr ob)
{
var self = (ConstructorBinding)GetManagedObject(ob);
Runtime.Py_CLEAR(ref self.repr);
return 0;
}
public static int tp_traverse(IntPtr ob, IntPtr visit, IntPtr arg)
{
var self = (ConstructorBinding)GetManagedObject(ob);
int res = PyVisit(self.pyTypeHndl, visit, arg);
if (res != 0) return res;
res = PyVisit(self.repr, visit, arg);
if (res != 0) return res;
return 0;
}
}
///
/// Implements a Python type that constructs the given Type given a particular ContructorInfo.
///
///
/// Here mostly because I wanted a new __repr__ function for the selected constructor.
/// An earlier implementation hung the __call__ on the ContructorBinding class and
/// returned an Incref()ed self.pyHandle from the __get__ function.
///
[Serializable]
internal class BoundContructor : ExtensionType
{
private Type type; // The managed Type being wrapped in a ClassObject
private IntPtr pyTypeHndl; // The python type tells GetInstHandle which Type to create.
private ConstructorBinder ctorBinder;
private ConstructorInfo ctorInfo;
private IntPtr repr;
public BoundContructor(Type type, IntPtr pyTypeHndl, ConstructorBinder ctorBinder, ConstructorInfo ci)
{
this.type = type;
this.pyTypeHndl = pyTypeHndl; // steal a type reference
this.ctorBinder = ctorBinder;
ctorInfo = ci;
repr = IntPtr.Zero;
}
///
/// BoundContructor.__call__(PyObject *callable_object, PyObject *args, PyObject *kw)
///
/// PyObject *callable_object
/// PyObject *args
/// PyObject *kw
/// A reference to a new instance of the class by invoking the selected ctor().
public static IntPtr tp_call(IntPtr op, IntPtr args, IntPtr kw)
{
var self = (BoundContructor)GetManagedObject(op);
// Even though a call with null ctorInfo just produces the old behavior
/*if (self.ctorInfo == null) {
string msg = "Usage: Class.Overloads[CLR_or_python_Type, ...]";
return Exceptions.RaiseTypeError(msg);
}*/
// Bind using ConstructorBinder.Bind and invoke the ctor providing a null instancePtr
// which will fire self.ctorInfo using ConstructorInfo.Invoke().
object obj = self.ctorBinder.InvokeRaw(IntPtr.Zero, args, kw, self.ctorInfo);
if (obj == null)
{
// XXX set an error
return IntPtr.Zero;
}
// Instantiate the python object that wraps the result of the method call
// and return the PyObject* to it.
return CLRObject.GetInstHandle(obj, self.pyTypeHndl);
}
///
/// BoundContructor __repr__ implementation [borrowed from MethodObject].
///
public static IntPtr tp_repr(IntPtr ob)
{
var self = (BoundContructor)GetManagedObject(ob);
if (self.repr != IntPtr.Zero)
{
Runtime.XIncref(self.repr);
return self.repr;
}
string name = self.type.FullName;
string str = self.ctorInfo.ToString();
int idx = str.IndexOf("(");
str = string.Format("returns a new {0}{1}", name, str.Substring(idx));
self.repr = Runtime.PyString_FromString(str);
Runtime.XIncref(self.repr);
return self.repr;
}
///
/// ConstructorBinding dealloc implementation.
///
public new static void tp_dealloc(IntPtr ob)
{
var self = (BoundContructor)GetManagedObject(ob);
Runtime.XDecref(self.repr);
self.Dealloc();
}
public static int tp_clear(IntPtr ob)
{
var self = (BoundContructor)GetManagedObject(ob);
Runtime.Py_CLEAR(ref self.repr);
return 0;
}
public static int tp_traverse(IntPtr ob, IntPtr visit, IntPtr arg)
{
var self = (BoundContructor)GetManagedObject(ob);
int res = PyVisit(self.pyTypeHndl, visit, arg);
if (res != 0) return res;
res = PyVisit(self.repr, visit, arg);
if (res != 0) return res;
return 0;
}
}
}