Skip to content

Params arg no params #1106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 16, 2020
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
together with Nuitka
- Fixes bug where delegates get casts (dotnetcore)
- Determine size of interpreter longs at runtime
- Handling exceptions ocurred in ModuleObject's getattribute
- Handling exceptions ocurred in ModuleObject's getattribute
- Fill `__classcell__` correctly for Python subclasses of .NET types
- Fixed issue with params methods that are not passed an array.

## [2.4.0][]

Expand Down
57 changes: 48 additions & 9 deletions src/runtime/methodbinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,41 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
return null;
}

static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out bool isNewReference)
{
isNewReference = false;
IntPtr op;
// for a params method, we may have a sequence or single/multiple items
// here we look to see if the item at the paramIndex is there or not
// and then if it is a sequence itself.
if ((pyArgCount - arrayStart) == 1)
{
// we only have one argument left, so we need to check it
// to see if it is a sequence or a single item
IntPtr item = Runtime.PyTuple_GetItem(args, arrayStart);
if (!Runtime.PyString_Check(item) && Runtime.PySequence_Check(item))
{
// it's a sequence (and not a string), so we use it as the op
op = item;
}
else
{
isNewReference = true;
op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount);
if (item != IntPtr.Zero)
{
Runtime.XDecref(item);
}
}
}
else
{
isNewReference = true;
op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount);
}
return op;
}

/// <summary>
/// Attempts to convert Python positional argument tuple and keyword argument table
/// into an array of managed objects, that can be passed to a method.
Expand Down Expand Up @@ -397,8 +432,9 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
{
var parameter = pi[paramIndex];
bool hasNamedParam = kwargDict.ContainsKey(parameter.Name);
bool isNewReference = false;

if (paramIndex >= pyArgCount && !hasNamedParam)
if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart)))
{
if (defaultArgList != null)
{
Expand All @@ -415,11 +451,14 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
}
else
{
op = (arrayStart == paramIndex)
// map remaining Python arguments to a tuple since
// the managed function accepts it - hopefully :]
? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount)
: Runtime.PyTuple_GetItem(args, paramIndex);
if(arrayStart == paramIndex)
{
op = HandleParamsArray(args, arrayStart, pyArgCount, out isNewReference);
}
else
{
op = Runtime.PyTuple_GetItem(args, paramIndex);
}
}

bool isOut;
Expand All @@ -428,7 +467,7 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
return null;
}

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

if (positionalArgumentCount == parameters.Length)
{
Expand Down Expand Up @@ -572,7 +611,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
// to be passed in as the parameter value
defaultArgList.Add(parameters[v].GetDefaultValue());
}
else
else if(!paramsArray)
{
match = false;
}
Expand Down
31 changes: 31 additions & 0 deletions src/tests/test_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,37 @@ def test_non_params_array_in_last_place():
result = MethodTest.TestNonParamsArrayInLastPlace(1, 2, 3)
assert result

def test_params_methods_with_no_params():
"""Tests that passing no arguments to a params method
passes an empty array"""
result = MethodTest.TestValueParamsArg()
assert len(result) == 0

result = MethodTest.TestOneArgWithParams('Some String')
assert len(result) == 0

result = MethodTest.TestTwoArgWithParams('Some String', 'Some Other String')
assert len(result) == 0

def test_params_methods_with_non_lists():
"""Tests that passing single parameters to a params
method will convert into an array on the .NET side"""
result = MethodTest.TestOneArgWithParams('Some String', [1, 2, 3, 4])
assert len(result) == 4

result = MethodTest.TestOneArgWithParams('Some String', 1, 2, 3, 4)
assert len(result) == 4

result = MethodTest.TestOneArgWithParams('Some String', [5])
assert len(result) == 1

result = MethodTest.TestOneArgWithParams('Some String', 5)
assert len(result) == 1

def test_params_method_with_lists():
"""Tests passing multiple lists to a params object[] method"""
result = MethodTest.TestObjectParamsArg([],[])
assert len(result) == 2

def test_string_out_params():
"""Test use of string out-parameters."""
Expand Down