diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadChecker.kt index 705e2bac01c..e69a8738b54 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadChecker.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.calls.results.isSignatureNotLessSpecific import org.jetbrains.kotlin.resolve.calls.tower.getTypeAliasConstructors import org.jetbrains.kotlin.resolve.descriptorUtil.hasLowPriorityInOverloadResolution import org.jetbrains.kotlin.resolve.descriptorUtil.varargParameterPosition +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.KotlinType @@ -80,14 +81,10 @@ class OverloadChecker(val specificityComparator: TypeSpecificityComparator) { EXTENSION_PROPERTY } - private fun DeclarationDescriptor.isExtensionProperty() = - this is PropertyDescriptor && - extensionReceiverParameter != null - private fun getDeclarationCategory(a: DeclarationDescriptor): DeclarationCategory = when (a) { is PropertyDescriptor -> - if (a.isExtensionProperty()) + if (a.isExtensionProperty) DeclarationCategory.EXTENSION_PROPERTY else DeclarationCategory.TYPE_OR_VALUE diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt new file mode 100644 index 00000000000..0a4bcc34e5c --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt @@ -0,0 +1,5 @@ +package foo + +class A(val x: Int) + +fun A() {} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.txt b/compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.txt new file mode 100644 index 00000000000..eb8e0a2823e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.txt @@ -0,0 +1,13 @@ +package + +package foo { + public fun A(): kotlin.Unit + + public final class A { + public constructor A(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt index e7ad302cc3d..f7f5e18372f 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/conflictingNamesFromSuperclass.kt @@ -12,7 +12,7 @@ class C : A, B { override fun g() {} } -abstract class D : A, B +abstract class D : A, B open class E { open fun f() {} @@ -20,4 +20,4 @@ open class E { open fun g() {} } -class F : E(), A, B +class F : E(), A, B diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameMissingOnAccessors.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameMissingOnAccessors.kt index 6ea3518417e..509ed98ebe9 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameMissingOnAccessors.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameMissingOnAccessors.kt @@ -6,8 +6,8 @@ class A { set(value) {} var y: Int - @JsName("get_y") get() = 23 - set(value) {} + get() = 23 + @JsName("set_y") set(value) {} var z: Int @JsName("get_z") get() = 23 diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index 0ac7efe2fc5..abcc2e88868 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -295,6 +295,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithJsStdLib/name"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("classAndFunction.kt") + public void testClassAndFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt"); + doTest(fileName); + } + @TestMetadata("classLevelMethodAndProperty.kt") public void testClassLevelMethodAndProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classLevelMethodAndProperty.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index bd66e92b00d..b1c7262c66c 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -322,5 +322,16 @@ private fun ClassDescriptor.getAllSuperClassesTypesIncludeItself(): List()) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt index 9c8435c2308..8051bfc0a3e 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt @@ -189,16 +189,18 @@ class NameSuggestion { overriddenDescriptor is CallableDescriptor && stable -> { getStableMangledName(baseName, getArgumentTypesAsString(overriddenDescriptor)) } - shouldMangleUnstable(overriddenDescriptor) -> { - val ownerName = descriptor.containingDeclaration!!.fqNameUnsafe.asString() - getStableMangledName(baseName, ownerName + ":" + getArgumentTypesAsString(overriddenDescriptor as CallableDescriptor)) - } + shouldMangleUnstable(overriddenDescriptor) -> getPrivateMangledName(baseName, overriddenDescriptor as CallableDescriptor) else -> baseName } return Pair(finalName, stable) } + @JvmStatic fun getPrivateMangledName(baseName: String, descriptor: CallableDescriptor): String { + val ownerName = descriptor.containingDeclaration.fqNameUnsafe.asString() + return getStableMangledName(baseName, ownerName + ":" + getArgumentTypesAsString(descriptor)) + } + private fun getArgumentTypesAsString(descriptor: CallableDescriptor): String { val argTypes = StringBuilder() @@ -226,7 +228,7 @@ class NameSuggestion { return false } - fun getStableMangledName(suggestedName: String, forCalculateId: String): String { + @JvmStatic fun getStableMangledName(suggestedName: String, forCalculateId: String): String { val suffix = if (forCalculateId.isEmpty()) "" else "_${mangledId(forCalculateId)}\$" return suggestedName + suffix } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index bca04fd7413..8812d201acb 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt @@ -39,7 +39,7 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { put(ErrorsJs.NATIVE_INNER_CLASS_PROHIBITED, "Native inner classes are prohibited") put(ErrorsJs.JS_NAME_CLASH, "JavaScript name ({0}) generated for this declaration clashes with another declaration: {1}", Renderers.STRING, Renderers.COMPACT) - put(ErrorsJs.JS_NAME_CLASH_SYNTHETIC, "JavaScript name {0} is generated from two different inherited members: {1} and {2}", + put(ErrorsJs.JS_FAKE_NAME_CLASH, "JavaScript name {0} is generated for different inherited members: {1} and {2}", Renderers.STRING, Renderers.COMPACT, Renderers.COMPACT) put(ErrorsJs.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors") put(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them") diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index f49d3433872..da59dbf44a9 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -44,7 +44,7 @@ public interface ErrorsJs { DiagnosticFactory0 NATIVE_INNER_CLASS_PROHIBITED = DiagnosticFactory0.create(ERROR); DiagnosticFactory2 JS_NAME_CLASH = DiagnosticFactory2.create( ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); - DiagnosticFactory3 JS_NAME_CLASH_SYNTHETIC = + DiagnosticFactory3 JS_FAKE_NAME_CLASH = DiagnosticFactory3.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt index 55247255df0..8935cc595e6 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameChecker.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.js.PredefinedAnnotation import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.source.getPsi diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt index 1faffe235c1..2343a1f215d 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt @@ -21,11 +21,11 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.js.naming.NameSuggestion import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty import org.jetbrains.kotlin.resolve.scopes.MemberScope class JsNameClashChecker : SimpleDeclarationChecker { @@ -40,20 +40,24 @@ class JsNameClashChecker : SimpleDeclarationChecker { diagnosticHolder: DiagnosticSink, bindingContext: BindingContext ) { - if (declaration !is KtProperty || !descriptor.isExtension) { + // We don't generate JS properties for extension properties, we generate methods instead, so in this case + // check name clash only for accessors, not properties + if (!descriptor.isExtensionProperty) { checkDescriptor(descriptor, declaration, diagnosticHolder) } } private fun checkDescriptor(descriptor: DeclarationDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) { + if (descriptor is ConstructorDescriptor && descriptor.isPrimary) return + val suggested = nameSuggestion.suggest(descriptor)!! - if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && isOpaque(suggested.descriptor)) { + if (suggested.stable && suggested.scope is ClassOrPackageFragmentDescriptor && presentsInGeneratedCode(suggested.descriptor)) { val scope = getScope(suggested.scope) val name = suggested.names.last() val existing = scope[name] if (existing != null && existing != suggested.descriptor) { diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing)) - val existingDeclaration = existing.findPsi() ?: declaration + val existingDeclaration = existing.findPsi() if (clashedDescriptors.add(existing) && existingDeclaration is KtDeclaration && existingDeclaration != declaration) { diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor)) } @@ -71,14 +75,14 @@ class JsNameClashChecker : SimpleDeclarationChecker { val name = overrideFqn.names.last() val existing = scope[name] if (existing != null && existing != overrideFqn.descriptor) { - diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH_SYNTHETIC.on(declaration, name, override, existing)) + diagnosticHolder.report(ErrorsJs.JS_FAKE_NAME_CLASH.on(declaration, name, override, existing)) break } val clashedOverrides = clashedFakeOverrides[override] if (clashedOverrides != null) { val (firstExample, secondExample) = clashedOverrides - diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH_SYNTHETIC.on(declaration, name, firstExample, secondExample)) + diagnosticHolder.report(ErrorsJs.JS_FAKE_NAME_CLASH.on(declaration, name, firstExample, secondExample)) break } } @@ -115,33 +119,29 @@ class JsNameClashChecker : SimpleDeclarationChecker { } val fqn = nameSuggestion.suggest(descriptor) ?: return - if (fqn.stable && isOpaque(fqn.descriptor)) { + if (fqn.stable && presentsInGeneratedCode(fqn.descriptor)) { target[fqn.names.last()] = fqn.descriptor (fqn.descriptor as? CallableMemberDescriptor)?.let { checkOverrideClashes(it, target) } } } private fun checkOverrideClashes(descriptor: CallableMemberDescriptor, target: MutableMap) { - var overridden = descriptor.overriddenDescriptors - while (overridden.isNotEmpty()) { - for (overridenDescriptor in overridden) { - val overriddenFqn = nameSuggestion.suggest(overridenDescriptor)!! - if (overriddenFqn.stable) { - val existing = target[overriddenFqn.names.last()] - if (existing != null) { - if (existing != descriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { - clashedFakeOverrides[descriptor] = Pair(existing, overridenDescriptor) - } - } - else { - target[overriddenFqn.names.last()] = descriptor + for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDeclarations(descriptor)) { + val overriddenFqn = nameSuggestion.suggest(overriddenDescriptor)!! + if (overriddenFqn.stable) { + val existing = target[overriddenFqn.names.last()] + if (existing != null) { + if (existing != descriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { + clashedFakeOverrides[descriptor] = Pair(existing, overriddenDescriptor) } } + else { + target[overriddenFqn.names.last()] = descriptor + } } - overridden = overridden.flatMap { it.overriddenDescriptors } } } - private fun isOpaque(descriptor: DeclarationDescriptor) = + private fun presentsInGeneratedCode(descriptor: DeclarationDescriptor) = !AnnotationsUtils.isNativeObject(descriptor) && !AnnotationsUtils.isLibraryObject(descriptor) } \ No newline at end of file diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java b/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java index 780a758a296..80751c7718c 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/translate/utils/AnnotationsUtils.java @@ -102,7 +102,14 @@ public final class AnnotationsUtils { } public static boolean isNativeObject(@NotNull DeclarationDescriptor descriptor) { - return hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.NATIVE); + if (hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.NATIVE)) return true; + + if (descriptor instanceof PropertyAccessorDescriptor) { + PropertyAccessorDescriptor accessor = (PropertyAccessorDescriptor) descriptor; + return hasAnnotationOrInsideAnnotatedClass(accessor.getCorrespondingProperty(), PredefinedAnnotation.NATIVE); + } + + return false; } public static boolean isLibraryObject(@NotNull DeclarationDescriptor descriptor) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/MultipleModulesTranslationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/MultipleModulesTranslationTest.kt new file mode 100644 index 00000000000..b41fe1e2d8f --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/MultipleModulesTranslationTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2015 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.kotlin.js.test + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.js.config.EcmaVersion +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.facade.MainCallParameters +import org.jetbrains.kotlin.js.test.rhino.RhinoFunctionResultChecker +import org.jetbrains.kotlin.js.test.utils.JsTestUtils.getAllFilesInDir +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.serialization.js.ModuleKind +import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils +import java.io.File +import java.util.* + +abstract class MultipleModulesTranslationTest(main: String) : BasicTest(main) { + private val MAIN_MODULE_NAME: String = "main" + private val OLD_MODULE_SUFFIX = "-old" + private var dependencies: Map>? = null + protected var moduleKind = ModuleKind.PLAIN + + override fun checkFooBoxIsOkByPath(filePath: String) { + val dirName = getTestName(true) + dependencies = readModuleDependencies(filePath) + + // KT-7428: !! is necessary here + for ((moduleName, dependencies) in dependencies!!) { + translateModule(dirName, filePath, moduleName, dependencies) + } + + val filename = getInputFilePath(getModuleDirectoryName(dirName, MAIN_MODULE_NAME) + File.separator + MAIN_MODULE_NAME + ".kt") + val packageName = getPackageName(filename) + runMultiModuleTest(dirName, packageName, BasicTest.TEST_FUNCTION, "OK") + } + + private fun runMultiModuleTest(dirName: String, packageName: String, functionName: String, expectedResult: Any) { + val moduleDirectoryName = getModuleDirectoryName(dirName, MAIN_MODULE_NAME) + val checker = RhinoFunctionResultChecker(MAIN_MODULE_NAME, packageName, functionName, expectedResult) + runRhinoTests(moduleDirectoryName, BasicTest.DEFAULT_ECMA_VERSIONS, checker) + } + + private fun translateModule(dirName: String, pathToDir: String, moduleName: String, dependencies: List) { + val moduleDirectoryName = getModuleDirectoryName(dirName, moduleName) + val fullFilePaths = getAllFilesInDir(pathToDir + File.separator + moduleName) + + BasicTest.DEFAULT_ECMA_VERSIONS.forEach { version -> + val libraries = arrayListOf() + for (dependencyName in dependencies) { + val moduleDir = getModuleDirectoryName(dirName, dependencyName) + libraries.add(getMetaFileOutputPath(moduleDir, version)) + } + generateJavaScriptFiles(fullFilePaths, moduleDirectoryName, MainCallParameters.noCall(), version, + moduleName.removeSuffix(OLD_MODULE_SUFFIX), libraries) + } + } + + private fun getMetaFileOutputPath(moduleDirectoryName: String, version: EcmaVersion) = + KotlinJavascriptMetadataUtils.replaceSuffix(getOutputFilePath(moduleDirectoryName, version)) + + override fun setupConfig(configuration: CompilerConfiguration) { + val method = try { + javaClass.getMethod(name) + } + catch (e: NoSuchMethodException) { + return + } + + method.getAnnotation(WithModuleKind::class.java)?.let { moduleKind = it.value } + configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind) + } + + override fun shouldGenerateMetaInfo() = true + + override fun additionalJsFiles(ecmaVersion: EcmaVersion): List { + val result = mutableListOf(MODULE_EMULATION_FILE) + result += super.additionalJsFiles(ecmaVersion) + val dirName = getTestName(true) + assert(dependencies != null) { "dependencies should not be null" } + + for (moduleName in dependencies!!.keys.filter { !it.endsWith(OLD_MODULE_SUFFIX) }) { + if (moduleName != MAIN_MODULE_NAME && !moduleName.endsWith(OLD_MODULE_SUFFIX)) { + result.add(getOutputFilePath(getModuleDirectoryName(dirName, moduleName), ecmaVersion)) + } + } + + return result + } + + override fun translateFiles(jetFiles: MutableList, outputFile: File, mainCallParameters: MainCallParameters, + config: JsConfig) { + super.translateFiles(jetFiles, outputFile, mainCallParameters, config) + + if (config.moduleKind == ModuleKind.COMMON_JS) { + val content = FileUtil.loadFile(outputFile, true) + val wrappedContent = "__beginModule__();\n" + + "$content\n" + + "__endModule__(\"${StringUtil.escapeStringCharacters(config.moduleId)}\");" + FileUtil.writeToFile(outputFile, wrappedContent) + // TODO: it would be better to wrap output before JS file is actually written + } + } + + private fun readModuleDependencies(testDataDir: String): Map> { + val dependenciesTxt = upsearchFile(testDataDir, "dependencies.txt") + assert(dependenciesTxt.isFile) { "moduleDependencies should not be null" } + + val result = LinkedHashMap>() + for (line in dependenciesTxt.readLines()) { + val split = line.split("->") + val module = split[0] + val dependencies = if (split.size > 1) split[1] else "" + val dependencyList = dependencies.split(",").filterNot { it.isEmpty() } + + result[module] = dependencyList + } + + return result + } + + private fun upsearchFile(startingDir: String, name: String): File { + var dir: File? = File(startingDir) + var file = File(dir, name) + + while (dir != null && dir.isDirectory && !file.isFile) { + dir = dir.parentFile + file = File(dir, name) + } + + return file + } +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index 650ee05a4f8..99683a0e7f1 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -25,22 +25,26 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.ClassDescriptor; +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor; import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.idea.KotlinLanguage; +import org.jetbrains.kotlin.incremental.components.NoLookupLocation; import org.jetbrains.kotlin.js.naming.NameSuggestion; +import org.jetbrains.kotlin.js.naming.SuggestedName; import org.jetbrains.kotlin.js.resolve.JsPlatform; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.FqNameUnsafe; +import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.DescriptorUtils; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import static com.google.dart.compiler.backend.js.ast.JsScopesKt.JsObjectScope; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn; import static org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName; -import static org.jetbrains.kotlin.js.translate.utils.ManglingUtils.getStableMangledNameForDescriptor; /** * Encapsulates different types of constants and naming conventions. @@ -300,6 +304,17 @@ public final class Namer { modulesMap = kotlin("modules"); } + // TODO: get rid of this function + @NotNull + public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) { + Collection 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 JsExpression classCreationMethodReference() { return kotlin(className); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index e3b350d88b4..67cb3550be9 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -113,8 +113,6 @@ public final class StaticContext { @NotNull private final Map backingFieldNameCache = new HashMap(); - private final Map> persistentNames = new HashMap>(); - @NotNull private final Map fqnCache = new HashMap(); @@ -224,13 +222,8 @@ public final class StaticContext { SuggestedName suggested = nameSuggestion.suggest(descriptor); if (suggested == null) { ModuleDescriptor module = DescriptorUtils.getContainingModule(descriptor); - if (currentModule == module) { - return pureFqn(Namer.getRootPackageName(), null); - } - else { - JsExpression result = getModuleExpressionFor(module); - return result != null ? result : JsAstUtils.pureFqn(rootScope.declareName(Namer.getRootPackageName()), null); - } + JsExpression result = getModuleExpressionFor(module); + return result != null ? result : pureFqn(Namer.getRootPackageName(), null); } JsExpression expression; @@ -239,22 +232,20 @@ public final class StaticContext { expression = Namer.kotlinObject(); partNames = Collections.singletonList(standardClasses.getStandardObjectName(suggested.getDescriptor())); } - else if (isLibraryObject(suggested.getDescriptor())) { - expression = Namer.kotlinObject(); - partNames = getActualNameFromSuggested(suggested); - } - else if (isNative(suggested.getDescriptor()) && !isNativeObject(suggested.getScope())) { - expression = null; - partNames = getActualNameFromSuggested(suggested); - } else { - if (suggested.getDescriptor() instanceof CallableDescriptor && suggested.getScope() instanceof FunctionDescriptor) { + partNames = getActualNameFromSuggested(suggested); + if (isLibraryObject(suggested.getDescriptor())) { + expression = Namer.kotlinObject(); + } + // Don't generate qualifier for top-level native declarations + // Don't generate qualifier for local declarations + else if (isNativeObject(suggested.getDescriptor()) && !isNativeObject(suggested.getScope()) || + suggested.getDescriptor() instanceof CallableDescriptor && suggested.getScope() instanceof FunctionDescriptor) { expression = null; } else { expression = getQualifiedExpression(suggested.getScope()); } - partNames = getActualNameFromSuggested(suggested); } for (JsName partName : partNames) { expression = new JsNameRef(partName, expression); @@ -264,17 +255,6 @@ public final class StaticContext { return expression; } - private static boolean isNative(DeclarationDescriptor descriptor) { - if (isNativeObject(descriptor)) return true; - - if (descriptor instanceof PropertyAccessorDescriptor) { - PropertyAccessorDescriptor accessor = (PropertyAccessorDescriptor) descriptor; - return isNative(accessor.getCorrespondingProperty()); - } - - return false; - } - @NotNull public JsNameRef getQualifiedReference(@NotNull FqName packageFqName) { JsName packageName = getNameForPackage(packageFqName); @@ -300,11 +280,7 @@ public final class StaticContext { assert fqn.getNames().size() == 1 : "Private names must always consist of exactly one name"; JsScope scope = getScopeForDescriptor(fqn.getScope()); - - if (DynamicCallsKt.isDynamic(property)) { - scope = JsDynamicScope.INSTANCE; - } - String baseName = fqn.getNames().get(0) + "_0"; + String baseName = NameSuggestion.getPrivateMangledName(fqn.getNames().get(0), property) + "_0"; name = scope.declareFreshName(baseName); backingFieldNameCache.put(property, name); } @@ -322,18 +298,9 @@ public final class StaticContext { List names = new ArrayList(); if (suggested.getStable()) { - Map scopeNames = persistentNames.get(scope); - if (scopeNames == null) { - scopeNames = new HashMap(); - persistentNames.put(scope, scopeNames); - } + for (String namePart : suggested.getNames()) { - JsName name = scopeNames.get(namePart); - if (name == null) { - name = scope.declareName(namePart); - scopeNames.put(namePart, name); - } - names.add(name); + names.add(scope.declareName(namePart)); } } else { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt index c6f02351415..938b30cfcc9 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DelegationTranslator.kt @@ -63,7 +63,7 @@ class DelegationTranslator( else { val classFqName = DescriptorUtils.getFqName(classDescriptor) val typeFqName = DescriptorUtils.getFqName(descriptor) - val idForMangling = "${classFqName.asString()}:${typeFqName.asString()}" + val idForMangling = classFqName.asString() val suggestedName = NameSuggestion.getStableMangledName(Namer.getDelegatePrefix(), idForMangling) val delegateName = context.getScopeForDescriptor(classDescriptor).declareFreshName("${suggestedName}_0") fields[specifier] = Field(delegateName, true) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java index f7a53929f06..149bb0fed18 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java @@ -53,7 +53,7 @@ import java.util.Map; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES; import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern; import static org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic.CallParametersAwareFunctionIntrinsic; -import static org.jetbrains.kotlin.js.translate.utils.ManglingUtils.getStableMangledNameForDescriptor; +import static org.jetbrains.kotlin.js.translate.context.Namer.getStableMangledNameForDescriptor; public final class TopLevelFIF extends CompositeFIF { public static final DescriptorPredicate EQUALS_IN_ANY = pattern("kotlin", "Any", "equals"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/ManglingUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/ManglingUtils.java deleted file mode 100644 index 747a1085fba..00000000000 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/ManglingUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2010-2015 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.kotlin.js.translate.utils; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.descriptors.ClassDescriptor; -import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor; -import org.jetbrains.kotlin.incremental.components.NoLookupLocation; -import org.jetbrains.kotlin.js.naming.NameSuggestion; -import org.jetbrains.kotlin.js.naming.SuggestedName; -import org.jetbrains.kotlin.name.Name; - -import java.util.Collection; - -public class ManglingUtils { - private ManglingUtils() {} - - @NotNull - public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) { - Collection 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); - } -} diff --git a/js/js.translator/testData/box/expression/function/overloadClassConstructorByFactoryMethod.kt b/js/js.translator/testData/box/expression/function/overloadClassConstructorByFactoryMethod.kt index 6f7a42a6800..f5e08f09d73 100644 --- a/js/js.translator/testData/box/expression/function/overloadClassConstructorByFactoryMethod.kt +++ b/js/js.translator/testData/box/expression/function/overloadClassConstructorByFactoryMethod.kt @@ -1,15 +1,13 @@ // KT-2995 creating factory methods to simulate overloaded constructors don't work in JavaScript -// This test is incorrect, since both constructor and function must have the same name. package foo class Foo(val name: String) -//fun Foo() = Foo("") +fun Foo(x: Int) = Foo("<$x>") fun box(): String { - // TODO: Don't know another way to suppress the test - /*assertEquals("", Foo().name) - assertEquals("BarBaz", Foo("BarBaz").name)*/ + assertEquals("<123>", Foo(123).name) + assertEquals("BarBaz", Foo("BarBaz").name) return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/box/jsName/classes.kt b/js/js.translator/testData/box/jsName/classes.kt index 0ba91572660..f414347b75f 100644 --- a/js/js.translator/testData/box/jsName/classes.kt +++ b/js/js.translator/testData/box/jsName/classes.kt @@ -1,11 +1,11 @@ package foo @JsName("AA") object A { - @JsName("foo") fun foo() = "A.foo" + @JsName("foo") fun bar() = "A.foo" } @JsName("BB") class B { - @JsName("foo") fun foo() = "B.foo" + @JsName("foo") fun bar() = "B.foo" } fun testA() = js(""" diff --git a/js/js.translator/testData/expression/function/cases/localInInitBlock.kt b/js/js.translator/testData/expression/function/cases/localInInitBlock.kt new file mode 100644 index 00000000000..70d54a96992 --- /dev/null +++ b/js/js.translator/testData/expression/function/cases/localInInitBlock.kt @@ -0,0 +1,16 @@ +package foo + +class A { + val x: String + + constructor() { + } + + init { + val o = "O" + fun baz() = o + "K" + x = baz() + } +} + +fun box() = A().x \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/dependencies.txt b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/dependencies.txt new file mode 100644 index 00000000000..f1f63b4136c --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/dependencies.txt @@ -0,0 +1,3 @@ +lib1-> +lib2-> +main->lib1,lib2 \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/lib1/lib1.kt b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/lib1/lib1.kt new file mode 100644 index 00000000000..0f5a04047f3 --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/lib1/lib1.kt @@ -0,0 +1,7 @@ +package lib1 + +interface A { + private fun foo() = "A.foo" + + fun bar() = foo() +} \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/lib2/lib2.kt b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/lib2/lib2.kt new file mode 100644 index 00000000000..c6b1bc876dd --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/lib2/lib2.kt @@ -0,0 +1,7 @@ +package lib2 + +interface B { + private fun foo() = "B.foo" + + fun bar() = foo() +} \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/main/main.kt b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/main/main.kt new file mode 100644 index 00000000000..372c32cabd3 --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateInterfaceNameClash/main/main.kt @@ -0,0 +1,32 @@ +package main + +import lib1.A +import lib2.B + +class Derived1 : A, B { + override fun bar() = super.bar() +} + +class Derived2 : A, B { + override fun bar() = super.bar() +} + +// NOTE. This test is fragile, it may fail due to unexpected (and correct) changes in algorithm that assigns +// unique identifiers to non-public declarations. However, we don't see any way of doing such test so that +// it won't report false positives eventually. So be patient and just update this test whenever you changed +// algorithm of assigning unique identifiers. +// Please, check that A.foo and B.foo have different JS names. +private fun checkJsNames(o: dynamic): Boolean = "foo_2pru9n\$_0" in o && "foo_2psha1\$_0" in o + +fun box(): String { + val a = Derived1() + if (a.bar() != "A.foo") return "fail1: ${a.bar()}" + + val b = Derived2() + if (b.bar() != "B.foo") return "fail2: ${b.bar()}" + + if (!checkJsNames(a)) return "fail3" + if (!checkJsNames(b)) return "fail4" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateNameClash/dependencies.txt b/js/js.translator/testData/multiModule/cases/privateNameClash/dependencies.txt new file mode 100644 index 00000000000..2011488bdff --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateNameClash/dependencies.txt @@ -0,0 +1,3 @@ +lib-> +lib-old-> +main->lib-old \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateNameClash/lib-old/lib.kt b/js/js.translator/testData/multiModule/cases/privateNameClash/lib-old/lib.kt new file mode 100644 index 00000000000..54969532846 --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateNameClash/lib-old/lib.kt @@ -0,0 +1,7 @@ +package lib + +open class A { + fun foo() = 12 +} + +inline fun check() = true \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateNameClash/lib/lib.kt b/js/js.translator/testData/multiModule/cases/privateNameClash/lib/lib.kt new file mode 100644 index 00000000000..3e338dffd6b --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateNameClash/lib/lib.kt @@ -0,0 +1,7 @@ +package lib + +open class A { + private val x = 23 + + fun foo() = x +} \ No newline at end of file diff --git a/js/js.translator/testData/multiModule/cases/privateNameClash/main/main.kt b/js/js.translator/testData/multiModule/cases/privateNameClash/main/main.kt new file mode 100644 index 00000000000..32507f58bcc --- /dev/null +++ b/js/js.translator/testData/multiModule/cases/privateNameClash/main/main.kt @@ -0,0 +1,31 @@ +package main + +import lib.A +import lib.check + +class B : A() { + private var x = 42 + + fun bar() = x +} + +// NOTE. This test is fragile, it may fail due to unexpected (and correct) changes in algorithm that assigns +// unique identifiers to non-public declarations. However, we don't see any way of doing such test so that +// it won't report false positives eventually. So be patient and just update this test whenever you changed +// algorithm of assigning unique identifiers. +// Please, check that A.x and B.x have different JS names. +private fun checkJsNames(o: dynamic): Boolean = "x_i8qwny\$_0" in o && "x_dqqnpp\$_0" in o + +fun box(): String { + if (!check()) return "check failed: did not compile agains old library" + + val a = A() + if (a.foo() != 23) return "fail1: ${a.foo()}" + + val b = B() + if (b.foo() != 23) return "fail2: ${b.foo()}" + if (b.bar() != 42) return "fail3: ${b.bar()}" + if (!checkJsNames(b)) return "fail4" + + return "OK" +} \ No newline at end of file