Skip to content

Commit 471deda

Browse files
committed
fix(kernel): Return object literals as references
Use the javascript `Proxy` class to coalesce object literals to an interface type, allowing them to be returned by reference instead of by value. Introduces a test in the `jsii-kernel` to demonstrate the feature works as intended. Fixes #248 Fixes aws/aws-cdk#774
1 parent b743a13 commit 471deda

File tree

13 files changed

+340
-8
lines changed

13 files changed

+340
-8
lines changed

Diff for: packages/jsii-calc/lib/compliance.ts

+8
Original file line numberDiff line numberDiff line change
@@ -887,3 +887,11 @@ export class AbstractClassReturner {
887887
}
888888
}
889889
}
890+
891+
export interface MutableObjectLiteral {
892+
value: string;
893+
}
894+
895+
export class ClassWithMutableObjectLiteralProperty {
896+
public mutableObject: MutableObjectLiteral = { value: 'default' };
897+
}

Diff for: packages/jsii-calc/test/assembly.jsii

+34-1
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,23 @@
10181018
}
10191019
]
10201020
},
1021+
"jsii-calc.ClassWithMutableObjectLiteralProperty": {
1022+
"assembly": "jsii-calc",
1023+
"fqn": "jsii-calc.ClassWithMutableObjectLiteralProperty",
1024+
"initializer": {
1025+
"initializer": true
1026+
},
1027+
"kind": "class",
1028+
"name": "ClassWithMutableObjectLiteralProperty",
1029+
"properties": [
1030+
{
1031+
"name": "mutableObject",
1032+
"type": {
1033+
"fqn": "jsii-calc.MutableObjectLiteral"
1034+
}
1035+
}
1036+
]
1037+
},
10211038
"jsii-calc.DefaultedConstructorArgument": {
10221039
"assembly": "jsii-calc",
10231040
"fqn": "jsii-calc.DefaultedConstructorArgument",
@@ -1883,6 +1900,22 @@
18831900
}
18841901
]
18851902
},
1903+
"jsii-calc.MutableObjectLiteral": {
1904+
"assembly": "jsii-calc",
1905+
"datatype": true,
1906+
"fqn": "jsii-calc.MutableObjectLiteral",
1907+
"kind": "interface",
1908+
"name": "MutableObjectLiteral",
1909+
"properties": [
1910+
{
1911+
"abstract": true,
1912+
"name": "value",
1913+
"type": {
1914+
"primitive": "string"
1915+
}
1916+
}
1917+
]
1918+
},
18861919
"jsii-calc.Negate": {
18871920
"assembly": "jsii-calc",
18881921
"base": {
@@ -3230,5 +3263,5 @@
32303263
}
32313264
},
32323265
"version": "0.7.6",
3233-
"fingerprint": "ecDtx3DHVZi7UjyCDhGncg4jbSRaD536bUyh6YzAxlY="
3266+
"fingerprint": "DFShLmIJQmPjTv5Dmz4JoBKqoCtAyifD1RpCuHo+sEc="
32343267
}

Diff for: packages/jsii-kernel/lib/kernel.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { TOKEN_DATE, TOKEN_ENUM, TOKEN_REF } from './api';
1414
*/
1515
const OBJID_PROP = '$__jsii__objid__$';
1616
const FQN_PROP = '$__jsii__fqn__$';
17+
const PROXIES_PROP = '$__jsii__proxies__$';
1718

