From 2ae46ceb4b3c30aad6771733e1960f42a11874d2 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 18 Jan 2017 19:48:58 +0300 Subject: [PATCH] JS: change how signature for mangling is generated, taking into account more information. See KT-15285 --- .../codegen/state/KotlinTypeMapper.java | 2 +- .../kotlin/codegen/state/typeMappingUtil.kt | 19 +- .../src/org/jetbrains/kotlin/types/Utils.kt | 16 ++ .../kotlin/js/naming/NameSuggestion.kt | 18 +- .../kotlin/js/naming/encodeSignature.kt | 153 ++++++++++++ .../kotlin/js/test/EncodeSignatureTest.kt | 233 ++++++++++++++++++ 6 files changed, 407 insertions(+), 34 deletions(-) create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/naming/encodeSignature.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/EncodeSignatureTest.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java index 341177745d3..6f4e8edad8d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -608,7 +608,7 @@ public class KotlinTypeMapper { mapType(argument.getType(), signatureVisitor, argumentMode.toGenericArgumentMode( - TypeMappingUtil.getEffectiveVariance(parameter.getVariance(), argument.getProjectionKind()))); + UtilsKt.getEffectiveVariance(parameter.getVariance(), argument.getProjectionKind()))); signatureVisitor.writeTypeArgumentEnd(); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt index 7df727e5523..86ca83c9a2c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.codegen.state import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES as BUILTIN_NAMES import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.load.kotlin.TypeMappingMode @@ -30,6 +29,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.getEffectiveVariance +import org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES as BUILTIN_NAMES fun KotlinType.isMostPreciseContravariantArgument(parameter: TypeParameterDescriptor): Boolean = // TODO: probably class upper bound should be used @@ -59,22 +60,6 @@ private fun KotlinType.canHaveSubtypesIgnoringNullability(): Boolean { return false } -fun getEffectiveVariance(parameterVariance: Variance, projectionKind: Variance): Variance { - if (parameterVariance === Variance.INVARIANT) { - return projectionKind - } - if (projectionKind === Variance.INVARIANT) { - return parameterVariance - } - if (parameterVariance === projectionKind) { - return parameterVariance - } - - // In = In<*> - // Out = Out<*> - return Variance.OUT_VARIANCE -} - val CallableDescriptor?.isMethodWithDeclarationSiteWildcards: Boolean get() { if (this !is CallableMemberDescriptor) return false diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/Utils.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/Utils.kt index e3b8b19293b..44cab976efd 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/Utils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/Utils.kt @@ -36,4 +36,20 @@ internal fun hackForTypeIntersector(types: Collection): KotlinType? ErrorTypesAreEqualToAnything.isSubtypeOf(candidate, it) } } +} + +fun getEffectiveVariance(parameterVariance: Variance, projectionKind: Variance): Variance { + if (parameterVariance === Variance.INVARIANT) { + return projectionKind + } + if (projectionKind === Variance.INVARIANT) { + return parameterVariance + } + if (parameterVariance === projectionKind) { + return parameterVariance + } + + // In = In<*> + // Out = Out<*> + return Variance.OUT_VARIANCE } \ No newline at end of file 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 316aab839f6..c2b653dcd5f 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 @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.js.naming import org.jetbrains.kotlin.builtins.ReflectionTypes import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor -import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.getNameForAnnotatedObject import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject import org.jetbrains.kotlin.resolve.DescriptorUtils @@ -267,7 +266,7 @@ class NameSuggestion { return regularAndUnstable() } - fun mangledAndStable() = NameAndStability(getStableMangledName(baseName, getArgumentTypesAsString(descriptor)), true) + fun mangledAndStable() = NameAndStability(getStableMangledName(baseName, encodeSignature(descriptor)), true) fun mangledPrivate() = NameAndStability(getPrivateMangledName(baseName, descriptor), false) val effectiveVisibility = descriptor.ownEffectiveVisibility @@ -315,20 +314,7 @@ class NameSuggestion { @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() - - val receiverParameter = descriptor.extensionReceiverParameter - if (receiverParameter != null) { - argTypes.append(receiverParameter.type.getJetTypeFqName(true)).append(".") - } - - argTypes.append(descriptor.valueParameters.joinToString(",") { it.type.getJetTypeFqName(true) }) - - return argTypes.toString() + return getStableMangledName(baseName, ownerName + ":" + encodeSignature(descriptor)) } @JvmStatic fun getStableMangledName(suggestedName: String, forCalculateId: String): String { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/encodeSignature.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/encodeSignature.kt new file mode 100644 index 00000000000..c5dd53559d9 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/encodeSignature.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2010-2017 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.naming + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeProjection +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.getEffectiveVariance + +fun encodeSignature(descriptor: CallableDescriptor): String { + val sig = StringBuilder() + + val typeParameterNames = nameTypeParameters(descriptor) + val currentParameters = descriptor.typeParameters.filter { !it.isCapturedFromOuterDeclaration }.toSet() + val usedTypeParameters = currentParameters.toMutableSet() + val typeParameterNamer = { typeParameter: TypeParameterDescriptor -> + usedTypeParameters += typeParameter.original + typeParameterNames[typeParameter.original]!! + } + + val receiverParameter = descriptor.extensionReceiverParameter + if (receiverParameter != null) { + sig.encodeForSignature(receiverParameter.type, typeParameterNamer).append('/') + } + + for (valueParameter in descriptor.valueParameters) { + if (valueParameter.index > 0) { + sig.append(",") + } + if (valueParameter.varargElementType != null) { + sig.append("*") + } + sig.encodeForSignature(valueParameter.type, typeParameterNamer) + } + + var first = true + for (typeParameter in typeParameterNames.keys.asSequence().filter { it in usedTypeParameters }) { + val upperBounds = typeParameter.upperBounds.filter { !KotlinBuiltIns.isNullableAny(it) } + if (upperBounds.isEmpty() && typeParameter !in currentParameters) continue + + sig.append(if (first) "|" else ",").append(typeParameterNames[typeParameter]) + first = false + if (upperBounds.isEmpty()) continue + + sig.append("<:") + for ((boundIndex, upperBound) in upperBounds.withIndex()) { + if (boundIndex > 0) { + sig.append("&") + } + sig.encodeForSignature(upperBound, typeParameterNamer) + } + } + + return sig.toString() +} + +private fun StringBuilder.encodeForSignature( + type: KotlinType, + typeParameterNamer: (TypeParameterDescriptor) -> String +): StringBuilder { + val declaration = type.constructor.declarationDescriptor!! + if (declaration is TypeParameterDescriptor) { + return append(typeParameterNamer(declaration)) + } + + append(DescriptorUtils.getFqName(declaration).asString()) + + if (type.arguments.isNotEmpty()) { + val parameters = declaration.typeConstructor.parameters + append("<") + for ((index, argument) in type.arguments.withIndex()) { + if (index > 0) { + append(",") + } + encodeForSignature(argument, parameters[index], typeParameterNamer) + } + append(">") + } + + if (type.isMarkedNullable) { + append("?") + } + + return this +} + +private fun StringBuilder.encodeForSignature( + projection: TypeProjection, + parameter: TypeParameterDescriptor, + typeParameterNamer: (TypeParameterDescriptor) -> String +): StringBuilder { + if (projection.isStarProjection) { + return append("*") + } + else { + when (getEffectiveVariance(parameter.variance, projection.projectionKind)) { + Variance.IN_VARIANCE -> append("-") + Variance.OUT_VARIANCE -> append("+") + Variance.INVARIANT -> {} + } + return encodeForSignature(projection.type, typeParameterNamer) + } +} + +private fun nameTypeParameters(descriptor: DeclarationDescriptor): Map { + val result = mutableMapOf() + for ((listIndex, list) in collectTypeParameters(descriptor).withIndex()) { + for ((indexInList, typeParameter) in list.withIndex()) { + result[typeParameter] = "$listIndex:$indexInList" + } + } + return result +} + +private fun collectTypeParameters(descriptor: DeclarationDescriptor): List> { + var currentDescriptor: DeclarationDescriptor? = descriptor + val result = mutableListOf>() + while (currentDescriptor != null) { + getOwnTypeParameters(currentDescriptor)?.let { result += it } + currentDescriptor = if (currentDescriptor is ConstructorDescriptor) { + currentDescriptor.constructedClass.containingDeclaration + } + else { + currentDescriptor.containingDeclaration + } + } + return result +} + +private fun getOwnTypeParameters(descriptor: DeclarationDescriptor): List? = + when (descriptor) { + is ClassDescriptor -> descriptor.declaredTypeParameters.filter { !it.isCapturedFromOuterDeclaration } + is PropertyAccessorDescriptor -> getOwnTypeParameters(descriptor.correspondingProperty) + is CallableDescriptor -> descriptor.typeParameters.filter { !it.isCapturedFromOuterDeclaration } + else -> null + } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/EncodeSignatureTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/EncodeSignatureTest.kt new file mode 100644 index 00000000000..356a299d05e --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/EncodeSignatureTest.kt @@ -0,0 +1,233 @@ +/* + * Copyright 2010-2017 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.mock.MockVirtualFileSystem +import com.intellij.openapi.Disposable +import com.intellij.psi.PsiManager +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.LibrarySourcesConfig +import org.jetbrains.kotlin.js.naming.encodeSignature +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.junit.Assert +import org.junit.Test + +class EncodeSignatureTest { + @Test + fun function() { + assertSignature("", "fun test() {}") + assertSignature("kotlin.Int", "fun test(x: Int) {}") + assertSignature("kotlin.Int,kotlin.Long", "fun test(x: Int, y: Long) {}") + } + + @Test + fun extensionFunction() { + assertSignature("kotlin.Int/", "fun Int.test() {}") + assertSignature("kotlin.Int/kotlin.Long", "fun Int.test(x: Long) {}") + } + + @Test + fun property() { + assertSignature("", "val test: Int = 23") + assertSignature("", "class A(val test: Int)") + } + + @Test + fun extensionProperty() { + assertSignature("kotlin.String/", "val String.test: Int = 23") + assertSignature("kotlin.String/", "val String.test: Int = 23", "") + assertSignature("kotlin.String/kotlin.Int", "var String.test: Int = 23", "") + + assertSignature("kotlin.String/", "class A { val String.test: Int get() = 23 }") + assertSignature("kotlin.String/", "class A { val String.test: Int get() = 23 }", "") + assertSignature("kotlin.String/kotlin.Int", "class A { var String.test: Int get() = 23; set(_) {} }", "") + } + + @Test + fun genericType() { + assertSignature("kotlin.Array", "fun test(x: Array) {}") + } + + @Test + fun varargParam() { + assertSignature("*kotlin.Array<+kotlin.String>", "fun test(vararg x: String) {}") + } + + @Test + fun nullableType() { + assertSignature("kotlin.String?", "fun test(x: String?) {}") + } + + @Test + fun ownTypeParameters() { + assertSignature("0:0,0:1|0:0,0:1", "fun test(x: S, y: T) {}") + assertSignature("0:1,0:0|0:0,0:1", "fun test(x: T, y: S) {}") + } + + @Test + fun enclosingTypeParameters() { + assertSignature("2:0,1:0,0:0|0:0", """ + class A { + inner class B { + fun test(x: S, y: T, z: U) {} + } + } + """) + } + + @Test + fun variance() { + assertSignature("A,A<-kotlin.Long>,A<+kotlin.String>", """ + class A + fun test(x: A, y: A, z: A) + """) + + assertSignature("A<-kotlin.Int>,A<-kotlin.Long>,A<+kotlin.String>", """ + class A + fun test(x: A, y: A, z: A) + """) + + assertSignature("A<+kotlin.Int>,A<+kotlin.Long>,A<+kotlin.String>", """ + class A + fun test(x: A, y: A, z: A) + """) + } + + @Test + fun starProjection() { + assertSignature("A<*>", """ + class A + fun test(x: A<*>) + """) + } + + @Test + fun typeConstraints() { + assertSignature("0:0|0:0<:A", """ + class A + fun > test(x: T) + """) + + assertSignature("0:0|0:0<:A&A", """ + class A + fun test(x: T) where T : A, T : A + """) + + assertSignature("1:0,0:0|0:0<:1:0", """ + class A { + fun test(x: T, x: S) where S : T + } + """) + } + + @Test + fun genericExtensionFunctionToInner() { + assertSignature("A.C/1:0", """ + class A { + inner class B { + fun C.test(x: S) {} + } + inner class C + } + fun > test(x: T) + """) + } + + @Test + fun typeAliases() { + assertSignature("C", """ + class C + typealias A = C + fun test(x: A) + """) + } + + @Test + fun typeParameterUsageInConstraints() { + assertSignature("0:0|0:0<:1:0,1:0<:I", """ + interface I + class A { + fun test(x: S) + } + """) + } + + @Test + fun recursiveType() { + assertSignature("1:0|1:0<:I<1:0>", """ + interface I> { + fun test(x: T) + } + """) + } + + private fun assertSignature(expectedEncoding: String, codeSnippet: String, name: String = "test") { + val environment = createEnvironment() + val project = environment.project + + val fs = MockVirtualFileSystem() + val file = fs.file("sample.kt", codeSnippet).findFileByPath("/sample.kt") + val psiManager = PsiManager.getInstance(project) + val psiFile = psiManager.findFile(file) as KtFile + + val configuration = environment.configuration.copy() + configuration.put(JSConfigurationKeys.LIBRARY_FILES, LibrarySourcesConfig.JS_STDLIB) + configuration.put(CommonConfigurationKeys.MODULE_NAME, "sample") + + val analysisResult = TopDownAnalyzerFacadeForJS.analyzeFiles(listOf(psiFile), LibrarySourcesConfig(project, configuration)) + val module = analysisResult.moduleDescriptor + val rootPackage = module.getPackage(FqName.ROOT) + + val testDescriptor = findTestCallable(rootPackage, name) ?: + error("Descriptor named `$name` was not found in provided snippet: $codeSnippet") + val actualEncoding = encodeSignature(testDescriptor) + Assert.assertEquals(expectedEncoding, actualEncoding) + } + + private fun findTestCallable(scope: MemberScope, name: String): CallableMemberDescriptor? { + return scope.getContributedDescriptors().asSequence().mapNotNull { findTestCallable(it, name) }.firstOrNull() + } + + private fun findTestCallable(descriptor: DeclarationDescriptor, name: String): CallableMemberDescriptor? { + return when (descriptor) { + is PackageViewDescriptor -> findTestCallable(descriptor.memberScope, name) + is ClassDescriptor -> findTestCallable(descriptor.unsubstitutedMemberScope, name) + is PropertyDescriptor -> { + if (descriptor.name.asString() == name) { + descriptor + } + else { + descriptor.accessors.asSequence().mapNotNull { findTestCallable(it, name) }.firstOrNull() + } + } + is CallableMemberDescriptor -> if (descriptor.name.asString() == name) descriptor else null + else -> null + } + } + + fun createEnvironment(): KotlinCoreEnvironment { + return KotlinCoreEnvironment.createForTests(Disposable { }, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES) + } +} \ No newline at end of file