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 new file mode 100644 index 00000000000..52ebeb313c0 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ClassObjectTest.java @@ -0,0 +1,22 @@ +package org.jetbrains.k2js.test.semantics; + +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +public final class ClassObjectTest extends SingleFileTranslationTest { + + public ClassObjectTest() { + super("class_object/"); + } + + public void testSimple() throws Exception { + checkFooBoxIsOk(); + } + + public void testWithExtension() throws Exception { + checkFooBoxIsOk(); + } + + public void testSetVar() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java index 2c05bf0422d..5492189607f 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java @@ -20,9 +20,11 @@ import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.Named; import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.plugin.JetLanguage; +import org.jetbrains.jet.lang.resolve.name.Name; /** * Encapuslates different types of constants and naming conventions. @@ -46,6 +48,16 @@ public final class Namer { private static final String RECEIVER_PARAMETER_NAME = "$receiver"; private static final String CLASSES_OBJECT_NAME = "classes"; private static final String THROW_NPE_FUN_NAME = "throwNPE"; + private static final String CLASS_OBJECT_GETTER = "object$"; + private static final String CLASS_OBJECT_INITIALIZER = "object_initializer$"; + + private static final Named CLASS_OBJECT_INITIALIZER_NAMED = new Named() { + @NotNull + @Override + public Name getName() { + return Name.identifier(CLASS_OBJECT_INITIALIZER); + } + }; @NotNull public static String getReceiverParameterName() { @@ -101,6 +113,14 @@ public final class Namer { return getNameWithPrefix(propertyName, SETTER_PREFIX); } + public static JsExpression getClassObjectAccessor(@NotNull JsExpression referenceToClass) { + return new JsInvocation(new JsNameRef(CLASS_OBJECT_GETTER, referenceToClass)); + } + + public static Named getNamedForClassObjectInitializer() { + return CLASS_OBJECT_INITIALIZER_NAMED; + } + @NotNull private static String getNameWithPrefix(@NotNull String name, @NotNull String prefix) { return prefix + name; @@ -206,6 +226,8 @@ public final class Namer { return new JsInvocation(traitCreationMethodReference()); case OBJECT: return new JsInvocation(objectCreationMethodReference()); + case CLASS_OBJECT: + return new JsInvocation(objectCreationMethodReference()); default: return new JsInvocation(classCreationMethodReference()); 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 88fba263404..4b18c333f17 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 @@ -133,15 +133,13 @@ public final class ClassTranslator extends AbstractTranslator { private void addClassOwnDeclarations(@NotNull List invocationArguments, @NotNull TranslationContext declarationContext) { final List properties = new SmartList(); - final List staticProperties; + final List staticProperties = new SmartList(); boolean isTopLevelDeclaration = context() == declarationContext; final JsNameRef qualifiedReference; if (!isTopLevelDeclaration) { - staticProperties = null; qualifiedReference = null; } else if (descriptor.getKind().isObject()) { - staticProperties = null; qualifiedReference = null; declarationContext.literalFunctionTranslator().setDefinitionPlace( new NotNullLazyValue, LabelGenerator, JsExpression>>() { @@ -154,7 +152,6 @@ public final class ClassTranslator extends AbstractTranslator { } else { qualifiedReference = getQualifiedReference(declarationContext, descriptor); - staticProperties = new SmartList(); declarationContext.literalFunctionTranslator().setDefinitionPlace( new NotNullLazyValue, LabelGenerator, JsExpression>>() { @Override @@ -176,13 +173,13 @@ public final class ClassTranslator extends AbstractTranslator { } translatePropertiesAsConstructorParameters(declarationContext, properties); - new DeclarationBodyVisitor(properties).traverseContainer(classDeclaration, declarationContext); + new DeclarationBodyVisitor(properties, staticProperties).traverseContainer(classDeclaration, declarationContext); if (isTopLevelDeclaration) { declarationContext.literalFunctionTranslator().setDefinitionPlace(null); } - boolean hasStaticProperties = staticProperties != null && !staticProperties.isEmpty(); + boolean hasStaticProperties = !staticProperties.isEmpty(); if (!properties.isEmpty() || hasStaticProperties) { if (properties.isEmpty()) { invocationArguments.add(JsLiteral.NULL); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java index 24f478a0571..4ed41f144ff 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java @@ -16,14 +16,12 @@ package org.jetbrains.k2js.translate.declaration; -import com.google.dart.compiler.backend.js.ast.JsExpression; -import com.google.dart.compiler.backend.js.ast.JsPropertyInitializer; +import com.google.dart.compiler.backend.js.ast.*; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.Modality; -import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; +import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.Translation; import org.jetbrains.k2js.translate.general.TranslatorVisitor; @@ -32,18 +30,20 @@ import org.jetbrains.k2js.translate.utils.JsAstUtils; import java.util.List; -import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor; -import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForObjectDeclaration; +import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createPropertyInitializer; +import static org.jetbrains.k2js.translate.utils.BindingUtils.*; public class DeclarationBodyVisitor extends TranslatorVisitor { protected final List result; + protected final List staticResult; public DeclarationBodyVisitor() { - this(new SmartList()); + this(new SmartList(), new SmartList()); } - public DeclarationBodyVisitor(List result) { + public DeclarationBodyVisitor(@NotNull List result, @NotNull List staticResult) { this.result = result; + this.staticResult = staticResult; } @NotNull @@ -56,6 +56,21 @@ public class DeclarationBodyVisitor extends TranslatorVisitor { return null; } + @Override + public Void visitClassObject( + JetClassObject classObject, TranslationContext context + ) { + JetObjectDeclaration declaration = classObject.getObjectDeclaration(); + assert declaration != null : "Declaration for class object must be not null"; + ClassDescriptor descriptor = getClassDescriptor(context.bindingContext(), declaration); + JsExpression value = ClassTranslator.generateClassCreation(declaration, descriptor, context); + + JsFunction fun = new JsFunction(context.getScopeForDescriptor(descriptor.getContainingDeclaration())); + fun.setBody(new JsBlock(new JsReturn(value))); + staticResult.add(createPropertyInitializer(Namer.getNamedForClassObjectInitializer(), fun, context)); + return null; + } + @Override public Void visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) { FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/ClassObjectAccessTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/ClassObjectAccessTranslator.java new file mode 100644 index 00000000000..f999d0a142f --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/ClassObjectAccessTranslator.java @@ -0,0 +1,83 @@ +/* + * 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.translate.reference; + +import com.google.dart.compiler.backend.js.ast.JsExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.ClassDescriptor; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.psi.JetReferenceExpression; +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; +import org.jetbrains.k2js.translate.context.Namer; +import org.jetbrains.k2js.translate.context.TemporaryVariable; +import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.general.AbstractTranslator; +import org.jetbrains.k2js.translate.utils.AnnotationsUtils; + +import java.util.Collections; +import java.util.List; + +import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference; +import static org.jetbrains.k2js.translate.utils.BindingUtils.getDescriptorForReferenceExpression; + +public class ClassObjectAccessTranslator extends AbstractTranslator implements CachedAccessTranslator { + @NotNull + /*package*/ static ClassObjectAccessTranslator newInstance(@NotNull JetSimpleNameExpression expression, + @NotNull TranslationContext context) { + DeclarationDescriptor referenceDescriptor = getDescriptorForReferenceExpression(context.bindingContext(), expression); + assert referenceDescriptor != null : "JetSimpleName expression must reference a descriptor " + expression.getText(); + return new ClassObjectAccessTranslator(referenceDescriptor, context); + } + + /*package*/ static boolean isClassObjectReference(@NotNull JetReferenceExpression expression, + @NotNull TranslationContext context) { + DeclarationDescriptor descriptor = getDescriptorForReferenceExpression(context.bindingContext(), expression); + return descriptor instanceof ClassDescriptor && !AnnotationsUtils.isNativeObject(descriptor); + } + + @NotNull + private final JsExpression referenceToClassObject; + + private ClassObjectAccessTranslator(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) { + super(context); + this.referenceToClassObject = Namer.getClassObjectAccessor(translateAsFQReference(descriptor, context())); + } + + @Override + @NotNull + public JsExpression translateAsGet() { + return referenceToClassObject; + } + + @Override + @NotNull + public JsExpression translateAsSet(@NotNull JsExpression toSetTo) { + throw new IllegalStateException("Class object can't set"); + } + + @NotNull + @Override + public CachedAccessTranslator getCached() { + return this; + } + + @NotNull + @Override + public List declaredTemporaries() { + return Collections.emptyList(); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/ReferenceTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/ReferenceTranslator.java index bde452cca57..cb27dd22814 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/ReferenceTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/ReferenceTranslator.java @@ -78,6 +78,9 @@ public final class ReferenceTranslator { if (PropertyAccessTranslator.canBePropertyAccess(referenceExpression, context)) { return PropertyAccessTranslator.newInstance(referenceExpression, receiver, CallType.NORMAL, context); } + if (ClassObjectAccessTranslator.isClassObjectReference(referenceExpression, context)) { + return ClassObjectAccessTranslator.newInstance(referenceExpression, context); + } return ReferenceAccessTranslator.newInstance(referenceExpression, context); } } diff --git a/js/js.translator/testFiles/class_object/cases/setVar.kt b/js/js.translator/testFiles/class_object/cases/setVar.kt new file mode 100644 index 00000000000..8ca559b6de1 --- /dev/null +++ b/js/js.translator/testFiles/class_object/cases/setVar.kt @@ -0,0 +1,43 @@ +/* + * 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 foo + +class A { + var a = 3 + class object { + var a = -2 + } +} + + + +fun box() : String { + A.a = 2 + if (A.a != 2) return "A.a != 2" + + val a = A + a.a = 3 + if (a.a != 3) return "a = A; a.a = 3; a != 3" + + if (A().a != 3) return "A().a != 3" + + val x = A() + x.a = 4 + if (x.a != 4) return "x = A(); x.a = 4; x.a != 4" + + return "OK" +} diff --git a/js/js.translator/testFiles/class_object/cases/simple.kt b/js/js.translator/testFiles/class_object/cases/simple.kt new file mode 100644 index 00000000000..cf3f98445b4 --- /dev/null +++ b/js/js.translator/testFiles/class_object/cases/simple.kt @@ -0,0 +1,39 @@ +/* + * 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 foo + +class A { + val a = 3 + class object { + val a = 2 + val b = 5 + } +} + + + +fun box() : String { + if (A.a != 2) return "A.a != 2" + if (A.b != 5) return "A.b != 5" + + val b = A + if (b.a != 2) return "b = A; b != 2" + + if (A().a != 3) return "A().a != 3" + + return "OK" +} diff --git a/js/js.translator/testFiles/class_object/cases/withExtension.kt b/js/js.translator/testFiles/class_object/cases/withExtension.kt new file mode 100644 index 00000000000..28aa89c3177 --- /dev/null +++ b/js/js.translator/testFiles/class_object/cases/withExtension.kt @@ -0,0 +1,47 @@ +/* + * 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 foo + +open class A { + val a = 3 + fun foo(): Int { + return 5 + } + class object: A() { + val c = a + } +} + +class B { + class object: A() { + } +} + +fun box() : String { + if (A.a != 3) return "A.a != 3" + if (A.foo() != 5) return "A.foo() != 5" + + val a = A + if (a.c != 3) return "a = A; a.c != 3" + + if (A().a != 3) return "A().a != 3" + + if (B.a != 3) return "B.a != 3" + val b = B + if (b.foo() != 5) return "b = B; b.foo() != 5" + return "OK" +} diff --git a/js/js.translator/testFiles/kotlin_lib_ecma3.js b/js/js.translator/testFiles/kotlin_lib_ecma3.js index 82e9fc59484..7a6bba549be 100644 --- a/js/js.translator/testFiles/kotlin_lib_ecma3.js +++ b/js/js.translator/testFiles/kotlin_lib_ecma3.js @@ -111,6 +111,7 @@ var Kotlin = {}; klass.addMethods = addMethods; klass.superclass = parent || null; klass.subclasses = []; + klass.object$ = object$; if (parent) { if (typeof (parent) == "function") { @@ -160,6 +161,14 @@ var Kotlin = {}; return this; } + function object$() { + if (typeof this.$object$ === "undefined") { + this.$object$ = this.object_initializer$(); + } + + return this.$object$; + } + return create; })(); diff --git a/js/js.translator/testFiles/kotlin_lib_ecma5.js b/js/js.translator/testFiles/kotlin_lib_ecma5.js index 4b46588641e..5de5b97c975 100644 --- a/js/js.translator/testFiles/kotlin_lib_ecma5.js +++ b/js/js.translator/testFiles/kotlin_lib_ecma5.js @@ -89,6 +89,14 @@ var Kotlin = Object.create(null); return o; }; + function class_object$() { + if (typeof this.$object$ === "undefined") { + this.$object$ = this.object_initializer$(); + } + + return this.$object$; + } + function createClass(bases, initializer, properties, staticProperties, isClass) { var proto; var baseInitializer; @@ -111,6 +119,9 @@ var Kotlin = Object.create(null); } var constructor = createConstructor(); + Object.defineProperty(constructor, "object$", {value: class_object$}); + Object.defineProperty(constructor, "$object$", {value: undefined, writable: true}); + Object.defineProperty(constructor, "proto", {value: proto}); Object.defineProperty(constructor, "properties", {value: properties || null}); if (isClass) { @@ -124,7 +135,7 @@ var Kotlin = Object.create(null); Object.defineProperties(constructor, staticProperties); } - Object.freeze(constructor); + Object.seal(constructor); return constructor; }