Skip to content

Commit

Permalink
Suppress useless companion for Kotlin-As-Java (#2681)
Browse files Browse the repository at this point in the history
  • Loading branch information
atyrin committed Oct 31, 2022
1 parent c0aece9 commit faca33f
Show file tree
Hide file tree
Showing 5 changed files with 768 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.jetbrains.dokka.kotlinAsJava.converters

import org.jetbrains.dokka.links.Callable
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.properties.PropertyContainer

private const val DEFAULT_COMPANION_NAME = "Companion"

internal fun DObject?.staticFunctionsForJava(): List<DFunction> {
if (this == null) return emptyList()
return functions.filter { it.isJvmStatic }
}

/**
* @return properties that will be visible as static for java.
* See [Static fields](https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-fields)
*/
internal fun DObject?.staticPropertiesForJava(): List<DProperty> {
if (this == null) return emptyList()
return properties.filter { it.isJvmField || it.isConst || it.isLateInit }
}

internal fun DObject.companionInstancePropertyForJava(): DProperty? {
if (hasNothingToRender()) return null // do not show if companion not rendered

return DProperty(
name = name ?: DEFAULT_COMPANION_NAME,
modifier = sourceSets.associateWith { JavaModifier.Final },
dri = dri.copy(callable = Callable(name ?: DEFAULT_COMPANION_NAME, null, emptyList())),
documentation = emptyMap(),
sources = emptyMap(),
visibility = sourceSets.associateWith {
JavaVisibility.Public
},
type = GenericTypeConstructor(dri, emptyList()),
setter = null,
getter = null,
sourceSets = sourceSets,
receiver = null,
generics = emptyList(),
expectPresentInSet = expectPresentInSet,
isExpectActual = false,
extra = PropertyContainer.withAll(sourceSets.map {
mapOf(it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)).toAdditionalModifiers()
})
)
}

internal fun DObject.companionAsJava(): DObject? {
if (hasNothingToRender()) return null

return asJava(
excludedProps = staticPropertiesForJava(),
excludedFunctions = staticFunctionsForJava()
)
}

