org.jetbrains.kotlin.js.naming.NameSuggestion.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* 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.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.js.common.isES5IdentifierPart
import org.jetbrains.kotlin.js.common.isES5IdentifierStart
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.getNameForAnnotatedObject
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject
import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isEnumValueOfMethod
import java.util.*
import kotlin.math.abs
class JsNameSuggestion : NameSuggestion()
class WasmNameSuggestion : NameSuggestion()
/**
* This class is responsible for generating names for declarations. It does not produce fully-qualified JS name, instead
* it tries to generate a simple name and specify a scoping declaration. This information can be used by the front-end
* to check whether names clash, by the code generator to place declarations to corresponding scopes and to produce
* fully-qualified names for static declarations.
*
* A new instance of this class can be created for each request, however, it's recommended to use stable instance, since
* [NameSuggestion] supports caching.
*/
open class NameSuggestion {
private val cache: MutableMap = Collections.synchronizedMap(WeakHashMap())
/**
* Generates names for declarations. Name consists of the following parts:
*
* * Aliasing declaration, if the given `descriptor` does not have its own entity in JS.
* * Scoping declaration. Declarations are usually compiled to the hierarchy of nested JS objects,
* this attribute allows to find out where to put the declaration.
* * Simple name, which is a name that object must (or may) get on the generated JS.
* * Whether the name is stable. Stable names are visible to other modules and to native JS.
* Unstable names do not require particular name, so the code generator can invent any name
* which does not clash with anything; however, it may derive the name from the suggested name to
* improve readability and debugging experience.
*
* This method returns `null` for root declarations (modules and root packages).
* It's guaranteed that a particular name is returned for any other declarations.
*
* Since packages in Kotlin do not always form hierarchy, suggested name is a list of strings. This
* list consists of exactly one string for any declaration except for package. Package name lists
* have at least one string.
*/
fun suggest(descriptor: DeclarationDescriptor, bindingContext: BindingContext) =
cache.getOrPut(descriptor) { generate(descriptor.original, bindingContext) }
private fun generate(descriptor: DeclarationDescriptor, bindingContext: BindingContext): SuggestedName? {
fun suggest(d: DeclarationDescriptor) = suggest(d, bindingContext)
// Members of companion objects of classes are treated as static members of these classes
if (isNativeObject(descriptor) && isCompanionObject(descriptor)) {
return suggest(descriptor.containingDeclaration!!, bindingContext)
}
if (descriptor is FunctionDescriptor && descriptor.isSuspend) {
descriptor.initialSignatureDescriptor?.let {
return suggest(it)
}
}
// Dynamic declarations always require stable names as defined in Kotlin source code
if (descriptor.isDynamic()) {
return SuggestedName(listOf(descriptor.name.asString()), true, descriptor, descriptor.containingDeclaration!!)
}
when (descriptor) {
// Modules are root declarations, we don't produce declarations for them, therefore they can't clash
is ModuleDescriptor -> return null
is PackageFragmentDescriptor -> {
return if (!descriptor.fqName.isRoot) {
SuggestedName(descriptor.fqName.pathSegments().map(Name::asString), true, descriptor,
descriptor.containingDeclaration)
}
else {
// Root packages are similar to modules
null
}
}
// It's a special case when an object has `invoke` operator defined, in this case we simply generate object itself
is FakeCallableDescriptorForObject -> return suggest(descriptor.getReferencedObject())
// For type alias constructor descriptors we generate references to underlying constructors
is TypeAliasConstructorDescriptor -> return suggest(descriptor.underlyingConstructorDescriptor)
// For primary constructors and constructors of native classes we generate references to containing classes
is ConstructorDescriptor -> {
if (descriptor.isPrimary || isNativeObject(descriptor)) {
return suggest(descriptor.containingDeclaration)
}
}
// Local functions and variables are always private with their own names as suggested names
is CallableDescriptor ->
if (DescriptorUtils.isDescriptorWithLocalVisibility(descriptor)) {
val ownName = getNameForAnnotatedObject(descriptor, bindingContext) ?: getSuggestedName(descriptor)
var name = ownName
var scope = descriptor.containingDeclaration
// Local functions always lifted to the closest class or package when they are contained inside public inline function
if (descriptor is FunctionDescriptor) {
var container = descriptor.containingDeclaration
var liftedName = ownName
var hasInline = false
while (container is FunctionDescriptor) {
if (container.isInline && container.ownEffectiveVisibility.isPublicAPI) {
hasInline = true
}
liftedName = getSuggestedName(container) + "$" + liftedName
container = container.containingDeclaration
}
if (hasInline) {
scope = container
name = liftedName
}
}
return SuggestedName(listOf(name), false, descriptor, scope)
}
}
return generateDefault(descriptor, bindingContext)
}
private fun generateDefault(descriptor: DeclarationDescriptor, bindingContext: BindingContext): SuggestedName {
// For any non-local declaration suggest its own suggested name and put it in scope of its containing declaration.
// For local declaration get a sequence for names of all containing functions and join their names with '$' symbol,
// and use container of topmost function, i.e.
//
// class A {
// fun foo() {
// fun bar() {
// fun baz() { ... }
// }
// }
// }
//
// `baz` gets name 'foo$bar$baz$' scoped in `A` class.
//
// The exception are secondary constructors which get suggested name with '_init' suffix and are put in
// the class's parent scope.
//
val parts = mutableListOf()
// For some strange reason we get FAKE_OVERRIDE for final functions called via subtype's receiver
var current: DeclarationDescriptor = descriptor
if (current is CallableMemberDescriptor && current.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
val overridden = getOverridden(current) as CallableMemberDescriptor
if (!overridden.isOverridableOrOverrides) {
current = overridden
}
}
val fixedDescriptor = current
parts += if (fixedDescriptor is ConstructorDescriptor) {
current = current.containingDeclaration!!
getSuggestedName(current) + "_init"
}
else {
getSuggestedName(fixedDescriptor)
}
if (current.containingDeclaration is FunctionDescriptor && current !is TypeParameterDescriptor) {
val outerFunctionName = suggest(current.containingDeclaration as FunctionDescriptor, bindingContext)!!
parts += outerFunctionName.names.single()
current = outerFunctionName.scope
}
else {
current = current.containingDeclaration!!
}
// Getters and setters have generation strategy similar to common declarations, except for they are declared as
// members of classes/packages, not corresponding properties.
if (current is PropertyDescriptor) {
current = current.containingDeclaration
}
parts.reverse()
val unmangledName = parts.joinToString("$")
val (id, stable) = mangleNameIfNecessary(unmangledName, fixedDescriptor, bindingContext)
return SuggestedName(listOf(id), stable, fixedDescriptor, current)
}
// For regular names suggest its string representation
// For property accessors suggest name of a property with 'get_' and 'set_' prefixes
// For anonymous declarations (i.e. lambdas and object expressions) suggest 'f'
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()
}
}
companion object {
private fun mangleNameIfNecessary(baseName: String, descriptor: DeclarationDescriptor, bindingContext: BindingContext): NameAndStability {
// If we have a callable descriptor (property or method) it can override method in a parent class.
// Traverse to the topmost overridden method.
// It does not matter which path to choose during traversal, since front-end must ensure
// that names required by different overridden method do no differ.
val overriddenDescriptor = if (descriptor is CallableDescriptor) {
generateSequence(descriptor) { it.overriddenDescriptors.firstOrNull()?.original }.last()
}
else {
descriptor
}
// If declaration is marked with either external, @native, @library or @JsName, return its stable name as is.
val nativeName = getNameForAnnotatedObject(overriddenDescriptor, bindingContext)
if (nativeName != null) return NameAndStability(nativeName, true)
if (overriddenDescriptor is FunctionDescriptor) {
when (overriddenDescriptor.fqNameUnsafe.asString()) {
"kotlin.CharSequence.get" -> return NameAndStability("charCodeAt", true)
"kotlin.Any.equals" -> return NameAndStability("equals", true)
}
val container = overriddenDescriptor.containingDeclaration
if (container is ClassDescriptor && ReflectionTypes.isNumberedKPropertyOrKMutablePropertyType(container.defaultType)) {
val name = overriddenDescriptor.name.asString()
when (name) {
"get",
"set" -> return NameAndStability(name, true)
}
}
}
else if (overriddenDescriptor is PropertyDescriptor) {
when (overriddenDescriptor.fqNameUnsafe.asString()) {
"kotlin.reflect.KCallable.name" -> return NameAndStability("callableName", true)
}
}
return mangleRegularNameIfNecessary(baseName, overriddenDescriptor)
}
private fun getOverridden(descriptor: CallableDescriptor): CallableDescriptor {
return generateSequence(descriptor) { it.overriddenDescriptors.firstOrNull()?.original }.last()
}
private fun mangleRegularNameIfNecessary(baseName: String, descriptor: DeclarationDescriptor): NameAndStability {
if (descriptor is ClassOrPackageFragmentDescriptor) {
return NameAndStability(baseName, !DescriptorUtils.isDescriptorWithLocalVisibility(descriptor))
}
fun regularAndUnstable() = NameAndStability(baseName, false)
if (descriptor !is CallableMemberDescriptor) {
// Actually, only reified types get here, and it would be properly to put assertion here
// However, it's better to generate wrong code than crash
return regularAndUnstable()
}
fun mangledAndStable() = NameAndStability(getStableMangledName(baseName, encodeSignature(descriptor)), true)
fun mangledInternal() = NameAndStability(getInternalMangledName(baseName, encodeSignature(descriptor)), true)
fun mangledPrivate() = NameAndStability(getPrivateMangledName(baseName, descriptor), false)
val effectiveVisibility = descriptor.ownEffectiveVisibility
val containingDeclaration = descriptor.containingDeclaration
return when (containingDeclaration) {
is PackageFragmentDescriptor -> when {
effectiveVisibility.isPublicAPI -> mangledAndStable()
effectiveVisibility == DescriptorVisibilities.INTERNAL -> mangledInternal()
else -> regularAndUnstable()
}
is ClassDescriptor -> when {
// valueOf() is created in the library with a mangled name for every enum class
descriptor is FunctionDescriptor && descriptor.isEnumValueOfMethod() -> mangledAndStable()
// Make all public declarations stable
effectiveVisibility == DescriptorVisibilities.PUBLIC -> mangledAndStable()
descriptor.isOverridableOrOverrides -> mangledAndStable()
// Make all protected declarations of non-final public classes stable
effectiveVisibility == DescriptorVisibilities.PROTECTED &&
!containingDeclaration.isFinalClass &&
containingDeclaration.visibility.isPublicAPI -> mangledAndStable()
effectiveVisibility == DescriptorVisibilities.INTERNAL -> mangledInternal()
// Mangle (but make unstable) all non-public API of public classes
containingDeclaration.visibility.isPublicAPI && !containingDeclaration.isFinalClass -> mangledPrivate()
else -> regularAndUnstable()
}
else -> {
assert(containingDeclaration is CallableMemberDescriptor) {
"containingDeclaration for descriptor have unsupported type for mangling, " +
"descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration
}
regularAndUnstable()
}
}
}
data class NameAndStability(val name: String, val stable: Boolean)
@JvmStatic fun getPrivateMangledName(baseName: String, descriptor: CallableDescriptor): String {
val ownerName = descriptor.containingDeclaration.fqNameUnsafe.asString()
// Base name presents here since name part gets sanitized, so we have to produce different suffixes to distinguish
// between, say `.` and `;`.
return getStableMangledName(sanitizeName(baseName), ownerName + "." + baseName + ":" + encodeSignature(descriptor))
}
fun getInternalMangledName(suggestedName: String, forCalculateId: String): String {
val suffix = "_${mangledId("internal:" + forCalculateId)}\$"
return suggestedName + suffix
}
@JvmStatic fun getStableMangledName(suggestedName: String, forCalculateId: String): String {
val suffix = if (forCalculateId.isEmpty()) "" else "_${mangledId(forCalculateId)}\$"
return suggestedName + suffix
}
private fun mangledId(forCalculateId: String): String {
val absHashCode = abs(forCalculateId.hashCode())
return if (absHashCode != 0) absHashCode.toString(Character.MAX_RADIX) else ""
}
private val DeclarationDescriptorWithVisibility.ownEffectiveVisibility: DescriptorVisibility
get() = visibility.effectiveVisibility(this, checkPublishedApi = true).toDescriptorVisibility()
@JvmStatic fun sanitizeName(name: String): String {
if (name.isEmpty()) return "_"
val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' }
return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("")
}
}
}