From 0374ce96b7b9fecc71cb38173ced5d0603e5e797 Mon Sep 17 00:00:00 2001 From: Zalim Bashorov Date: Sat, 21 Dec 2013 21:52:14 +0400 Subject: [PATCH] JS backend: introduce the stable mangling for functions which belong to the public API or not final. --- .../k2js/test/semantics/FunctionTest.java | 9 ++ .../k2js/translate/context/StaticContext.java | 45 +----- .../translate/utils/TranslationUtils.java | 149 ++++++++++++++++-- .../expression/function/cases/mangling.kt | 149 ++++++++++++++++++ .../cases/overloadingWithInheritance.kt | 24 +++ 5 files changed, 328 insertions(+), 48 deletions(-) create mode 100644 js/js.translator/testFiles/expression/function/cases/mangling.kt create mode 100644 js/js.translator/testFiles/expression/function/cases/overloadingWithInheritance.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java index e89e8228992..9a2cf7dd37b 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java @@ -112,4 +112,13 @@ public class FunctionTest extends AbstractExpressionTest { public void testCallFunInInit() throws Exception { fooBoxTest(); } + + public void testMangling() throws Exception { + checkFooBoxIsOk(); + } + + public void testOverloadingWithInheritance() throws Exception { + checkFooBoxIsOk(); + } + } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java index ee6f94c4e44..9e24e6be6c3 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java @@ -35,9 +35,6 @@ import org.jetbrains.k2js.translate.intrinsic.Intrinsics; import org.jetbrains.k2js.translate.utils.AnnotationsUtils; import org.jetbrains.k2js.translate.utils.JsAstUtils; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; import java.util.Map; import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.getNameForAnnotatedObject; @@ -225,44 +222,14 @@ public final class StaticContext { @Nullable public JsName apply(@NotNull DeclarationDescriptor descriptor) { JsScope scope = getEnclosingScope(descriptor); - DeclarationDescriptor declaration = descriptor.getContainingDeclaration(); - if (!(descriptor instanceof FunctionDescriptor) || !(declaration instanceof ClassDescriptor)) { - return scope.declareFreshName(descriptor.getName().asString()); + + String suggestedName = descriptor.getName().asString(); + + if (descriptor instanceof FunctionDescriptor) { + suggestedName = getMangledName((FunctionDescriptor) descriptor); } - Collection functions = - ((ClassDescriptor) declaration).getDefaultType().getMemberScope().getFunctions(descriptor.getName()); - String name = descriptor.getName().asString(); - int counter = -1; - if (functions.size() > 1) { - // see testOverloadedFun - FunctionDescriptor[] sorted = functions.toArray(new FunctionDescriptor[functions.size()]); - Arrays.sort(sorted, new Comparator() { - @Override - public int compare(@NotNull FunctionDescriptor a, @NotNull FunctionDescriptor b) { - Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility()); - if (result == null) { - return 0; - } - else if (result == 0) { - // open fun > not open fun - int aWeight = a.getModality().isOverridable() ? 1 : 0; - int bWeight = b.getModality().isOverridable() ? 1 : 0; - return bWeight - aWeight; - } - - return result; - } - }); - for (FunctionDescriptor function : sorted) { - if (function == descriptor) { - break; - } - counter++; - } - } - - return scope.declareName(counter == -1 ? name : name + '_' + counter); + return scope.declareFreshName(suggestedName); } }; Rule constructorHasTheSameNameAsTheClass = new Rule() { 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 057208a32ec..2d8f9e8ce0a 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 @@ -17,21 +17,22 @@ package org.jetbrains.k2js.translate.utils; import com.google.dart.compiler.backend.js.ast.*; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; -import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor; -import org.jetbrains.jet.lang.descriptors.Visibilities; +import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.scopes.JetScope; +import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.k2js.translate.context.TemporaryConstVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.Translation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*; import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName; @@ -41,6 +42,8 @@ import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment; import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor; public final class TranslationUtils { + public static final Comparator OVERLOADED_FUNCTION_COMPARATOR = new OverloadedFunctionComparator(); + private TranslationUtils() { } @@ -133,8 +136,110 @@ public final class TranslationUtils { @NotNull public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) { - int absHashCode = Math.abs(getFqName(descriptor).asString().hashCode()); - return suggestedName + "_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$"; + return getStableMangledName(suggestedName, getFqName(descriptor).asString()); + } + + @NotNull + public static String getMangledName(@NotNull FunctionDescriptor descriptor) { + if (needsStableMangling(descriptor)) { + return getStableMangledName(descriptor); + } + + return getSimpleMangledName(descriptor); + } + + private static boolean needsStableMangling(FunctionDescriptor descriptor) { + if (descriptor.getVisibility() == Visibilities.PUBLIC || !descriptor.getOverriddenDescriptors().isEmpty()) { + return true; + } + + DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration(); + + if (containingDeclaration instanceof PackageFragmentDescriptor) { + return false; + } + + if (containingDeclaration instanceof MemberDescriptor) { + return ((MemberDescriptor) containingDeclaration).getModality().isOverridable(); + } + + return true; + } + + @NotNull + private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) { + int absHashCode = Math.abs(forCalculateId.hashCode()); + String suffix = absHashCode == 0 ? "" : ("_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$"); + return suggestedName + suffix; + } + + @NotNull + private static String getStableMangledName(@NotNull FunctionDescriptor descriptor) { + return getStableMangledName(descriptor.getName().asString(), getArgumentTypesAsString(descriptor)); + } + + @NotNull + private static String getSimpleMangledName(@NotNull FunctionDescriptor descriptor) { + DeclarationDescriptor declaration = descriptor.getContainingDeclaration(); + + JetScope jetScope = null; + if (declaration instanceof PackageFragmentDescriptor) { + jetScope = ((PackageFragmentDescriptor) declaration).getMemberScope(); + } + else if (declaration instanceof ClassDescriptor) { + jetScope = ((ClassDescriptor) declaration).getDefaultType().getMemberScope(); + } + + int counter = 0; + + if (jetScope != null) { + Collection functions = jetScope.getFunctions(descriptor.getName()); + List overloadedFunctions = ContainerUtil.filter(functions, new Condition() { + @Override + public boolean value(FunctionDescriptor descriptor) { + return !needsStableMangling(descriptor) && !AnnotationsUtils.isNativeObject(descriptor); + } + }); + + if (overloadedFunctions.size() > 1) { + Collections.sort(overloadedFunctions, OVERLOADED_FUNCTION_COMPARATOR); + counter = ContainerUtil.indexOfIdentity(overloadedFunctions, descriptor); + assert counter >= 0; + } + } + + String name = descriptor.getName().asString(); + return counter == 0 ? name : name + '_' + counter; + } + + private static String getArgumentTypesAsString(FunctionDescriptor descriptor) { + StringBuilder argTypes = new StringBuilder(); + + ReceiverParameterDescriptor receiverParameter = descriptor.getReceiverParameter(); + if (receiverParameter != null) { + argTypes.append(getJetTypeName(receiverParameter.getType())).append("."); + } + + argTypes.append(StringUtil.join(descriptor.getValueParameters(), new Function() { + @Override + public String fun(ValueParameterDescriptor descriptor) { + return getJetTypeName(descriptor.getType()); + } + }, ",")); + + return argTypes.toString(); + } + + @NotNull + private static String getJetTypeName(@NotNull JetType jetType) { + ClassifierDescriptor declaration = jetType.getConstructor().getDeclarationDescriptor(); + assert declaration != null; + + if (declaration instanceof TypeParameterDescriptor) { + return getJetTypeName(((TypeParameterDescriptor) declaration).getUpperBoundsAsType()); + } + + return getFqName(declaration).asString(); } @NotNull @@ -260,4 +365,30 @@ public final class TranslationUtils { return ensureNotNull; } + + private static class OverloadedFunctionComparator implements Comparator { + @Override + public int compare(@NotNull FunctionDescriptor a, @NotNull FunctionDescriptor b) { + // be visibility + // Actually "internal" > "private", but we want to have less number for "internal", so compare b with a instead of a with b. + Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility()); + if (result != null && result != 0) return result; + + // by arity + int aArity = arity(a); + int bArity = arity(b); + if (aArity != bArity) return aArity - bArity; + + // by stringify argument types + String aArguments = getArgumentTypesAsString(a); + String bArguments = getArgumentTypesAsString(b); + assert aArguments != bArguments; + + return aArguments.compareTo(bArguments); + } + + private static int arity(FunctionDescriptor descriptor) { + return descriptor.getValueParameters().size() + (descriptor.getReceiverParameter() == null ? 0 : 1); + } + } } diff --git a/js/js.translator/testFiles/expression/function/cases/mangling.kt b/js/js.translator/testFiles/expression/function/cases/mangling.kt new file mode 100644 index 00000000000..15daabf9166 --- /dev/null +++ b/js/js.translator/testFiles/expression/function/cases/mangling.kt @@ -0,0 +1,149 @@ +package foo + +fun internal_foo(): Int = 1 +native fun internal_foo(a: Array) = "should be ingnored" +fun internal_foo(i: Int): Int = 2 + +fun internal_boo(i: Int): Int = 2 +fun internal_boo(s: String): Int = 3 +fun internal_boo(): Int = 1 + +val internal_f = { internal_foo() + internal_foo(1) } +val internal_b = { internal_boo() + internal_boo(1) } + + +public fun public_foo(): Int = 1 +native public fun public_foo(a: Array): String = "should be ingnored" +public fun public_foo(i: Int): Int = 2 + +public fun public_boo(i: Int): Int = 2 +public fun public_boo(s: String): Int = 3 +public fun public_boo(): Int = 1 + +val public_f = { public_foo() + public_foo(1) } +val public_b = { public_boo() + public_boo(1) } + + +native private fun private_foo(a: Array): String = "should be ingnored" +private fun private_foo(): Int = 1 +private fun private_foo(i: Int): Int = 2 + +private fun private_boo(i: Int): Int = 2 +private fun private_boo(s: String): Int = 3 +private fun private_boo(): Int = 1 + +val private_f = { private_foo() + private_foo(1) } +val private_b = { private_boo() + private_boo(1) } + + +public fun mixed_foo(s: String): Int = 3 +fun mixed_foo(): Int = 1 +native fun mixed_foo(a: Array) = "should be ingnored" +private fun mixed_foo(s: String, i: Int): Int = 4 +fun mixed_foo(i: Int): Int = 2 + +fun mixed_boo(i: Int): Int = 2 +private fun mixed_boo(s: String, i: Int): Int = 4 +public fun mixed_boo(s: String): Int = 3 +fun mixed_boo(): Int = 1 + +val mixed_f = { mixed_foo() + mixed_foo(1) + mixed_foo("str") + mixed_foo("str", 44) } +val mixed_b = { mixed_boo() + mixed_boo(1) + mixed_boo("str") + mixed_boo("str", 44) } + + +class TestInternal { + fun foo(): Int = 1 + fun foo(i: Int): Int = 2 + + fun boo(i: Int): Int = 2 + fun boo(s: String): Int = 3 + fun boo(): Int = 1 +} + +val internal_in_class_f = { TestInternal().foo() + TestInternal().foo(1) } +val internal_in_class_b = { TestInternal().boo() + TestInternal().boo(1) } + + +class TestPublic { + public fun foo(): Int = 1 + public fun foo(i: Int): Int = 2 + + public fun boo(i: Int): Int = 2 + public fun boo(s: String): Int = 3 + public fun boo(): Int = 1 +} + +val public_in_class_f = { TestPublic().foo() + TestPublic().foo(1) } +val public_in_class_b = { TestPublic().boo() + TestPublic().boo(1) } + + +class TestPrivate { + private fun foo(): Int = 1 + private fun foo(i: Int): Int = 2 + + private fun boo(i: Int): Int = 2 + private fun boo(s: String): Int = 3 + private fun boo(): Int = 1 + + val f = { foo() + foo(1) } + val b = { boo() + boo(1) } +} + +val private_in_class_f = TestPrivate().f +val private_in_class_b = TestPrivate().b + + +class TestMixed { + public fun foo(s: String): Int = 3 + fun foo(): Int = 1 + private fun foo(s: String, i: Int): Int = 4 + fun foo(i: Int): Int = 2 + + fun boo(i: Int): Int = 2 + private fun boo(s: String, i: Int): Int = 4 + public fun boo(s: String): Int = 3 + fun boo(): Int = 1 + + val f = { foo() + foo(1) } + val b = { boo() + boo(1) } +} + +val mixed_in_class_f = TestMixed().f +val mixed_in_class_b = TestMixed().b + + +native +fun eval(expr: String): Any? = noImpl + +val PACKAGE = "Kotlin.modules.JS_TESTS.foo" + +fun funToString(name: String) = eval("$PACKAGE.$name.toString()") as String + +native +fun String.replace(regexp: RegExp, replacement: String): String = noImpl + +fun String.replaceAll(regexp: String, replacement: String): String = replace(RegExp(regexp, "g"), replacement) + +native +class RegExp(regexp: String, flags: String) + +fun test(prefix: String) { + val fs = funToString("${prefix}_f") + val bs = funToString("${prefix}_b").replaceAll("boo", "foo") + + if (fs != bs) throw Exception("FAILED on ${prefix}:\n fs = \"$fs\"\n bs = \"$bs\"") +} + +fun box(): String { + test("internal") + test("public") + test("private") + test("mixed") + + test("internal_in_class") + test("public_in_class") + test("private_in_class") + test("mixed_in_class") + + return "OK" +} diff --git a/js/js.translator/testFiles/expression/function/cases/overloadingWithInheritance.kt b/js/js.translator/testFiles/expression/function/cases/overloadingWithInheritance.kt new file mode 100644 index 00000000000..dc740bdd075 --- /dev/null +++ b/js/js.translator/testFiles/expression/function/cases/overloadingWithInheritance.kt @@ -0,0 +1,24 @@ +package foo + +trait A { + fun foo(i: Int) = "A" +} + +trait B { + fun foo(s: String) = "B" +} + +class C : A, B { + fun foo() = "C" +} + +fun assertEquals(expected: Any, actual: Any) { + if (expected != actual) throw Exception("expected = $expected\nactual = $actual") +} +fun box(): String { + assertEquals(C().foo(1), "A") + assertEquals(C().foo(""), "B") + assertEquals(C().foo(), "C") + + return "OK" +}