JS backend: Add class object support.

This commit is contained in:
Erokhin Stanislav
2013-08-09 14:59:09 +04:00
parent e786e62fd7
commit 9d1ae5e3d9
11 changed files with 307 additions and 16 deletions
@@ -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();
}
}
@@ -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());
@@ -133,15 +133,13 @@ public final class ClassTranslator extends AbstractTranslator {
private void addClassOwnDeclarations(@NotNull List<JsExpression> invocationArguments, @NotNull TranslationContext declarationContext) {
final List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
final List<JsPropertyInitializer> staticProperties;
final List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
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<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
@@ -154,7 +152,6 @@ public final class ClassTranslator extends AbstractTranslator {
}
else {
qualifiedReference = getQualifiedReference(declarationContext, descriptor);
staticProperties = new SmartList<JsPropertyInitializer>();
declarationContext.literalFunctionTranslator().setDefinitionPlace(
new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, 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);
@@ -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<Void> {
protected final List<JsPropertyInitializer> result;
protected final List<JsPropertyInitializer> staticResult;
public DeclarationBodyVisitor() {
this(new SmartList<JsPropertyInitializer>());
this(new SmartList<JsPropertyInitializer>(), new SmartList<JsPropertyInitializer>());
}
public DeclarationBodyVisitor(List<JsPropertyInitializer> result) {
public DeclarationBodyVisitor(@NotNull List<JsPropertyInitializer> result, @NotNull List<JsPropertyInitializer> staticResult) {
this.result = result;
this.staticResult = staticResult;
}
@NotNull
@@ -56,6 +56,21 @@ public class DeclarationBodyVisitor extends TranslatorVisitor<Void> {
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);
@@ -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<TemporaryVariable> declaredTemporaries() {
return Collections.emptyList();
}
}
@@ -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);
}
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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;
})();
+12 -1
View File
@@ -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;
}