diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClassObjectTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClassObjectTest.java index 0e686c87c3b..c1a107792cf 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClassObjectTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClassObjectTest.java @@ -16,11 +16,15 @@ public final class ClassObjectTest extends SingleFileTranslationTest { checkFooBoxIsOk(); } - public void testWithExtension() throws Exception { + public void testWithInheritance() throws Exception { checkFooBoxIsOk(); } public void testSetVar() throws Exception { checkFooBoxIsOk(); } + + public void testAccessing() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java index 850b8433d0c..cb78ee283cc 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClosureTest.java @@ -163,4 +163,8 @@ public final class ClosureTest extends SingleFileTranslationTest { public void testClosureThisInLambdaInsideMethod() throws Exception { checkFooBoxIsOk(); } + + public void testClosureThisAndClassObject() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java index 8dc744d38f7..4f56a9fb61e 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java @@ -123,7 +123,7 @@ public class TranslationContext { } @NotNull - public TranslationContext innerContextWithAliased(@NotNull DeclarationDescriptor correspondingDescriptor, @NotNull JsNameRef alias) { + public TranslationContext innerContextWithAliased(@NotNull DeclarationDescriptor correspondingDescriptor, @NotNull JsExpression alias) { return this.innerWithAliasingContext(aliasingContext.inner(correspondingDescriptor, alias)); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java index ae7b5278126..168a7f16a1c 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java @@ -22,12 +22,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.descriptors.ClassKind; import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; +import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor; import org.jetbrains.jet.lang.psi.JetClassOrObject; import org.jetbrains.jet.lang.psi.JetObjectDeclaration; import org.jetbrains.jet.lang.psi.JetParameter; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.TypeConstructor; import org.jetbrains.k2js.translate.context.DefinitionPlace; +import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.expression.ExpressionPackage; import org.jetbrains.k2js.translate.general.AbstractTranslator; @@ -39,9 +41,11 @@ import java.util.*; import static org.jetbrains.jet.lang.resolve.DescriptorUtils.*; import static org.jetbrains.jet.lang.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances; import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createClassObjectInitializer; +import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference; import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor; import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter; import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass; +import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration; import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getSupertypesWithoutFakes; import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters; import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction; @@ -117,6 +121,8 @@ public final class ClassTranslator extends AbstractTranslator { declarationContext = declarationContext.newDeclaration(descriptor, definitionPlace); } + declarationContext = fixContextForClassObjectAccessing(declarationContext); + invocationArguments.add(getSuperclassReferences(declarationContext)); if (!isTrait()) { JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(); @@ -148,6 +154,26 @@ public final class ClassTranslator extends AbstractTranslator { return invocationArguments; } + private TranslationContext fixContextForClassObjectAccessing(TranslationContext declarationContext) { + // In Kotlin we can access to class object members without qualifier just by name, but we should translate it to access with FQ name. + // So create alias for class object receiver parameter. + ClassDescriptor classObjectDescriptor = descriptor.getClassObjectDescriptor(); + if (classObjectDescriptor != null) { + JsExpression referenceToClass = translateAsFQReference(classObjectDescriptor.getContainingDeclaration(), declarationContext); + JsExpression classObjectAccessor = Namer.getClassObjectAccessor(referenceToClass); + ReceiverParameterDescriptor classObjectReceiver = getReceiverParameterForDeclaration(classObjectDescriptor); + declarationContext.aliasingContext().registerAlias(classObjectReceiver, classObjectAccessor); + } + + // Overlap alias of class object receiver for accessing from containing class(see previous if block), + // because inside class object we should use simple name for access. + if (descriptor.getKind() == ClassKind.CLASS_OBJECT) { + declarationContext = declarationContext.innerContextWithAliased(descriptor.getThisAsReceiverParameter(), JsLiteral.THIS); + } + + return declarationContext; + } + private void mayBeAddEnumEntry(@NotNull List enumEntryList, @NotNull List staticProperties, @NotNull TranslationContext declarationContext diff --git a/js/js.translator/testData/classObject/cases/accessing.kt b/js/js.translator/testData/classObject/cases/accessing.kt new file mode 100644 index 00000000000..00dcaf6047b --- /dev/null +++ b/js/js.translator/testData/classObject/cases/accessing.kt @@ -0,0 +1,57 @@ +// KT-4130 object fields are not evaluated correctly + +package foo + +class Foo() { + class object { + val bar = "Foo.bar "; + var boo = "FAIL"; + fun baz() = "Foo.baz() " + + fun testImplicitThis(): String { + boo = "Implicit" + return baz() + bar + boo + } + fun testExplicitThis(): String { + this.boo = "Explicit" + return this.baz() + this.bar + this.boo + } + } + + val a = bar + val b = Foo.bar + val c = baz() + val d = Foo.baz() + val e: String + val f: String + + { + e = bar + f = Foo.bar + boo = "O" + Foo.boo += "K" + } +} + +fun assertEquals(expected: T, actual: T, id: String) { + if (expected != actual) throw Exception("Failed on $id, expected = '$expected', actual = '$actual'") +} + +fun box(): String { + assertEquals("Foo.baz() Foo.bar Implicit", Foo.testImplicitThis(), "testImplicitThis") + assertEquals("Foo.baz() Foo.bar Explicit", Foo.testExplicitThis(), "testExplicitThis") + + val foo = Foo() + assertEquals("Foo.bar ", foo.a, "foo.a") + assertEquals("Foo.bar ", foo.b, "foo.b") + assertEquals("Foo.baz() ", foo.c, "foo.c") + assertEquals("Foo.baz() ", foo.d, "foo.d") + assertEquals("Foo.bar ", foo.e, "foo.e") + assertEquals("Foo.bar ", foo.f, "foo.f") + + assertEquals("OK", Foo.boo, "Foo.boo") + assertEquals("Foo.bar ", Foo.bar, "Foo.bar") + assertEquals("Foo.baz() ", Foo.baz(), "Foo.baz()") + + return "OK" +} diff --git a/js/js.translator/testData/classObject/cases/withExtension.kt b/js/js.translator/testData/classObject/cases/withInheritance.kt similarity index 100% rename from js/js.translator/testData/classObject/cases/withExtension.kt rename to js/js.translator/testData/classObject/cases/withInheritance.kt diff --git a/js/js.translator/testData/closure/cases/closureThisAndClassObject.kt b/js/js.translator/testData/closure/cases/closureThisAndClassObject.kt new file mode 100644 index 00000000000..93157f91b68 --- /dev/null +++ b/js/js.translator/testData/closure/cases/closureThisAndClassObject.kt @@ -0,0 +1,12 @@ +package foo + +class A { + fun foo() = "O" + class object { + fun bar() = "K" + } + + val f = { foo() + bar() } +} + +fun box(): String = A().f()