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

JvmOverloads #1712

Merged
merged 3 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jetbrains.dokka.kotlinAsJava.converters

import org.jetbrains.dokka.kotlinAsJava.hasJvmOverloads
import org.jetbrains.dokka.kotlinAsJava.jvmField
import org.jetbrains.dokka.kotlinAsJava.transformers.JvmNameProvider
import org.jetbrains.dokka.kotlinAsJava.transformers.withCallableName
Expand Down Expand Up @@ -43,7 +44,7 @@ internal fun DPackage.asJava(): DPackage {
.filterNot { it.isConst || it.isJvmField }
.flatMap { it.javaAccessors(relocateToClass = syntheticClassName.name) } +
nodes.filterIsInstance<DFunction>()
.map { it.asJava(syntheticClassName.name) }), // TODO: methods are static and receiver is a param
.flatMap { it.asJava(syntheticClassName.name, true) }), // TODO: methods are static and receiver is a param
classlikes = emptyList(),
sources = emptyMap(),
expectPresentInSet = null,
Expand Down Expand Up @@ -169,12 +170,12 @@ internal fun DProperty.javaAccessors(isTopLevel: Boolean = false, relocateToClas
}
)


internal fun DFunction.asJava(containingClassName: String): DFunction {
val newName = when {
isConstructor -> containingClassName
else -> name
}
private fun DFunction.asJava(
containingClassName: String,
newName: String,
parameters: List<DParameter>,
isTopLevel: Boolean = false
): DFunction {
return copy(
dri = dri.copy(classNames = containingClassName, callable = dri.callable?.copy(name = newName)),
name = newName,
Expand All @@ -184,8 +185,54 @@ internal fun DFunction.asJava(containingClassName: String): DFunction {
else sourceSets.map { it to modifier.values.first() }.toMap(),
parameters = listOfNotNull(receiver?.asJava()) + parameters.map { it.asJava() },
visibility = visibility.map { (sourceSet, visibility) -> Pair(sourceSet, visibility.asJava()) }.toMap(),
receiver = null
) // TODO static if toplevel
receiver = null,
extra = if (isTopLevel) {
extra + extra.mergeAdditionalModifiers(
sourceSets.map {
it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}.toMap()
)
} else {
extra
}
)
}

private fun DFunction.withJvmOverloads(
containingClassName: String,
newName: String,
isTopLevel: Boolean = false
): List<DFunction>? {
val (paramsWithDefaults, paramsWithoutDefaults) = parameters
.withIndex()
.partition { (_, p) -> p.extra[DefaultValue] != null }
return paramsWithDefaults
.runningFold(paramsWithoutDefaults) { acc, param -> (acc + param) }
.map { params ->
asJava(
containingClassName,
newName,
params
.sortedBy(IndexedValue<DParameter>::index)
.map { it.value },
isTopLevel
)
}
.reversed()
.takeIf { it.isNotEmpty() }
}

internal fun DFunction.asJava(containingClassName: String, isTopLevel: Boolean = false): List<DFunction> {
val newName = when {
isConstructor -> containingClassName
else -> name
}
val baseFunction = asJava(containingClassName, newName, parameters, isTopLevel)
return if (hasJvmOverloads()) {
withJvmOverloads(containingClassName, newName, isTopLevel) ?: listOf(baseFunction)
} else {
listOf(baseFunction)
}
}

internal fun DClasslike.asJava(): DClasslike = when (this) {
Expand All @@ -198,7 +245,7 @@ internal fun DClasslike.asJava(): DClasslike = when (this) {
}

internal fun DClass.asJava(): DClass = copy(
constructors = constructors.map { it.asJava(dri.classNames ?: name) }, // name may not always be valid here, however classNames should always be not null
constructors = constructors.flatMap { it.asJava(dri.classNames ?: name) }, // name may not always be valid here, however classNames should always be not null
functions = functionsInJava(),
properties = properties.map { it.asJava() },
classlikes = classlikes.map { it.asJava() },
Expand All @@ -211,9 +258,10 @@ internal fun DClass.asJava(): DClass = copy(

internal fun DClass.functionsInJava(): List<DFunction> =
(properties.filter { it.jvmField() == null }
.flatMap { property -> listOfNotNull(property.getter, property.setter) } + functions).map {
it.asJava(dri.classNames ?: name)
}
.flatMap { property -> listOfNotNull(property.getter, property.setter) } + functions)
.flatMap {
it.asJava(dri.classNames ?: name)
}

private fun DTypeParameter.asJava(): DTypeParameter = copy(
variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()),
Expand Down Expand Up @@ -251,8 +299,8 @@ private fun Bound.asJava(): Bound = when (this) {
}

internal fun DEnum.asJava(): DEnum = copy(
constructors = constructors.map { it.asJava(dri.classNames ?: name) },
functions = (functions + properties.map { it.getter } + properties.map { it.setter }).filterNotNull().map {
constructors = constructors.flatMap { it.asJava(dri.classNames ?: name) },
functions = (functions + properties.map { it.getter } + properties.map { it.setter }).filterNotNull().flatMap {
it.asJava(dri.classNames ?: name)
},
properties = properties.map { it.asJava() },
Expand All @@ -264,7 +312,7 @@ internal fun DEnum.asJava(): DEnum = copy(
internal fun DObject.asJava(): DObject = copy(
functions = (functions + properties.map { it.getter } + properties.map { it.setter })
.filterNotNull()
.map { it.asJava(dri.classNames ?: name.orEmpty()) },
.flatMap { it.asJava(dri.classNames ?: name.orEmpty()) },
properties = properties.map { it.asJava() } +
DProperty(
name = "INSTANCE",
Expand Down Expand Up @@ -294,7 +342,7 @@ internal fun DObject.asJava(): DObject = copy(
internal fun DInterface.asJava(): DInterface = copy(
functions = (functions + properties.map { it.getter } + properties.map { it.setter })
.filterNotNull()
.map { it.asJava(dri.classNames ?: name) },
.flatMap { it.asJava(dri.classNames ?: name) },
properties = emptyList(),
classlikes = classlikes.map { it.asJava() }, // TODO: public static final class DefaultImpls with impls for methods
generics = generics.map { it.asJava() },
Expand Down
14 changes: 14 additions & 0 deletions plugins/kotlin-as-java/src/main/kotlin/jvmOverloads.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.jetbrains.dokka.kotlinAsJava

import org.jetbrains.dokka.model.Annotations
import org.jetbrains.dokka.model.Documentable
import org.jetbrains.dokka.model.properties.WithExtraProperties

internal fun WithExtraProperties<out Documentable>.hasJvmOverloads(): Boolean {
return extra[Annotations]
?.directAnnotations
?.entries
?.any { (_, annotations) ->
annotations.any { it.dri.packageName == "kotlin.jvm" && it.dri.classNames == "JvmOverloads" }
} == true
}
56 changes: 56 additions & 0 deletions plugins/kotlin-as-java/src/test/kotlin/JvmOverloadsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package kotlinAsJavaPlugin

import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class JvmOverloadsTest : BaseAbstractTest() {
private val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
classpath += jvmStdlibPath!!
}
}
}

@Test
fun `should generate multiple functions`() {
testInline(
"""
|/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
|package kotlinAsJavaPlugin
|@JvmOverloads
|fun sample(a: Int = 0, b: String, c: Int = 0): String = ""
""".trimMargin(),
configuration,
) {
documentablesTransformationStage = { module ->
val functions = module.packages.flatMap { it.classlikes }.flatMap { it.functions }
assertEquals(3, functions.size)
assertEquals(3, functions[0].parameters.size)
assertEquals(2, functions[1].parameters.size)
assertEquals(1, functions[2].parameters.size)
}
}
}

@Test
fun `should do nothing if there is no default values`() {
testInline(
"""
|/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
|package kotlinAsJavaPlugin
|@JvmOverloads
|fun sample(a: Int, b: String, c: Int): String = ""
""".trimMargin(),
configuration,
) {
documentablesTransformationStage = { module ->
val functions = module.packages.flatMap { it.classlikes }.flatMap { it.functions }
assertEquals(1, functions.size)
assertEquals(3, functions[0].parameters.size)
}
}
}
}
33 changes: 33 additions & 0 deletions plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,39 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() {
}
}
}

@Test
fun `function in top level`() {
val writerPlugin = TestOutputWriterPlugin()
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
externalDocumentationLinks = listOf(
DokkaConfiguration.ExternalDocumentationLink.jdk(8),
stdlibExternalDocumentationLink
)
}
}
}
testInline(
"""
|/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
|package kotlinAsJavaPlugin
|
|fun sample(a: Int) = ""
""".trimMargin(),
configuration,
pluginOverrides = listOf(writerPlugin),
cleanupOutput = true
) {
renderingStage = { _, _ ->
writerPlugin.writer.renderedContent("root/kotlinAsJavaPlugin/-test-kt/sample.html").signature().first().match(
"final static ", A("String"), A("sample"), "(", A("Integer"), "a)", Span()
)
}
}
}
}

private val ContentNode.mainContents: List<ContentNode>
Expand Down