From ed55923bb0ee2c15ee763f5ebe9f95006aa4a736 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 24 May 2016 15:49:34 +0300 Subject: [PATCH] KT-2752: a new approach to compose fully-qualified names that does not depend on code generation and can be reused in JS front-end --- .../js/descriptorUtils/descriptorUtils.kt | 11 + .../kotlin/js/naming/FQNGenerator.kt | 199 ++++++++++++++++++ .../org/jetbrains/kotlin/js/naming/FQNPart.kt | 21 ++ .../jetbrains/kotlin/js/naming/FQNPartType.kt | 23 ++ .../kotlin/js/naming/FQNPartipcipant.kt | 23 ++ 5 files changed, 277 insertions(+) create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNGenerator.kt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPart.kt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartType.kt create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartipcipant.kt diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/descriptorUtils/descriptorUtils.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/descriptorUtils/descriptorUtils.kt index 0011f6e3c7c..2616e7a3298 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/descriptorUtils/descriptorUtils.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/descriptorUtils/descriptorUtils.kt @@ -18,12 +18,15 @@ package org.jetbrains.kotlin.js.descriptorUtils import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.utils.addToStdlib.check val KotlinType.nameIfStandardType: Name? @@ -56,3 +59,11 @@ fun KotlinType.getJetTypeFqName(printTypeArguments: Boolean): String { } fun ClassDescriptor.hasPrimaryConstructor(): Boolean = unsubstitutedPrimaryConstructor != null + +fun FunctionDescriptor.isEnumValueOfMethod(): Boolean { + val methodTypeParameters = valueParameters + val nullableString = TypeUtils.makeNullable(builtIns.stringType) + return DescriptorUtils.ENUM_VALUE_OF == name + && methodTypeParameters.size == 1 + && KotlinTypeChecker.DEFAULT.isSubtypeOf(methodTypeParameters[0].type, nullableString) +} diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNGenerator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNGenerator.kt new file mode 100644 index 00000000000..153b34fd1a6 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNGenerator.kt @@ -0,0 +1,199 @@ +/* + * Copyright 2010-2016 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.descriptors.* +import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName +import org.jetbrains.kotlin.js.descriptorUtils.isEnumValueOfMethod +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isLibraryObject +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject + +class FQNGenerator(private val participants: List = listOf()) { + private val cache = mutableMapOf>() + + fun generate(descriptor: DeclarationDescriptor) = cache.getOrPut(descriptor) { generateCacheMiss(descriptor) } + + private fun generateCacheMiss(descriptor: DeclarationDescriptor): List { + for (participant in participants) { + val result = participant.participate(descriptor, this) + if (result != null) { + return result + } + } + + when (descriptor) { + is ModuleDescriptor -> return listOf(FQNPart(descriptor.name.asString(), FQNPartType.MODULE, descriptor)) + is PackageFragmentDescriptor -> { + val result = generate(descriptor.containingDeclaration).toMutableList() + if (!descriptor.name.isSpecial) { + result += descriptor.fqName.pathSegments().map { FQNPart(it.asString(), FQNPartType.PUBLIC, descriptor) } + } + return result + } + is ConstructorDescriptor -> if (descriptor.isPrimary) return generate(descriptor.containingDeclaration) + is CallableMemberDescriptor -> + if (DescriptorUtils.isDescriptorWithLocalVisibility(descriptor)) { + val name = getMangledName(getSuggestedName(descriptor), descriptor) + return listOf(FQNPart(name, FQNPartType.PRIVATE, descriptor)) + } + } + + val (localName, shared, parent) = getLocalName(descriptor) + val localPart = FQNPart(localName, if (shared) FQNPartType.PUBLIC else FQNPartType.PRIVATE, descriptor) + + val qualifier = if (parent != null) generate(parent) else listOf() + return qualifier + localPart + } + + private fun getLocalName(descriptor: DeclarationDescriptor): LocalName { + val parts = mutableListOf() + var current: DeclarationDescriptor? = descriptor + do { + current!! + parts += getSuggestedName(current) + var last = current + current = current.containingDeclaration + if (last is ConstructorDescriptor && !last.isPrimary) { + last = current + parts[parts.lastIndex] = getSuggestedName(current!!) + "_init" + current = current.containingDeclaration + } + } while (current != null && DescriptorUtils.isDescriptorWithLocalVisibility(last) && current !is ClassDescriptor) + + parts.reverse() + return LocalName(getMangledName(parts.joinToString("$"), descriptor), needsStableMangling(descriptor), current) + } + + private data class LocalName(val id: String, val shared: Boolean, val parent: DeclarationDescriptor?) + + private fun getSuggestedName(descriptor: DeclarationDescriptor): String { + val name = descriptor.name + return if (name.isSpecial) { + when (descriptor) { + is PropertyGetterDescriptor -> "get_" + getSuggestedName(descriptor.correspondingProperty) + is PropertySetterDescriptor -> "set_" + getSuggestedName(descriptor.correspondingProperty) + else -> "f" + } + } + else { + name.asString() + } + } + + private fun getMangledName(baseName: String, descriptor: DeclarationDescriptor): String { + if (needsStableMangling(descriptor)) { + return if (descriptor is CallableMemberDescriptor) { + getStableMangledName(baseName, getArgumentTypesAsString(descriptor)) + } + else { + baseName + } + } + + return baseName + } + + 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() + } + + private fun getStableMangledName(suggestedName: String, forCalculateId: String): String { + val absHashCode = Math.abs(forCalculateId.hashCode()) + val suffix = if (absHashCode == 0) "" else "_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$" + return suggestedName + suffix + } + + private fun needsStableMangling(descriptor: DeclarationDescriptor): Boolean { + if (descriptor is ClassOrPackageFragmentDescriptor) return true + if (descriptor !is CallableMemberDescriptor) return false + + // Use stable mangling for overrides because we use stable mangling when any function inside a overridable declaration + // for avoid clashing names when inheritance. + if (DescriptorUtils.isOverride(descriptor)) return true + + val containingDeclaration = descriptor.containingDeclaration + + return when (containingDeclaration) { + is PackageFragmentDescriptor -> descriptor.visibility.isPublicAPI + is ClassDescriptor -> { + // Use stable mangling when it's inside an overridable declaration to avoid clashing names on inheritance. + if (!containingDeclaration.isFinalOrEnum) return true + + // valueOf() is created in the library with a mangled name for every enum class + if (descriptor is FunctionDescriptor && descriptor.isEnumValueOfMethod()) return true + + // Don't use stable mangling when it inside a non-public API declaration. + if (!containingDeclaration.visibility.isPublicAPI) return false + + // Ignore the `protected` visibility because it can be use outside a containing declaration + // only when the containing declaration is overridable. + if (descriptor.visibility === Visibilities.PUBLIC) return true + + return false + } + else -> { + assert(containingDeclaration is CallableMemberDescriptor) { + "containingDeclaration for descriptor have unsupported type for mangling, " + + "descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration + } + false + } + } + } + + object NativeParticipant : FQNPartipcipant { + override fun participate(descriptor: DeclarationDescriptor, generator: FQNGenerator): List? { + return when { + isNativeObject(descriptor) && isCompanionObject(descriptor) -> { + generator.generate(descriptor.containingDeclaration!!) + } + descriptor is PropertyAccessorDescriptor && isNativeObject(descriptor.correspondingProperty) -> { + generator.generate(descriptor.correspondingProperty) + } + isNativeObject(descriptor) || isLibraryObject(descriptor) -> { + if (descriptor is ConstructorDescriptor) { + generator.generate(descriptor.containingDeclaration) + } + else { + val name = AnnotationsUtils.getNameForAnnotatedObjectWithOverrides(descriptor) + val qualifier = when { + descriptor is CallableDescriptor && DescriptorUtils.isDescriptorWithLocalVisibility(descriptor) -> listOf() + descriptor is ClassDescriptor && descriptor.containingDeclaration is PackageFragmentDescriptor -> listOf() + descriptor is ClassDescriptor && isNativeObject(descriptor) && + !isNativeObject(descriptor.containingDeclaration) -> listOf() + else -> generator.generate(descriptor.containingDeclaration!!) + } + qualifier + listOf(FQNPart(name ?: descriptor.name.asString(), FQNPartType.PUBLIC, descriptor)) + } + } + else -> null + } + } + } +} diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPart.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPart.kt new file mode 100644 index 00000000000..7246d0917a8 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPart.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2016 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.descriptors.DeclarationDescriptor + +data class FQNPart(val name: String, val type: FQNPartType, val descriptor: DeclarationDescriptor) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartType.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartType.kt new file mode 100644 index 00000000000..693696d1c4d --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartType.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2016 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 + +enum class FQNPartType { + PUBLIC, + PRIVATE, + MODULE +} diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartipcipant.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartipcipant.kt new file mode 100644 index 00000000000..1dea86ee61c --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/FQNPartipcipant.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2016 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.descriptors.DeclarationDescriptor + +interface FQNPartipcipant { + fun participate(descriptor: DeclarationDescriptor, generator: FQNGenerator): List? +}