Skip to content

Commit

Permalink
fix(jsii-runtime): treat "null" as "undefined" (#297)
Browse files Browse the repository at this point in the history
Since most languages do not have a distinction between "null" and
"undefined", jsii will effectively convert any "null" value passed into
an argument, a property or inside an object to "undefined".

Adds a compliance test to Java and .NET called "NullShouldBeTreatedAsUndefined".

Fixes aws/aws-cdk#157
Fixes #282
  • Loading branch information
Elad Ben-Israel authored Nov 7, 2018
1 parent cdf5a53 commit 43fb16a
Show file tree
Hide file tree
Showing 15 changed files with 660 additions and 4 deletions.
46 changes: 46 additions & 0 deletions packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -948,3 +948,49 @@ export class DoNotRecognizeAnyAsOptional {

}
}

/**
* jsii#282, aws-cdk#157: null should be treated as "undefined"
*/
export class NullShouldBeTreatedAsUndefined {
public changeMeToUndefined? = "hello";

constructor(_param1: string, optional?: any) {
if (optional !== undefined) {
throw new Error('Expecting second constructor argument to be "undefined"');
}
}

public giveMeUndefined(value?: any) {
if (value !== undefined) {
throw new Error('I am disappointed. I expected undefined and got: ' + JSON.stringify(value));
}
}

public giveMeUndefinedInsideAnObject(input: NullShouldBeTreatedAsUndefinedData) {
if (input.thisShouldBeUndefined !== undefined) {
throw new Error('I am disappointed. I expected undefined in "thisShouldBeUndefined" and got: ' + JSON.stringify(input));
}

const array = input.arrayWithThreeElementsAndUndefinedAsSecondArgument;
if (array.length !== 3) {
throw new Error('Expecting "arrayWithThreeElementsAndUndefinedAsSecondArgument" to have three elements: ' + JSON.stringify(input));
}

if (array[1] !== undefined) {
throw new Error('Expected arrayWithThreeElementsAndUndefinedAsSecondArgument[1] to be undefined: ' + JSON.stringify(input))
}
}

public verifyPropertyIsUndefined() {
if (this.changeMeToUndefined !== undefined) {
throw new Error('Expecting property "changeMeToUndefined" to be undefined, and it is: ' + this.changeMeToUndefined);
}
}
}

export interface NullShouldBeTreatedAsUndefinedData {
thisShouldBeUndefined?: any;
arrayWithThreeElementsAndUndefinedAsSecondArgument: any[];
}

95 changes: 94 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,99 @@
}
]
},
"jsii-calc.NullShouldBeTreatedAsUndefined": {
"assembly": "jsii-calc",
"docs": {
"comment": "jsii#282, aws-cdk#157: null should be treated as \"undefined\""
},
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefined",
"initializer": {
"initializer": true,
"parameters": [
{
"name": "_param1",
"type": {
"primitive": "string"
}
},
{
"name": "optional",
"type": {
"optional": true,
"primitive": "any"
}
}
]
},
"kind": "class",
"methods": [
{
"name": "giveMeUndefined",
"parameters": [
{
"name": "value",
"type": {
"optional": true,
"primitive": "any"
}
}
]
},
{
"name": "giveMeUndefinedInsideAnObject",
"parameters": [
{
"name": "input",
"type": {
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData"
}
}
]
},
{
"name": "verifyPropertyIsUndefined"
}
],
"name": "NullShouldBeTreatedAsUndefined",
"properties": [
{
"name": "changeMeToUndefined",
"type": {
"optional": true,
"primitive": "string"
}
}
]
},
"jsii-calc.NullShouldBeTreatedAsUndefinedData": {
"assembly": "jsii-calc",
"datatype": true,
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData",
"kind": "interface",
"name": "NullShouldBeTreatedAsUndefinedData",
"properties": [
{
"abstract": true,
"name": "arrayWithThreeElementsAndUndefinedAsSecondArgument",
"type": {
"collection": {
"elementtype": {
"primitive": "any"
},
"kind": "array"
}
}
},
{
"abstract": true,
"name": "thisShouldBeUndefined",
"type": {
"optional": true,
"primitive": "any"
}
}
]
},
"jsii-calc.NumberGenerator": {
"assembly": "jsii-calc",
"docs": {
Expand Down Expand Up @@ -3444,5 +3537,5 @@
}
},
"version": "0.7.8",
"fingerprint": "2BaszImarh4WChl9DFUcygfTpEfXU17fHQT2wgEptfM="
"fingerprint": "FZk0ePQ2XUte84CmnOjU3PPCl6QUA88ke6wHIJKhyzo="
}
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,27 @@ public void TestReturnInterfaceFromOverride()
Assert.Equal(4 * n, obj.Test(arg));
}

