Repository: Kotlin/dokka (kotlin)

Issue:
[K2] Missed `override` modifier for generic functions
```kt
class A<T>  : C<T>()

open class C<T> : D<T> {
    override fun f(a: T) = 1
}

interface D<T> {
    fun f(a: T) = 0
}
```
The signature of `A.f` does not have `override` modifier.
K1:
![image](https://github.com/user-attachments/assets/b19a2eed-90e5-4802-a965-9634786cd3c3)

K2:
![image](https://github.com/user-attachments/assets/eb66a812-cc76-494d-b180-69477b8cdea2)



Related #3677

Pull request:
fix #3848

Patch:
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt
index fdbc38659c..aa1d0d8afc 100644
--- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt
+++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt
@@ -508,7 +508,7 @@ internal class DokkaSymbolVisitor(
                 generics = generics,
                 isExpectActual = (isExpect || isActual),
                 extra = PropertyContainer.withAll(
-                    propertySymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(),
+                    additionalExtrasOfProperty(propertySymbol)?.toSourceSetDependent()?.toAdditionalModifiers(),
                     getDokkaAnnotationsFrom(propertySymbol)?.toSourceSetDependent()?.toAnnotations(),
                     propertySymbol.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) },
                     inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
@@ -715,7 +715,7 @@ internal class DokkaSymbolVisitor(
                 isExpectActual = (isExpect || isActual),
                 extra = PropertyContainer.withAll(
                     inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
-                    functionSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(),
+                    additionalExtrasOfFunction(functionSymbol)?.toSourceSetDependent()?.toAdditionalModifiers(),
                     getDokkaAnnotationsFrom(functionSymbol)
                         ?.toSourceSetDependent()?.toAnnotations(),
                     ObviousMember.takeIf { isObvious(functionSymbol, inheritedFrom) },
@@ -894,6 +894,37 @@ internal class DokkaSymbolVisitor(
 
 
     // ----------- Translators of modifiers ----------------------------------------------------------------------------
+    /**
+     * Dokka has its own conditions for the keyword `override`
+     */
+    private fun KaSession.isDokkaOverride(symbol: KaCallableSymbol): Boolean {
+        fun KaCallableSymbol.isCompilerOverride() =
+            if (this is KaPropertySymbol) isOverride else if (this is KaNamedFunctionSymbol) isOverride else error("Should be property or function but was '${this::class}'")
+        fun KaSymbol.isFake() = origin == KaSymbolOrigin.SUBSTITUTION_OVERRIDE || origin == KaSymbolOrigin.INTERSECTION_OVERRIDE
+
+
+        /**
+         * In the following example, class A has two fake functions: one with the keyword `override`, and the other without.
+         * In the compiler, both are treated as overrides (`isOverride=true`).
+         *
+         * ```kt
+         * class A<T>  : C<T>()
+         *
+         *  open class C<T> : D<T> {
+         *     override fun f(a: T) = 1
+         *     fun f2(a: T) = 1
+         * }
+         *
+         * //...
+         * ```
+         */
+        return if(symbol.isFake()) {
+            symbol.allOverriddenSymbols.firstOrNull { !it.isFake() }?.isCompilerOverride() ?: false
+        } else {
+            symbol.isCompilerOverride()
+        }
+    }
+
     private fun KaDeclarationSymbol.getDokkaModality(): KotlinModifier {
         val isInterface = this is KaClassSymbol && classKind == KaClassKind.INTERFACE
         return if (isInterface) {
@@ -926,33 +957,38 @@ internal class DokkaSymbolVisitor(
         ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { isOverride }
     ).toSet().takeUnless { it.isEmpty() }
 
-    private fun KaPropertySymbol.additionalExtras() = listOfNotNull(
-        ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { (this as? KaKotlinPropertySymbol)?.isConst == true },
-        ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { (this as? KaKotlinPropertySymbol)?.isLateInit == true },
-        //ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
-        ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
-        //ExtraModifiers.KotlinOnlyModifiers.Static.takeIf { isStatic },
-        ExtraModifiers.KotlinOnlyModifiers.Override.takeIf {
-            origin != KaSymbolOrigin.SUBSTITUTION_OVERRIDE && origin != KaSymbolOrigin.INTERSECTION_OVERRIDE && isOverride
-        }
-    ).toSet().takeUnless { it.isEmpty() }
+
+    private fun KaSession.additionalExtrasOfProperty(symbol: KaPropertySymbol) = with(symbol) {
+        listOfNotNull(
+            ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { (this as? KaKotlinPropertySymbol)?.isConst == true },
+            ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { (this as? KaKotlinPropertySymbol)?.isLateInit == true },
+            //ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
+            ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+            //ExtraModifiers.KotlinOnlyModifiers.Static.takeIf { isStatic },
+            ExtraModifiers.KotlinOnlyModifiers.Override.takeIf {
+                isDokkaOverride(this)
+            }
+        ).toSet().takeUnless { it.isEmpty() }
+    }
 
     private fun KaJavaFieldSymbol.additionalExtras() = listOfNotNull(
         ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isStatic }
     ).toSet().takeUnless { it.isEmpty() }
 
-    private fun KaNamedFunctionSymbol.additionalExtras() = listOfNotNull(
-        ExtraModifiers.KotlinOnlyModifiers.Infix.takeIf { isInfix },
-        ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
-        ExtraModifiers.KotlinOnlyModifiers.Suspend.takeIf { isSuspend },
-        ExtraModifiers.KotlinOnlyModifiers.Operator.takeIf { isOperator },
+    private fun KaSession.additionalExtrasOfFunction(symbol: KaNamedFunctionSymbol) = with(symbol) {
+        listOfNotNull(
+            ExtraModifiers.KotlinOnlyModifiers.Infix.takeIf { isInfix },
+            ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
+            ExtraModifiers.KotlinOnlyModifiers.Suspend.takeIf { isSuspend },
+            ExtraModifiers.KotlinOnlyModifiers.Operator.takeIf { isOperator },
 //ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
-        ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { isTailRec },
-        ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
-        ExtraModifiers.KotlinOnlyModifiers.Override.takeIf {
-            origin != KaSymbolOrigin.SUBSTITUTION_OVERRIDE && origin != KaSymbolOrigin.INTERSECTION_OVERRIDE && isOverride
-        }
-    ).toSet().takeUnless { it.isEmpty() }
+            ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { isTailRec },
+            ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+            ExtraModifiers.KotlinOnlyModifiers.Override.takeIf {
+                isDokkaOverride(this)
+            }
+        ).toSet().takeUnless { it.isEmpty() }
+    }
 
     private fun KaNamedClassSymbol.additionalExtras() = listOfNotNull(
         ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { (this.psi as? KtClass)?.isInline() == true },


Test patch:
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt
index 59f7c15c37..f2ac64b288 100644
--- a/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt
@@ -572,6 +572,51 @@ class InheritorsTest : AbstractModelTest("/src/main/kotlin/inheritors/Test.kt",
         }
     }
 
+    @Test
+    fun `substitution override fun and prop (fake override) for override declaration should have the override keyword`() {
+        inlineModelTest(
+            """
+               class A<T>  : C<T>()
+
+                open class C<T> : D<T> {
+                    override fun f(a: T) = 1
+                    override val p: T? = null
+                }
+                
+                interface D<T> {
+                    fun f(a: T) = 0
+                    val p: T? = null
+                }
+            """.trimMargin()
+        ) {
+            with((this / "inheritors" / "A"/ "f").cast<DFunction>()) {
+                name equals "f"
+                extra[AdditionalModifiers]?.content?.values?.firstOrNull()
+                    ?.firstOrNull() equals ExtraModifiers.KotlinOnlyModifiers.Override
+            }
+            with((this / "inheritors" / "C"/ "f").cast<DFunction>()) {
+                name equals "f"
+                extra[AdditionalModifiers]?.content?.values?.firstOrNull()
+                    ?.firstOrNull() equals ExtraModifiers.KotlinOnlyModifiers.Override
+            }
+
+            with((this / "inheritors" / "A"/ "p").cast<DProperty>()) {
+                name equals "p"
+                extra[AdditionalModifiers]?.content?.values?.firstOrNull()
+                    ?.firstOrNull() equals ExtraModifiers.KotlinOnlyModifiers.Override
+            }
+            with((this / "inheritors" / "C"/ "p").cast<DProperty>()) {
+                name equals "p"
+                extra[AdditionalModifiers]?.content?.values?.firstOrNull()
+                    ?.firstOrNull() equals ExtraModifiers.KotlinOnlyModifiers.Override
+            }
+            with((this / "inheritors" / "D"/ "f").cast<DFunction>()) {
+                name equals "f"
+                extra[AdditionalModifiers]?.content?.values?.firstOrNull()?.firstOrNull() equals null
+            }
+        }
+    }
+
     @Test
     fun `substitution override (fake override) fun and prop should not have the override modifier`() {
         inlineModelTest(
@@ -711,4 +756,4 @@ class InheritorsTest : AbstractModelTest("/src/main/kotlin/inheritors/Test.kt",
             }
         }
     }
-}
+}
\ No newline at end of file