diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DelegatePropertyTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DelegatePropertyTest.java index 99bbb5760ea..cd70b6c3d30 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DelegatePropertyTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/DelegatePropertyTest.java @@ -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(); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/VariableCallCases.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/VariableCallCases.kt index 9c7b64093cc..7ae96a0e64e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/VariableCallCases.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/VariableCallCases.kt @@ -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) } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt index 27f5ba0e3e3..40d0130f9d5 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt @@ -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, 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, context: TranslationContext ) { @@ -64,7 +66,7 @@ fun translateAccessors( } fun MutableList.addGetterAndSetter( - descriptor: PropertyDescriptor, + descriptor: VariableDescriptorWithAccessors, context: TranslationContext, generateGetter: () -> JsPropertyInitializer, generateSetter: () -> JsPropertyInitializer @@ -85,7 +87,7 @@ fun MutableList.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 + getterDescriptor: VariableAccessorDescriptor, + delegatedCall: ResolvedCall ): 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, accessor: PropertyAccessorDescriptor + context: TranslationContext, delegatedCall: ResolvedCall, 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}") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index d8788545bb6..2eb6d546daa 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -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 { // 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 propertyInitializers = new SmartList(); + 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()); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java index 9ccde9782e2..3ed96ce9e3b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java @@ -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)), ""); diff --git a/js/js.translator/testData/delegateProperty/cases/capturedLocalVal.kt b/js/js.translator/testData/delegateProperty/cases/capturedLocalVal.kt new file mode 100644 index 00000000000..c5cb191472a --- /dev/null +++ b/js/js.translator/testData/delegateProperty/cases/capturedLocalVal.kt @@ -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" } +} diff --git a/js/js.translator/testData/delegateProperty/cases/capturedLocalValNoInline.kt b/js/js.translator/testData/delegateProperty/cases/capturedLocalValNoInline.kt new file mode 100644 index 00000000000..c5bd2d0b831 --- /dev/null +++ b/js/js.translator/testData/delegateProperty/cases/capturedLocalValNoInline.kt @@ -0,0 +1,14 @@ +package foo + +import kotlin.reflect.KProperty + +fun 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" } +} diff --git a/js/js.translator/testData/delegateProperty/cases/capturedLocalVar.kt b/js/js.translator/testData/delegateProperty/cases/capturedLocalVar.kt new file mode 100644 index 00000000000..d25d07ae483 --- /dev/null +++ b/js/js.translator/testData/delegateProperty/cases/capturedLocalVar.kt @@ -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" } +} diff --git a/js/js.translator/testData/delegateProperty/cases/capturedLocalVarNoInline.kt b/js/js.translator/testData/delegateProperty/cases/capturedLocalVarNoInline.kt new file mode 100644 index 00000000000..67aa8e2127a --- /dev/null +++ b/js/js.translator/testData/delegateProperty/cases/capturedLocalVarNoInline.kt @@ -0,0 +1,20 @@ +package foo + +import kotlin.reflect.KProperty + +fun 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" } +} diff --git a/js/js.translator/testData/delegateProperty/cases/localVal.kt b/js/js.translator/testData/delegateProperty/cases/localVal.kt new file mode 100644 index 00000000000..cd3f7178453 --- /dev/null +++ b/js/js.translator/testData/delegateProperty/cases/localVal.kt @@ -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" +} diff --git a/js/js.translator/testData/delegateProperty/cases/localVar.kt b/js/js.translator/testData/delegateProperty/cases/localVar.kt new file mode 100644 index 00000000000..606bc60c871 --- /dev/null +++ b/js/js.translator/testData/delegateProperty/cases/localVar.kt @@ -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" +}