8000 disable fallback to parameterless constructor · losttech/pythonnet@0171e08 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 0171e08

Browse files
committed
disable fallback to parameterless constructor
1 parent 71f6ed9 commit 0171e08

File tree

4 files changed

+39
-25
lines changed

4 files changed

+39
-25
lines changed

src/embed_tests/Inheritance.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ public void BaseClearIsCalled()
110110
scope.Set("exn", null);
111111
Assert.AreEqual(1, msg.Refcount);
112112
}
113+
114+
// regression for https://github.com/pythonnet/pythonnet/issues/238
115+
[Test]
116+
public void NoFallbackToParameterlessConstructor()
117+
{
118+
using var scope = Py.CreateScope();
119+
scope.Import(typeof(ConstructorOverloadDefault).Namespace, asname: "test");
120+
var cls = scope.Eval("test." + nameof(ConstructorOverloadDefault));
121+
var inst = cls.Invoke(new PyInt(2));
122+
Assert.AreEqual(2.0, inst.GetAttr(nameof(ConstructorOverloadDefault.a)).As<double>(), 0.001);
123+
var err = Assert.Throws<PythonException>(() => cls.Invoke(new PyString("2")));
124+
Assert.AreEqual("No constructor matches given arguments: (<class 'str'>)", err.Message);
125+
}
113126
}
114127

115128
class ExtraBaseTypeProvider : IPythonBaseTypeProvider
@@ -182,4 +195,12 @@ public int XProp
182195
set => this.extras[nameof(this.XProp)] = value;
183196
}
184197
}
198+
199+
public class ConstructorOverloadDefault
200+
{
201+
public ConstructorOverloadDefault() { a = 1; }
202+
public ConstructorOverloadDefault(double ax) { a = ax; }
203+
204+
public double a;
205+
}
185206
}

src/runtime/constructorbinder.cs

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -87,32 +87,18 @@ internal object InvokeRaw(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info)
8787

8888
if (binding == null)
8989
{
90-
// It is possible for __new__ to be invoked on construction
91-
// of a Python subclass of a managed class, so args may
92-
// reflect more args than are required to instantiate the
93-
// class. So if we cant find a ctor that matches, we'll see
94-
// if there is a default constructor and, if so, assume that
95-
// any extra args are intended for the subclass' __init__.
96-
97-
IntPtr eargs = Runtime.PyTuple_New(0);
98-
binding = Bind(inst, eargs, IntPtr.Zero);
99-
Runtime.XDecref(eargs);
100-
101-
if (binding == null)
90+
var errorMessage = new StringBuilder("No constructor matches given arguments");
91+
if (info != null && info.IsConstructor && info.DeclaringType != null)
10292
{
103-
var errorMessage = new StringBuilder("No constructor matches given arguments");
104-
if (info != null && info.IsConstructor && info.DeclaringType != null)
105-
{
106-
errorMessage.Append(" for ").Append(info.DeclaringType.Name);
107-
}
108-
109-
errorMessage.Append(": ");
110-
Runtime.PyErr_Fetch(out var errType, out var errVal, out var errTrace);
111-
AppendArgumentTypes(to: errorMessage, args);
112-
Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), errTrace.StealNullable());
113-
Exceptions.RaiseTypeError(errorMessage.ToString());
114-
return null;
93+
errorMessage.Append(" for ").Append(info.DeclaringType.Name);
11594
}
95+
96+
errorMessage.Append(": ");
97+
Runtime.PyErr_Fetch(out var errType, out var errVal, out var errTrace);
98+
AppendArgumentTypes(to: errorMessage, args);
99+
Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), errTrace.StealNullable());
100+
Exceptions.RaiseTypeError(errorMessage.ToString());
101+
return null;
116102
}
117103

118104
// Fire the selected ctor and catch errors...

tests/test_class.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,14 @@ def test_subclass_with_no_arg_constructor():
9595
from Python.Test import ClassCtorTest1
9696

9797
class SubClass(ClassCtorTest1):
98+
def __new__(cls, *args, **kwargs):
99+
return super().__new__(cls)
98100
def __init__(self, name):
99101
self.name = name
100102

101103
# This failed in earlier versions
102-
_ = SubClass('test')
104+
inst = SubClass('test')
105+
assert inst.name == 'test'
103106

104107

105108
def test_subclass_with_various_constructors():

tests/test_subclass.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ def test_namespace_and_init():
239239
calls = []
240240
class TestX(System.Object):
241241
__namespace__ = "test_clr_subclass_with_init_args"
242+
def __new__(cls, *args, **kwargs):
243+
return super().__new__()
242244
def __init__(self, *args, **kwargs):
243245
calls.append((args, kwargs))
244246
t = TestX(1,2,3,foo="bar")
@@ -269,6 +271,8 @@ def test_construction_from_clr():
269271
calls = []
270272
class TestX(System.Object):
271273
__namespace__ = "test_clr_subclass_init_from_clr"
274+
def __new__(cls, *args, **kwargs):
275+
super().__new__()
272276
@clr.clrmethod(None, [int, str])
273277
def __init__(self, i, s):
274278
calls.append((i, s))

0 commit comments

Comments
 (0)
0