JS: change how signature for mangling is generated, taking into account more information. See KT-15285

This commit is contained in:
Alexey Andreev
2017-01-18 19:48:58 +03:00
parent 119bf52adf
commit 2ae46ceb4b
6 changed files with 407 additions and 34 deletions
@@ -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();
}
@@ -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<out X> = In<*>
// Out<in X> = Out<*>
return Variance.OUT_VARIANCE
}
val CallableDescriptor?.isMethodWithDeclarationSiteWildcards: Boolean
get() {
if (this !is CallableMemberDescriptor) return false
@@ -36,4 +36,20 @@ internal fun hackForTypeIntersector(types: Collection<KotlinType>): 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<out X> = In<*>
// Out<in X> = Out<*>
return Variance.OUT_VARIANCE
}
@@ -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 {
@@ -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<TypeParameterDescriptor, String> {
val result = mutableMapOf<TypeParameterDescriptor, String>()
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<List<TypeParameterDescriptor>> {
var currentDescriptor: DeclarationDescriptor? = descriptor
val result = mutableListOf<List<TypeParameterDescriptor>>()
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<TypeParameterDescriptor>? =
when (descriptor) {
is ClassDescriptor -> descriptor.declaredTypeParameters.filter { !it.isCapturedFromOuterDeclaration }
is PropertyAccessorDescriptor -> getOwnTypeParameters(descriptor.correspondingProperty)
is CallableDescriptor -> descriptor.typeParameters.filter { !it.isCapturedFromOuterDeclaration }
else -> null
}
@@ -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", "<get-test>")
assertSignature("kotlin.String/kotlin.Int", "var String.test: Int = 23", "<set-test>")
assertSignature("kotlin.String/", "class A { val String.test: Int get() = 23 }")
assertSignature("kotlin.String/", "class A { val String.test: Int get() = 23 }", "<get-test>")
assertSignature("kotlin.String/kotlin.Int", "class A { var String.test: Int get() = 23; set(_) {} }", "<set-test>")
}
@Test
fun genericType() {
assertSignature("kotlin.Array<kotlin.String>", "fun test(x: Array<String>) {}")
}
@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 <S, T> test(x: S, y: T) {}")
assertSignature("0:1,0:0|0:0,0:1", "fun <S, T> test(x: T, y: S) {}")
}
@Test
fun enclosingTypeParameters() {
assertSignature("2:0,1:0,0:0|0:0", """
class A<S> {
inner class B<T> {
fun <U> test(x: S, y: T, z: U) {}
}
}
""")
}
@Test
fun variance() {
assertSignature("A<kotlin.Int>,A<-kotlin.Long>,A<+kotlin.String>", """
class A<T>
fun test(x: A<Int>, y: A<in Long>, z: A<out String>)
""")
assertSignature("A<-kotlin.Int>,A<-kotlin.Long>,A<+kotlin.String>", """
class A<in T>
fun test(x: A<Int>, y: A<in Long>, z: A<out String>)
""")
assertSignature("A<+kotlin.Int>,A<+kotlin.Long>,A<+kotlin.String>", """
class A<out T>
fun test(x: A<Int>, y: A<in Long>, z: A<out String>)
""")
}
@Test
fun starProjection() {
assertSignature("A<*>", """
class A<T>
fun test(x: A<*>)
""")
}
@Test
fun typeConstraints() {
assertSignature("0:0|0:0<:A<kotlin.Int>", """
class A<T>
fun <T : A<Int>> test(x: T)
""")
assertSignature("0:0|0:0<:A<kotlin.Int>&A<kotlin.String>", """
class A<T>
fun <T> test(x: T) where T : A<Int>, T : A<String>
""")
assertSignature("1:0,0:0|0:0<:1:0", """
class A<T> {
fun <S> test(x: T, x: S) where S : T
}
""")
}
@Test
fun genericExtensionFunctionToInner() {
assertSignature("A.C<kotlin.String,2:0>/1:0", """
class A<T> {
inner class B<S> {
fun C<String>.test(x: S) {}
}
inner class C<U>
}
fun <T : A<Int>> test(x: T)
""")
}
@Test
fun typeAliases() {
assertSignature("C<kotlin.String>", """
class C<T>
typealias A = C<String>
fun test(x: A)
""")
}
@Test
fun typeParameterUsageInConstraints() {
assertSignature("0:0|0:0<:1:0,1:0<:I", """
interface I
class A<T : I> {
fun <S : T> test(x: S)
}
""")
}
@Test
fun recursiveType() {
assertSignature("1:0|1:0<:I<1:0>", """
interface I<T : I<T>> {
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)
}
}