JS backend: added delegate property support.

This commit is contained in:
Erokhin Stanislav
2013-08-14 19:40:43 +04:00
parent 8ee5911a87
commit 8c952ebfdb
18 changed files with 407 additions and 11 deletions
@@ -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;
}
@@ -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"
}
+9
View File
@@ -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;