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

Skip to content

Commit 49a230b

Browse files
authored
Fix params argument handling (pythonnet#1106)
Add a check to see if the last parameter is a params parameter. Set the correct flag to show that it is a paramsArray function. Fixes pythonnet#943 and pythonnet#331.
1 parent 4a92d80 commit 49a230b

File tree

3 files changed

+81
-10
lines changed

3 files changed

+81
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
3939
together with Nuitka
4040
- Fixes bug where delegates get casts (dotnetcore)
4141
- Determine size of interpreter longs at runtime
42-
- Handling exceptions ocurred in ModuleObject's getattribute
42+
- Handling exceptions ocurred in ModuleObject's getattribute
4343
- Fill `__classcell__` correctly for Python subclasses of .NET types
44+
- Fixed issue with params methods that are not passed an array.
4445

4546
## [2.4.0][]
4647

src/runtime/methodbinder.cs

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,41 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
369369
return null;
370370
}
371371

372+
static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out bool isNewReference)
373+
{
374+
isNewReference = false;
375+
IntPtr op;
376+
// for a params method, we may have a sequence or single/multiple items
377+
// here we look to see if the item at the paramIndex is there or not
378+
// and then if it is a sequence itself.
379+
if ((pyArgCount - arrayStart) == 1)
380+
{
381+
// we only have one argument left, so we need to check it
382+
// to see if it is a sequence or a single item
383+
IntPtr item = Runtime.PyTuple_GetItem(args, arrayStart);
384+
if (!Runtime.PyString_Check(item) && Runtime.PySequence_Check(item))
385+
{
386+
// it's a sequence (and not a string), so we use it as the op
387+
op = item;
388+
}
389+
else
390+
{
391+
isNewReference = true;
392+
op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount);
393+
if (item != IntPtr.Zero)
394+
{
395+
Runtime.XDecref(item);
396+
}
397+
}
398+
}
399+
else
400+
{
401+
isNewReference = true;
402+
op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount);
403+
}
404+
return op;
405+
}
406+
372407
/// <summary>
373408
/// Attempts to convert Python positional argument tuple and keyword argument table
374409
/// into an array of managed objects, that can be passed to a method.
@@ -397,8 +432,9 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
397432
{
398433
var parameter = pi[paramIndex];
399434
bool hasNamedParam = kwargDict.ContainsKey(parameter.Name);
435+
bool isNewReference = false;
400436

401-
if (paramIndex >= pyArgCount && !hasNamedParam)
437+
if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart)))
402438
{
403439
if (defaultArgList != null)
404440
{
@@ -415,11 +451,14 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
415451
}
416452
else
417453
{
418-
op = (arrayStart == paramIndex)
419-
// map remaining Python arguments to a tuple since
420-
// the managed function accepts it - hopefully :]
421-
? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount)
422-
: Runtime.PyTuple_GetItem(args, paramIndex);
454+
if(arrayStart == paramIndex)
455+
{
456+
op = HandleParamsArray(args, arrayStart, pyArgCount, out isNewReference);
457+
}
458+
else
459+
{
460+
op = Runtime.PyTuple_GetItem(args, paramIndex);
461+
}
423462
}
424463

425464
bool isOut;
@@ -428,7 +467,7 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
428467
return null;
429468
}
430469

431-
if (arrayStart == paramIndex)
470+
if (isNewReference)
432471
{
433472
// TODO: is this a bug? Should this happen even if the conversion fails?
434473
// GetSlice() creates a new reference but GetItem()
@@ -543,7 +582,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
543582
{
544583
defaultArgList = null;
545584
var match = false;
546-
paramsArray = false;
585+
paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false;
547586

548587
if (positionalArgumentCount == parameters.Length)
549588
{
@@ -572,7 +611,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
572611
// to be passed in as the parameter value
573612
defaultArgList.Add(parameters[v].GetDefaultValue());
574613
}
575-
else
614+
else if(!paramsArray)
576615
{
577616
match = false;
578617
}

src/tests/test_method.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,37 @@ def test_non_params_array_in_last_place():
257257
result = MethodTest.TestNonParamsArrayInLastPlace(1, 2, 3)
258258
assert result
259259

260+
def test_params_methods_with_no_params():
261+
"""Tests that passing no arguments to a params method
262+
passes an empty array"""
263+
result = MethodTest.TestValueParamsArg()
264+
assert len(result) == 0
265+
266+
result = MethodTest.TestOneArgWithParams('Some String')
267+
assert len(result) == 0
268+
269+
result = MethodTest.TestTwoArgWithParams('Some String', 'Some Other String')
270+
assert len(result) == 0
271+
272+
def test_params_methods_with_non_lists():
273+
"""Tests that passing single parameters to a params
274+
method will convert into an array on the .NET side"""
275+
result = MethodTest.TestOneArgWithParams('Some String', [1, 2, 3, 4])
276+
assert len(result) == 4
277+
278+
result = MethodTest.TestOneArgWithParams('Some String', 1, 2, 3, 4)
279+
assert len(result) == 4
280+
281+
result = MethodTest.TestOneArgWithParams('Some String', [5])
282+
assert len(result) == 1
283+
284+
result = MethodTest.TestOneArgWithParams('Some String', 5)
285+
assert len(result) == 1
286+
287+
def test_params_method_with_lists():
288+
"""Tests passing multiple lists to a params object[] method"""
289+
result = MethodTest.TestObjectParamsArg([],[])
290+
assert len(result) == 2
260291

261292
def test_string_out_params():
262293
"""Test use of string out-parameters."""

0 commit comments

Comments
 (0)