Skip to content

Commit e8b5a35

Browse files
Hamza AssyadRomainMuller
authored andcommitted
feat(dotnet): handling optional and variadic parameters (#680)
Handling optional and variadic parameters in .NET Emits the =null or params[] keywords when required in constructors or methods. Ran pack.sh in the CDK with this change, and the S3 construct now looks better: **Optionals:** `public Bucket(Amazon.CDK.Construct scope, string id, Amazon.CDK.AWS.S3.IBucketProps props = null): base(new DeputyProps(new object[]{scope, id, props})) { } ` Making the C# call look like: ` var bucket = new Bucket(this, "bucketName");` Rather than ` var bucket = new Bucket(this, "bucketName", null);` **Variadic:** Tested with null values, empty array, one value array, multiple values array. ``` // Array with no value in constructor params var variadicClassNoParams = new VariadicMethod(); // Array with null value in constructor params var variadicClassNullParams = new VariadicMethod(null); // Array with one value in constructor params var variadicClassOneParam = new VariadicMethod(1); // Array with multiple values in constructor params var variadicClassMultipleParams = new VariadicMethod(1, 2, 3, 4); ``` Fixes #153 Fixes #210
1 parent 2f40eeb commit e8b5a35

File tree

21 files changed

+134
-44
lines changed

21 files changed

+134
-44
lines changed

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using Amazon.JSII.Runtime.Deputy;
45
using Amazon.JSII.Tests.CalculatorNamespace;
56
using CompositeOperation = Amazon.JSII.Tests.CalculatorNamespace.composition.CompositeOperation;
@@ -861,6 +862,56 @@ public void NullShouldBeTreatedAsUndefined()
861862
obj.VerifyPropertyIsUndefined();
862863
}
863864

