Delegated Properties: Code generation for local properties (JS)

This commit is contained in:
Dotlin
2015-09-30 02:34:28 +03:00
committed by Mikhael Bogdanov
parent 4c223845b6
commit 0e071b4748
11 changed files with 185 additions and 20 deletions
@@ -27,6 +27,30 @@ public class DelegatePropertyTest extends SingleFileTranslationTest {
checkFooBoxIsOk();
}
public void testLocalVal() throws Exception {
checkFooBoxIsOk();
}
public void testLocalVar() throws Exception {
checkFooBoxIsOk();
}
public void testCapturedLocalVal() throws Exception {
checkFooBoxIsOk();
}
public void testCapturedLocalVar() throws Exception {
checkFooBoxIsOk();
}
public void testCapturedLocalValNoInline() throws Exception {
checkFooBoxIsOk();
}
public void testCapturedLocalVarNoInline() throws Exception {
checkFooBoxIsOk();
}
public void testPropertyMetadata() throws Exception {
checkFooBoxIsOk();
}
@@ -21,7 +21,9 @@ import com.google.dart.compiler.backend.js.ast.JsInvocation
import com.google.dart.compiler.backend.js.ast.JsNameRef
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.js.translate.context.Namer.getCapturedVarAccessor
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.resolve.BindingContextUtils.isVarCapturedInClosure
import java.util.*
@@ -53,6 +55,17 @@ object DefaultVariableAccessCase : VariableAccessCase() {
functionRef
}
val localVariableDescriptor = resolvedCall.resultingDescriptor as? LocalVariableDescriptor
val accessorDescriptor = if (isGetAccess()) localVariableDescriptor?.getter else localVariableDescriptor?.setter
if (accessorDescriptor != null) {
val funRef = JsNameRef(TranslationUtils.getAccessorFunctionName(accessorDescriptor), ref)
return if (isGetAccess()) {
JsInvocation(funRef)
} else {
JsInvocation(funRef, value!!)
}
}
return constructAccessExpression(ref)
}
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.translate.declaration
import com.google.dart.compiler.backend.js.ast.*
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableAccessorDescriptor
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.Namer.getDelegateNameRef
@@ -45,18 +46,19 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
*/
fun translateAccessors(
descriptor: PropertyDescriptor,
descriptor: VariableDescriptorWithAccessors,
declaration: KtProperty?,
result: MutableList<JsPropertyInitializer>,
context: TranslationContext
) {
if (descriptor.modality == Modality.ABSTRACT || JsDescriptorUtils.isSimpleFinalProperty(descriptor)) return
if (descriptor is PropertyDescriptor
&& (descriptor.modality == Modality.ABSTRACT || JsDescriptorUtils.isSimpleFinalProperty(descriptor))) return
PropertyTranslator(descriptor, declaration, context).translate(result)
}
fun translateAccessors(
descriptor: PropertyDescriptor,
descriptor: VariableDescriptorWithAccessors,
result: MutableList<JsPropertyInitializer>,
context: TranslationContext
) {
@@ -64,7 +66,7 @@ fun translateAccessors(
}
fun MutableList<JsPropertyInitializer>.addGetterAndSetter(
descriptor: PropertyDescriptor,
descriptor: VariableDescriptorWithAccessors,
context: TranslationContext,
generateGetter: () -> JsPropertyInitializer,
generateSetter: () -> JsPropertyInitializer
@@ -85,7 +87,7 @@ fun MutableList<JsPropertyInitializer>.addGetterAndSetter(
}
private class PropertyTranslator(
val descriptor: PropertyDescriptor,
val descriptor: VariableDescriptorWithAccessors,
val declaration: KtProperty?,
context: TranslationContext
) : AbstractTranslator(context) {
@@ -119,7 +121,7 @@ private class PropertyTranslator(
return generateDefaultAccessor(getterDescriptor, generateDefaultGetterFunction(getterDescriptor))
}
private fun generateDefaultGetterFunction(getterDescriptor: PropertyGetterDescriptor): JsFunction {
private fun generateDefaultGetterFunction(getterDescriptor: VariableAccessorDescriptor): JsFunction {
val delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor)
if (delegatedCall != null) {
@@ -127,16 +129,17 @@ private class PropertyTranslator(
}
assert(!descriptor.isExtension) { "Unexpected extension property $descriptor}" }
assert(descriptor is PropertyDescriptor) { "Property descriptor expected: $descriptor" }
val scope = context().getScopeForDescriptor(getterDescriptor.containingDeclaration)
val result = backingFieldReference(context(), descriptor)
val result = backingFieldReference(context(), descriptor as PropertyDescriptor)
val body = JsBlock(JsReturn(result))
return JsFunction(scope, body, accessorDescription(getterDescriptor))
}
private fun generateDelegatedGetterFunction(
getterDescriptor: PropertyGetterDescriptor,
delegatedCall: ResolvedCall<FunctionDescriptor>
getterDescriptor: VariableAccessorDescriptor,
delegatedCall: ResolvedCall<FunctionDescriptor>
): JsFunction {
val scope = context().getScopeForDescriptor(getterDescriptor.containingDeclaration)
val function = JsFunction(scope, JsBlock(), accessorDescription(getterDescriptor))
@@ -158,9 +161,9 @@ private class PropertyTranslator(
}
private fun contextWithPropertyMetadataCreationIntrinsified(
context: TranslationContext, delegatedCall: ResolvedCall<FunctionDescriptor>, accessor: PropertyAccessorDescriptor
context: TranslationContext, delegatedCall: ResolvedCall<FunctionDescriptor>, accessor: VariableAccessorDescriptor
): TranslationContext {
val propertyNameLiteral = context.program().getStringLiteral(accessor.correspondingProperty.name.asString())
val propertyNameLiteral = context.program().getStringLiteral(accessor.correspondingVariable.name.asString())
// 0th argument is instance, 1st is KProperty, 2nd (for setter) is value
val fakeArgumentExpression =
(delegatedCall.valueArgumentsByIndex!![1] as ExpressionValueArgument).valueArgument!!.getArgumentExpression()
@@ -175,12 +178,12 @@ private class PropertyTranslator(
return generateDefaultAccessor(setterDescriptor, generateDefaultSetterFunction(setterDescriptor))
}
private fun generateDefaultSetterFunction(setterDescriptor: PropertySetterDescriptor): JsFunction {
private fun generateDefaultSetterFunction(setterDescriptor: VariableAccessorDescriptor): JsFunction {
val containingScope = context().getScopeForDescriptor(setterDescriptor.containingDeclaration)
val function = JsFunction(containingScope, JsBlock(), accessorDescription(setterDescriptor))
assert(setterDescriptor.valueParameters.size == 1) { "Setter must have 1 parameter" }
val correspondingPropertyName = setterDescriptor.correspondingProperty.name.asString()
val correspondingPropertyName = setterDescriptor.correspondingVariable.name.asString()
val valueParameter = function.addParameter(correspondingPropertyName).name
val withAliased = context().innerContextWithAliased(setterDescriptor.valueParameters[0], valueParameter.makeRef())
val delegatedCall = context().bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, setterDescriptor)
@@ -199,25 +202,26 @@ private class PropertyTranslator(
}
else {
assert(!descriptor.isExtension) { "Unexpected extension property $descriptor}" }
val assignment = assignmentToBackingField(withAliased, descriptor, valueParameter.makeRef())
assert(descriptor is PropertyDescriptor) { "Property descriptor expected: $descriptor" }
val assignment = assignmentToBackingField(withAliased, descriptor as PropertyDescriptor, valueParameter.makeRef())
function.addStatement(assignment.makeStmt())
}
return function
}
private fun generateDefaultAccessor(accessorDescriptor: PropertyAccessorDescriptor, function: JsFunction): JsPropertyInitializer =
private fun generateDefaultAccessor(accessorDescriptor: VariableAccessorDescriptor, function: JsFunction): JsPropertyInitializer =
translateFunctionAsEcma5PropertyDescriptor(function, accessorDescriptor, context())
private fun translateCustomAccessor(expression: KtPropertyAccessor): JsPropertyInitializer =
Translation.functionTranslator(expression, context()).translateAsEcma5PropertyDescriptor()
private fun accessorDescription(accessorDescriptor: PropertyAccessorDescriptor): String {
private fun accessorDescription(accessorDescriptor: VariableAccessorDescriptor): String {
val accessorType =
when(accessorDescriptor) {
is PropertyGetterDescriptor ->
when (accessorDescriptor) {
is PropertyGetterDescriptor, is LocalVariableAccessorDescriptor.Getter ->
"getter"
is PropertySetterDescriptor ->
is PropertySetterDescriptor, is LocalVariableAccessorDescriptor.Setter ->
"setter"
else ->
throw IllegalArgumentException("Unknown accessor type ${accessorDescriptor.javaClass}")
@@ -20,14 +20,21 @@ import com.google.dart.compiler.backend.js.ast.*;
import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention;
import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.declaration.ClassTranslator;
import org.jetbrains.kotlin.js.translate.declaration.PropertyTranslatorKt;
import org.jetbrains.kotlin.js.translate.expression.loopTranslator.LoopTranslator;
import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor;
@@ -216,11 +223,25 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
// assume it is a local variable declaration
public JsNode visitProperty(@NotNull KtProperty expression, @NotNull TranslationContext context) {
VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
JsExpression initializer = translateInitializerForProperty(expression, context);
if (initializer != null && JsAstUtils.isEmptyExpression(initializer)) {
return context.getEmptyExpression();
}
KtExpression delegateExpression = expression.getDelegateExpression();
if (delegateExpression != null) {
SmartList<JsPropertyInitializer> propertyInitializers = new SmartList<JsPropertyInitializer>();
PropertyTranslatorKt.translateAccessors((VariableDescriptorWithAccessors) descriptor, propertyInitializers, context);
assert propertyInitializers.size() == 1 : descriptor;
initializer = propertyInitializers.get(0).getValueExpr();
JsPropertyInitializer delegateInitializer = new JsPropertyInitializer(
context.program().getStringLiteral(Namer.getDelegateName(descriptor.getName().asString())),
Translation.translateAsExpression(delegateExpression, context)
);
((JsObjectLiteral) initializer).getPropertyInitializers().add(delegateInitializer);
}
JsName name = context.getNameForDescriptor(descriptor);
if (isVarCapturedInClosure(context.bindingContext(), descriptor)) {
JsNameRef alias = getCapturedVarAccessor(name.makeRef());
@@ -24,6 +24,8 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.StaticContext;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableAccessorDescriptor;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.Translation;
@@ -56,11 +58,17 @@ public final class TranslationUtils {
return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
}
else {
JsStringLiteral getOrSet = context.program().getStringLiteral(descriptor instanceof PropertyGetterDescriptor ? "get" : "set");
JsStringLiteral getOrSet = context.program().getStringLiteral(getAccessorFunctionName(descriptor));
return new JsPropertyInitializer(getOrSet, function);
}
}
@NotNull
public static String getAccessorFunctionName(@NotNull FunctionDescriptor descriptor) {
boolean isGetter = descriptor instanceof PropertyGetterDescriptor || descriptor instanceof LocalVariableAccessorDescriptor.Getter;
return isGetter ? "get" : "set";
}
@NotNull
public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)), "<simpleReturnFunction>");
@@ -0,0 +1,12 @@
package foo
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(t: Any?, p: KProperty<*>): Int = 1
}
fun box(): String {
val prop: Int by Delegate()
return run { if (prop == 1) "OK" else "fail" }
}
@@ -0,0 +1,14 @@
package foo
import kotlin.reflect.KProperty
fun <T> myRun(f: () -> T) = f()
class Delegate {
operator fun getValue(t: Any?, p: KProperty<*>): Int = 1
}
fun box(): String {
val prop: Int by Delegate()
return myRun { if (prop == 1) "OK" else "fail" }
}
@@ -0,0 +1,18 @@
package foo
import kotlin.reflect.KProperty
class Delegate {
var inner = 1
operator fun getValue(t: Any?, p: KProperty<*>): Int = inner
operator fun setValue(t: Any?, p: KProperty<*>, i: Int) {
inner = i
}
}
fun box(): String {
var prop: Int by Delegate()
run { prop = 2 }
if (prop != 2) return "fail get"
return run { if (prop != 2) "fail set" else "OK" }
}
@@ -0,0 +1,20 @@
package foo
import kotlin.reflect.KProperty
fun <T> myRun(f: () -> T) = f()
class Delegate {
var inner = 1
operator fun getValue(t: Any?, p: KProperty<*>): Int = inner
operator fun setValue(t: Any?, p: KProperty<*>, i: Int) {
inner = i
}
}
fun box(): String {
var prop: Int by Delegate()
myRun { prop = 2 }
if (prop != 2) return "fail get"
return myRun { if (prop != 2) "fail set" else "OK" }
}
@@ -0,0 +1,12 @@
package foo
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(t: Any?, p: KProperty<*>): Int = 1
}
fun box(): String {
val prop: Int by Delegate()
return if (prop == 1) "OK" else "fail"
}
@@ -0,0 +1,19 @@
package foo
import kotlin.reflect.KProperty
class Delegate {
var inner = 1
operator fun getValue(t: Any?, p: KProperty<*>): Int = inner
operator fun setValue(t: Any?, p: KProperty<*>, i: Int) {
inner = i
}
}
fun box(): String {
var prop: Int by Delegate()
if (prop != 1) return "fail get"
prop = 2
if (prop != 2) return "fail set"
return "OK"
}