[JS] JsExport diagnostics and legacy support

Account for JsExport in legacy backend namer. It means we
catch overloaded exported function conflicts for free!

Add error diagnostics:
* NESTED_JS_EXPORT (Fixes KT-36798)
* WRONG_EXPORTED_DECLARATION (Part of the fix for KT-37752)
* NON_EXPORTABLE_TYPE (Fixes KT-37771)
This commit is contained in:
Svyatoslav Kuzmich
2019-08-29 18:25:09 +03:00
parent 4076bf40a9
commit 6e3d3831c2
52 changed files with 972 additions and 72 deletions
@@ -167,6 +167,7 @@ class K2JSTranslator @JvmOverloads constructor(
reporter,
config,
analysisResult.bindingTrace,
bindingTrace.bindingContext,
translationResult
).process()
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
@@ -34,12 +34,12 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.TypeCheck;
import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.naming.NameSuggestion;
import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices;
import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.ArrayFIF;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils;
import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import java.util.Arrays;
@@ -57,8 +57,8 @@ public final class Namer {
public static final String KOTLIN_NAME = KotlinLanguage.NAME;
public static final String KOTLIN_LOWER_NAME = KOTLIN_NAME.toLowerCase();
public static final String EQUALS_METHOD_NAME = getStableMangledNameForDescriptor(JsPlatformAnalyzerServices.INSTANCE.getBuiltIns().getAny(), "equals");
public static final String COMPARE_TO_METHOD_NAME = getStableMangledNameForDescriptor(JsPlatformAnalyzerServices.INSTANCE.getBuiltIns().getComparable(), "compareTo");
public static final String EQUALS_METHOD_NAME = "equals";
public static final String COMPARE_TO_METHOD_NAME = "compareTo_11rb$";
public static final String LONG_FROM_NUMBER = "fromNumber";
public static final String LONG_TO_NUMBER = "toNumber";
public static final String LONG_FROM_INT = "fromInt";
@@ -134,7 +134,11 @@ public final class Namer {
public static final String SAM_FIELD_NAME = "function$";
@NotNull
public static String getFunctionTag(@NotNull CallableDescriptor functionDescriptor, @NotNull JsConfig config) {
public static String getFunctionTag(
@NotNull CallableDescriptor functionDescriptor,
@NotNull JsConfig config,
@NotNull BindingContext bindingContext
) {
String intrinsicTag = ArrayFIF.INSTANCE.getTag(functionDescriptor, config);
if (intrinsicTag != null) return intrinsicTag;
@@ -147,7 +151,7 @@ public final class Namer {
qualifier = fqNameParent.asString();
}
SuggestedName suggestedName = new NameSuggestion().suggest(functionDescriptor);
SuggestedName suggestedName = new NameSuggestion(bindingContext).suggest(functionDescriptor);
assert suggestedName != null : "Suggested name can be null only for module descriptors: " + functionDescriptor;
String mangledName = suggestedName.getNames().get(0);
return StringUtil.join(Arrays.asList(moduleName, qualifier, mangledName), ".");
@@ -225,18 +229,6 @@ public final class Namer {
callSetProperty = kotlin("callSetter");
}
// TODO: get rid of this function
@NotNull
private static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) {
Collection<? extends SimpleFunctionDescriptor> functions = descriptor.getDefaultType().getMemberScope().getContributedFunctions(
Name.identifier(functionName), NoLookupLocation.FROM_BACKEND
);
assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor;
SuggestedName suggested = new NameSuggestion().suggest(functions.iterator().next());
assert suggested != null : "Suggested name for class members is always non-null: " + functions.iterator().next();
return suggested.getNames().get(0);
}
@NotNull
public static JsNameRef kotlin(@NotNull JsName name) {
return pureFqn(name, kotlinObject());
@@ -111,7 +111,7 @@ public final class StaticContext {
private final JsImportedModule currentModuleAsImported;
@NotNull
private final NameSuggestion nameSuggestion = new NameSuggestion();
private final NameSuggestion nameSuggestion;
@NotNull
private final Map<DeclarationDescriptor, JsName> nameCache = new HashMap<>();
@@ -169,6 +169,7 @@ public final class StaticContext {
fragment = new JsProgramFragment(rootFunction.getScope(), packageFqn);
this.bindingTrace = bindingTrace;
this.nameSuggestion = new NameSuggestion(bindingTrace.getBindingContext());
this.namer = Namer.newInstance(program.getRootScope());
this.intrinsics = new Intrinsics();
this.rootScope = fragment.getScope();
@@ -801,7 +802,7 @@ public final class StaticContext {
public void addInlineCall(@NotNull CallableDescriptor descriptor) {
descriptor = (CallableDescriptor) JsDescriptorUtils.findRealInlineDeclaration(descriptor);
String tag = Namer.getFunctionTag(descriptor, config);
String tag = Namer.getFunctionTag(descriptor, config, getBindingContext());
JsExpression moduleExpression = exportModuleForInline(DescriptorUtils.getContainingModule(descriptor));
if (moduleExpression == null) {
moduleExpression = getModuleExpressionFor(descriptor);
@@ -179,13 +179,13 @@ public class TranslationContext {
@NotNull
public TranslationContext newFunctionBodyWithUsageTracker(@NotNull JsFunction fun, @NotNull MemberDescriptor descriptor) {
DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor);
UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, bindingContext());
return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker, descriptor);
}
@NotNull
public TranslationContext innerWithUsageTracker(@NotNull MemberDescriptor descriptor) {
UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor);
UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, bindingContext());
return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, descriptor);
}
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsScope
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda
import org.jetbrains.kotlin.js.naming.NameSuggestion
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.*
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
@@ -30,7 +31,8 @@ private val CAPTURED_RECEIVER_NAME_PREFIX : String = "this$"
class UsageTracker(
private val parent: UsageTracker?,
val containingDescriptor: MemberDescriptor
val containingDescriptor: MemberDescriptor,
val bindingContext: BindingContext
) {
private val captured = linkedMapOf<DeclarationDescriptor, JsName>()
@@ -175,7 +177,7 @@ class UsageTracker(
// Append 'closure$' prefix to avoid name clash between closure and member fields in case of local classes
else -> {
val mangled = NameSuggestion.sanitizeName(NameSuggestion().suggest(this)!!.names.last())
val mangled = NameSuggestion.sanitizeName(NameSuggestion(bindingContext).suggest(this)!!.names.last())
"closure\$$mangled"
}
}
@@ -30,7 +30,7 @@ class InlineMetadata(val tag: JsStringLiteral, val function: FunctionWithWrapper
companion object {
@JvmStatic
fun compose(function: JsFunction, descriptor: CallableDescriptor, context: TranslationContext): InlineMetadata {
val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, context.config))
val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, context.config, context.bindingContext()))
val inliningContext = context.inlineFunctionContext!!
val block = JsBlock(inliningContext.importBlock.statements + inliningContext.prototypeBlock.statements +
inliningContext.declarationsBlock.statements + JsReturn(function))
+3 -4
View File
@@ -1,16 +1,15 @@
// SKIP_MINIFICATION
// Exported declaration uses non-exportable return type: Char
// IGNORE_BACKEND: JS_IR
@JsName("foo")
@JsExport
fun foo(): Char = '1'
@JsExport
val p1: Char = '2'
@JsExport
var p2: Char = '3'
@JsExport
var p3: Char = '4'
get() = field + 1
set(value) {
@@ -1,7 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1276
// IGNORE_BACKEND: JS_IR
@JsExport
class A(val x: Char)
fun typeOf(x: dynamic): String = js("typeof x")
@@ -4,7 +4,6 @@ interface I {
val a: Char
}
@JsExport
object X : I {
override var a = '#'
}
@@ -1,7 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1289
// IGNORE_BACKEND: JS_IR
@JsExport
open class A {
val foo: Char
get() = 'X'
@@ -1,5 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1282
@JsExport
interface I {
fun ok(): String
}
@@ -12,7 +12,6 @@ class A {
}
}
@JsExport
val A.z: Int
@JsName("getZ_") get() = 42
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
@file:JsExport
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
@file:JsExport
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
// FILE: file1.kt
@@ -2,7 +2,6 @@ declare namespace JS_TESTS {
type Nullable<T> = T | null | undefined
namespace foo {
const _any: any;
const _unit: void;
function _nothing(): never
const _throwable: Error;
const _string: string;
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
@file:JsExport
@@ -8,8 +9,6 @@ package foo
val _any: Any = Any()
val _unit: Unit = Unit
fun _nothing(): Nothing { throw Throwable() }
val _throwable: Throwable = Throwable()
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
// FILE: file1.kt
package foo
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JS_IR
// CHECK_TYPESCRIPT_DECLARATIONS
// RUN_PLAIN_BOX_FUNCTION
// SKIP_MINIFICATION
// SKIP_NODE_JS
// TODO fix statics export in DCE-driven mode
// SKIP_DCE_DRIVEN