Repository: Kotlin/dokka (kotlin)

Issue:
[K2] KMP: `Multiple pages associated with key` reported for an actual class overriding method from java parent
Create a KMP project with JVM + any native target.
```Kotlin
// commonMain
expect class Skiko()

// nativeMain
actual class Skiko actual constructor(){
    fun isShowing(): Boolean {
        return false
    }
}

// jvmMain
file: JavaParent.java
public class JavaParent {
    public boolean isShowing() {
        return true;
    }
}

file: kotlin.kt
actual class Skiko actual constructor(): JavaParent(){
     override fun isShowing(): Boolean {
        return false
    }
}

```

dokkaHtml fails with enabled K2 mode:
```
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':module:dokkaHtml'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:149)
	at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:147)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:135)
	at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
	at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
	at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:337)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:324)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:317)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:463)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:380)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
Caused by: java.lang.AssertionError: Multiple pages associated with key: /Skiko/isShowing/#/PointingToDeclaration//[DisplaySourceSet(sourceSetIDs=CompositeSourceSetID(children=[:module:dokkaHtml/jvmMain]), name=jvm, platform=jvm)]
	at org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider.<init>(DokkaLocationProvider.kt:60)
	at org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider.<init>(DokkaLocationProvider.kt:17)
	at org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory$getLocationProvider$1.invoke(DokkaLocationProviderFactory.kt:18)
	at org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory$getLocationProvider$1.invoke(DokkaLocationProviderFactory.kt:17)
	at org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory$sam$java_util_function_Function$0.apply(DokkaLocationProviderFactory.kt)
	at org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory.getLocationProvider(DokkaLocationProviderFactory.kt:17)
	at org.jetbrains.dokka.base.renderers.PackageListService.createPackageList(PackageListService.kt:29)
	at org.jetbrains.dokka.base.renderers.PackageListCreator.packageList(preprocessors.kt:29)
	at org.jetbrains.dokka.base.renderers.PackageListCreator.access$packageList(preprocessors.kt:17)
	at org.jetbrains.dokka.base.renderers.PackageListCreator$invoke$1.invoke(preprocessors.kt:24)
	at org.jetbrains.dokka.base.renderers.PackageListCreator$invoke$1.invoke(preprocessors.kt:23)
	at org.jetbrains.dokka.pages.RootPageNode.transformNode(PageNodes.kt:69)
	at org.jetbrains.dokka.pages.RootPageNode.transformNode(PageNodes.kt:70)
	at org.jetbrains.dokka.pages.RootPageNode.transformPageNodeTree(PageNodes.kt:62)
	at org.jetbrains.dokka.base.renderers.PackageListCreator.invoke(preprocessors.kt:23)
	at org.jetbrains.dokka.base.renderers.DefaultRenderer.render(DefaultRenderer.kt:226)
	at org.jetbrains.dokka.base.renderers.html.HtmlRenderer.render(HtmlRenderer.kt:930)
	at org.jetbrains.dokka.base.generation.SingleModuleGeneration.render(SingleModuleGeneration.kt:94)
	at org.jetbrains.dokka.base.generation.SingleModuleGeneration.generate(SingleModuleGeneration.kt:53)
	at org.jetbrains.dokka.DokkaGenerator$generate$1.invoke(DokkaGenerator.kt:34)
	at org.jetbrains.dokka.DokkaGenerator$generate$1.invoke(DokkaGenerator.kt:27)
	at org.jetbrains.dokka.DokkaGeneratorKt.timed(DokkaGenerator.kt:82)
	at org.jetbrains.dokka.DokkaGeneratorKt.access$timed(DokkaGenerator.kt:1)
	at org.jetbrains.dokka.DokkaGenerator.generate(DokkaGenerator.kt:27)
	at org.jetbrains.dokka.DokkaBootstrapImpl.generate(DokkaBootstrapImpl.kt:67)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.jetbrains.dokka.gradle.DelegatedInvocationHandler.invoke(automagicTypedProxy.kt:44)
```
For K1 works fine.

**Installation**
- Dokka version: 2.0.0