1819
/**
1920
* A special FQN that can be used to create empty javascript objects.
@@ -923,12 +924,17 @@ export class Kernel {
923924
// so the client receives a real object.
924925
if (typeof(v) === 'object' && targetType && spec.isNamedTypeReference(targetType)) {
925926
this._debug('coalescing to', targetType);
926-
const newObjRef = this._create({ fqn: targetType.fqn });
927-
const newObj = this._findObject(newObjRef);
928-
for (const k of Object.keys(v)) {
929-
newObj[k] = v[k];
930-
}
931-
return newObjRef;
927+
const proxies = v[PROXIES_PROP] = v[PROXIES_PROP] || {};
928+
if (proxies[targetType.fqn]) { return proxies[targetType.fqn]; }
929+
const proxy = new Proxy(v, {
930+
has(target: any, propertyKey: string) {
931+
return propertyKey in target || propertyKey === FQN_PROP;
932+
},
933+
get(target: any, propertyKey: string) {
934+
return propertyKey === FQN_PROP ? targetType.fqn : target[propertyKey];
935+
}
936+
});
937+
return this._createObjref(proxy, targetType.fqn);
932938
}
933939

934940
// date (https://stackoverflow.com/a/643827/737957)

Diff for: packages/jsii-kernel/test/test.kernel.ts

+15
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,21 @@ defineTest('node.js standard library', async (test, sandbox) => {
873873
{ result: "6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50" });
874874
});
875875

876+
// @see awslabs/jsii#248
877+
defineTest('object literals are returned by reference', async (test, sandbox) => {
878+
const objref = sandbox.create({ fqn: 'jsii-calc.ClassWithMutableObjectLiteralProperty' });
879+
const property = sandbox.get({ objref, property: 'mutableObject' }).value;
880+
881+
const newValue = 'Bazinga!1!';
882+
sandbox.set({ objref: property, property: 'value', value: newValue });
883+
884+
test.equal(newValue,
885+
sandbox.get({
886+
objref: sandbox.get({ objref, property: 'mutableObject' }).value,
887+
property: 'value'
888+
}).value);
889+
});
890+
876891
const testNames: { [name: string]: boolean } = { };
877892

878893
async function createCalculatorSandbox(name: string) {

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

+34-1
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,23 @@
10181018
}
10191019
]
10201020
},
1021+
"jsii-calc.ClassWithMutableObjectLiteralProperty": {
1022+
"assembly": "jsii-calc",
1023+
"fqn": "jsii-calc.ClassWithMutableObjectLiteralProperty",
1024+
"initializer": {
1025+
"initializer": true
1026+
},
1027+
"kind": "class",
1028+
"name": "ClassWithMutableObjectLiteralProperty",
1029+
"properties": [
1030+
{
1031+
"name": "mutableObject",
1032+
"type": {
1033+
"fqn": "jsii-calc.MutableObjectLiteral"
1034+
}
1035+
}
1036+
]
1037+
},
10211038
"jsii-calc.DefaultedConstructorArgument": {
10221039
"assembly": "jsii-calc",
10231040
"fqn": "jsii-calc.DefaultedConstructorArgument",
@@ -1883,6 +1900,22 @@
18831900
}
18841901
]
18851902
},
1903+
"jsii-calc.MutableObjectLiteral": {
1904+
"assembly": "jsii-calc",
1905+
"datatype": true,
1906+
"fqn": "jsii-calc.MutableObjectLiteral",
1907+
"kind": "interface",
1908+
"name": "MutableObjectLiteral",
1909+
"properties": [
1910+
{
1911+
"abstract": true,
1912+
"name": "value",
1913+
"type": {
1914+
"primitive": "string"
1915+
}
1916+
}
1917+
]
1918+
},
18861919
"jsii-calc.Negate": {
18871920
"assembly": "jsii-calc",
18881921
"base": {
@@ -3230,5 +3263,5 @@
32303263
}
32313264
},
32323265
"version": "0.7.6",
3233-
"fingerprint": "ecDtx3DHVZi7UjyCDhGncg4jbSRaD536bUyh6YzAxlY="
3266+
"fingerprint": "DFShLmIJQmPjTv5Dmz4JoBKqoCtAyifD1RpCuHo+sEc="
32343267
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.CalculatorNamespace
4+
{
5+
[JsiiClass(typeof(ClassWithMutableObjectLiteralProperty), "jsii-calc.ClassWithMutableObjectLiteralProperty", "[]")]
6+
public class ClassWithMutableObjectLiteralProperty : DeputyBase
7+
{
8+
public ClassWithMutableObjectLiteralProperty(): base(new DeputyProps(new object[]{}))
9+
{
10+
}
11+
12+
protected ClassWithMutableObjectLiteralProperty(ByRefValue reference): base(reference)
13+
{
14+
}
15+
16+
protected ClassWithMutableObjectLiteralProperty(DeputyProps props): base(props)
17+
{
18+
}
19+
20+
[JsiiProperty("mutableObject", "{\"fqn\":\"jsii-calc.MutableObjectLiteral\"}")]
21+
public virtual IMutableObjectLiteral MutableObject
22+
{
23+
get => GetInstanceProperty<IMutableObjectLiteral>();
24+
set => SetInstanceProperty(value);
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.CalculatorNamespace
4+
{
5+
[JsiiInterface(typeof(IMutableObjectLiteral), "jsii-calc.MutableObjectLiteral")]
6+
public interface IMutableObjectLiteral
7+
{
8+
[JsiiProperty("value", "{\"primitive\":\"string\"}")]
9+
string Value
10+
{
11+
get;
12+
set;
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.CalculatorNamespace
4+
{
5+
public class MutableObjectLiteral : DeputyBase, IMutableObjectLiteral
6+
{
7+
[JsiiProperty("value", "{\"primitive\":\"string\"}", true)]
8+
public string Value
9+
{
10+
get;
11+
set;
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.CalculatorNamespace
4+
{
5+
[JsiiTypeProxy(typeof(IMutableObjectLiteral), "jsii-calc.MutableObjectLiteral")]
6+
internal sealed class MutableObjectLiteralProxy : DeputyBase, IMutableObjectLiteral
7+
{
8+
private MutableObjectLiteralProxy(ByRefValue reference): base(reference)
9+
{
10+
}
11+
12+
[JsiiProperty("value", "{\"primitive\":\"string\"}")]
13+
public string Value
14+
{
15+
get => GetInstanceProperty<string>();
16+
set => SetInstanceProperty(value);
17+
}
18+
}
19+
}

Diff for: packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ protected Class<?> resolveClass(final String fqn) throws ClassNotFoundException
2929
case "jsii-calc.BinaryOperation": return software.amazon.jsii.tests.calculator.BinaryOperation.class;
3030
case "jsii-calc.Calculator": return software.amazon.jsii.tests.calculator.Calculator.class;
3131
case "jsii-calc.CalculatorProps": return software.amazon.jsii.tests.calculator.CalculatorProps.class;
32+
case "jsii-calc.ClassWithMutableObjectLiteralProperty": return software.amazon.jsii.tests.calculator.ClassWithMutableObjectLiteralProperty.class;
3233
case "jsii-calc.DefaultedConstructorArgument": return software.amazon.jsii.tests.calculator.DefaultedConstructorArgument.class;
3334
case "jsii-calc.DerivedClassHasNoProperties.Base": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Base.class;
3435
case "jsii-calc.DerivedClassHasNoProperties.Derived": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Derived.class;
@@ -51,6 +52,7 @@ protected Class<?> resolveClass(final String fqn) throws ClassNotFoundException
5152
case "jsii-calc.JSObjectLiteralToNativeClass": return software.amazon.jsii.tests.calculator.JSObjectLiteralToNativeClass.class;
5253
case "jsii-calc.JavaReservedWords": return software.amazon.jsii.tests.calculator.JavaReservedWords.class;
5354
case "jsii-calc.Multiply": return software.amazon.jsii.tests.calculator.Multiply.class;
55+
case "jsii-calc.MutableObjectLiteral": return software.amazon.jsii.tests.calculator.MutableObjectLiteral.class;
5456
case "jsii-calc.Negate": return software.amazon.jsii.tests.calculator.Negate.class;
5557
case "jsii-calc.NodeStandardLibrary": return software.amazon.jsii.tests.calculator.NodeStandardLibrary.class;
5658
case "jsii-calc.NumberGenerator": return software.amazon.jsii.tests.calculator.NumberGenerator.class;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package software.amazon.jsii.tests.calculator;
2+
3+
@javax.annotation.Generated(value = "jsii-pacmak")
4+
@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.ClassWithMutableObjectLiteralProperty")
5+
public class ClassWithMutableObjectLiteralProperty extends software.amazon.jsii.JsiiObject {
6+
protected ClassWithMutableObjectLiteralProperty(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
7+
super(mode);
8+
}
9+
public ClassWithMutableObjectLiteralProperty() {
10+
super(software.amazon.jsii.JsiiObject.InitializationMode.Jsii);
11+
software.amazon.jsii.JsiiEngine.getInstance().createNewObject(this);
12+
}
13+
14+
public software.amazon.jsii.tests.calculator.MutableObjectLiteral getMutableObject() {
15+
return this.jsiiGet("mutableObject", software.amazon.jsii.tests.calculator.MutableObjectLiteral.class);
16+
}
17+
18+
public void setMutableObject(final software.amazon.jsii.tests.calculator.MutableObjectLiteral value) {
19+
this.jsiiSet("mutableObject", java.util.Objects.requireNonNull(value, "mutableObject is required"));
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package software.amazon.jsii.tests.calculator;
2+
3+
@javax.annotation.Generated(value = "jsii-pacmak")
4+
public interface MutableObjectLiteral extends software.amazon.jsii.JsiiSerializable {
5+
java.lang.String getValue();
6+
void setValue(final java.lang.String value);
7+
8+
/**
9+
* @return a {@link Builder} of {@link MutableObjectLiteral}
10+
*/
11+
static Builder builder() {
12+
return new Builder();
13+
}
14+
15+
/**
16+
* A builder for {@link MutableObjectLiteral}
17+
*/
18+
final class Builder {
19+
private java.lang.String _value;
20+
21+
/**
22+
* Sets the value of Value
23+
* @param value the value to be set
24+
* @return {@code this}
25+
*/
26+
public Builder withValue(final java.lang.String value) {
27+
this._value = java.util.Objects.requireNonNull(value, "value is required");
28+
return this;
29+
}
30+
31+
/**
32+
* Builds the configured instance.
33+
* @return a new instance of {@link MutableObjectLiteral}
34+
* @throws NullPointerException if any required attribute was not provided
35+
*/
36+
public MutableObjectLiteral build() {
37+
return new MutableObjectLiteral() {
38+
private java.lang.String $value = java.util.Objects.requireNonNull(_value, "value is required");
39+
40+
@Override
41+
public java.lang.String getValue() {
42+
return this.$value;
43+
}
44+
45+
@Override
46+
public void setValue(final java.lang.String value) {
47+
this.$value = java.util.Objects.requireNonNull(value, "value is required");
48+
}
49+
50+
};
51+
}
52+
}
53+
54+
/**
55+
* A proxy class which represents a concrete javascript instance of this type.
56+
*/
57+
final static class Jsii$Proxy extends software.amazon.jsii.JsiiObject implements software.amazon.jsii.tests.calculator.MutableObjectLiteral {
58+
protected Jsii$Proxy(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
59+
super(mode);
60+
}
61+
62+
@Override
63+
public java.lang.String getValue() {
64+
return this.jsiiGet("value", java.lang.String.class);
65+
}
66+
67+
@Override
68+
public void setValue(final java.lang.String value) {
69+
this.jsiiSet("value", java.util.Objects.requireNonNull(value, "value is required"));
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)