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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy