KT-11030 Implements closures in classes. Represent local object literals as classes

This commit is contained in:
Alexey Andreev
2016-03-03 19:44:44 +03:00
parent 1f8c3375fe
commit 8e7d949dd4
11 changed files with 181 additions and 68 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -78,7 +78,7 @@ public class DescriptorUtils {
return false;
}
private static boolean isDescriptorWithLocalVisibility(DeclarationDescriptor current) {
public static boolean isDescriptorWithLocalVisibility(DeclarationDescriptor current) {
return current instanceof DeclarationDescriptorWithVisibility &&
((DeclarationDescriptorWithVisibility) current).getVisibility() == Visibilities.LOCAL;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -41,7 +41,7 @@ public final class ObjectTest extends SingleFileTranslationTest {
}
public void testObjectInObject() throws Exception {
fooBoxTest();
checkFooBoxIsOk();
}
public void testObjectInheritingFromATrait() throws Exception {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -17,9 +17,7 @@
package org.jetbrains.kotlin.js.translate.callTranslator
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.PredefinedAnnotation
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
@@ -212,11 +210,18 @@ object ConstructorCallCase : FunctionCallCase() {
val arguments = argumentsInfo.getArguments()
val constructorDescriptor = callableDescriptor as ConstructorDescriptor
val classDescriptor = constructorDescriptor.containingDeclaration
val closure = context.getLocalClassClosure(classDescriptor)
var closureArgs = emptyList<JsExpression>()
if (closure != null) {
closureArgs = closure.asSequence().map { context.getParameterNameRefForInvocation(it) }.toList()
}
if (constructorDescriptor.isPrimary || AnnotationsUtils.isNativeObject(constructorDescriptor)) {
return JsNew(functionRef, arguments)
return JsNew(functionRef, closureArgs + arguments)
}
else {
return JsInvocation(functionRef, arguments)
return JsInvocation(functionRef, closureArgs + arguments)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -438,9 +438,7 @@ public final class Namer {
return objectCreationMethodReference();
case ANNOTATION_CLASS:
case CLASS:
return DescriptorUtils.isAnonymousObject(descriptor)
? objectCreationMethodReference()
: classCreationMethodReference();
return classCreationMethodReference();
default:
throw new UnsupportedOperationException("Unsupported class kind: " + descriptor);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.translate.context;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.dart.compiler.backend.js.ast.*;
import com.google.dart.compiler.backend.js.ast.metadata.HasMetadata;
@@ -98,6 +99,9 @@ public final class StaticContext {
@NotNull
private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap();
@NotNull
private final Map<ClassDescriptor, List<DeclarationDescriptor>> localClassesClosure = Maps.newHashMap();
@NotNull
private final Config config;
@@ -262,13 +266,17 @@ public final class StaticContext {
@Nullable
@Override
public JsName apply(@NotNull DeclarationDescriptor descriptor) {
if (!DescriptorUtils.isLocal(descriptor) || !DescriptorUtils.isClass(descriptor)) return null;
if (!DescriptorUtils.isDescriptorWithLocalVisibility(descriptor) ||
!DescriptorUtils.isClass(descriptor)) {
return null;
}
List<String> parts = new ArrayList<String>();
do {
parts.add(descriptor.getName().asString());
parts.add(descriptor.getName().isSpecial() ? "f" : descriptor.getName().getIdentifier());
descriptor = descriptor.getContainingDeclaration();
} while (!(descriptor instanceof ClassOrPackageFragmentDescriptor));
} while (descriptor != null && !(descriptor instanceof ClassOrPackageFragmentDescriptor));
assert descriptor != null;
Collections.reverse(parts);
StringBuilder suggestedName = new StringBuilder(parts.get(0));
@@ -580,10 +588,7 @@ public final class StaticContext {
if (!(descriptor instanceof ClassDescriptor)) {
return null;
}
DeclarationDescriptor container = descriptor.getContainingDeclaration();
if (container != null && !(container instanceof ClassDescriptor)) {
container = DescriptorUtils.getContainingClass(container);
}
DeclarationDescriptor container = getEnclosingNonSingleton(descriptor.getContainingDeclaration());
if (container == null) {
return null;
}
@@ -608,7 +613,9 @@ public final class StaticContext {
@Nullable
@Override
public JsExpression apply(@NotNull DeclarationDescriptor descriptor) {
if (!DescriptorUtils.isLocal(descriptor) || !(descriptor instanceof ClassDescriptor)) return null;
if (!DescriptorUtils.isDescriptorWithLocalVisibility(descriptor) || !(descriptor instanceof ClassDescriptor)) {
return null;
}
descriptor = descriptor.getContainingDeclaration();
while (descriptor != null && !(descriptor instanceof ClassOrPackageFragmentDescriptor)) {
@@ -631,6 +638,14 @@ public final class StaticContext {
}
}
@Nullable
private static ClassDescriptor getEnclosingNonSingleton(@NotNull DeclarationDescriptor descriptor) {
while (descriptor != null && (!(descriptor instanceof ClassDescriptor) || DescriptorUtils.isObject(descriptor))) {
descriptor = descriptor.getContainingDeclaration();
}
return (ClassDescriptor) descriptor;
}
private static JsExpression applySideEffects(JsExpression expression, DeclarationDescriptor descriptor) {
if (expression instanceof HasMetadata) {
if (descriptor instanceof FunctionDescriptor ||
@@ -658,4 +673,14 @@ public final class StaticContext {
addRule(propertiesInClassHaveNoQualifiers);
}
}
public void putLocalClassClosure(@NotNull ClassDescriptor localClass, @NotNull List<DeclarationDescriptor> closure) {
localClassesClosure.put(localClass, Lists.newArrayList(closure));
}
@Nullable
public List<DeclarationDescriptor> getLocalClassClosure(@NotNull ClassDescriptor localClass) {
List<DeclarationDescriptor> result = localClassesClosure.get(localClass);
return result != null ? Lists.newArrayList(result) : null;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.jetbrains.kotlin.js.translate.context.UsageTrackerKt.getNameForCapturedDescriptor;
@@ -135,6 +136,13 @@ public class TranslationContext {
this.definitionPlace, descriptor);
}
@NotNull
public TranslationContext innerWithUsageTracker(@NotNull JsScope scope, @NotNull MemberDescriptor descriptor) {
UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, scope);
return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, definitionPlace,
descriptor);
}
@NotNull
public TranslationContext innerBlock(@NotNull JsBlock block) {
return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker, null,
@@ -154,8 +162,7 @@ public class TranslationContext {
@NotNull
private TranslationContext innerWithAliasingContext(AliasingContext aliasingContext) {
return new TranslationContext(this, this.staticContext, this.dynamicContext, aliasingContext, this.usageTracker, null,
this.declarationDescriptor);
return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, null, declarationDescriptor);
}
@NotNull
@@ -414,12 +421,27 @@ public class TranslationContext {
usageTracker.used(descriptor);
JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
if (name != null) return name.makeRef();
if (name != null) {
JsNameRef result = name.makeRef();
if (shouldCaptureViaThis()) {
result.setQualifier(JsLiteral.THIS);
}
return result;
}
}
return null;
}
private boolean shouldCaptureViaThis() {
if (declarationDescriptor == null) return false;
if (DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor)) {
return !(declarationDescriptor instanceof FunctionDescriptor);
}
return true;
}
@Nullable
private static ClassDescriptor getNearestClass(@NotNull DeclarationDescriptor declaration) {
DeclarationDescriptor decl = declaration;
@@ -438,4 +460,26 @@ public class TranslationContext {
public DeclarationDescriptor getDeclarationDescriptor() {
return declarationDescriptor;
}
public void putLocalClassClosure(@NotNull ClassDescriptor localClass, @NotNull List<DeclarationDescriptor> closure) {
staticContext.putLocalClassClosure(localClass, closure);
}
@Nullable
public List<DeclarationDescriptor> getLocalClassClosure(@NotNull ClassDescriptor localClass) {
return staticContext.getLocalClassClosure(localClass);
}
@NotNull
public JsExpression getParameterNameRefForInvocation(@NotNull DeclarationDescriptor descriptor) {
JsExpression alias = getAliasForDescriptor(descriptor);
if (alias != null) return alias;
if (descriptor instanceof ReceiverParameterDescriptor) return JsLiteral.THIS;
return getNameForDescriptor(descriptor).makeRef();
}
@NotNull
public JsScope getRootScope() {
return staticContext.getRootScope();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.DescriptorUtils.isAncestor
import com.google.dart.compiler.backend.js.ast.JsName
import org.jetbrains.kotlin.js.translate.utils.ManglingUtils.getSuggestedName
import com.google.dart.compiler.backend.js.ast.JsFunctionScope
import com.google.dart.compiler.backend.js.ast.JsScope
import org.jetbrains.kotlin.resolve.DescriptorUtils
private val CAPTURED_RECEIVER_NAME_PREFIX : String = "this$"
@@ -28,7 +28,7 @@ private val CAPTURED_RECEIVER_NAME_PREFIX : String = "this$"
class UsageTracker(
private val parent: UsageTracker?,
val containingDescriptor: MemberDescriptor,
private val scope: JsFunctionScope
private val scope: JsScope
) {
private val captured = linkedMapOf<DeclarationDescriptor, JsName>()
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -28,8 +28,8 @@ import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator
import org.jetbrains.kotlin.js.translate.context.DefinitionPlace
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.context.*
import org.jetbrains.kotlin.js.translate.expression.FunctionTranslator
import org.jetbrains.kotlin.js.translate.expression.withCapturedParameters
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator
import org.jetbrains.kotlin.js.translate.initializer.ClassInitializerTranslator
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator.translateAsFQReference
@@ -46,6 +46,7 @@ import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
import org.jetbrains.kotlin.resolve.BindingContextUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.*
import org.jetbrains.kotlin.types.CommonSupertypes.topologicallySortSuperclassesAndRecordAllInstances
import org.jetbrains.kotlin.types.KotlinType
@@ -64,9 +65,10 @@ class ClassTranslator private constructor(
private val descriptor = getClassDescriptor(context.bindingContext(), classDeclaration)
private fun translateObjectLiteralExpression(): JsExpression {
getContainingClass(descriptor) ?: return translate(context())
return translateObjectInsideClass(context())
return if (descriptor.containingDeclaration is ClassDescriptor && descriptor.kind == ClassKind.OBJECT)
translateObjectInsideClass(context())
else
translate(context())
}
private fun translate(declarationContext: TranslationContext = context()): JsInvocation {
@@ -82,28 +84,19 @@ class ClassTranslator private constructor(
val properties = SmartList<JsPropertyInitializer>()
val staticProperties = SmartList<JsPropertyInitializer>()
val isTopLevelDeclaration = context() == context
var qualifiedReference: JsNameRef? = null
if (isTopLevelDeclaration) {
var definitionPlace: DefinitionPlace? = null
if (!descriptor.kind.isSingleton && !isAnonymousObject(descriptor)) {
qualifiedReference = context.getQualifiedReference(descriptor)
val scope = context().getScopeForDescriptor(descriptor)
definitionPlace = DefinitionPlace(scope as JsObjectScope, qualifiedReference, staticProperties)
}
context = context.newDeclaration(descriptor, definitionPlace)
}
val qualifiedReference = context.getQualifiedReference(descriptor)
val scope = context().getScopeForDescriptor(descriptor)
val definitionPlace = DefinitionPlace(scope as JsObjectScope, qualifiedReference, staticProperties)
context = context.newDeclaration(descriptor, definitionPlace)
context = fixContextForCompanionObjectAccessing(context)
invocationArguments.add(getSuperclassReferences(context))
val delegationTranslator = DelegationTranslator(classDeclaration, context())
var initializer: JsFunction? = null
if (!isTrait()) {
val initializer = ClassInitializerTranslator(classDeclaration, context).generateInitializeMethod(delegationTranslator)
invocationArguments.add(if (initializer.body.statements.isEmpty()) JsLiteral.NULL else initializer)
initializer = ClassInitializerTranslator(classDeclaration, context).generateInitializeMethod(delegationTranslator)
invocationArguments.add(initializer)
}
translatePropertiesAsConstructorParameters(context, properties)
@@ -129,10 +122,8 @@ class ClassTranslator private constructor(
invocationArguments.add(JsLiteral.NULL)
}
else {
if (qualifiedReference != null) {
// about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
invocationArguments.add(JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, JsNameRef("prototype", qualifiedReference)))
}
// about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
invocationArguments.add(JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, JsNameRef("prototype", qualifiedReference)))
invocationArguments.add(JsObjectLiteral(properties, true))
}
}
@@ -141,6 +132,22 @@ class ClassTranslator private constructor(
invocationArguments.add(JsObjectLiteral(staticProperties, true))
}
val tracker = context.usageTracker()
if (tracker != null && initializer != null && tracker.hasCapturedExceptContaining()) {
val captured = tracker.capturedDescriptorToJsName
val keysAsList = captured.keys.toList()
for ((i, key) in keysAsList.withIndex()) {
val name = captured[key]!!
initializer.parameters.add(i, JsParameter(name))
initializer.body.statements.add(i, JsAstUtils.defineSimpleProperty(name.ident, name.makeRef()))
}
context.putLocalClassClosure(descriptor, keysAsList)
}
if (initializer != null && initializer.body.isEmpty) {
invocationArguments.replaceAll { if (it == initializer) JsLiteral.NULL else it }
}
return invocationArguments
}
@@ -214,12 +221,30 @@ class ClassTranslator private constructor(
}
private fun translateObjectInsideClass(outerClassContext: TranslationContext): JsExpression {
val function = JsFunction(outerClassContext.scope(), JsBlock(), "initializer for " + descriptor.name.asString())
val funContext = outerClassContext.newFunctionBodyWithUsageTracker(function, descriptor)
var outerDeclaration = descriptor.containingDeclaration.containingDeclaration
if (outerDeclaration != null && outerDeclaration !is ClassDescriptor) {
outerDeclaration = DescriptorUtils.getContainingClass(outerDeclaration)
}
val scope = if (outerDeclaration != null)
outerClassContext.getScopeForDescriptor(outerDeclaration)
else
outerClassContext.rootScope
function.body.statements.add(JsReturn(translate(funContext)))
val classContext = outerClassContext.innerWithUsageTracker(scope, descriptor)
return function.withCapturedParameters(funContext, outerClassContext, descriptor)
var declarationArgs = getClassCreateInvocationArguments(classContext)
val jsClass = JsInvocation(context().namer().classCreationMethodReference(), declarationArgs)
val name = outerClassContext.getNameForDescriptor(descriptor)
val constructor = outerClassContext.define(name, jsClass)
val closure = outerClassContext.getLocalClassClosure(descriptor)
var closureArgs = emptyList<JsExpression>()
if (closure != null) {
closureArgs = closure.map { context().getParameterNameRefForInvocation(it) }.toList()
}
return JsNew(constructor, closureArgs)
}
private fun generatedBridgeMethods(properties: MutableList<JsPropertyInitializer>) {
@@ -289,6 +314,10 @@ class ClassTranslator private constructor(
}
@JvmStatic fun generateObjectLiteral(objectDeclaration: KtObjectDeclaration, context: TranslationContext): JsExpression {
return ClassTranslator(objectDeclaration, context).translateObjectInsideClass(context)
}
@JvmStatic fun generateObjectDeclaration(objectDeclaration: KtObjectDeclaration, context: TranslationContext): JsExpression {
return ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression()
}
@@ -518,8 +518,7 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
@Override
@NotNull
public JsNode visitObjectLiteralExpression(@NotNull KtObjectLiteralExpression expression,
@NotNull TranslationContext context) {
public JsNode visitObjectLiteralExpression(@NotNull KtObjectLiteralExpression expression, @NotNull TranslationContext context) {
return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
}
@@ -569,8 +568,12 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
@Override
public JsNode visitClass(@NotNull KtClass klass, TranslationContext context) {
JsInvocation jsClass = ClassTranslator.generateClassCreation(klass, context);
DeclarationDescriptor descriptor = BindingUtils.getClassDescriptor(context.bindingContext(), klass);
ClassDescriptor descriptor = BindingUtils.getClassDescriptor(context.bindingContext(), klass);
JsObjectScope scope = new JsObjectScope(context.scope(), descriptor.toString(), "");
TranslationContext classContext = context.innerWithUsageTracker(scope, descriptor);
JsInvocation jsClass = ClassTranslator.generateClassCreation(klass, classContext);
JsName name = context.getNameForDescriptor(descriptor);
return context.define(name, jsClass);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 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.
@@ -64,7 +64,7 @@ public final class InitializerUtils {
@NotNull List<JsStatement> initializers,
@NotNull TranslationContext context
) {
JsExpression value = ClassTranslator.generateObjectLiteral(declaration, context);
JsExpression value = ClassTranslator.generateObjectDeclaration(declaration, context);
ClassDescriptor descriptor = getClassDescriptor(context.bindingContext(), declaration);
JsExpression expression = assignment(new JsNameRef(descriptor.getName().asString(), JsLiteral.THIS), value);
initializers.add(expression.makeStmt());
+12 -3
View File
@@ -22,7 +22,16 @@ class C {
}
}
fun box() = A.query.status == "complete" && B.query.status == "completed"
// todo fix after KT-3868 will be fixed
// && C.query.status == "completed"
fun box(): String {
var result = A.query.status
if (result != "complete") return "fail1: $result"
result = B.query.status
if (result != "completed") return "fail2: $result"
result = C.query.status
if (result != "completed") return "fail3: $result"
return "OK"
}