Skip to content

Commit 43fb16a

Browse files
author
Elad Ben-Israel
authored
fix(jsii-runtime): treat "null" as "undefined" (#297)
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
1 parent cdf5a53 commit 43fb16a

File tree

15 files changed

+660
-4
lines changed

15 files changed

+660
-4
lines changed

packages/jsii-calc/lib/compliance.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,3 +948,49 @@ export class DoNotRecognizeAnyAsOptional {
948948

949949
}
950950
}
951+
952+
/**
953+
* jsii#282, aws-cdk#157: null should be treated as "undefined"
954+
*/
955+
export class NullShouldBeTreatedAsUndefined {
956+
public changeMeToUndefined? = "hello";
957+
958+
constructor(_param1: string, optional?: any) {
959+
if (optional !== undefined) {
960+
throw new Error('Expecting second constructor argument to be "undefined"');
961+
}
962+
}
963+
964+
public giveMeUndefined(value?: any) {
965+
if (value !== undefined) {
966+
throw new Error('I am disappointed. I expected undefined and got: ' + JSON.stringify(value));
967+
}
968+
}
969+
970+
public giveMeUndefinedInsideAnObject(input: NullShouldBeTreatedAsUndefinedData) {
971+
if (input.thisShouldBeUndefined !== undefined) {
972+
throw new Error('I am disappointed. I expected undefined in "thisShouldBeUndefined" and got: ' + JSON.stringify(input));
973+
}
974+
975+
const array = input.arrayWithThreeElementsAndUndefinedAsSecondArgument;
976+
if (array.length !== 3) {
977+
throw new Error('Expecting "arrayWithThreeElementsAndUndefinedAsSecondArgument" to have three elements: ' + JSON.stringify(input));
978+
}
979+
980+
if (array[1] !== undefined) {
981+
throw new Error('Expected arrayWithThreeElementsAndUndefinedAsSecondArgument[1] to be undefined: ' + JSON.stringify(input))
982+
}
983+
}
984+
985+
public verifyPropertyIsUndefined() {
986+
if (this.changeMeToUndefined !== undefined) {
987+
throw new Error('Expecting property "changeMeToUndefined" to be undefined, and it is: ' + this.changeMeToUndefined);
988+
}
989+
}
990+
}
991+
992+
export interface NullShouldBeTreatedAsUndefinedData {
993+
thisShouldBeUndefined?: any;
994+
arrayWithThreeElementsAndUndefinedAsSecondArgument: any[];
995+
}
996+

packages/jsii-calc/test/assembly.jsii

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2273,6 +2273,99 @@
22732273
}
22742274
]
22752275
},
2276+
"jsii-calc.NullShouldBeTreatedAsUndefined": {
2277+
"assembly": "jsii-calc",
2278+
"docs": {
2279+
"comment": "jsii#282, aws-cdk#157: null should be treated as \"undefined\""
2280+
},
2281+
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefined",
2282+
"initializer": {
2283+
"initializer": true,
2284+
"parameters": [
2285+
{
2286+
"name": "_param1",
2287+
"type": {
2288+
"primitive": "string"
2289+
}
2290+
},
2291+
{
2292+
"name": "optional",
2293+
"type": {
2294+
"optional": true,
2295+
"primitive": "any"
2296+
}
2297+
}
2298+
]
2299+
},
2300+
"kind": "class",
2301+
"methods": [
2302+
{
2303+
"name": "giveMeUndefined",
2304+
"parameters": [
2305+
{
2306+
"name": "value",
2307+
"type": {
2308+
"optional": true,
2309+
"primitive": "any"
2310+
}
2311+
}
2312+
]
2313+
},
2314+
{
2315+
"name": "giveMeUndefinedInsideAnObject",
2316+
"parameters": [
2317+
{
2318+
"name": "input",
2319+
"type": {
2320+
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData"
2321+
}
2322+
}
2323+
]
2324+
},
2325+
{
2326+
"name": "verifyPropertyIsUndefined"
2327+
}
2328+
],
2329+
"name": "NullShouldBeTreatedAsUndefined",
2330+
"properties": [
2331+
{
2332+
"name": "changeMeToUndefined",
2333+
"type": {
2334+
"optional": true,
2335+
"primitive": "string"
2336+
}
2337+
}
2338+
]
2339+
},
2340+
"jsii-calc.NullShouldBeTreatedAsUndefinedData": {
2341+
"assembly": "jsii-calc",
2342+
"datatype": true,
2343+
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData",
2344+
"kind": "interface",
2345+
"name": "NullShouldBeTreatedAsUndefinedData",
2346+
"properties": [
2347+
{
2348+
"abstract": true,
2349+
"name": "arrayWithThreeElementsAndUndefinedAsSecondArgument",
2350+
"type": {
2351+
"collection": {
2352+
"elementtype": {
2353+
"primitive": "any"
2354+
},
2355+
"kind": "array"
2356+
}
2357+
}
2358+
},
2359+
{
2360+
"abstract": true,
2361+
"name": "thisShouldBeUndefined",
2362+
"type": {
2363+
"optional": true,
2364+
"primitive": "any"
2365+
}
2366+
}
2367+
]
2368+
},
22762369
"jsii-calc.NumberGenerator": {
22772370
"assembly": "jsii-calc",
22782371
"docs": {
@@ -3444,5 +3537,5 @@
34443537
}
34453538
},
34463539
"version": "0.7.8",
3447-
"fingerprint": "2BaszImarh4WChl9DFUcygfTpEfXU17fHQT2wgEptfM="
3540+
"fingerprint": "FZk0ePQ2XUte84CmnOjU3PPCl6QUA88ke6wHIJKhyzo="
34483541
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,27 @@ public void TestReturnInterfaceFromOverride()
827827
Assert.Equal(4 * n, obj.Test(arg));
828828
}
829829

