Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suppress useless companion for Kotlin-As-Java #2681

Merged
merged 6 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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(): List<DProperty> {
atyrin marked this conversation as resolved.
Show resolved Hide resolved
if (this == null) return emptyList()
if (hasNothingToRender()) return emptyList() // do not show if companion not rendered

return listOf(
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 (this == null) return null
if (hasNothingToRender()) return null

return asJava(
excludedProps = staticPropertiesForJava(),
excludedFunctions = staticFunctionsForJava()
)
}
IgnatBeresnev marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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,
isBelongToObjectOrCompanion: Boolean = false
atyrin marked this conversation as resolved.
Show resolved Hide resolved
) =
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) || (isBelongToObjectOrCompanion && 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 || (isBelongToObjectOrCompanion && isJvmField) || (isBelongToObjectOrCompanion && 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 companionAsJava = companion.companionAsJava()

val classlikes = classlikes
.filter { it.name != companion?.name }
.map { it.asJava() }
return if (companionAsJava != null) classlikes.plus(companionAsJava) else classlikes
atyrin marked this conversation as resolved.
Show resolved Hide resolved
}


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(isBelongToObjectOrCompanion = true) }
val companionInstanceProperty = companion.companionInstancePropertyForJava()
val ownProperties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() }

return propertiesFromCompanion + ownProperties + 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()
atyrin marked this conversation as resolved.
Show resolved Hide resolved
): 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(isBelongToObjectOrCompanion = 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" }