Skip to content

Commit

Permalink
fix(kernel): Return object literals as references
Browse files Browse the repository at this point in the history
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
  • Loading branch information
RomainMuller committed Sep 27, 2018
1 parent b743a13 commit 471deda
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 8 deletions.
8 changes: 8 additions & 0 deletions packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,3 +887,11 @@ export class AbstractClassReturner {
}
}
}

export interface MutableObjectLiteral {
value: string;
}

export class ClassWithMutableObjectLiteralProperty {
public mutableObject: MutableObjectLiteral = { value: 'default' };
}
35 changes: 34 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,23 @@
}
]
},
"jsii-calc.ClassWithMutableObjectLiteralProperty": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.ClassWithMutableObjectLiteralProperty",
"initializer": {
"initializer": true
},
"kind": "class",
"name": "ClassWithMutableObjectLiteralProperty",
"properties": [
{
"name": "mutableObject",
"type": {
"fqn": "jsii-calc.MutableObjectLiteral"
}
}
]
},
"jsii-calc.DefaultedConstructorArgument": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.DefaultedConstructorArgument",
Expand Down Expand Up @@ -1883,6 +1900,22 @@
}
]
},
"jsii-calc.MutableObjectLiteral": {
"assembly": "jsii-calc",
"datatype": true,
"fqn": "jsii-calc.MutableObjectLiteral",
"kind": "interface",
"name": "MutableObjectLiteral",
"properties": [
{
"abstract": true,
"name": "value",
"type": {
"primitive": "string"
}
}
]
},
"jsii-calc.Negate": {
"assembly": "jsii-calc",
"base": {
Expand Down Expand Up @@ -3230,5 +3263,5 @@
}
},
"version": "0.7.6",
"fingerprint": "ecDtx3DHVZi7UjyCDhGncg4jbSRaD536bUyh6YzAxlY="
"fingerprint": "DFShLmIJQmPjTv5Dmz4JoBKqoCtAyifD1RpCuHo+sEc="
}
18 changes: 12 additions & 6 deletions packages/jsii-kernel/lib/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TOKEN_DATE, TOKEN_ENUM, TOKEN_REF } from './api';
*/
const OBJID_PROP = '$__jsii__objid__$';
const FQN_PROP = '$__jsii__fqn__$';
const PROXIES_PROP = '$__jsii__proxies__$';

