JS backend: introduce the stable mangling for functions which belong to the public API or not final.

This commit is contained in:
Zalim Bashorov
2013-12-21 21:52:14 +04:00
parent 0af1ae360f
commit 0374ce96b7
5 changed files with 328 additions and 48 deletions
@@ -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();
}
}
@@ -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<FunctionDescriptor> 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<FunctionDescriptor>() {
@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<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() {
@@ -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<FunctionDescriptor> 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<FunctionDescriptor> functions = jetScope.getFunctions(descriptor.getName());
List<FunctionDescriptor> overloadedFunctions = ContainerUtil.filter(functions, new Condition<FunctionDescriptor>() {
@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<ValueParameterDescriptor, String>() {
@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<FunctionDescriptor> {
@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);
}
}
}
@@ -0,0 +1,149 @@
package foo
fun internal_foo(): Int = 1
native fun internal_foo(a: Array<Int>) = "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<Int>): 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<Int>): 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<Int>) = "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"
}
@@ -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"
}