diff --git a/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java b/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java index d18bab52218..f2b57a06e6c 100644 --- a/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java +++ b/compiler/backend-common/src/org/jetbrains/jet/backend/common/CodegenUtil.java @@ -18,18 +18,19 @@ package org.jetbrains.jet.backend.common; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.ClassDescriptor; -import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; +import org.jetbrains.jet.lang.descriptors.*; +import org.jetbrains.jet.lang.psi.JetDelegationSpecifier; +import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; +import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.calls.CallResolverUtil; +import org.jetbrains.jet.lang.resolve.calls.callUtil.CallUtilPackage; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.*; /** * Backend-independent utility class. @@ -91,6 +92,66 @@ public class CodegenUtil { return function; } + @Nullable + public static PropertyDescriptor getDelegatePropertyIfAny(JetExpression expression, ClassDescriptor classDescriptor, BindingContext bindingContext) { + PropertyDescriptor propertyDescriptor = null; + if (expression instanceof JetSimpleNameExpression) { + ResolvedCall call = CallUtilPackage.getResolvedCall(expression, bindingContext); + if (call != null) { + CallableDescriptor callResultingDescriptor = call.getResultingDescriptor(); + if (callResultingDescriptor instanceof ValueParameterDescriptor) { + ValueParameterDescriptor valueParameterDescriptor = (ValueParameterDescriptor) callResultingDescriptor; + // constructor parameter + if (valueParameterDescriptor.getContainingDeclaration() instanceof ConstructorDescriptor) { + // constructor of my class + if (valueParameterDescriptor.getContainingDeclaration().getContainingDeclaration() == classDescriptor) { + propertyDescriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, valueParameterDescriptor); + } + } + } + + // todo: when and if frontend will allow properties defined not as constructor parameters to be used in delegation specifier + } + } + return propertyDescriptor; + } + + public static boolean isFinalPropertyWithBackingField(PropertyDescriptor propertyDescriptor, BindingContext bindingContext) { + return propertyDescriptor != null && + !propertyDescriptor.isVar() && + Boolean.TRUE.equals(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)); + } + + public static Map getDelegates(ClassDescriptor descriptor, ClassDescriptor toClass) { + Map result = new LinkedHashMap(); + for (DeclarationDescriptor declaration : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) { + if (declaration instanceof CallableMemberDescriptor) { + CallableMemberDescriptor callableMemberDescriptor = (CallableMemberDescriptor) declaration; + if (callableMemberDescriptor.getKind() == CallableMemberDescriptor.Kind.DELEGATION) { + Set overriddenDescriptors = callableMemberDescriptor.getOverriddenDescriptors(); + for (CallableMemberDescriptor overriddenDescriptor : overriddenDescriptors) { + if (overriddenDescriptor.getContainingDeclaration() == toClass) { + assert !result.containsKey(callableMemberDescriptor) : + "overridden is already set for " + callableMemberDescriptor; + result.put(callableMemberDescriptor, overriddenDescriptor); + } + } + } + } + } + return result; + } + + @NotNull + public static ClassDescriptor getSuperClassByDelegationSpecifier(@NotNull JetDelegationSpecifier specifier, @NotNull BindingContext bindingContext) { + JetType superType = bindingContext.get(BindingContext.TYPE, specifier.getTypeReference()); + assert superType != null : "superType should not be null: " + specifier.getText(); + + ClassDescriptor superClassDescriptor = (ClassDescriptor) superType.getConstructor().getDeclarationDescriptor(); + assert superClassDescriptor != null : "superClassDescriptor should not be null: " + specifier.getText(); + return superClassDescriptor; + } + private static boolean valueParameterClassesMatch( @NotNull List parameters, @NotNull List classifiers diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index 7114622acc7..696e62dc7e1 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -1313,14 +1313,11 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { for (JetDelegationSpecifier specifier : delegationSpecifiers) { if (specifier instanceof JetDelegatorByExpressionSpecifier) { JetExpression expression = ((JetDelegatorByExpressionSpecifier) specifier).getDelegateExpression(); - PropertyDescriptor propertyDescriptor = getDelegatePropertyIfAny(expression); + PropertyDescriptor propertyDescriptor = CodegenUtil.getDelegatePropertyIfAny(expression, descriptor, bindingContext); ClassDescriptor superClassDescriptor = getSuperClass(specifier); - if (propertyDescriptor != null && - !propertyDescriptor.isVar() && - Boolean.TRUE.equals(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor))) { - // final property with backing field + if (CodegenUtil.isFinalPropertyWithBackingField(propertyDescriptor, bindingContext)) { result.addField((JetDelegatorByExpressionSpecifier) specifier, propertyDescriptor); } else { @@ -1334,12 +1331,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { @NotNull private ClassDescriptor getSuperClass(@NotNull JetDelegationSpecifier specifier) { - JetType superType = bindingContext.get(BindingContext.TYPE, specifier.getTypeReference()); - assert superType != null; - - ClassDescriptor superClassDescriptor = (ClassDescriptor) superType.getConstructor().getDeclarationDescriptor(); - assert superClassDescriptor != null; - return superClassDescriptor; + return CodegenUtil.getSuperClassByDelegationSpecifier(specifier, bindingContext); } private void genCallToDelegatorByExpressionSpecifier( @@ -1361,26 +1353,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { @Nullable private PropertyDescriptor getDelegatePropertyIfAny(JetExpression expression) { - PropertyDescriptor propertyDescriptor = null; - if (expression instanceof JetSimpleNameExpression) { - ResolvedCall call = CallUtilPackage.getResolvedCall(expression, bindingContext); - if (call != null) { - CallableDescriptor callResultingDescriptor = call.getResultingDescriptor(); - if (callResultingDescriptor instanceof ValueParameterDescriptor) { - ValueParameterDescriptor valueParameterDescriptor = (ValueParameterDescriptor) callResultingDescriptor; - // constructor parameter - if (valueParameterDescriptor.getContainingDeclaration() instanceof ConstructorDescriptor) { - // constructor of my class - if (valueParameterDescriptor.getContainingDeclaration().getContainingDeclaration() == descriptor) { - propertyDescriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, valueParameterDescriptor); - } - } - } - - // todo: when and if frontend will allow properties defined not as constructor parameters to be used in delegation specifier - } - } - return propertyDescriptor; + return CodegenUtil.getDelegatePropertyIfAny(expression, descriptor, bindingContext); } private void lookupConstructorExpressionsInClosureIfPresent(final ConstructorContext constructorContext) { @@ -1737,24 +1710,16 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { } protected void generateDelegates(ClassDescriptor toClass, DelegationFieldsInfo.Field field) { - for (DeclarationDescriptor declaration : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) { - if (declaration instanceof CallableMemberDescriptor) { - CallableMemberDescriptor callableMemberDescriptor = (CallableMemberDescriptor) declaration; - if (callableMemberDescriptor.getKind() == CallableMemberDescriptor.Kind.DELEGATION) { - Set overriddenDescriptors = callableMemberDescriptor.getOverriddenDescriptors(); - for (CallableMemberDescriptor overriddenDescriptor : overriddenDescriptors) { - if (overriddenDescriptor.getContainingDeclaration() == toClass) { - if (declaration instanceof PropertyDescriptor) { - propertyCodegen - .genDelegate((PropertyDescriptor) declaration, (PropertyDescriptor) overriddenDescriptor, field.getStackValue()); - } - else if (declaration instanceof FunctionDescriptor) { - functionCodegen - .genDelegate((FunctionDescriptor) declaration, (FunctionDescriptor) overriddenDescriptor, field.getStackValue()); - } - } - } - } + for (Map.Entry entry : CodegenUtil.getDelegates(descriptor, toClass).entrySet()) { + CallableMemberDescriptor callableMemberDescriptor = entry.getKey(); + CallableMemberDescriptor overriddenDescriptor = entry.getValue(); + if (callableMemberDescriptor instanceof PropertyDescriptor) { + propertyCodegen + .genDelegate((PropertyDescriptor) callableMemberDescriptor, (PropertyDescriptor) overriddenDescriptor, field.getStackValue()); + } + else if (callableMemberDescriptor instanceof FunctionDescriptor) { + functionCodegen + .genDelegate((FunctionDescriptor) callableMemberDescriptor, (FunctionDescriptor) overriddenDescriptor, field.getStackValue()); } } } diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/DelegationTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DelegationTest.java new file mode 100644 index 00000000000..4b40d9af614 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DelegationTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2014 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.test.semantics; + +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +public class DelegationTest extends SingleFileTranslationTest { + + public DelegationTest() { + super("delegation/"); + } + + public void testDelegation2() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegation3() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegation4() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationGenericArg() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationMethodsWithArgs() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationByInh() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationChain() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationByExprWithArgs() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationByArg() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationByNewInstance() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationByFunExpr() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationByIfExpr() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationEvaluationOrder1() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationEvaluationOrder2() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationExtFun1() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationExtFun2() throws Exception { + checkFooBoxIsOk(); + } + + public void testDelegationExtProp() 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 c76614d4af6..636f54f5247 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 @@ -53,7 +53,7 @@ public final class Namer { private static final String SETTER_PREFIX = "set_"; private static final String GETTER_PREFIX = "get_"; private static final String BACKING_FIELD_PREFIX = "$"; - private static final String DELEGATE_POSTFIX = "$delegate"; + private static final String DELEGATE = "$delegate"; private static final String SUPER_METHOD_NAME = "baseInitializer"; @@ -131,9 +131,14 @@ public final class Namer { return new JsNameRef(getPrototypeName(), classOrTraitExpression); } + @NotNull + public static String getDelegatePrefix() { + return DELEGATE; + } + @NotNull public static String getDelegateName(@NotNull String propertyName) { - return propertyName + DELEGATE_POSTFIX; + return propertyName + DELEGATE; } @NotNull 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 b131baf59dd..79aac87c198 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 @@ -125,14 +125,16 @@ public final class ClassTranslator extends AbstractTranslator { declarationContext = fixContextForClassObjectAccessing(declarationContext); invocationArguments.add(getSuperclassReferences(declarationContext)); + DelegationTranslator delegationTranslator = new DelegationTranslator(classDeclaration, context()); if (!isTrait()) { - JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(); + JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(delegationTranslator); invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer); } translatePropertiesAsConstructorParameters(declarationContext, properties); DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties); bodyVisitor.traverseContainer(classDeclaration, declarationContext); + delegationTranslator.generateDelegated(properties); mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext); if (KotlinBuiltIns.getInstance().isData(descriptor)) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DelegationTranslator.kt b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DelegationTranslator.kt new file mode 100644 index 00000000000..8baddf70294 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DelegationTranslator.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2010-2014 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.declaration + + +import com.google.dart.compiler.backend.js.ast.* +import com.intellij.util.SmartList +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.AbstractTranslator +import org.jetbrains.k2js.translate.general.Translation +import org.jetbrains.k2js.translate.utils.BindingUtils +import org.jetbrains.k2js.translate.utils.JsAstUtils +import org.jetbrains.k2js.translate.utils.JsDescriptorUtils +import org.jetbrains.k2js.translate.utils.TranslationUtils.* + +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.backend.common.CodegenUtil +import java.util.HashMap +import com.google.dart.compiler.backend.js.ast.JsLiteral +import org.jetbrains.k2js.translate.declaration.propertyTranslator.addGetterAndSetter + +public class DelegationTranslator( + private val classDeclaration: JetClassOrObject, + context: TranslationContext +) : AbstractTranslator(context) { + + private val classDescriptor: ClassDescriptor = + BindingUtils.getClassDescriptor(context.bindingContext(), classDeclaration); + + private val delegationBySpecifiers = + classDeclaration.getDelegationSpecifiers().filterIsInstance(javaClass()); + + private class Field (val name: String, val generateField: Boolean) + private val fields = HashMap(); + + { + for (specifier in delegationBySpecifiers) { + val expression = specifier.getDelegateExpression() ?: + throw IllegalArgumentException("delegate expression should not be null: ${specifier.getText()}") + val descriptor = getSuperClass(specifier) + val propertyDescriptor = CodegenUtil.getDelegatePropertyIfAny(expression, classDescriptor, bindingContext()) + + if (CodegenUtil.isFinalPropertyWithBackingField(propertyDescriptor, bindingContext())) { + fields.put(specifier, Field(propertyDescriptor!!.getName().asString(), false)) + } + else { + val typeFqName = DescriptorUtils.getFqNameSafe(descriptor) + val delegateName = getMangledMemberNameForExplicitDelegation(Namer.getDelegatePrefix(), classDeclaration.getFqName(), typeFqName) + fields.put(specifier, Field(delegateName, true)) + } + } + } + + public fun addInitCode(statements: MutableList) { + for (specifier in delegationBySpecifiers) { + val field = fields.get(specifier)!! + if (field.generateField) { + val expression = specifier.getDelegateExpression()!! + val delegateInitExpr = Translation.translateAsExpression(expression, context()) + statements.add(JsAstUtils.defineSimpleProperty(field.name, delegateInitExpr)) + } + } + } + + public fun generateDelegated(properties: MutableList) { + for (specifier in delegationBySpecifiers) { + generateDelegates(getSuperClass(specifier), fields.get(specifier)!!, properties) + } + } + + private fun getSuperClass(specifier: JetDelegationSpecifier): ClassDescriptor = + CodegenUtil.getSuperClassByDelegationSpecifier(specifier, bindingContext()) + + private fun generateDelegates(toClass: ClassDescriptor, field: Field, properties: MutableList) { + for ((descriptor, overriddenDescriptor) in CodegenUtil.getDelegates(classDescriptor, toClass)) { + when (descriptor) { + is PropertyDescriptor -> + generateDelegateCallForPropertyMember(descriptor, field.name, properties) + is FunctionDescriptor -> + generateDelegateCallForFunctionMember(descriptor, overriddenDescriptor as FunctionDescriptor, field.name, properties) + else -> + throw IllegalArgumentException("Expected property or function ${descriptor}") + } + } + } + + private fun generateDelegateCallForPropertyMember( + descriptor: PropertyDescriptor, + delegateName: String, + properties: MutableList + ) { + val propertyName: String = descriptor.getName().asString() + + fun generateDelegateGetterFunction(getterDescriptor: PropertyGetterDescriptor): JsFunction { + val delegateRefName = context().getScopeForDescriptor(getterDescriptor).declareName(delegateName) + val delegateRef = JsNameRef(delegateRefName, JsLiteral.THIS) + + val returnExpression = if (JsDescriptorUtils.isExtension(descriptor)) { + val getterName = context().getNameForDescriptor(getterDescriptor) + val receiver = Namer.getReceiverParameterName() + JsInvocation(JsNameRef(getterName, delegateRef), JsNameRef(receiver)) + } + else { + JsNameRef(propertyName, delegateRef): JsExpression // TODO remove explicit type specification after resolving KT-5569 + } + + val jsFunction = simpleReturnFunction(context().getScopeForDescriptor(getterDescriptor.getContainingDeclaration()), returnExpression) + if (JsDescriptorUtils.isExtension(descriptor)) { + val receiverName = jsFunction.getScope().declareName(Namer.getReceiverParameterName()) + jsFunction.getParameters().add(JsParameter(receiverName)) + } + return jsFunction + } + + fun generateDelegateSetterFunction(setterDescriptor: PropertySetterDescriptor): JsFunction { + val jsFunction = JsFunction(context().getScopeForDescriptor(setterDescriptor.getContainingDeclaration())) + + assert(setterDescriptor.getValueParameters().size() == 1, "Setter must have 1 parameter") + val defaultParameter = JsParameter(jsFunction.getScope().declareTemporary()) + val defaultParameterRef = defaultParameter.getName().makeRef() + + val delegateRefName = context().getScopeForDescriptor(setterDescriptor).declareName(delegateName) + val delegateRef = JsNameRef(delegateRefName, JsLiteral.THIS) + + val setExpression = if (JsDescriptorUtils.isExtension(descriptor)) { + val setterName = context().getNameForDescriptor(setterDescriptor) + val setterNameRef = JsNameRef(setterName, delegateRef) + val extensionFunctionReceiverName = jsFunction.getScope().declareName(Namer.getReceiverParameterName()) + jsFunction.getParameters().add(JsParameter(extensionFunctionReceiverName)) + JsInvocation(setterNameRef, JsNameRef(extensionFunctionReceiverName), defaultParameterRef) + } + else { + val propertyNameRef = JsNameRef(propertyName, delegateRef) + JsAstUtils.assignment(propertyNameRef, defaultParameterRef) + } + + jsFunction.getParameters().add(defaultParameter) + jsFunction.setBody(JsBlock(setExpression.makeStmt())) + return jsFunction + } + + fun generateDelegateAccessor(accessorDescriptor: PropertyAccessorDescriptor, function: JsFunction): JsPropertyInitializer = + translateFunctionAsEcma5PropertyDescriptor(function, accessorDescriptor, context()) + + fun generateDelegateGetter(): JsPropertyInitializer { + val getterDescriptor = descriptor.getGetter() ?: throw IllegalStateException("Getter descriptor should not be null") + return generateDelegateAccessor(getterDescriptor, generateDelegateGetterFunction(getterDescriptor)) + } + + fun generateDelegateSetter(): JsPropertyInitializer { + val setterDescriptor = descriptor.getSetter() ?: throw IllegalStateException("Setter descriptor should not be null") + return generateDelegateAccessor(setterDescriptor, generateDelegateSetterFunction(setterDescriptor)) + } + + properties.addGetterAndSetter(descriptor, context(), ::generateDelegateGetter, ::generateDelegateSetter + ) + } + + + private fun generateDelegateCallForFunctionMember( + descriptor: FunctionDescriptor, + overriddenDescriptor: FunctionDescriptor, + delegateName: String, + properties: MutableList + ) { + val delegateMemberFunctionName = context().getNameForDescriptor(descriptor) + val overriddenMemberFunctionName = context().getNameForDescriptor(overriddenDescriptor) + val delegateRefName = context().getScopeForDescriptor(descriptor).declareName(delegateName) + val delegateRef = JsNameRef(delegateRefName, JsLiteral.THIS) + val overriddenMemberFunctionRef = JsNameRef(overriddenMemberFunctionName, delegateRef) + + val parameters = SmartList() + val args = SmartList() + val functionScope = context().getScopeForDescriptor(descriptor); + + if (JsDescriptorUtils.isExtension(descriptor)) { + val extensionFunctionReceiverName = functionScope.declareName(Namer.getReceiverParameterName()) + parameters.add(JsParameter(extensionFunctionReceiverName)) + args.add(JsNameRef(extensionFunctionReceiverName)) + } + + for (param in descriptor.getValueParameters()) { + val paramName = param.getName().asString() + val jsParamName = functionScope.declareName(paramName) + parameters.add(JsParameter(jsParamName)) + args.add(JsNameRef(jsParamName)) + } + + val functionObject = simpleReturnFunction(context().getScopeForDescriptor(descriptor), JsInvocation(overriddenMemberFunctionRef, args)) + functionObject.getParameters().addAll(parameters) + properties.add(JsPropertyInitializer(delegateMemberFunctionName.makeRef(), functionObject)) + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.kt b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.kt index 48d9472d3a5..1ed7e0ce2de 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.kt @@ -54,6 +54,27 @@ public fun translateAccessors( translateAccessors(descriptor, null, result, context) } +public fun MutableList.addGetterAndSetter( + descriptor: PropertyDescriptor, + context: TranslationContext, + generateGetter: () -> JsPropertyInitializer, + generateSetter: () -> JsPropertyInitializer +) { + val to: MutableList + if (!JsDescriptorUtils.isExtension(descriptor)) { + to = SmartList() + this.add(JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), JsObjectLiteral(to, true))) + } + else { + to = this + } + + to.add(generateGetter()) + if (descriptor.isVar()) { + to.add(generateSetter()) + } +} + private class PropertyTranslator( val descriptor: PropertyDescriptor, val declaration: JetProperty?, @@ -63,19 +84,7 @@ private class PropertyTranslator( private val propertyName: String = descriptor.getName().asString() fun translate(result: MutableList) { - val to: MutableList - if (!JsDescriptorUtils.isExtension(descriptor)) { - to = SmartList() - result.add(JsPropertyInitializer(context().getNameForDescriptor(descriptor).makeRef(), JsObjectLiteral(to, true))) - } - else { - to = result - } - - to.add(generateGetter()) - if (descriptor.isVar()) { - to.add(generateSetter()) - } + result.addGetterAndSetter(descriptor, context(), { generateGetter() }, { generateSetter() }) } private fun generateGetter(): JsPropertyInitializer = @@ -84,9 +93,9 @@ private class PropertyTranslator( private fun generateSetter(): JsPropertyInitializer = if (hasCustomSetter()) translateCustomAccessor(getCustomSetterDeclaration()) else generateDefaultSetter() - private fun hasCustomGetter() = declaration != null && declaration.getGetter() != null && getCustomGetterDeclaration().hasBody() + private fun hasCustomGetter() = declaration?.getGetter() != null && getCustomGetterDeclaration().hasBody() - private fun hasCustomSetter() = declaration != null && declaration.getSetter() != null && getCustomSetterDeclaration().hasBody() + private fun hasCustomSetter() = declaration?.getSetter() != null && getCustomSetterDeclaration().hasBody() private fun getCustomGetterDeclaration(): JetPropertyAccessor = declaration?.getGetter() ?: diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java index 3d09dffb5a0..585573deb81 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java @@ -33,6 +33,7 @@ import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.declaration.DelegationTranslator; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.reference.CallArgumentTranslator; @@ -62,7 +63,7 @@ public final class ClassInitializerTranslator extends AbstractTranslator { } @NotNull - public JsFunction generateInitializeMethod() { + public JsFunction generateInitializeMethod(DelegationTranslator delegationTranslator) { //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way ConstructorDescriptor primaryConstructor = getConstructor(bindingContext(), classDeclaration); JsFunction result = context().getFunctionObject(primaryConstructor); @@ -70,6 +71,7 @@ public final class ClassInitializerTranslator extends AbstractTranslator { // for properties declared as constructor parameters result.getParameters().addAll(translatePrimaryConstructorParameters()); mayBeAddCallToSuperMethod(result); + delegationTranslator.addInitCode(initializerStatements); new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context()); List statements = result.getBody().getStatements(); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java index e48d5e44bb4..cf31c83532b 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java @@ -27,6 +27,7 @@ import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.OverrideResolver; +import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.k2js.translate.context.TemporaryConstVariable; @@ -202,6 +203,12 @@ public final class TranslationUtils { return false; } + @NotNull + public static String getMangledMemberNameForExplicitDelegation(@NotNull String suggestedName, FqName classFqName, FqName typeFqName) { + String forCalculateId = classFqName.asString() + ":" + typeFqName.asString(); + return getStableMangledName(suggestedName, forCalculateId); + } + @NotNull private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) { int absHashCode = Math.abs(forCalculateId.hashCode()); diff --git a/js/js.translator/testData/delegation/cases/delegation2.kt b/js/js.translator/testData/delegation/cases/delegation2.kt new file mode 100644 index 00000000000..348b9f177c8 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegation2.kt @@ -0,0 +1,27 @@ +// This test was adapted from compiler/testData/codegen/box/classes +package foo + +trait Trait1 { + fun foo(): String +} + +trait Trait2 { + fun bar(): String +} + +class T1 : Trait1 { + override fun foo() = "aaa" +} + +class T2 : Trait2 { + override fun bar() = "bbb" +} + +class C(a: Trait1, b: Trait2) : Trait1 by a, Trait2 by b + +fun box(): String { + val c = C(T1(), T2()) + if (c.foo() != "aaa") return "fail" + if (c.bar() != "bbb") return "fail" + return "OK" +} diff --git a/js/js.translator/testData/delegation/cases/delegation3.kt b/js/js.translator/testData/delegation/cases/delegation3.kt new file mode 100644 index 00000000000..73646e4ca50 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegation3.kt @@ -0,0 +1,38 @@ +// This test was adapted from compiler/testData/codegen/box/classes +package foo + +trait One { + public open fun foo(): Int + public open fun faz(): Int = 10 +} +trait Two { + public open fun foo(): Int + public open fun quux(): Int = 100 +} + +class OneImpl : One { + public override fun foo() = 1 +} +class TwoImpl : Two { + public override fun foo() = 2 +} + +class Test2(a: One, b: Two) : Two by b, One by a { + public override fun foo() = 0 +} + +fun box(): String { + var t2 = Test2(OneImpl(), TwoImpl()) + if (t2.foo() != 0) + return "Fail #1" + if (t2.faz() != 10) + return "Fail #2" + if (t2.quux() != 100) + return "Fail #3" + if (t2 !is One) + return "Fail #4" + if (t2 !is Two) + return "Fail #5" + + return "OK" +} diff --git a/js/js.translator/testData/delegation/cases/delegation4.kt b/js/js.translator/testData/delegation/cases/delegation4.kt new file mode 100644 index 00000000000..3b619332d8b --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegation4.kt @@ -0,0 +1,31 @@ +// This test was adapted from compiler/testData/codegen/box/classes +package foo + +open trait First { + public open fun foo(): Int +} + +open trait Second : First { + public open fun bar(): Int +} + +class Impl : Second { + public override fun foo() = 1 + public override fun bar() = 2 +} + +class Test(s: Second) : Second by s {} + +fun box(): String { + var t = Test(Impl()) + if (t.foo() != 1) + return "Fail #1" + if (t.bar() != 2) + return "Fail #2" + if (t !is First) + return "Fail #3" + if (t !is Second) + return "Fail #4" + + return "OK" +} diff --git a/js/js.translator/testData/delegation/cases/delegationByArg.kt b/js/js.translator/testData/delegation/cases/delegationByArg.kt new file mode 100644 index 00000000000..0ab2e99976a --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationByArg.kt @@ -0,0 +1,27 @@ +package foo + +trait Base { + abstract fun foo(x: String): String + var prop: String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String): String = "Base: ${s}:${x}" + override var prop: String = "prop" +} + +class Derived(b: Base) : Base by b + + +fun box(): String { + var d = Derived(BaseImpl("test")) + assertEquals("Base: test:!!", d.foo("!!"), "delegation by argument, function member") + assertEquals("prop", d.prop, "delegation by argument, get property") + + d.prop = "new value" + assertEquals("new value", d.prop, "delegation by argument, set property") + + return "OK" +} + + diff --git a/js/js.translator/testData/delegation/cases/delegationByExprWithArgs.kt b/js/js.translator/testData/delegation/cases/delegationByExprWithArgs.kt new file mode 100644 index 00000000000..8e7d3adc315 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationByExprWithArgs.kt @@ -0,0 +1,18 @@ +package foo + +trait Base { + abstract fun foo(arg: String): String +} + +class BaseImpl(val s1: String, val s2: String) : Base { + override fun foo(arg: String): String = "BaseImpl:foo ${s1}:${s2}:${arg}" +} + +class Derived(s1: String, s2: String) : Base by BaseImpl(s1, s2) + +fun box(): String { + assertEquals("BaseImpl:foo arg1:arg2:!!", Derived("arg1", "arg2").foo("!!"), "delegation with two arguments") + + return "OK" +} + diff --git a/js/js.translator/testData/delegation/cases/delegationByFunExpr.kt b/js/js.translator/testData/delegation/cases/delegationByFunExpr.kt new file mode 100644 index 00000000000..0a2d87f62fb --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationByFunExpr.kt @@ -0,0 +1,20 @@ +package foo + +trait Base { + abstract fun foo(x: String): String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String): String = "Base: ${s}:${x}" +} + +fun newBase(s: String): Base = BaseImpl(s) + +class Derived() : Base by newBase("test") + + +fun box(): String { + assertEquals("Base: test:!!", Derived().foo("!!"), "delegation by function expression") + + return "OK" +} diff --git a/js/js.translator/testData/delegation/cases/delegationByIfExpr.kt b/js/js.translator/testData/delegation/cases/delegationByIfExpr.kt new file mode 100644 index 00000000000..4d3955d0704 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationByIfExpr.kt @@ -0,0 +1,22 @@ +package foo + +trait Base { + abstract fun foo(x: String): String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String): String = "Base: ${s}:${x}" +} + +var global = true + +class Derived() : Base by if (global) BaseImpl("then") else BaseImpl("else") + +fun box(): String { + assertEquals("Base: then:!!", Derived().foo("!!"), "delegation by if expression") + global = false + assertEquals("Base: else:!!", Derived().foo("!!"), "delegation by if expression") + + return "OK" +} + diff --git a/js/js.translator/testData/delegation/cases/delegationByInh.kt b/js/js.translator/testData/delegation/cases/delegationByInh.kt new file mode 100644 index 00000000000..2a05374ba42 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationByInh.kt @@ -0,0 +1,47 @@ +package foo + +trait Base { + abstract fun foo(s: String): String + var prop: String +} + +trait Base1 : Base { +} + +trait Base2 : Base1 { + override fun foo(s: String): String = "Base2:foo ${s}" +} + +class Base2Impl() : Base2 { + override var prop: String = "" + set(value) { + $prop = "prop:${value}" + } +} + +class Derived() : Base by Base2Impl() + +class Derived1() : Base1 by Base2Impl() + +class Derived2() : Base2 by Base2Impl() + +fun box(): String { + assertEquals("Base2:foo !!", Derived().foo("!!"), "delegation (Base)") + assertEquals("Base2:foo !!", Derived1().foo("!!"), "delegation (Base1)") + assertEquals("Base2:foo !!", Derived2().foo("!!"), "delegation (Base2)") + + var d = Derived() + d.prop = "A" + assertEquals("prop:A", d.prop, "delegation (Base) set property") + + var d1 = Derived1() + d1.prop = "B" + assertEquals("prop:B", d1.prop, "delegation (Base1) set property") + + var d2 = Derived1() + d2.prop = "C" + assertEquals("prop:C", d2.prop, "delegation (Base2) set property") + + return "OK" +} + diff --git a/js/js.translator/testData/delegation/cases/delegationByNewInstance.kt b/js/js.translator/testData/delegation/cases/delegationByNewInstance.kt new file mode 100644 index 00000000000..3dd4a2bc22b --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationByNewInstance.kt @@ -0,0 +1,17 @@ +package foo + +trait Base { + abstract fun foo(x: String): String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String): String = "Base: ${s}:${x}" +} + +class Derived() : Base by BaseImpl("test") + +fun box(): String { + assertEquals("Base: test:!!", Derived().foo("!!"), "delegation by new instance") + + return "OK" +} diff --git a/js/js.translator/testData/delegation/cases/delegationChain.kt b/js/js.translator/testData/delegation/cases/delegationChain.kt new file mode 100644 index 00000000000..7f53218117b --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationChain.kt @@ -0,0 +1,31 @@ +package foo + +trait Base { + abstract fun foo(x: String): String + var prop: String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String) = "BaseImpl.foo: ${s}:${x}" + override var prop: String = "init" + set(value) { + $prop = "prop:${value}" + } +} + +class Base2Impl(val s: String) : Base by BaseImpl("${s} by BaseImpl") + +class Derived(val s: String) : Base by Base2Impl("${s} by Base2Impl") + +fun box(): String { + assertEquals("BaseImpl.foo: Derived by Base2Impl by BaseImpl:!!", Derived("Derived").foo("!!")) + + var d = Derived("Derived") + assertEquals("init", d.prop) + + d.prop = "A" + assertEquals("prop:A", d.prop) + + return "OK" +} + diff --git a/js/js.translator/testData/delegation/cases/delegationEvaluationOrder1.kt b/js/js.translator/testData/delegation/cases/delegationEvaluationOrder1.kt new file mode 100644 index 00000000000..263fe58e750 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationEvaluationOrder1.kt @@ -0,0 +1,45 @@ +package foo + +trait Base { + abstract fun foo(x: String): String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String): String = "Base: ${s}:${x}" +} + +var global = "" + +open class DerivedBase() { + { + global += ":DerivedBase" + } +} + +fun newBase(): Base { + global += ":newBase" + return BaseImpl("test") +} + +class Derived() : DerivedBase(), Base by newBase() { + { + global += ":Derived" + } +} + +class Derived1() : Base by newBase(), DerivedBase() { + { + global += ":Derived" + } +} + +fun box(): String { + var d = Derived() + assertEquals(":DerivedBase:newBase:Derived", global, "evaluation order") + + global = "" + var d1 = Derived1() + assertEquals(":DerivedBase:newBase:Derived", global, "evaluation order") + + return "OK" +} diff --git a/js/js.translator/testData/delegation/cases/delegationEvaluationOrder2.kt b/js/js.translator/testData/delegation/cases/delegationEvaluationOrder2.kt new file mode 100644 index 00000000000..e122d87effa --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationEvaluationOrder2.kt @@ -0,0 +1,68 @@ +package foo + +trait Base { + abstract fun foo(x: String): String +} + +class BaseImpl(val s: String) : Base { + override fun foo(x: String): String = "Base: ${s}:${x}" +} + +trait Base2 { + abstract fun bar(x: String): String +} + +class Base2Impl(val s: String) : Base2 { + override fun bar(x: String): String = "Base2: ${s}:${x}" +} + +var global = "" + +open class DerivedBase() { + { + global += ":DerivedBase" + } +} + +fun newBase(): Base { + global += ":newBase" + return BaseImpl("test") +} + +fun newBase2(): Base2 { + global += ":newBase2" + return Base2Impl("test") +} + +class Derived() : DerivedBase(), Base by newBase(), Base2 by newBase2() { + { + global += ":Derived" + } +} + +class Derived1() : Base by newBase(), DerivedBase(), Base2 by newBase2() { + { + global += ":Derived" + } +} + +class Derived2() : Base by newBase(), Base2 by newBase2(), DerivedBase() { + { + global += ":Derived" + } +} + +fun box(): String { + var d = Derived() + assertEquals(":DerivedBase:newBase:newBase2:Derived", global, "evaluation order 1") + + global = "" + var d1 = Derived1() + assertEquals(":DerivedBase:newBase:newBase2:Derived", global, "evaluation order 2") + + global = "" + var d2 = Derived2() + assertEquals(":DerivedBase:newBase:newBase2:Derived", global, "evaluation order 3") + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/delegation/cases/delegationExtFun1.kt b/js/js.translator/testData/delegation/cases/delegationExtFun1.kt new file mode 100644 index 00000000000..a3f86436ad9 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationExtFun1.kt @@ -0,0 +1,19 @@ +package foo + +trait Base { + abstract fun Int.foo(): String +} + +open class BaseImpl(val s: String) : Base { + override fun Int.foo(): String = "Int.foo ${s}:${this}" +} + +class Derived() : Base by BaseImpl("test") { + fun bar(x: Int): String = x.foo() +} + +fun box(): String { + assertEquals("Int.foo test:5", Derived().bar(5)) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/delegation/cases/delegationExtFun2.kt b/js/js.translator/testData/delegation/cases/delegationExtFun2.kt new file mode 100644 index 00000000000..c1f3e041ff6 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationExtFun2.kt @@ -0,0 +1,19 @@ +package foo + +trait Base { + abstract fun String.foo(arg: String): String +} + +open class BaseImpl(val s: String) : Base { + override fun String.foo(arg: String): String = "Int.foo ${s}:${this}:${arg}" +} + +class Derived() : Base by BaseImpl("test") { + fun bar(x: String, arg: String): String = x.foo(arg) +} + +fun box(): String { + assertEquals("Int.foo test:A:B", Derived().bar("A", "B")) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/delegation/cases/delegationExtProp.kt b/js/js.translator/testData/delegation/cases/delegationExtProp.kt new file mode 100644 index 00000000000..c9d37263fcc --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationExtProp.kt @@ -0,0 +1,33 @@ +package foo + +trait Base { + var prop: String + var Int.foo: String +} + +open class BaseImpl(val s: String) : Base { + override var prop: String = "init" + override var Int.foo: String + get() = "get Int.foo:${s}:${this}" + set(value) { + prop = "set Int.foo:${s}:${this}:${value}" + } + +} + +class Derived() : Base by BaseImpl("test") { + fun getFooValue(x: Int): String = x.foo + fun setFooValue(x: Int, value: String) { + x.foo = value + } +} + +fun box(): String { + var d = Derived() + assertEquals("get Int.foo:test:5", d.getFooValue(5)) + + d.setFooValue(10, "A") + assertEquals("set Int.foo:test:10:A", d.prop) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/delegation/cases/delegationGenericArg.kt b/js/js.translator/testData/delegation/cases/delegationGenericArg.kt new file mode 100644 index 00000000000..04eb57f7ab8 --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationGenericArg.kt @@ -0,0 +1,15 @@ +// This test was adapted from compiler/testData/codegen/box/classes +package foo + +trait A { + fun foo(t: T): String +} + +class Derived(a: A) : A by a + +fun box(): String { + val o = object : A { + override fun foo(t: Int) = if (t == 42) "OK" else "Fail $t" + } + return Derived(o).foo(42) +} diff --git a/js/js.translator/testData/delegation/cases/delegationMethodsWithArgs.kt b/js/js.translator/testData/delegation/cases/delegationMethodsWithArgs.kt new file mode 100644 index 00000000000..dde24624dbc --- /dev/null +++ b/js/js.translator/testData/delegation/cases/delegationMethodsWithArgs.kt @@ -0,0 +1,35 @@ +// This test was adapted from compiler/testData/codegen/box/classes +package foo + +trait TextField { + fun getText(): String +} + +trait InputTextField : TextField { + fun setText(text: String) +} + +trait MooableTextField : InputTextField { + fun moo(a: Int, b: Int, c: Int): Int +} + +class SimpleTextField : MooableTextField { + private var text2 = "" + override fun getText() = text2 + override fun setText(text: String) { + this.text2 = text + } + override fun moo(a: Int, b: Int, c: Int) = a + b + c +} + +class TextFieldWrapper(textField: MooableTextField) : MooableTextField by textField + +fun box(): String { + val textField = TextFieldWrapper(SimpleTextField()) + textField.setText("hello world!") + + if (!textField.getText().equals("hello world!")) return "FAIL #!1" + if (textField.moo(1, 2, 3) != 6) return "FAIL #2" + + return "OK" +}