JS backend: introduce the stable mangling for functions which belong to the public API or not final.
This commit is contained in:
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user