The project: [Skiko](https://github.com/JetBrains/skiko/blob/master/skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt#L199)

Pull request:
The main idea of this fix is that pages with implicit expect/actual declarations and a single declaration should have the same name since they are merged by `PageMerger` based on the page name. Before the fix, pages with the same DRI/path were unmerged, which caused an exception.

Patch:
diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt
index e051c4d556..e5d86c4ec7 100644
--- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt
+++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt
@@ -117,8 +117,7 @@ public open class DefaultPageCreator(
                     props.mergeClashingDocumentable().map(::pageForProperties)
         else
             classlikes.renameClashingDocumentable().map(::pageForClasslike) +
-                    functions.renameClashingDocumentable().map(::pageForFunction) +
-                    props.renameClashingDocumentable().mapNotNull(::pageForProperty)
+                    (functions + props).renameClashingDocumentable().mapNotNull(::pageForMember)
 
         return ClasslikePageNode(
             documentables.first().nameAfterClash(), contentForClasslikesAndEntries(documentables), dri, documentables,
@@ -158,8 +157,7 @@ public open class DefaultPageCreator(
                             entries.mergeClashingDocumentable().map(::pageForEnumEntries)
                 else
                     nestedClasslikes.renameClashingDocumentable().map(::pageForClasslike) +
-                            functions.renameClashingDocumentable().map(::pageForFunction) +
-                            props.renameClashingDocumentable().mapNotNull(::pageForProperty) +
+                            (functions + props).renameClashingDocumentable().mapNotNull(::pageForMember)  +
                             entries.renameClashingDocumentable().map(::pageForEnumEntry)
 
 
@@ -207,6 +205,12 @@ public open class DefaultPageCreator(
         return MemberPageNode(fs.first().nameAfterClash(), contentForMembers(fs), dri, fs)
     }
 
+    private fun pageForMember(d: Documentable): MemberPageNode? = when (d) {
+        is DProperty -> pageForProperty(d)
+        is DFunction -> pageForFunction(d)
+        else -> throw IllegalStateException()
+    }
+
     public open fun pageForProperty(p: DProperty): MemberPageNode? =
         MemberPageNode(p.nameAfterClash(), contentForProperty(p), setOf(p.dri), listOf(p))
 


Test patch:
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/expectActuals/ExpectActualsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/expectActuals/ExpectActualsTest.kt
index 248f6feb7f..b6169d7738 100644
--- a/dokka-subprojects/plugin-base/src/test/kotlin/expectActuals/ExpectActualsTest.kt
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/expectActuals/ExpectActualsTest.kt
@@ -6,8 +6,12 @@ package expectActuals
 
 import org.jetbrains.dokka.DokkaSourceSetID
 import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.DProperty
+import org.jetbrains.dokka.model.dfs
 import org.jetbrains.dokka.model.withDescendants
 import org.jetbrains.dokka.pages.ClasslikePageNode
+import org.jetbrains.dokka.pages.MemberPageNode
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
@@ -435,4 +439,68 @@ class ExpectActualsTest : BaseAbstractTest() {
             }
         }
     }
+
+    @Test
+    fun `should merge an implicit-expectActual function with a single property #3685`() = testInline(
+        """
+        /src/common/test.kt
+        expect class Skiko
+
+        /src/jvm/test.kt
+        actual class Skiko actual constructor() {
+             val isShowing = false
+             fun isShowing(): Boolean {
+                return false
+            }
+        }
+        
+        /src/native/test.kt
+        actual class Skiko actual constructor(){
+            fun isShowing(): Boolean {
+                return false
+            }
+        }
+        """.trimMargin(),
+        multiplatformConfiguration
+    ) {
+        pagesGenerationStage = { root ->
+            val cl = root.dfs { it.name == "Skiko" && it is ClasslikePageNode } ?: throw IllegalStateException()
+            assertEquals(2, cl.children.count { it.name == "[jvm]isShowing" })
+        }
+        renderingStage = { root, _ ->
+            val documentables = (root.dfs { it.name == "[jvm]isShowing" } as MemberPageNode).documentables
+            assertEquals(listOf(DFunction::class, DProperty::class), documentables.map { it::class })
+        }
+    }
+
+    @Test
+    fun `should merge an implicit-expectActual property with a single function #3685`() = testInline(
+        """
+        /src/common/test.kt
+        expect class Skiko
+
+        /src/jvm/test.kt
+        actual class Skiko actual constructor() {
+             val isShowing = false
+             fun isShowing(): Boolean {
+                return false
+            }
+        }
+        
+        /src/native/test.kt
+        actual class Skiko actual constructor(){
+            val isShowing = false
+        }
+        """.trimMargin(),
+        multiplatformConfiguration
+    ) {
+        pagesGenerationStage = { root ->
+            val cl = root.dfs { it.name == "Skiko" && it is ClasslikePageNode } ?: throw IllegalStateException()
+            assertEquals(2, cl.children.count { it.name == "[jvm]isShowing" })
+        }
+        renderingStage = { root, _ ->
+            val documentables = (root.dfs { it.name == "[jvm]isShowing" } as MemberPageNode).documentables
+            assertEquals(listOf(DFunction::class, DProperty::class), documentables.map { it::class })
+        }
+    }
 }