/**
* Hide companion object if there isn't members of parents.
* Properties and functions that are moved to outer class are not counted as members.
*/
private fun DObject.hasNothingToRender(): Boolean {
val nonStaticPropsCount = properties.size - staticPropertiesForJava().size
val nonStaticFunctionsCount = functions.size - staticFunctionsForJava().size
val classLikesCount = classlikes.size
val superTypesCount = supertypes.values.firstOrNull()?.size ?: 0

return nonStaticFunctionsCount + nonStaticPropsCount +
classLikesCount + superTypesCount == 0
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.jetbrains.dokka.kotlinAsJava.converters

import org.jetbrains.dokka.kotlinAsJava.hasJvmOverloads
import org.jetbrains.dokka.kotlinAsJava.hasJvmSynthetic
import org.jetbrains.dokka.kotlinAsJava.jvmField
import org.jetbrains.dokka.kotlinAsJava.*
import org.jetbrains.dokka.kotlinAsJava.transformers.JvmNameProvider
import org.jetbrains.dokka.kotlinAsJava.transformers.withCallableName
import org.jetbrains.dokka.links.Callable
Expand All @@ -17,17 +15,20 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType

val jvmNameProvider = JvmNameProvider()
internal const val OBJECT_INSTANCE_NAME = "INSTANCE"

private val DProperty.isConst: Boolean
get() = extra[AdditionalModifiers]
?.content
?.any { (_, modifiers) ->
ExtraModifiers.KotlinOnlyModifiers.Const in modifiers
} == true
internal val DProperty.isConst: Boolean
get() = hasModifier(ExtraModifiers.KotlinOnlyModifiers.Const)

internal val DProperty.isLateInit: Boolean
get() = hasModifier(ExtraModifiers.KotlinOnlyModifiers.LateInit)

private val DProperty.isJvmField: Boolean
internal val DProperty.isJvmField: Boolean
get() = jvmField() != null

internal val DFunction.isJvmStatic: Boolean
get() = jvmStatic() != null

internal fun DPackage.asJava(): DPackage {
val syntheticClasses =
(properties.map { jvmNameProvider.nameForSyntheticClass(it) to it }
Expand Down Expand Up @@ -76,7 +77,11 @@ internal fun DPackage.asJava(): DPackage {
)
}

internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: String? = null) =
internal fun DProperty.asJava(
isTopLevel: Boolean = false,
relocateToClass: String? = null,
isFromObjectOrCompanion: Boolean = false
) =
copy(
dri = if (relocateToClass.isNullOrBlank()) {
dri
Expand All @@ -85,9 +90,7 @@ internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: Stri
},
modifier = javaModifierFromSetter(),
visibility = visibility.mapValues {
if (isTopLevel && isConst) {
JavaVisibility.Public
} else if (jvmField() != null || (getter == null && setter == null)) {
if (isConst || isJvmField || (getter == null && setter == null) || (isFromObjectOrCompanion && isLateInit)) {
it.value.asJava()
} else {
it.value.propertyVisibilityAsJava()
Expand All @@ -96,12 +99,12 @@ internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: Stri
type = type.asJava(), // TODO: check
setter = null,
getter = null, // Removing getters and setters as they will be available as functions
extra = if (isTopLevel) extra +
extra.mergeAdditionalModifiers(
sourceSets.associateWith {
setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}
)
extra = if (isTopLevel || isConst || (isFromObjectOrCompanion && isJvmField) || (isFromObjectOrCompanion && isLateInit))
extra + extra.mergeAdditionalModifiers(
sourceSets.associateWith {
setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}
)
else extra
)

Expand Down Expand Up @@ -192,7 +195,7 @@ private fun DFunction.asJava(
parameters = listOfNotNull(receiver?.asJava()) + parameters.map { it.asJava() },
visibility = visibility.map { (sourceSet, visibility) -> Pair(sourceSet, visibility.asJava()) }.toMap(),
receiver = null,
extra = if (isTopLevel) {
extra = if (isTopLevel || isJvmStatic) {
extra + extra.mergeAdditionalModifiers(
sourceSets.associateWith {
setOf(ExtraModifiers.JavaOnlyModifiers.Static)
Expand Down Expand Up @@ -259,24 +262,51 @@ internal fun DClass.asJava(): DClass = copy(
)
}, // name may not always be valid here, however classNames should always be not null
functions = functionsInJava(),
properties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() },
classlikes = classlikes.map { it.asJava() },
properties = propertiesInJava(),
classlikes = classlikesInJava(),
generics = generics.map { it.asJava() },
companion = companion?.companionAsJava(),
supertypes = supertypes.mapValues { it.value.map { it.asJava() } },
modifier = if (modifier.all { (_, v) -> v is KotlinModifier.Empty }) sourceSets.associateWith { JavaModifier.Final }
else sourceSets.associateWith { modifier.values.first() }
)

/**
* Companion objects requires some custom logic for rendering as Java.
* They are excluded from usual classlikes rendering and added after.
*/
internal fun DClass.classlikesInJava(): List<DClasslike> {
val classlikes = classlikes
.filter { it.name != companion?.name }
.map { it.asJava() }

val companionAsJava = companion?.companionAsJava()
return if (companionAsJava != null) classlikes.plus(companionAsJava) else classlikes
}


internal fun DClass.functionsInJava(): List<DFunction> =
properties
.filter { it.jvmField() == null && !it.hasJvmSynthetic() }
.filter { !it.isJvmField && !it.hasJvmSynthetic() }
.flatMap { property -> listOfNotNull(property.getter, property.setter) }
.plus(functions)
.plus(companion.staticFunctionsForJava())
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(it.dri.classNames ?: it.name) }

internal fun DClass.propertiesInJava(): List<DProperty> {
val propertiesFromCompanion = companion
.staticPropertiesForJava()
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava(isFromObjectOrCompanion = true) }
val companionInstanceProperty = companion?.companionInstancePropertyForJava()
val ownProperties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() }

return propertiesFromCompanion + ownProperties + listOfNotNull(companionInstanceProperty)
}

private fun DTypeParameter.asJava(): DTypeParameter = copy(
variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()),
bounds = bounds.map { it.asJava() }
Expand Down Expand Up @@ -318,7 +348,7 @@ internal fun DEnum.asJava(): DEnum = copy(
functions = functions
.plus(
properties
.filter { it.jvmField() == null && !it.hasJvmSynthetic() }
.filter { !it.isJvmField && !it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
Expand All @@ -332,23 +362,33 @@ internal fun DEnum.asJava(): DEnum = copy(
// , entries = entries.map { it.asJava() }
)

internal fun DObject.asJava(): DObject = copy(
/**
* Parameters [excludedProps] and [excludedFunctions] used for rendering companion objects
* where some members (that lifted to outer class) are not rendered
*/
internal fun DObject.asJava(
excludedProps: List<DProperty> = emptyList(),
excludedFunctions: List<DFunction> = emptyList()
): DObject = copy(
functions = functions
.plus(
properties
.filter { it.jvmField() == null && !it.hasJvmSynthetic() }
.filterNot { it in excludedProps }
.filter { !it.isJvmField && !it.isConst && !it.isLateInit && !it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
.filterNot { it in excludedFunctions }
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name.orEmpty()) },
properties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() } +
.filterNot { it in excludedProps }
.map { it.asJava(isFromObjectOrCompanion = true) } +
DProperty(
name = "INSTANCE",
name = OBJECT_INSTANCE_NAME,
modifier = sourceSets.associateWith { JavaModifier.Final },
dri = dri.copy(callable = Callable("INSTANCE", null, emptyList())),
dri = dri.copy(callable = Callable(OBJECT_INSTANCE_NAME, null, emptyList())),
documentation = emptyMap(),
sources = emptyMap(),
visibility = sourceSets.associateWith {
Expand Down Expand Up @@ -450,3 +490,8 @@ private fun AdditionalModifiers.squash(second: AdditionalModifiers) =

internal fun ClassId.classNames(): String =
shortClassName.identifier + (outerClassId?.classNames()?.let { ".$it" } ?: "")

private fun DProperty.hasModifier(modifier: ExtraModifiers.KotlinOnlyModifiers): Boolean =
extra[AdditionalModifiers]
?.content
?.any { (_, modifiers) -> modifier in modifiers } == true
12 changes: 12 additions & 0 deletions plugins/kotlin-as-java/src/main/kotlin/jvmStatic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jetbrains.dokka.kotlinAsJava

import org.jetbrains.dokka.model.Annotations
import org.jetbrains.dokka.model.Documentable
import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.kotlin.util.firstNotNullResult

internal fun WithExtraProperties<out Documentable>.jvmStatic(): Annotations.Annotation? =
extra[Annotations]?.directAnnotations?.entries?.firstNotNullResult { (_, annotations) -> annotations.jvmStaticAnnotation() }

internal fun List<Annotations.Annotation>.jvmStaticAnnotation(): Annotations.Annotation? =
firstOrNull { it.dri.packageName == "kotlin.jvm" && it.dri.classNames == "JvmStatic" }

0 comments on commit faca33f

Please sign in to comment.