diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/PropertyAccessTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/PropertyAccessTest.java index cb8806867fd..af856b38ea7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/PropertyAccessTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/PropertyAccessTest.java @@ -98,6 +98,10 @@ public final class PropertyAccessTest extends SingleFileTranslationTest { checkFooBoxIsOk(); } + public void testPropertyAssignment() throws Exception { + checkFooBoxIsOk(); + } + @Override @NotNull protected List additionalJsFiles(@NotNull EcmaVersion ecmaVersion) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java index a1e47f2393d..090fe9e23aa 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/TranslationContext.java @@ -359,13 +359,12 @@ public class TranslationContext { } private boolean isConstructorOrDirectScope(DeclarationDescriptor descriptor) { - if (descriptor == declarationDescriptor) { - return true; + if (declarationDescriptor instanceof ClassDescriptor && !DescriptorUtils.isCompanionObject(declarationDescriptor)) { + return descriptor == declarationDescriptor; } - if (declarationDescriptor instanceof ConstructorDescriptor) { - return descriptor == declarationDescriptor.getContainingDeclaration(); + else { + return declarationDescriptor != null && descriptor == DescriptorUtils.getContainingClass(declarationDescriptor); } - return false; } @NotNull @@ -392,7 +391,7 @@ public class TranslationContext { } @NotNull - private DefinitionPlace getDefinitionPlace() { + public DefinitionPlace getDefinitionPlace() { if (definitionPlace != null) return definitionPlace; if (parent != null) return parent.getDefinitionPlace(); @@ -430,4 +429,9 @@ public class TranslationContext { } return null; } + + @Nullable + public DeclarationDescriptor getDeclarationDescriptor() { + return declarationDescriptor; + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.java index 02cff3705cf..077f5cd8300 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.java @@ -108,6 +108,7 @@ public class DeclarationBodyVisitor extends TranslatorVisitor { return null; } + context = context.newDeclaration(descriptor, context.getDefinitionPlace()); JsPropertyInitializer methodAsPropertyInitializer = Translation.functionTranslator(expression, context).translateAsMethod(); result.add(methodAsPropertyInitializer); return null; @@ -116,6 +117,7 @@ public class DeclarationBodyVisitor extends TranslatorVisitor { @Override public Void visitProperty(@NotNull KtProperty expression, TranslationContext context) { PropertyDescriptor propertyDescriptor = BindingUtils.getPropertyDescriptor(context.bindingContext(), expression); + context.newDeclaration(propertyDescriptor, context.getDefinitionPlace()); PropertyTranslatorKt.translateAccessors(propertyDescriptor, expression, result, context); return null; } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/AssignmentTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/AssignmentTranslator.java index 012d56250db..77712a8d9bf 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/AssignmentTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/AssignmentTranslator.java @@ -19,18 +19,18 @@ package org.jetbrains.kotlin.js.translate.operation; import com.google.dart.compiler.backend.js.ast.JsBlock; import com.google.dart.compiler.backend.js.ast.JsExpression; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.PropertyDescriptor; -import org.jetbrains.kotlin.lexer.KtToken; -import org.jetbrains.kotlin.psi.KtBinaryExpression; -import org.jetbrains.kotlin.psi.KtExpression; -import org.jetbrains.kotlin.psi.KtSimpleNameExpression; -import org.jetbrains.kotlin.types.expressions.OperatorConventions; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; import org.jetbrains.kotlin.js.translate.reference.AccessTranslationUtils; import org.jetbrains.kotlin.js.translate.reference.AccessTranslator; import org.jetbrains.kotlin.js.translate.reference.BackingFieldAccessTranslator; +import org.jetbrains.kotlin.lexer.KtToken; +import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.types.expressions.OperatorConventions; import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDescriptorForReferenceExpression; import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.isVariableReassignment; @@ -72,7 +72,7 @@ public abstract class AssignmentTranslator extends AbstractTranslator { JsBlock rightBlock = new JsBlock(); this.right = translateRightExpression(context, expression, rightBlock); - if (isValProperty(left, context)) { + if (isReferenceToBackingFieldFromConstructor(left, context)) { KtSimpleNameExpression simpleName = getSimpleName(left); assert simpleName != null; this.accessTranslator = BackingFieldAccessTranslator.newInstance(simpleName, context); @@ -83,17 +83,45 @@ public abstract class AssignmentTranslator extends AbstractTranslator { context.addStatementsToCurrentBlockFrom(rightBlock); } - private static boolean isValProperty( + private static boolean isReferenceToBackingFieldFromConstructor( @NotNull KtExpression expression, @NotNull TranslationContext context ) { - KtSimpleNameExpression simpleNameExpression = getSimpleName(expression); - - if (simpleNameExpression != null) { - DeclarationDescriptor descriptor = getDescriptorForReferenceExpression(context.bindingContext(), simpleNameExpression); - return descriptor instanceof PropertyDescriptor && !((PropertyDescriptor) descriptor).isVar(); + if (expression instanceof KtSimpleNameExpression) { + KtSimpleNameExpression nameExpression = (KtSimpleNameExpression) expression; + DeclarationDescriptor descriptor = getDescriptorForReferenceExpression(context.bindingContext(), nameExpression); + return isReferenceToBackingFieldFromConstructor(descriptor, context); + } + else if (expression instanceof KtDotQualifiedExpression) { + KtDotQualifiedExpression qualifiedExpression = (KtDotQualifiedExpression) expression; + if (qualifiedExpression.getReceiverExpression() instanceof KtThisExpression && + qualifiedExpression.getSelectorExpression() instanceof KtSimpleNameExpression) { + KtSimpleNameExpression nameExpression = (KtSimpleNameExpression) qualifiedExpression.getSelectorExpression(); + DeclarationDescriptor descriptor = getDescriptorForReferenceExpression(context.bindingContext(), nameExpression); + return isReferenceToBackingFieldFromConstructor(descriptor, context); + } } - return false; } + + private static boolean isReferenceToBackingFieldFromConstructor( + @Nullable DeclarationDescriptor descriptor, + @NotNull TranslationContext context + ) { + if (!(descriptor instanceof PropertyDescriptor)) { + return false; + } + PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor; + + if (!(context.getDeclarationDescriptor() instanceof ClassDescriptor)) { + return false; + } + ClassDescriptor classDescriptor = (ClassDescriptor) context.getDeclarationDescriptor(); + + if (classDescriptor != propertyDescriptor.getContainingDeclaration()) { + return false; + } + + return !propertyDescriptor.isVar(); + } } diff --git a/js/js.translator/testData/propertyAccess/cases/propertyAssignment.kt b/js/js.translator/testData/propertyAccess/cases/propertyAssignment.kt new file mode 100644 index 00000000000..1020a6664d1 --- /dev/null +++ b/js/js.translator/testData/propertyAccess/cases/propertyAssignment.kt @@ -0,0 +1,21 @@ +// See https://youtrack.jetbrains.com/issue/KT-10785 +package foo + +class A(var x: Int) { + operator fun plusAssign(other: A) { + x += other.x + } +} + +object B { + private var holder = A(42) + + val foo: A + get() = holder +} + +fun box(): String { + B.foo += A(23) + if (B.foo.x != 65) return "failed: ${B.foo.x}" + return "OK" +} \ No newline at end of file