865+
[Fact(DisplayName = Prefix + nameof(OptionalAndVariadicArgumentsTest))]
866+
public void OptionalAndVariadicArgumentsTest()
867+
{
868+
// ctor
869+
var objWithOptionalProvided = new NullShouldBeTreatedAsUndefined("param1", null);
870+
var objWithoutOptionalProvided = new NullShouldBeTreatedAsUndefined("param1");
871+
872+
// method argument called with null value
873+
objWithoutOptionalProvided.GiveMeUndefined(null);
874+
875+
// method argument called without null value
876+
objWithoutOptionalProvided.GiveMeUndefined();
877+
878+
// Array with no value in constructor params
879+
var variadicClassNoParams = new VariadicMethod();
880+
881+
// Array with null value in constructor params
882+
var variadicClassNullParams = new VariadicMethod(null);
883+
884+
// Array with one value in constructor params
885+
var variadicClassOneParam = new VariadicMethod(1);
886+
887+
// Array with multiple values in constructor params
888+
var variadicClassMultipleParams = new VariadicMethod(1, 2, 3, 4);
889+
890+
// Variadic parameter with null passed
891+
variadicClassNoParams.AsArray(Double.MinValue, null);
892+
893+
// Variadic parameter with default value used
894+
variadicClassNoParams.AsArray(Double.MinValue);
895+
896+
var list = new List<double>();
897+
898+
// Variadic parameter with array with no value
899+
variadicClassNoParams.AsArray(Double.MinValue, list.ToArray());
900+
901+
// Variadic parameter with array with one value
902+
list.Add(1d);
903+
variadicClassNoParams.AsArray(Double.MinValue, list.ToArray());
904+
905+
// Variadic parameter with array with multiple value
906+
list.Add(2d);
907+
list.Add(3d);
908+
list.Add(4d);
909+
list.Add(5d);
910+
list.Add(6d);
911+
912+
variadicClassNoParams.AsArray(Double.MinValue, list.ToArray());
913+
}
914+
864915
[Fact(DisplayName = Prefix + nameof(JsiiAgent))]
865916
public void JsiiAgent()
866917
{

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/DeputyBase.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,40 @@ static object[] ConvertArguments(Parameter[] parameters, params object[] argumen
321321
throw new ArgumentException("Arguments do not match method parameters", nameof(arguments));
322322
}
323323

324+
var cleanedArgs = new List<object>(arguments);
325+
var cleanedParams = new List<Parameter>(parameters);
326+
327+
// Handling variadic parameters (null array, empty array, one value array, n values array..)
328+
if (parameters.Length > 0 && parameters.Last().IsVariadic)
329+
{
330+
// Last parameter is variadic, let's explode the .NET attributes
331+
Array variadicValues = arguments.Last() as Array;
332+
333+
// We remove the last argument (the variadic array);
334+
cleanedArgs.RemoveAt(cleanedArgs.Count - 1);
335+
336+
// A null value could be passed as a params
337+
if (variadicValues != null)
338+
{
339+
// We save the last parameter to backfill the parameters list
340+
var lastParameter = cleanedParams.Last();
341+
342+
for (int i = 0; i < variadicValues.Length; i++)
343+
{
344+
// Backfill the arguments
345+
cleanedArgs.Add(variadicValues.GetValue(i));
346+
347+
// Backfill the parameters if necessary, for a 1:1 mirror with the cleanedArgs
348+
if (cleanedArgs.Count != cleanedParams.Count)
349+
cleanedParams.Add(lastParameter);
350+
}
351+
}
352+
}
353+
324354
IFrameworkToJsiiConverter converter = serviceProvider.GetRequiredService<IFrameworkToJsiiConverter>();
325355
IReferenceMap referenceMap = serviceProvider.GetRequiredService<IReferenceMap>();
326356

327-
return parameters.Zip(arguments, (parameter, frameworkArgument) =>
357+
return cleanedParams.Zip(cleanedArgs, (parameter, frameworkArgument) =>
328358
{
329359
if (!converter.TryConvert(parameter, referenceMap, frameworkArgument, out object jsiiArgument))
330360
{

packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -229,26 +229,20 @@ export class DotNetGenerator extends Generator {
229229
this.code.openBlock(`public${inner}${absPrefix} class ${className}${implementsExpr}`);
230230

231231
// Compute the class parameters
232-
// TODO: add the support for optional parameters
233-
// Not achievable as of today and not supported by the current generator
234-
// https://github.com/awslabs/jsii/issues/210
235232
let parametersDefinition = '';
236233
let parametersBase = '';
237234
const initializer = cls.initializer;
238235
if (initializer) {
239236
this.dotnetDocGenerator.emitDocs(initializer);
240237
this.dotnetRuntimeGenerator.emitDeprecatedAttributeIfNecessary(initializer);
241238
if (initializer.parameters) {
239+
parametersDefinition = this.renderParametersString(initializer.parameters);
242240
for (const p of initializer.parameters) {
243-
const pType = this.typeresolver.toDotNetType(p.type);
244-
const isOptionalPrimitive = this.isOptionalPrimitive(p) ? '?' : '';
245-
if (parametersDefinition !== '') {
246-
parametersDefinition += ', ';
241+
parametersBase += `${this.nameutils.convertParameterName(p.name)}`;
242+
// If this is not the last parameter, append ,
243+
if (initializer.parameters.indexOf(p) !== initializer.parameters.length - 1) {
247244
parametersBase += ', ';
248245
}
249-
parametersDefinition += `${pType}${isOptionalPrimitive} ${this.nameutils.convertParameterName(p.name)}`;
250-
parametersBase += `${this.nameutils.convertParameterName(p.name)}`;
251-
252246
}
253247
}
254248

@@ -443,18 +437,34 @@ export class DotNetGenerator extends Generator {
443437
}
444438
}
445439

446-
// TODO: I uncovered an issue with optional parameters and ordering them
447-
// They are currently not supported by the generator.
448-
// In C#, optional parameters need to be declared after required parameters
449-
// We could make changes to the parameters ordering in the jsii model to support them
450-
// But then an optional parameter becoming non optional would create a mess
451-
// https://github.com/awslabs/jsii/issues/210
440+
/**
441+
* Renders method parameters string
442+
*/
452443
private renderMethodParameters(method: spec.Method): string {
444+
return this.renderParametersString(method.parameters);
445+
}
446+
447+
/**
448+
* Renders parameters string for methods or constructors
449+
*/
450+
private renderParametersString(parameters: spec.Parameter[] | undefined): string {
453451
const params = [];
454-
if (method.parameters) {
455-
for (const p of method.parameters) {
456-
const isOptionalPrimitive = this.isOptionalPrimitive(p) ? '?' : '';
457-
const st = `${this.typeresolver.toDotNetType(p.type)}${isOptionalPrimitive} ${this.nameutils.convertParameterName(p.name)}`;
452+
if (parameters) {
453+
for (const p of parameters) {
454+
let optionalPrimitive = '';
455+
let optionalKeyword = '';
456+
let type = this.typeresolver.toDotNetType(p.type);
457+
if (p.optional) {
458+
optionalKeyword = ' = null';
459+
if (this.isOptionalPrimitive(p)) {
460+
optionalPrimitive = '?';
461+
462+
}
463+
} else if (p.variadic) {
464+
type = `params ${type}[]`;
465+
}
466+
const st =
467+
`${type}${optionalPrimitive} ${this.nameutils.convertParameterName(p.name)}${optionalKeyword}`;
458468
params.push(st);
459469
}
460470
}
@@ -637,7 +647,6 @@ export class DotNetGenerator extends Generator {
637647
let isVirtualKeyWord = '';
638648
// If the prop parent is a class
639649
if (cls.kind === spec.TypeKind.Class) {
640-
641650
const implementedInBase = this.isMemberDefinedOnAncestor(cls as spec.ClassType, prop);
642651
if (implementedInBase || datatype || proxy) {
643652
// Override if the property is in a datatype or proxy class or declared in a parent class

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/Calculator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class Calculator : Amazon.JSII.Tests.CalculatorNamespace.composition.Comp
1414
/// <remarks>
1515
/// stability: Experimental
1616
/// </remarks>
17-
public Calculator(Amazon.JSII.Tests.CalculatorNamespace.ICalculatorProps props): base(new DeputyProps(new object[]{props}))
17+
public Calculator(Amazon.JSII.Tests.CalculatorNamespace.ICalculatorProps props = null): base(new DeputyProps(new object[]{props}))
1818
{
1919
}
2020

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/DataRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected DataRenderer(DeputyProps props): base(props)
2828
/// stability: Experimental
2929
/// </remarks>
3030
[JsiiMethod(name: "render", returnsJson: "{\"type\":{\"primitive\":\"string\"}}", parametersJson: "[{\"name\":\"data\",\"optional\":true,\"type\":{\"fqn\":\"@scope/jsii-calc-lib.MyFirstStruct\"}}]")]
31-
public virtual string Render(Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.IMyFirstStruct data)
31+
public virtual string Render(Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.IMyFirstStruct data = null)
3232
{
3333
return InvokeInstanceMethod<string>(new object[]{data});
3434
}

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/DefaultedConstructorArgument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class DefaultedConstructorArgument : DeputyBase
1111
/// <remarks>
1212
/// stability: Experimental
1313
/// </remarks>
14-
public DefaultedConstructorArgument(double? arg1, string arg2, System.DateTime? arg3): base(new DeputyProps(new object[]{arg1, arg2, arg3}))
14+
public DefaultedConstructorArgument(double? arg1 = null, string arg2 = null, System.DateTime? arg3 = null): base(new DeputyProps(new object[]{arg1, arg2, arg3}))
1515
{
1616
}
1717

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/DeprecatedClass.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class DeprecatedClass : DeputyBase
1313
/// stability: Deprecated
1414
/// </remarks>
1515
[System.Obsolete("this constructor is \"just\" okay")]
16-
public DeprecatedClass(string readonlyString, double? mutableNumber): base(new DeputyProps(new object[]{readonlyString, mutableNumber}))
16+
public DeprecatedClass(string readonlyString, double? mutableNumber = null): base(new DeputyProps(new object[]{readonlyString, mutableNumber}))
1717
{
1818
}
1919

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/DoNotRecognizeAnyAsOptional.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected DoNotRecognizeAnyAsOptional(DeputyProps props): base(props)
2525
/// stability: Experimental
2626
/// </remarks>
2727
[JsiiMethod(name: "method", parametersJson: "[{\"name\":\"_requiredAny\",\"type\":{\"primitive\":\"any\"}},{\"name\":\"_optionalAny\",\"optional\":true,\"type\":{\"primitive\":\"any\"}},{\"name\":\"_optionalString\",\"optional\":true,\"type\":{\"primitive\":\"string\"}}]")]
28-
public virtual void Method(object requiredAny, object optionalAny, string optionalString)
28+
public virtual void Method(object requiredAny, object optionalAny = null, string optionalString = null)
2929
{
3030
InvokeInstanceVoidMethod(new object[]{requiredAny, optionalAny, optionalString});
3131
}

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/DocumentedClass.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected DocumentedClass(DeputyProps props): base(props)
3636
/// stability: Stable
3737
/// </remarks>
3838
[JsiiMethod(name: "greet", returnsJson: "{\"type\":{\"primitive\":\"number\"}}", parametersJson: "[{\"docs\":{\"summary\":\"The person to be greeted.\"},\"name\":\"greetee\",\"optional\":true,\"type\":{\"fqn\":\"jsii-calc.Greetee\"}}]")]
39-
public virtual double Greet(Amazon.JSII.Tests.CalculatorNamespace.IGreetee greetee)
39+
public virtual double Greet(Amazon.JSII.Tests.CalculatorNamespace.IGreetee greetee = null)
4040
{
4141
return InvokeInstanceMethod<double>(new object[]{greetee});
4242
}

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/DontComplainAboutVariadicAfterOptional.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ protected DontComplainAboutVariadicAfterOptional(DeputyProps props): base(props)
2424
/// stability: Experimental
2525
/// </remarks>
2626
[JsiiMethod(name: "optionalAndVariadic", returnsJson: "{\"type\":{\"primitive\":\"string\"}}", parametersJson: "[{\"name\":\"optional\",\"optional\":true,\"type\":{\"primitive\":\"string\"}},{\"name\":\"things\",\"type\":{\"primitive\":\"string\"},\"variadic\":true}]")]
27-
public virtual string OptionalAndVariadic(string optional, string things)
27+
public virtual string OptionalAndVariadic(string optional = null, params string[] things)
2828
{
2929
return InvokeInstanceMethod<string>(new object[]{optional, things});
3030
}

0 commit comments

Comments
 (0)