[Fact(DisplayName = Prefix + nameof(NullShouldBeTreatedAsUndefined))]
public void NullShouldBeTreatedAsUndefined()
{
// ctor
var obj = new NullShouldBeTreatedAsUndefined("param1", null);

// method argument
obj.GiveMeUndefined(null);

// inside object
obj.GiveMeUndefinedInsideAnObject(new NullShouldBeTreatedAsUndefinedData
{
ThisShouldBeUndefined = null,
ArrayWithThreeElementsAndUndefinedAsSecondArgument = new[] { "hello", null, "world" }
});

// property
obj.ChangeMeToUndefined = null;
obj.VerifyPropertyIsUndefined();
}

class NumberReturner : DeputyBase, IIReturnsNumber
{
public NumberReturner(double number)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import software.amazon.jsii.tests.calculator.Multiply;
import software.amazon.jsii.tests.calculator.Negate;
import software.amazon.jsii.tests.calculator.NodeStandardLibrary;
import software.amazon.jsii.tests.calculator.NullShouldBeTreatedAsUndefined;
import software.amazon.jsii.tests.calculator.NullShouldBeTreatedAsUndefinedData;
import software.amazon.jsii.tests.calculator.NumberGenerator;
import software.amazon.jsii.tests.calculator.Polymorphism;
import software.amazon.jsii.tests.calculator.Power;
Expand Down Expand Up @@ -927,6 +929,18 @@ public void classWithPrivateConstructorAndAutomaticProperties() {
assertEquals("Hello", obj.getReadOnlyString());
}

@Test
public void nullShouldBeTreatedAsUndefined() {
NullShouldBeTreatedAsUndefined obj = new NullShouldBeTreatedAsUndefined("hello", null);
obj.giveMeUndefined(null);
obj.giveMeUndefinedInsideAnObject(NullShouldBeTreatedAsUndefinedData.builder()
.withThisShouldBeUndefined(null)
.withArrayWithThreeElementsAndUndefinedAsSecondArgument(Arrays.asList("hello", null, "boom"))
.build());
obj.setChangeMeToUndefined(null);
obj.verifyPropertyIsUndefined();
}

static class MulTen extends Multiply {
public MulTen(final int value) {
super(new Number(value), new Number(10));
Expand Down
5 changes: 3 additions & 2 deletions packages/jsii-kernel/lib/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,9 +877,10 @@ export class Kernel {
return undefined;
}

// null
// null is treated as "undefined" because most languages do not have this distinction
// see awslabs/aws-cdk#157 and awslabs/jsii#282
if (v === null) {
return null;
return undefined;
}

// pointer
Expand Down
23 changes: 23 additions & 0 deletions packages/jsii-kernel/test/test.kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,29 @@ defineTest('overrides: skip overrides of private properties', async (test, sandb
test.deepEqual(result.result, 'privateProperty');
});

defineTest('nulls are converted to undefined - ctor', async (_test, sandbox) => {
sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo", null ] });
});

defineTest('nulls are converted to undefined - method arguments', async (_test, sandbox) => {
const objref = sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo" ] });
sandbox.invoke({ objref, method: 'giveMeUndefined', args: [ null ] });
});

defineTest('nulls are converted to undefined - inside objects', async (_test, sandbox) => {
const objref = sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo" ] });
sandbox.invoke({ objref, method: 'giveMeUndefinedInsideAnObject', args: [ {
thisShouldBeUndefined: null,
arrayWithThreeElementsAndUndefinedAsSecondArgument: [ 'one', null, 'two' ]
} ]});
});

defineTest('nulls are converted to undefined - properties', async (_test, sandbox) => {
const objref = sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo" ] });
sandbox.set({ objref, property: 'changeMeToUndefined', value: null });
sandbox.invoke({ objref, method: 'verifyPropertyIsUndefined' });
});

// =================================================================================================

const testNames: { [name: string]: boolean } = { };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,99 @@
}
]
},
"jsii-calc.NullShouldBeTreatedAsUndefined": {
"assembly": "jsii-calc",
"docs": {
"comment": "jsii#282, aws-cdk#157: null should be treated as \"undefined\""
},
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefined",
"initializer": {
"initializer": true,
"parameters": [
{
"name": "_param1",
"type": {
"primitive": "string"
}
},
{
"name": "optional",
"type": {
"optional": true,
"primitive": "any"
}
}
]
},
"kind": "class",
"methods": [
{
"name": "giveMeUndefined",
"parameters": [
{
"name": "value",
"type": {
"optional": true,
"primitive": "any"
}
}
]
},
{
"name": "giveMeUndefinedInsideAnObject",
"parameters": [
{
"name": "input",
"type": {
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData"
}
}
]
},
{
"name": "verifyPropertyIsUndefined"
}
],
"name": "NullShouldBeTreatedAsUndefined",
"properties": [
{
"name": "changeMeToUndefined",
"type": {
"optional": true,
"primitive": "string"
}
}
]
},
"jsii-calc.NullShouldBeTreatedAsUndefinedData": {
"assembly": "jsii-calc",
"datatype": true,
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData",
"kind": "interface",
"name": "NullShouldBeTreatedAsUndefinedData",
"properties": [
{
"abstract": true,
"name": "arrayWithThreeElementsAndUndefinedAsSecondArgument",
"type": {
"collection": {
"elementtype": {
"primitive": "any"
},
"kind": "array"
}
}
},
{
"abstract": true,
"name": "thisShouldBeUndefined",
"type": {
"optional": true,
"primitive": "any"
}
}
]
},
"jsii-calc.NumberGenerator": {
"assembly": "jsii-calc",
"docs": {
Expand Down Expand Up @@ -3444,5 +3537,5 @@
}
},
"version": "0.7.8",
"fingerprint": "2BaszImarh4WChl9DFUcygfTpEfXU17fHQT2wgEptfM="
"fingerprint": "FZk0ePQ2XUte84CmnOjU3PPCl6QUA88ke6wHIJKhyzo="
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Amazon.JSII.Runtime.Deputy;

namespace Amazon.JSII.Tests.CalculatorNamespace
{
[JsiiInterface(typeof(INullShouldBeTreatedAsUndefinedData), "jsii-calc.NullShouldBeTreatedAsUndefinedData")]
public interface INullShouldBeTreatedAsUndefinedData
{
[JsiiProperty("arrayWithThreeElementsAndUndefinedAsSecondArgument", "{\"collection\":{\"kind\":\"array\",\"elementtype\":{\"primitive\":\"any\"}}}")]
object[] ArrayWithThreeElementsAndUndefinedAsSecondArgument
{
get;
set;
}

[JsiiProperty("thisShouldBeUndefined", "{\"primitive\":\"any\",\"optional\":true}")]
object ThisShouldBeUndefined
{
get;
set;
}
}
}
Loading

0 comments on commit 43fb16a

Please sign in to comment.