JS backend: added delegate property support.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2010-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.k2js.test.semantics;
|
||||
|
||||
import org.jetbrains.k2js.test.SingleFileTranslationTest;
|
||||
|
||||
public class DelegatePropertyTest extends SingleFileTranslationTest {
|
||||
public DelegatePropertyTest() {
|
||||
super("delegateProperty/");
|
||||
}
|
||||
|
||||
public void testSimple() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testPropertyMetadata() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testWithGenerics() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
|
||||
public void testDelegateByTopLevelFun() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testDelegateByTopLevelProperty() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testDelegateForExtProperty() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testGetAsExtensionFun() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testSetAsExtensionFun() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testTopLevelVal() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
public void testTopLevelVar() throws Exception {
|
||||
checkFooBoxIsOk();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,6 +52,10 @@ public final class Namer {
|
||||
private static final String CLASS_OBJECT_GETTER = "object$";
|
||||
private static final String CLASS_OBJECT_INITIALIZER = "object_initializer$";
|
||||
|
||||
|
||||
private static final String DELEGATE_POSTFIX = "$delegate";
|
||||
private static final String PROPERTY_METADATA = "PropertyMetadata";
|
||||
|
||||
private static final Named CLASS_OBJECT_INITIALIZER_NAMED = new Named() {
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -114,19 +118,32 @@ public final class Namer {
|
||||
return getNameWithPrefix(propertyName, SETTER_PREFIX);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression getClassObjectAccessor(@NotNull JsExpression referenceToClass) {
|
||||
return new JsInvocation(new JsNameRef(CLASS_OBJECT_GETTER, referenceToClass));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Named getNamedForClassObjectInitializer() {
|
||||
return CLASS_OBJECT_INITIALIZER_NAMED;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getDelegateName(@NotNull String propertyName) {
|
||||
return propertyName + DELEGATE_POSTFIX;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsNameRef getDelegateNameRef(String propertyName) {
|
||||
return new JsNameRef(getDelegateName(propertyName), JsLiteral.THIS);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getNameWithPrefix(@NotNull String name, @NotNull String prefix) {
|
||||
return prefix + name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Namer newInstance(@NotNull JsScope rootScope) {
|
||||
return new Namer(rootScope);
|
||||
}
|
||||
@@ -193,6 +210,11 @@ public final class Namer {
|
||||
return new JsNameRef(THROW_NPE_FUN_NAME, kotlinObject());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JsNameRef propertyMetadataRef() {
|
||||
return new JsNameRef(PROPERTY_METADATA, kotlinObject());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private JsNameRef kotlin(@NotNull JsName name) {
|
||||
return new JsNameRef(name, kotlinObject());
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator.createPlace;
|
||||
import static org.jetbrains.k2js.translate.initializer.InitializerUtils.generateInitializerForDelegate;
|
||||
import static org.jetbrains.k2js.translate.initializer.InitializerUtils.generateInitializerForProperty;
|
||||
import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptor;
|
||||
|
||||
@@ -213,6 +214,10 @@ final class NamespaceTranslator extends AbstractTranslator {
|
||||
initializerStatements.add(generateInitializerForProperty(context, propertyDescriptor, value));
|
||||
}
|
||||
}
|
||||
|
||||
JsStatement delegate = generateInitializerForDelegate(context, property);
|
||||
if (delegate != null) initializerStatements.add(delegate);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+62
-11
@@ -20,23 +20,26 @@ import com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.PropertySetterDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.*;
|
||||
import org.jetbrains.jet.lang.psi.JetProperty;
|
||||
import org.jetbrains.jet.lang.psi.JetPropertyAccessor;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.k2js.translate.context.TranslationContext;
|
||||
import org.jetbrains.k2js.translate.expression.FunctionTranslator;
|
||||
import org.jetbrains.k2js.translate.general.AbstractTranslator;
|
||||
import org.jetbrains.k2js.translate.general.Translation;
|
||||
import org.jetbrains.k2js.translate.reference.CallBuilder;
|
||||
import org.jetbrains.k2js.translate.reference.CallType;
|
||||
import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
|
||||
import org.jetbrains.k2js.translate.utils.TranslationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.k2js.translate.utils.TranslationUtils.assignmentToBackingField;
|
||||
import static org.jetbrains.k2js.translate.utils.TranslationUtils.backingFieldReference;
|
||||
import static org.jetbrains.k2js.translate.context.Namer.getDelegateNameRef;
|
||||
import static org.jetbrains.k2js.translate.utils.TranslationUtils.*;
|
||||
|
||||
/**
|
||||
* Translates single property /w accessors.
|
||||
@@ -134,11 +137,50 @@ public final class PropertyTranslator extends AbstractTranslator {
|
||||
return generateDefaultAccessor(getterDescriptor, generateDefaultGetterFunction(getterDescriptor));
|
||||
}
|
||||
|
||||
private JsExpression createPropertyMetadata() {
|
||||
JsNameRef propertyMetadataRef = context().namer().propertyMetadataRef();
|
||||
JsExpression argument = context().program().getStringLiteral(getPropertyName());
|
||||
if (context().isEcma5()) {
|
||||
return new JsInvocation(propertyMetadataRef, argument);
|
||||
} else {
|
||||
return new JsNew(propertyMetadataRef, Collections.singletonList(argument));
|
||||
}
|
||||
}
|
||||
|
||||
private JsExpression getDelegateCall(ResolvedCall<FunctionDescriptor> call, List<JsExpression> args) {
|
||||
return CallBuilder.build(context())
|
||||
.receiver(getDelegateNameRef(getPropertyName()))
|
||||
.args(args)
|
||||
.resolvedCall(call)
|
||||
.type(CallType.NORMAL)
|
||||
.translate();
|
||||
}
|
||||
|
||||
private String getPropertyName() {
|
||||
return descriptor.getName().asString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private JsFunction generateDefaultGetterFunction(@NotNull PropertyGetterDescriptor descriptor) {
|
||||
JsFunction fun = new JsFunction(context().getScopeForDescriptor(descriptor.getContainingDeclaration()));
|
||||
fun.setBody(new JsBlock(new JsReturn(backingFieldReference(context(), this.descriptor))));
|
||||
return fun;
|
||||
private JsFunction generateDefaultGetterFunction(@NotNull PropertyGetterDescriptor getterDescriptor) {
|
||||
JsExpression value;
|
||||
ResolvedCall<FunctionDescriptor> delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor);
|
||||
if (delegatedCall != null) {
|
||||
value = getDelegateCall(delegatedCall, getDelegateCallArgs(null));
|
||||
} else {
|
||||
value = backingFieldReference(context(), this.descriptor);
|
||||
}
|
||||
return simpleReturnFunction(context().getScopeForDescriptor(getterDescriptor.getContainingDeclaration()), value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<JsExpression> getDelegateCallArgs(@Nullable JsExpression valueExpression) {
|
||||
List<JsExpression> args = new ArrayList<JsExpression>();
|
||||
args.add(JsLiteral.THIS);
|
||||
args.add(createPropertyMetadata());
|
||||
if (valueExpression != null) {
|
||||
args.add(valueExpression);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -153,7 +195,16 @@ public final class PropertyTranslator extends AbstractTranslator {
|
||||
JsFunction fun = new JsFunction(context().getScopeForDescriptor(setterDescriptor.getContainingDeclaration()));
|
||||
JsParameter defaultParameter = new JsParameter(propertyAccessContext(setterDescriptor).scope().declareTemporary());
|
||||
fun.getParameters().add(defaultParameter);
|
||||
fun.setBody(new JsBlock(assignmentToBackingField(context(), descriptor, defaultParameter.getName().makeRef()).makeStmt()));
|
||||
JsExpression setExpression;
|
||||
|
||||
ResolvedCall<FunctionDescriptor> delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, setterDescriptor);
|
||||
JsNameRef defaultParameterRef = defaultParameter.getName().makeRef();
|
||||
if (delegatedCall != null) {
|
||||
setExpression = getDelegateCall(delegatedCall, getDelegateCallArgs(defaultParameterRef));
|
||||
} else {
|
||||
setExpression = assignmentToBackingField(context(), descriptor, defaultParameterRef);
|
||||
}
|
||||
fun.setBody(new JsBlock(setExpression.makeStmt()));
|
||||
return fun;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,13 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.Named;
|
||||
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
|
||||
import org.jetbrains.jet.lang.psi.JetExpression;
|
||||
import org.jetbrains.jet.lang.psi.JetObjectDeclaration;
|
||||
import org.jetbrains.jet.lang.psi.JetProperty;
|
||||
import org.jetbrains.k2js.translate.context.Namer;
|
||||
import org.jetbrains.k2js.translate.context.TranslationContext;
|
||||
import org.jetbrains.k2js.translate.declaration.ClassTranslator;
|
||||
import org.jetbrains.k2js.translate.general.Translation;
|
||||
import org.jetbrains.k2js.translate.utils.JsAstUtils;
|
||||
|
||||
import java.util.List;
|
||||
@@ -49,6 +53,18 @@ public final class InitializerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JsStatement generateInitializerForDelegate(@NotNull TranslationContext context, @NotNull JetProperty property) {
|
||||
JetExpression delegate = property.getDelegateExpression();
|
||||
if (delegate != null) {
|
||||
JsExpression value = Translation.translateAsExpression(delegate, context);
|
||||
String name = property.getName();
|
||||
assert name != null: "Delegate property must have name";
|
||||
return JsAstUtils.defineSimpleProperty(Namer.getDelegateName(name), value, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void generate(@NotNull JetObjectDeclaration declaration,
|
||||
@NotNull List<JsStatement> initializers,
|
||||
@Nullable List<JsPropertyInitializer> definitions,
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.k2js.translate.general.TranslatorVisitor;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.k2js.translate.general.Translation.translateAsStatement;
|
||||
import static org.jetbrains.k2js.translate.initializer.InitializerUtils.generateInitializerForDelegate;
|
||||
import static org.jetbrains.k2js.translate.initializer.InitializerUtils.generateInitializerForProperty;
|
||||
import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptor;
|
||||
|
||||
@@ -44,6 +45,9 @@ public final class InitializerVisitor extends TranslatorVisitor<Void> {
|
||||
Translation.translateAsExpression(initializer, context)));
|
||||
}
|
||||
|
||||
JsStatement delegate = generateInitializerForDelegate(context, property);
|
||||
if (delegate != null) result.add(delegate);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -238,6 +238,15 @@ public final class JsAstUtils {
|
||||
return invocation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value, @NotNull TranslationContext context) {
|
||||
if (context.isEcma5()) {
|
||||
return defineProperty(name, createDataDescriptor(value, false), context).makeStmt();
|
||||
} else {
|
||||
return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsObjectLiteral createPropertyDataDescriptor(@NotNull FunctionDescriptor descriptor,
|
||||
@NotNull JsExpression value) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
var inner = 1
|
||||
fun get(t: Any?, p: PropertyMetadata): Int = inner
|
||||
fun set(t: Any?, p: PropertyMetadata, i: Int) { inner = i }
|
||||
}
|
||||
|
||||
fun foo() = Delegate()
|
||||
|
||||
class A {
|
||||
var prop: Int by foo()
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val c = A()
|
||||
if(c.prop != 1) return "fail get"
|
||||
c.prop = 2
|
||||
if (c.prop != 2) return "fail set"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
var inner = 1
|
||||
fun get(t: Any?, p: PropertyMetadata): Int = inner
|
||||
fun set(t: Any?, p: PropertyMetadata, i: Int) { inner = i }
|
||||
}
|
||||
|
||||
val p = Delegate()
|
||||
|
||||
class A {
|
||||
var prop: Int by p
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val c = A()
|
||||
if(c.prop != 1) return "fail get"
|
||||
c.prop = 2
|
||||
if (c.prop != 2) return "fail set"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
fun get(t: A, p: PropertyMetadata): Int = 1
|
||||
}
|
||||
|
||||
val A.prop: Int by Delegate()
|
||||
|
||||
class A {
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
return if(A().prop == 1) "OK" else "fail"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
}
|
||||
|
||||
fun Delegate.get(t: Any?, p: PropertyMetadata): Int = 1
|
||||
|
||||
class A {
|
||||
val prop: Int by Delegate()
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
return if(A().prop == 1) "OK" else "fail"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package foo
|
||||
|
||||
trait WithName {
|
||||
var name: String
|
||||
}
|
||||
|
||||
class GetPropertyName() {
|
||||
fun get(withName: WithName, property: PropertyMetadata) : String {
|
||||
return withName.name + ":" + property.name;
|
||||
}
|
||||
fun set(withName: WithName, property: PropertyMetadata, value: String) {
|
||||
withName.name = value + ":" + property.name
|
||||
}
|
||||
}
|
||||
|
||||
class A : WithName {
|
||||
override var name = "propertyName"
|
||||
val d = GetPropertyName()
|
||||
|
||||
val a by d
|
||||
var OK by d
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val a = A()
|
||||
if (a.a != "propertyName:a") return "a.a != 'propertyName:a', it: " + a.a
|
||||
if (a.OK != "propertyName:OK") return "a.OK != 'propertyName:aOK', it: " + a.OK
|
||||
a.OK = "property"
|
||||
|
||||
if (a.a != "property:OK:a") return "a.a != 'property:OK:a', it: " + a.a
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
var inner = 1
|
||||
fun get(t: Any?, p: PropertyMetadata): Int = inner
|
||||
}
|
||||
|
||||
fun Delegate.set(t: Any?, p: PropertyMetadata, i: Int) { inner = i }
|
||||
|
||||
class A {
|
||||
var prop: Int by Delegate()
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val c = A()
|
||||
if(c.prop != 1) return "fail get"
|
||||
c.prop = 2
|
||||
if (c.prop != 2) return "fail set"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package foo
|
||||
|
||||
trait WithNumber {
|
||||
var number: Int
|
||||
}
|
||||
|
||||
class IncNumber(val inc: Int) {
|
||||
fun get(withNumber: WithNumber, property: PropertyMetadata) : Int {
|
||||
return withNumber.number + inc;
|
||||
}
|
||||
fun set(withNumber: WithNumber, property: PropertyMetadata, value: Int) {
|
||||
withNumber.number = value;
|
||||
}
|
||||
}
|
||||
|
||||
class A : WithNumber {
|
||||
override var number: Int = 5
|
||||
var nextNumber by IncNumber(3)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
if (A().nextNumber != 8) return "A().nextNumber != 8"
|
||||
|
||||
val a = A()
|
||||
a.nextNumber = 10;
|
||||
if (a.number != 10) return "a.number != 10, it: " + a.number
|
||||
if (a.nextNumber != 13) return "a.nextNumber != 13, it: " + a.nextNumber
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
fun get(t: Any?, p: PropertyMetadata): Int = 1
|
||||
}
|
||||
|
||||
val prop: Int by Delegate()
|
||||
|
||||
fun box(): String {
|
||||
return if(prop == 1) "OK" else "fail"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package foo
|
||||
class Delegate {
|
||||
var inner = 1
|
||||
fun get(t: Any?, p: PropertyMetadata): Int = inner
|
||||
fun set(t: Any?, p: PropertyMetadata, i: Int) { inner = i }
|
||||
}
|
||||
|
||||
var prop: Int by Delegate()
|
||||
|
||||
fun box(): String {
|
||||
if(prop != 1) return "fail get"
|
||||
prop = 2
|
||||
if (prop != 2) return "fail set"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package foo
|
||||
|
||||
trait Getter<T> {
|
||||
fun get(): T
|
||||
}
|
||||
|
||||
class Delegate<T>(val getter: Getter<T>) {
|
||||
var t: T? = null
|
||||
fun get(obj: Any, property: PropertyMetadata) : T {
|
||||
if (t != null) {
|
||||
return t!!
|
||||
}
|
||||
return getter.get()
|
||||
}
|
||||
fun set(obj: Any, property: PropertyMetadata, value: T) {
|
||||
t = value
|
||||
}
|
||||
}
|
||||
|
||||
class A : Getter<Int> {
|
||||
var value = 0
|
||||
override fun get(): Int {
|
||||
return value
|
||||
}
|
||||
val delegate = Delegate(this)
|
||||
|
||||
val a by delegate
|
||||
var b by delegate
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val a = A()
|
||||
if (a.a != 0) return "a.a != 0"
|
||||
if (a.b != 0) return "a.b != 0"
|
||||
|
||||
a.b = 4
|
||||
if (a.a != 4) return "a.a != 4"
|
||||
if (a.b != 4) return "a.b != 4"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -172,6 +172,15 @@ String.prototype.contains = function (s) {
|
||||
};
|
||||
})();
|
||||
|
||||
Kotlin.PropertyMetadata = Kotlin.$createClass(null, {
|
||||
initialize: function(name) {
|
||||
this.$name = name;
|
||||
},
|
||||
get_name: function () {
|
||||
return this.$name;
|
||||
}
|
||||
});
|
||||
|
||||
Kotlin.AbstractCollection = Kotlin.$createClass(Kotlin.Collection, {
|
||||
size: function () {
|
||||
return this.$size;
|
||||
|
||||
Reference in New Issue
Block a user