/**
* A special FQN that can be used to create empty javascript objects.
Expand Down Expand Up @@ -923,12 +924,17 @@ export class Kernel {
// so the client receives a real object.
if (typeof(v) === 'object' && targetType && spec.isNamedTypeReference(targetType)) {
this._debug('coalescing to', targetType);
const newObjRef = this._create({ fqn: targetType.fqn });
const newObj = this._findObject(newObjRef);
for (const k of Object.keys(v)) {
newObj[k] = v[k];
}
return newObjRef;
const proxies = v[PROXIES_PROP] = v[PROXIES_PROP] || {};
if (proxies[targetType.fqn]) { return proxies[targetType.fqn]; }
const proxy = new Proxy(v, {
has(target: any, propertyKey: string) {
return propertyKey in target || propertyKey === FQN_PROP;
},
get(target: any, propertyKey: string) {
return propertyKey === FQN_PROP ? targetType.fqn : target[propertyKey];
}
});
return this._createObjref(proxy, targetType.fqn);
}

// date (https://stackoverflow.com/a/643827/737957)
Expand Down
15 changes: 15 additions & 0 deletions packages/jsii-kernel/test/test.kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,21 @@ defineTest('node.js standard library', async (test, sandbox) => {
{ result: "6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50" });
});

// @see awslabs/jsii#248
defineTest('object literals are returned by reference', async (test, sandbox) => {
const objref = sandbox.create({ fqn: 'jsii-calc.ClassWithMutableObjectLiteralProperty' });
const property = sandbox.get({ objref, property: 'mutableObject' }).value;

const newValue = 'Bazinga!1!';
sandbox.set({ objref: property, property: 'value', value: newValue });

test.equal(newValue,
sandbox.get({
objref: sandbox.get({ objref, property: 'mutableObject' }).value,
property: 'value'
}).value);
});

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

async function createCalculatorSandbox(name: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,23 @@
}
]
},
"jsii-calc.ClassWithMutableObjectLiteralProperty": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.ClassWithMutableObjectLiteralProperty",
"initializer": {
"initializer": true
},
"kind": "class",
"name": "ClassWithMutableObjectLiteralProperty",
"properties": [
{
"name": "mutableObject",
"type": {
"fqn": "jsii-calc.MutableObjectLiteral"
}
}
]
},
"jsii-calc.DefaultedConstructorArgument": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.DefaultedConstructorArgument",
Expand Down Expand Up @@ -1883,6 +1900,22 @@
}
]
},
"jsii-calc.MutableObjectLiteral": {
"assembly": "jsii-calc",
"datatype": true,
"fqn": "jsii-calc.MutableObjectLiteral",
"kind": "interface",
"name": "MutableObjectLiteral",
"properties": [
{
"abstract": true,
"name": "value",
"type": {
"primitive": "string"
}
}
]
},
"jsii-calc.Negate": {
"assembly": "jsii-calc",
"base": {
Expand Down Expand Up @@ -3230,5 +3263,5 @@
}
},
"version": "0.7.6",
"fingerprint": "ecDtx3DHVZi7UjyCDhGncg4jbSRaD536bUyh6YzAxlY="
"fingerprint": "DFShLmIJQmPjTv5Dmz4JoBKqoCtAyifD1RpCuHo+sEc="
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Amazon.JSII.Runtime.Deputy;

namespace Amazon.JSII.Tests.CalculatorNamespace
{
[JsiiClass(typeof(ClassWithMutableObjectLiteralProperty), "jsii-calc.ClassWithMutableObjectLiteralProperty", "[]")]
public class ClassWithMutableObjectLiteralProperty : DeputyBase
{
public ClassWithMutableObjectLiteralProperty(): base(new DeputyProps(new object[]{}))
{
}

protected ClassWithMutableObjectLiteralProperty(ByRefValue reference): base(reference)
{
}

protected ClassWithMutableObjectLiteralProperty(DeputyProps props): base(props)
{
}

[JsiiProperty("mutableObject", "{\"fqn\":\"jsii-calc.MutableObjectLiteral\"}")]
public virtual IMutableObjectLiteral MutableObject
{
get => GetInstanceProperty<IMutableObjectLiteral>();
set => SetInstanceProperty(value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Amazon.JSII.Runtime.Deputy;

namespace Amazon.JSII.Tests.CalculatorNamespace
{
[JsiiInterface(typeof(IMutableObjectLiteral), "jsii-calc.MutableObjectLiteral")]
public interface IMutableObjectLiteral
{
[JsiiProperty("value", "{\"primitive\":\"string\"}")]
string Value
{
get;
set;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Amazon.JSII.Runtime.Deputy;

namespace Amazon.JSII.Tests.CalculatorNamespace
{
public class MutableObjectLiteral : DeputyBase, IMutableObjectLiteral
{
[JsiiProperty("value", "{\"primitive\":\"string\"}", true)]
public string Value
{
get;
set;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Amazon.JSII.Runtime.Deputy;

namespace Amazon.JSII.Tests.CalculatorNamespace
{
[JsiiTypeProxy(typeof(IMutableObjectLiteral), "jsii-calc.MutableObjectLiteral")]
internal sealed class MutableObjectLiteralProxy : DeputyBase, IMutableObjectLiteral
{
private MutableObjectLiteralProxy(ByRefValue reference): base(reference)
{
}

[JsiiProperty("value", "{\"primitive\":\"string\"}")]
public string Value
{
get => GetInstanceProperty<string>();
set => SetInstanceProperty(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protected Class<?> resolveClass(final String fqn) throws ClassNotFoundException
case "jsii-calc.BinaryOperation": return software.amazon.jsii.tests.calculator.BinaryOperation.class;
case "jsii-calc.Calculator": return software.amazon.jsii.tests.calculator.Calculator.class;
case "jsii-calc.CalculatorProps": return software.amazon.jsii.tests.calculator.CalculatorProps.class;
case "jsii-calc.ClassWithMutableObjectLiteralProperty": return software.amazon.jsii.tests.calculator.ClassWithMutableObjectLiteralProperty.class;
case "jsii-calc.DefaultedConstructorArgument": return software.amazon.jsii.tests.calculator.DefaultedConstructorArgument.class;
case "jsii-calc.DerivedClassHasNoProperties.Base": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Base.class;
case "jsii-calc.DerivedClassHasNoProperties.Derived": return software.amazon.jsii.tests.calculator.DerivedClassHasNoProperties.Derived.class;
Expand All @@ -51,6 +52,7 @@ protected Class<?> resolveClass(final String fqn) throws ClassNotFoundException
case "jsii-calc.JSObjectLiteralToNativeClass": return software.amazon.jsii.tests.calculator.JSObjectLiteralToNativeClass.class;
case "jsii-calc.JavaReservedWords": return software.amazon.jsii.tests.calculator.JavaReservedWords.class;
case "jsii-calc.Multiply": return software.amazon.jsii.tests.calculator.Multiply.class;
case "jsii-calc.MutableObjectLiteral": return software.amazon.jsii.tests.calculator.MutableObjectLiteral.class;
case "jsii-calc.Negate": return software.amazon.jsii.tests.calculator.Negate.class;
case "jsii-calc.NodeStandardLibrary": return software.amazon.jsii.tests.calculator.NodeStandardLibrary.class;
case "jsii-calc.NumberGenerator": return software.amazon.jsii.tests.calculator.NumberGenerator.class;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package software.amazon.jsii.tests.calculator;

@javax.annotation.Generated(value = "jsii-pacmak")
@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.ClassWithMutableObjectLiteralProperty")
public class ClassWithMutableObjectLiteralProperty extends software.amazon.jsii.JsiiObject {
protected ClassWithMutableObjectLiteralProperty(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
super(mode);
}
public ClassWithMutableObjectLiteralProperty() {
super(software.amazon.jsii.JsiiObject.InitializationMode.Jsii);
software.amazon.jsii.JsiiEngine.getInstance().createNewObject(this);
}

public software.amazon.jsii.tests.calculator.MutableObjectLiteral getMutableObject() {
return this.jsiiGet("mutableObject", software.amazon.jsii.tests.calculator.MutableObjectLiteral.class);
}

public void setMutableObject(final software.amazon.jsii.tests.calculator.MutableObjectLiteral value) {
this.jsiiSet("mutableObject", java.util.Objects.requireNonNull(value, "mutableObject is required"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package software.amazon.jsii.tests.calculator;

@javax.annotation.Generated(value = "jsii-pacmak")
public interface MutableObjectLiteral extends software.amazon.jsii.JsiiSerializable {
java.lang.String getValue();
void setValue(final java.lang.String value);

/**
* @return a {@link Builder} of {@link MutableObjectLiteral}
*/
static Builder builder() {
return new Builder();
}