830+
[Fact(DisplayName = Prefix + nameof(NullShouldBeTreatedAsUndefined))]
831+
public void NullShouldBeTreatedAsUndefined()
832+
{
833+
// ctor
834+
var obj = new NullShouldBeTreatedAsUndefined("param1", null);
835+
836+
// method argument
837+
obj.GiveMeUndefined(null);
838+
839+
// inside object
840+
obj.GiveMeUndefinedInsideAnObject(new NullShouldBeTreatedAsUndefinedData
841+
{
842+
ThisShouldBeUndefined = null,
843+
ArrayWithThreeElementsAndUndefinedAsSecondArgument = new[] { "hello", null, "world" }
844+
});
845+
846+
// property
847+
obj.ChangeMeToUndefined = null;
848+
obj.VerifyPropertyIsUndefined();
849+
}
850+
830851
class NumberReturner : DeputyBase, IIReturnsNumber
831852
{
832853
public NumberReturner(double number)

packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import software.amazon.jsii.tests.calculator.Multiply;
2626
import software.amazon.jsii.tests.calculator.Negate;
2727
import software.amazon.jsii.tests.calculator.NodeStandardLibrary;
28+
import software.amazon.jsii.tests.calculator.NullShouldBeTreatedAsUndefined;
29+
import software.amazon.jsii.tests.calculator.NullShouldBeTreatedAsUndefinedData;
2830
import software.amazon.jsii.tests.calculator.NumberGenerator;
2931
import software.amazon.jsii.tests.calculator.Polymorphism;
3032
import software.amazon.jsii.tests.calculator.Power;
@@ -927,6 +929,18 @@ public void classWithPrivateConstructorAndAutomaticProperties() {
927929
assertEquals("Hello", obj.getReadOnlyString());
928930
}
929931

932+
@Test
933+
public void nullShouldBeTreatedAsUndefined() {
934+
NullShouldBeTreatedAsUndefined obj = new NullShouldBeTreatedAsUndefined("hello", null);
935+
obj.giveMeUndefined(null);
936+
obj.giveMeUndefinedInsideAnObject(NullShouldBeTreatedAsUndefinedData.builder()
937+
.withThisShouldBeUndefined(null)
938+
.withArrayWithThreeElementsAndUndefinedAsSecondArgument(Arrays.asList("hello", null, "boom"))
939+
.build());
940+
obj.setChangeMeToUndefined(null);
941+
obj.verifyPropertyIsUndefined();
942+
}
943+
930944
static class MulTen extends Multiply {
931945
public MulTen(final int value) {
932946
super(new Number(value), new Number(10));

packages/jsii-kernel/lib/kernel.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,9 +877,10 @@ export class Kernel {
877877
return undefined;
878878
}
879879

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

885886
// pointer

packages/jsii-kernel/test/test.kernel.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,29 @@ defineTest('overrides: skip overrides of private properties', async (test, sandb
936936
test.deepEqual(result.result, 'privateProperty');
937937
});
938938

939+
defineTest('nulls are converted to undefined - ctor', async (_test, sandbox) => {
940+
sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo", null ] });
941+
});
942+
943+
defineTest('nulls are converted to undefined - method arguments', async (_test, sandbox) => {
944+
const objref = sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo" ] });
945+
sandbox.invoke({ objref, method: 'giveMeUndefined', args: [ null ] });
946+
});
947+
948+
defineTest('nulls are converted to undefined - inside objects', async (_test, sandbox) => {
949+
const objref = sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo" ] });
950+
sandbox.invoke({ objref, method: 'giveMeUndefinedInsideAnObject', args: [ {
951+
thisShouldBeUndefined: null,
952+
arrayWithThreeElementsAndUndefinedAsSecondArgument: [ 'one', null, 'two' ]
953+
} ]});
954+
});
955+
956+
defineTest('nulls are converted to undefined - properties', async (_test, sandbox) => {
957+
const objref = sandbox.create({ fqn: 'jsii-calc.NullShouldBeTreatedAsUndefined', args: [ "foo" ] });
958+
sandbox.set({ objref, property: 'changeMeToUndefined', value: null });
959+
sandbox.invoke({ objref, method: 'verifyPropertyIsUndefined' });
960+
});
961+
939962
// =================================================================================================
940963

941964
const testNames: { [name: string]: boolean } = { };

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

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2273,6 +2273,99 @@
22732273
}
22742274
]
22752275
},
2276+
"jsii-calc.NullShouldBeTreatedAsUndefined": {
2277+
"assembly": "jsii-calc",
2278+
"docs": {
2279+
"comment": "jsii#282, aws-cdk#157: null should be treated as \"undefined\""
2280+
},
2281+
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefined",
2282+
"initializer": {
2283+
"initializer": true,
2284+
"parameters": [
2285+
{
2286+
"name": "_param1",
2287+
"type": {
2288+
"primitive": "string"
2289+
}
2290+
},
2291+
{
2292+
"name": "optional",
2293+
"type": {
2294+
"optional": true,
2295+
"primitive": "any"
2296+
}
2297+
}
2298+
]
2299+
},
2300+
"kind": "class",
2301+
"methods": [
2302+
{
2303+
"name": "giveMeUndefined",
2304+
"parameters": [
2305+
{
2306+
"name": "value",
2307+
"type": {
2308+
"optional": true,
2309+
"primitive": "any"
2310+
}
2311+
}
2312+
]
2313+
},
2314+
{
2315+
"name": "giveMeUndefinedInsideAnObject",
2316+
"parameters": [
2317+
{
2318+
"name": "input",
2319+
"type": {
2320+
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData"
2321+
}
2322+
}
2323+
]
2324+
},
2325+
{
2326+
"name": "verifyPropertyIsUndefined"
2327+
}
2328+
],
2329+
"name": "NullShouldBeTreatedAsUndefined",
2330+
"properties": [
2331+
{
2332+
"name": "changeMeToUndefined",
2333+
"type": {
2334+
"optional": true,
2335+
"primitive": "string"
2336+
}
2337+
}
2338+
]
2339+
},
2340+
"jsii-calc.NullShouldBeTreatedAsUndefinedData": {
2341+
"assembly": "jsii-calc",
2342+
"datatype": true,
2343+
"fqn": "jsii-calc.NullShouldBeTreatedAsUndefinedData",
2344+
"kind": "interface",
2345+
"name": "NullShouldBeTreatedAsUndefinedData",
2346+
"properties": [
2347+
{
2348+
"abstract": true,
2349+
"name": "arrayWithThreeElementsAndUndefinedAsSecondArgument",
2350+
"type": {
2351+
"collection": {
2352+
"elementtype": {
2353+
"primitive": "any"
2354+
},
2355+
"kind": "array"
2356+
}
2357+
}
2358+
},
2359+
{
2360+
"abstract": true,
2361+
"name": "thisShouldBeUndefined",
2362+
"type": {
2363+
"optional": true,
2364+
"primitive": "any"
2365+
}
2366+
}
2367+
]
2368+
},
22762369
"jsii-calc.NumberGenerator": {
22772370
"assembly": "jsii-calc",
22782371
"docs": {
@@ -3444,5 +3537,5 @@
34443537
}
34453538
},
34463539
"version": "0.7.8",
3447-
"fingerprint": "2BaszImarh4WChl9DFUcygfTpEfXU17fHQT2wgEptfM="
3540+
"fingerprint": "FZk0ePQ2XUte84CmnOjU3PPCl6QUA88ke6wHIJKhyzo="
34483541
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.CalculatorNamespace
4+
{
5+
[JsiiInterface(typeof(INullShouldBeTreatedAsUndefinedData), "jsii-calc.NullShouldBeTreatedAsUndefinedData")]
6+
public interface INullShouldBeTreatedAsUndefinedData
7+
{
8+
[JsiiProperty("arrayWithThreeElementsAndUndefinedAsSecondArgument", "{\"collection\":{\"kind\":\"array\",\"elementtype\":{\"primitive\":\"any\"}}}")]
9+
object[] ArrayWithThreeElementsAndUndefinedAsSecondArgument
10+
{
11+
get;
12+
set;
13+
}
14+
15+
[JsiiProperty("thisShouldBeUndefined", "{\"primitive\":\"any\",\"optional\":true}")]
16+
object ThisShouldBeUndefined
17+
{
18+
get;
19+
set;
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)