/**
* A builder for {@link MutableObjectLiteral}
*/
final class Builder {
private java.lang.String _value;

/**
* Sets the value of Value
* @param value the value to be set
* @return {@code this}
*/
public Builder withValue(final java.lang.String value) {
this._value = java.util.Objects.requireNonNull(value, "value is required");
return this;
}

/**
* Builds the configured instance.
* @return a new instance of {@link MutableObjectLiteral}
* @throws NullPointerException if any required attribute was not provided
*/
public MutableObjectLiteral build() {
return new MutableObjectLiteral() {
private java.lang.String $value = java.util.Objects.requireNonNull(_value, "value is required");

@Override
public java.lang.String getValue() {
return this.$value;
}

@Override
public void setValue(final java.lang.String value) {
this.$value = java.util.Objects.requireNonNull(value, "value is required");
}

};
}
}

/**
* A proxy class which represents a concrete javascript instance of this type.
*/
final static class Jsii$Proxy extends software.amazon.jsii.JsiiObject implements software.amazon.jsii.tests.calculator.MutableObjectLiteral {
protected Jsii$Proxy(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
super(mode);
}

@Override
public java.lang.String getValue() {
return this.jsiiGet("value", java.lang.String.class);
}

@Override
public void setValue(final java.lang.String value) {
this.jsiiSet("value", java.util.Objects.requireNonNull(value, "value is required"));
}
}
}
Loading

0 comments on commit 471deda

Please sign in to comment.