From 9a3f9b8734d192205530cf3d5babe4ee17120db1 Mon Sep 17 00:00:00 2001 From: Oleg Makeev Date: Tue, 15 Jul 2025 12:25:09 +0200 Subject: [PATCH 1/2] [KEEP-0389] Adjust the KEEP according to Dokka / Analysis API design discussions See KT-76607 --- ...oc-streamline-KDoc-ambiguity-references.md | 1026 ++++++++++++----- 1 file changed, 734 insertions(+), 292 deletions(-) diff --git a/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md b/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md index b65f83ae4..bb9a6d040 100644 --- a/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md +++ b/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md @@ -1,6 +1,6 @@ # Streamline KDoc ambiguity links -* **Type**: KDoc proposal +* **Type**: Proposal * **Author**: Vadim Mishenev * **Status**: Submitted * **Issues**: [dokka/#3451](https://github.com/Kotlin/dokka/issues/3451), [dokka/#3179](https://github.com/Kotlin/dokka/issues/3179), [dokka/#3334](https://github.com/Kotlin/dokka/issues/3334) @@ -8,17 +8,22 @@ # Summary -This document introduces clarifications and improvements to the existing implementation of [KDoc](https://kotlinlang.org/docs/kotlin-doc.html) regarding resolving ambiguous KDoc links. It addresses the range of issues ([dokka/#3451](https://github.com/Kotlin/dokka/issues/3451), [dokka/#3179](https://github.com/Kotlin/dokka/issues/3179), [dokka/#3334](https://github.com/Kotlin/dokka/issues/3334) ) that Dokka and the IntelliJ Plugin faced during the migration to K2 - Analysis API. +This document introduces clarifications and improvements to the existing implementation of [KDoc](https://kotlinlang.org/docs/kotlin-doc.html) regarding resolving ambiguous KDoc links. +It addresses the range of issues ([dokka/#3451](https://github.com/Kotlin/dokka/issues/3451), [dokka/#3179](https://github.com/Kotlin/dokka/issues/3179), [dokka/#3334](https://github.com/Kotlin/dokka/issues/3334)) that Dokka and the IntelliJ Plugin faced during the migration to the K2 resolver from **Kotlin Analysis API**. - # Motivation - - ## Introduction +# Motivation +## Introduction + +[KDoc](https://kotlinlang.org/docs/kotlin-doc.html) is a language used to write Kotlin documentation. +It's an equivalent of Javadoc from Java. +As well as Javadoc, it allows embedding navigatable references to code declarations right in the documentation using square brackets `[]`. + There are two types of KDoc links to a declaration: - Fully qualified ones, for example `[com.example.classA]`, starting with a full package name -- Relative ones (also known as short names), for example `[memberProperty]` +- Relative ones (also known as short names), for example `[memberProperty]` / `[classA.myObject.method]`. -Also, KDoc allows to refer to: +Also, KDoc allows referring to: - Functional parameters `[p]` or type parameters `[T]`. They can be not only in `@param [p]` or `@param p` - A receiver via `[this]` - Packages @@ -31,7 +36,12 @@ package com.example * [classA], [member], [extension] - relative link */ class classA { - /** + object B { + class C + } + + /** + * [B.C] -- a relative link * [classA], [member], [extension] - relative link */ val member = 0 @@ -47,103 +57,156 @@ class classA { * @param p is also a link */ fun classA.extension(p: Int) = 0 -``` -*Note: This document does not consider the case of extensions (links to extensions) for the sake of simplicity. It is a non-goal and deserves another dedicated document.* +``` +### What is an ambiguity? -## Problem +An ambiguity in KDoc name resolution is a case when there are multiple symbols available for the same KDoc reference. -There are cases when KDoc links are ambiguous meaning there is more than one possible candidate from the users point of view. These cases were discovered by the migration of Dokka to K2 and behave differently in K1 and K2. +Consider the following example: +```kotlin +package A.A -### 1. Self-links +import foo.bar.A -Javadoc and KDoc allow to have links to itself. It is a quite spread practice. For example, self-links are widely used in the Kotlinx libraries because of visual consistency. -However, it can also lead to ambiguous links: -```kotlin - /** - * [A] In K1, it leads to the nested class A.A. In K2 - to the outer class A - */ -class A { +class A + +object Something { class A -} -``` -The case (a nested class with the same name as the enclosing class) can be unpopular since it is banned in Java, C#. -There is a more practical case in Kotlin with a factory function : -```kotlin -/** [A] */ -class A -/** [A] */ -fun A(p: Int) = A() -``` -In K1, both links lead to the class A. In K2, they lead to itself. -This case can be applied to all possible pairs of declaration kinds (function and function also known as overloads problem; property and class...). + fun foo() { + val A = 5 -Also, a constructor has the name of a class. -```kotlin -class A { - /** - * [A] In K1, it leads to the class A. In K2 - to the constructor - */ - constructor(s: String) + /** + * [A] + */ + } } + +fun A(x: Int) {} +fun A(x: String) {} ``` -For Javadoc, see the section `Other languages`. -### 2. links to constructor parameters +It contains various declarations with the same name `A`: imports, packages, classifiers, variables, functions, etc. +To which symbol should the KDoc name `A` resolve to? +This is why KDoc resolution requires a set of strict rules and priorities, +which can help to create a consistent and disambiguating resolution strategy. + +## How does the current K1 / K2 implementation prevent ambiguity? + +There can be various ambiguous cases, +which both K1 and K2 handle using the same mechanisms and rules to deal with them. + +### Self-links +Javadoc and KDoc allow having links to the elements from the context declaration, it is a quite popular practice. +Such links are called `self-links`. +Such links are widely used in the various libraries (e.g. Kotlinx) because of their visual consistency and simplicity. -This case seems valid. ```kotlin -val abc: String = "" +fun foo() {} +val x = 0 +class T + /** -* [abc] to the parameter in K1 and K2. -* For the property, a fully qualified link can be used. -*/ -fun f(abc: String) = 0 + * [foo], [x], [T] - self-links, point to symbols from the context declaration, + * i.e. `foo` function + */ +fun foo(x: Int) {} ``` +Such context symbols have the highest priority among all other symbols with the same name. +Their principle is quite natural and convenient in use, as they cannot be broken by introducing some other declaration. -However, in a primary constructor, the same link can refer to parameters and properties. It does not matter for IDE. Opposite, Dokka has different locations for parameters and properties. +For example, ```kotlin -/** -* [abc] K1 refers to the property `abc`, K2 - to the parameter -*/ -class A(val abc: String) +/** [A] - to val A */ +val A = 0 ``` -From the point of IDE view, the link `[abc]` leads to the same position in a source file independently of whether the `abc` is a parameter or property. - -#### Related problem: Availability/Visibility of constructor parameters -The availability of parameters inside a scope can result in ambiguous links. +After adding a function, the link is left the same. ```kotlin -class A(a: Int) { - /** - * [a] is unresolved in K1. In K2, it is resolved - */ - fun usage() = 0 -} +fun A() = 0 +/** [A] - to val A */ +val A = 0 ``` -## Ambiguity in other cases -Also, there are other cases that can be considered as ambiguous, but their behavior is consistent in K1 and K2. - ### By a kind of a declaration -For some trivial cases (ambiguous links are inside a single scope) there are the predefined priorities of KDoc candidates in the Dokka and IDE K1 implementations: -- Class +### Disambiguation by a kind of declaration and by order of scopes + +When there are no suitable self-links found, +the current resolution strategy handles symbol priorities based on two properties: +- Declaration kind +- Scope of origin + +There are four main kinds of declarations (from higher priority to lower): +- Classifier - Package - Function - Property -For example, +For some name `x`, the resolver iterates through these kinds and considers them one-by-one. +For each declaration kind (except for packages, as their search is global) the resolver tries to find +an instance of this declaration kind with the given name `x` in every scope that is visible from the KDoc position. +It starts from local scopes (function body scope, type scope, etc.) +and proceeds to global scopes (package scope, explicit import scopes, star import scopes, scopes of other packages). +The result of such an approach is a single most local symbol of the highest priority kind possible. + +This strategy will be then referred to as the **declaration-kind-first approach**. + +Some examples: ```kotlin val x = 0 fun x() = 0 -/** here [x] refers to the function */ +/** here [x] refers to the function, as it has higher priority */ fun usage() = 0 ``` -These priorities allow Dokka to avoid ambiguity in some links except the case `Self-links` above. -### By overloads +```kotlin +class Something + +object A { + fun Something() {} + + /** here [Something] refers to the class, as classes have higher priority than functions */ + fun usage() = 0 +} +``` + +```kotlin +class Something + +object A { + class Something + + /** Here [Something] refers to the nested class, + * as the nested class is from a local scope, which is seen from this position + */ + fun usage() = 0 +} +``` + + +Please note again that this only applies to cases when no `self-links` (mentioned above) were found: + +```kotlin +class Something + +object A { + /** Here [Something] refers to the function, as it is our context declaration. + * This link is considered to be a so-called self-link + */ + fun Something() {} +} +``` + +So the general priority order can be described as following: +- Self-links (symbols from the context declaration) +- Classifier (from local to global) +- Packages (global search) +- Functions (from local to global) +- Properties (from local to global) + +### Disambiguation by overloads In the case of overload functions, a KDoc link leads to the first occurrence in code. For example, ```kotlin @@ -164,33 +227,46 @@ fun x() = 0 fun usage() = 0 ``` -### Order of scopes +## Additional notes: K1 / K2 inconsistencies -Currently, an inner scope already has a priority over outer scopes. -Let's consider the following general example to understand the current resolve of KDoc link : -```kotlin -class B +The resolution behavior in the following cases is inconsistent between K1 and the current K2 implementations. -/** [B] - K1, Javadoc and K2 refer to the nested class A.B */ +### 1. Inconsistency with self-links + +```kotlin + /** + * [A] In K1, it leads to the nested class A.A. In K2 - to the outer class A + */ class A { - class B + class A } ``` -The search for the declaration is initially done in the members. Therefore, K1 (IDE and Dokka), K2, and Javadoc refer to a nested class B. -For example, Swift has the opposite behavior. +The case (a nested class with the same name as the enclosing class) can be unpopular since it is banned in Java, C#. -Here is another example: +There is a more practical case in Kotlin with a factory function : ```kotlin -val a = 0 -fun a() = 0 - -/** [a] K1 and K2 refer to the parameter */ -fun f(a: Int) = 0 +/** [A] */ +class A +/** [A] */ +fun A(p: Int) = A() ``` +In K1, both links lead to the class A. In K2, each of them leads to its context declaration. +This case can be applied to all possible pairs of declaration kinds +(function and function, also known as the overload problem; property and class...). +Also note that constructors have the name of the corresponding class during the resolution. +```kotlin +class A { + /** + * [A] In K1, it leads to the class A. In K2 - to the constructor + */ + constructor(s: String) +} +``` -#### Related problem: Availability/Visibility of nested classes from base classes -However, "inherited" nested classes are in question: +### Related problem: Availability/Visibility of nested classes from base classes +At the moment, the resolution of references to "inherited" nested classes is different between two implementations, +as it's not supported by K1: ```kotlin open class DateBased { class DayBased @@ -201,24 +277,129 @@ open class DateBased { */ class MonthBased : DateBased() ``` -This causes inconsistent behaviour. In K1, the link `[B]` is unresolved, but it resolves inherited members. +K1 implementation +failing to determine what declarations the current context of KDoc contains leads to unresolved references. +However, inherited members are still seen and resolved. -It is a problem of determining what declarations the current context of KDoc contains. Together with that, an undefined name resolution for a referred declaration causes ambiguous links. - +### 2. Ambiguity with links to constructor parameters + +In a primary constructor, the same link can refer to parameters and properties. +This does not matter for the IDE. +Opposite, Dokka treats parameters and properties differently. +```kotlin +/** +* [abc] K1 refers to the property `abc`, K2 - to the parameter +*/ +class A(val abc: String) +``` +From the point of IDE view, +the link `[abc]` leads to the same position in a source file independently of whether the `abc` is a parameter or property. + +### Related problem: Availability/Visibility of constructor parameters +The availability of parameters inside a scope can result in ambiguous links (in case of other symbols with the same name inside the type scope). +```kotlin +class A(a: Int) { + /** + * [a] is unresolved in K1. In K2, it is resolved + */ + fun usage() = 0 +} +``` + +# Issues with the existing K2 implementation + +## Global declarations breaking the local resolution + +The main concern is the order of priorities in the **declaration-kind-first** approach. +With the current approach, global context can easily break the local resolution. + +Take a look at the following example: +```kotlin +class bar + +fun foo() { + fun bar() {} + /** + * [bar] -- Points to the local function until a global class is introduced. + * No way to refer to the function afterward + */ +} +``` + +An introduction of a global declaration with a higher priority makes +all local references with the same name point to it. + +And it's not always that easy to track it down. +Accidental introduction of an import with the same name also poses the same danger: +```kotlin +import foo.bar + +fun foo() { + fun bar() {} + /** + * [bar] -- Points to the local function until a global class is introduced. + * No way to refer to the function afterward + */ +} +``` + +The same question can be applied to packages having the second-highest priority. +This local shadowing issue is even harder to track down, as now it's not limited to just a single file. + +```kotlin +val io = 5 + +/** [io] -- points to `val io` until some `io` package is introduced somewhere else */ +fun foo() {} +``` + +The **declaration-scope-first** approach is inconsistent by itself, +as it initially assigns self-links (i.e., local resolution) with the highest priority +but then proceeds to prefer global symbols to local ones: +```kotlin +class foo + +object Something { + /** + * [foo] - POINTS TO THE FUNCTION + */ + fun foo() {} + + /** + * [foo] - POINTS TO THE CLASS + */ + fun bar() {} +} +``` + +## KDoc names pointing to a single symbol +Since some declarations might have overloads, +KDoc names being able to point just to a single declaration feel too restricting. +Sometimes it’s crucial to support this behavior +(in the case with Dokka and other documentation-generating tools). +However, we could enhance the user experience by showing a drop-down list with all the found overloads in the IDE. +For use-cases when just a single declaration is required, +users of the KDoc resolver could pick the first element from the resulting collection, +which preserves the behavior. # Proposal -This document proposes that existing KDoc links should point to only one specific location in the same way as the compiler does for name resolution. Meanwhile, as stated above, the ambiguity of KDoc links is inevitable +This document proposes that existing KDoc links should point to only one +(or multiple in case of overloads) specific location in the same way +as the compiler does for name resolution. -Just a warning about ambiguity does not make sense as there is no disambiguator at this time. Therefore, the proposed approach is to prioritize candidates for KDoc links to choose the only one. +Meanwhile, the ambiguity of KDoc links is inevitable. +Just a warning about ambiguity does not make sense as there is no disambiguator at this time. +Therefore, the proposed approach is to prioritize candidates for KDoc links +to deterministically choose a subset of all suitable declarations. -To have unambiguous links and resolve the origin problem, the following questions should be covered: - - Which names are available in a current KDoc - - If there is more than one candidate, what priorities there are - - If there are candidates that have the same priority, how choose the only one. +To provide this disambiguation mechanism, the following questions should be covered: + - Which names are available from the current KDoc? + - If there is more than one candidate, what should be prioritized? + - If there are candidates that have the same priority, which should be chosen? -The following sections address these 3 questions. +The following sections addresses these three questions. ## Context of KDoc @@ -227,12 +408,12 @@ For example, the KDoc context of a class should correspond to the first line of ```kotlin /** - * [prop] is resolved + * [p] is resolved * [param] ERROR: unresolved */ class A(param: Int) { companion object { - val prop = 0 + val p = 0 } } ``` @@ -252,19 +433,25 @@ class A { } } ``` -That means that the context of the KDoc link should contain all available names on the first line of a fake function. Note the rule ignores the visibility of declarations. -Similarly, the KDoc context of functions/constructors corresponds to the beginning of the first line of a body and the KDoc context of properties corresponds to the beginning of property initializers. +That means that the KDoc link should have the set of available names as the first line of a fake function. +Note the rule ignores the visibility of declarations. +Similarly, the KDoc context of functions/constructors corresponds to the beginning of the first line of a body, +and the KDoc context of properties corresponds to the beginning of property initializers. - ### KDoc tag sections -The KDoc tag sections (`@constructor`, `@param`, `@property`) introduce their new scope in the documentation. +### KDoc tag sections +The KDoc tag sections (`@constructor`, `@param`, `@property`) should introduce their new scope in the documentation. +Previously, they've never affected the resolution. +All other tag sections must not affect the resolution in any sense. -The tag `@constructor` has context where parameters of a primary constructor are available. +#### @constructor section + +The tag `@constructor` can be applied to classes and secondary constructors +and has context where parameters of the context constructor and the constructor itself are available. ```kotlin /** * [p] ERROR: unresolved * @constructor [p] is resolved - * @property abc [p] is resolved */ class A(p: Int, val abc: String) { /** @@ -280,257 +467,509 @@ class A(p: Int, val abc: String) { val prop = p } ``` -Also, an illustrative example of the concept behind a dedicated scope for `@constructor` is provided in the next section. +There is another example for `@constructor`: +```kotlin +/** + * [A] - to the class, [abc] - to the property + * @constructor [A] - to the constructor, [abc] - to the parameter + */ +class A(var abc: String) +``` -## Priorities +The idea behind this resolution is rather simple. +The constructor from the example above can be easily refactored to be a secondary constructor. +Here we clearly see that the `abc` parameter only belongs to this constructor and not seen from the class +(i.e., from the first line of a fake function). +However, the property is fully accessible. -In order to resolve the ambiguity of KDoc links, rules of priorities can be introduced. The pivot point is to give the highest priority to self-links and reuse already existing priorities. +```kotlin +/** + * [A] - to the class, [abc] - to the property + */ +class A private constructor() { + /** + * [A] - to the constructor, [abc] - to the parameter + */ + constructor (abc: String): this() + lateinit var abc: String +} +``` -Ultimately, the logic of disambiguation can be formulated as follows: -1. Treat as a short name link the following priorities from highest to lowest: -- Self-link -- Class. Also, it includes annotation classes, interfaces and objects. -- Package -- Function -- Property ```kotlin -/** [A] - to class A */ -class A -/** [A] - to fun A(a: Int) */ -fun A(a: Int) +/** +* [A] - to the class, [a] - unresolved +* @constructor [A] - to the current constructor, [a] - to the parameter +*/ +class A(a: Int) { + + /** + * [A] - to the current constructor, [a] - unresolved + * @constructor [A] - to the current constructor, [a] - to the current parameter + */ + constructor(a: String) : this(a.length) +} +``` +Note that the constructor symbol is prioritized over parameter symbols: +```kotlin /** - * [A] leads to the class A - * since the priority of a class is higher than a function - */ - fun usage() = 0 + * [abc] - to the class + * @constructor [abc] - to the constructor + */ +class abc(var abc: String) ``` -```kotlin -/** [b] - to val b */ -val b = 0 -/** [b] - to fun b() */ -fun b() = 0 +#### @param section + +`@param` section can be applied to classes and functions. +When applied to classes, it also makes the constructor and its parameters available the same way `@constructor` does; +however, parameters have higher priority. +Also `@param` doesn't affect the resolution for secondary constructors, as it doesn't make any sense to do so. -/** [b] - to fun b() */ -fun usage() = 0 -``` -Here is an example with a nested class: ```kotlin /** - * [A] - to the top-level class A - * [A.A] - to the nested class A - */ -class A { - /** [A] - to the nested class A */ - class A - /** [A] - to fun A(p: Int) */ - fun A(p: Int) { - return A() - } -} + * [A] - to the class, [abc] - to the property + * @param [A] - to the constructor, [abc] - to the parameter + */ +class A(var abc: String) ``` -In the case of overload ambiguity, the previous behaviour (to the first occurrence) is left unchanged. + ```kotlin -/** [b] - to fun c(a: String) */ -val c(a: String) = 0 -/** [c] - to fun c() */ -fun c() = 0 +/** + * [abc] - to the class + * @param [abc] - to the parameter + */ +class abc(var abc: String) +``` -/** [c] - to fun c(a: String) */ -fun usage() = 0 +In the case of functions, all the parameters are available from a regular KDoc. +`@param` section can be used to refer to the parameter instead of the function in case of name clashes. +```kotlin +/** + * [abc] - to the function + * @param [abc] - to the parameter + */ +fun abc(var abc: String) {} ``` -2. Otherwise, if no short name link is found, treat it as a fully qualified link with the priorities from highest to lowest: -- Class. Also, it includes annotation classes, interfaces and objects. -- Package -- Function -- Property +#### @property section -```kotlin -package com -/** [com.A] - to class A */ -class A -/** [com.A] - to class A */ -fun A(a: Int) +`@property` section can be applied to classes to prioritize properties from this class. +```kotlin /** - * [com.A] leads to the class A - * since the priority of a class is higher than a function - */ - fun usage() = 0 + * [abc] - to the class + * @property [abc] - to the property + */ +class abc(var abc: String) ``` -Note that self links are not prioritized for fully qualified names so it allows referring to a returning class from a factory function. +A small summary example: +```kotlin +/** +* [abc] - to the class +* + * @constructor [abc] - to the constructor + * @param [abc] - to the parameter + * @property [abc] - to the property + */ +class abc(var abc: String) +``` -### Self-links +## Resolution Strategy -The idea behind giving high priority to self-links is that they do not depend on context and are more consistent. +We would like to make the resolution of KDoc names closer to what the compiler does when resolving references in code. +This approach is more natural, clear to the user, and convenient, as users don't have to +The base logic here is that if some name `X` is resolved to declaration `Y` in context `C` during the compilation, +then the same KDoc reference `X` located in some KDoc in the same context `C` should be resolved to `Y`. -For example, +Generally speaking, +the compiler also prioritizes symbols during resolution based on declaration kinds and scopes of origin. +However, the priorities are switched: locality of scopes has a higher priority than declaration kinds. +The compiler also doesn't have a strict order of declaration kinds, +as it has more information about a required symbol from the use-site +(not just name as KDoc does). ```kotlin -/** [A] - to val A */ -val A = 0 +fun foo() {} + +class foo(val x: Int) + +fun main() { + foo() // call to the function +} ``` -after adding a function, the link is left the same. Otherwise, a function has a higher priority than a property. + ```kotlin -fun A() = 0 -/** [A] - to val A */ -val A = 0 +val foo = 9 + +fun main() { + class foo() + + foo // variable access +} ``` -There is another example for `@constructor`: ```kotlin -/** - * [A] - to the class, [abc] - to the property - * @constructor [A] - to the constructor, [abc] - to the parameter - */ -class A(var abc: String) +fun foo() {} + +class foo() + +fun main() { + foo() // call to the function +} ``` -That can be refactored to + ```kotlin -/** - * [A] - to the class, [abc] - to the property - * Note [A.A] is unresolved - */ -class A() { - /** - * [A] - to the constructor, [abc] - to the parameter - */ - constructor (abc: String): this() {} - lateinit var abc: String +class foo() + +fun main() { + fun foo() {} + + foo() // call to the function } ``` -Additionally, he highest priority of self links can be applied within KDoc sections such as `@property` and `@param`. - +Since the only information the KDoc resolver has is the name of the symbol it's looking for, +prioritization of declaration kinds is vital. +But the main point of this switch is to truly and always prefer local over global +by iterating through scopes from local to global and then looking for various symbols +(w.r.t. their declaration kinds) just inside the current scope. +This approach will be referred to as the **scope-first approach**. + +More info on the compiler resolution can be found on the following resources: +* [Name Resolution | Compiler Specification Docs](https://github.com/JetBrains/kotlin/blob/4c3bbc5d4b8d8ada9f8738504b53c44019843d3b/spec-docs/NameResolution.adoc) (can be outdated) +* [Linked scopes | Kotlin Specification](https://kotlinlang.org/spec/scopes-and-identifiers.html#linked-scopes) +* [Overload resolution | Kotlin Specification](https://kotlinlang.org/spec/overload-resolution.html#overload-resolution) + +But now we have to deal with the global package search, as we cannot integrate it into this local scope traversal. +So this search should either go first (have the highest priority) or go last. +The first option obviously contradicts the idea of preferring local declarations as much as possible. +So the proposed idea is to assign it with the lowest priority. +It makes it impossible to refer to packages in some cases, +but it's inevitable when there is no any disambiguating syntax: ```kotlin -/** - * [Abc] - to the class - * @property [Abc] - to the property - */ -class Abc(val Abc: Int) +package io -/** - * [Abc] - to the function - * @param [Abc] - to the param - */ -fun Abc(Abc: Int) +val io = 5 + +/** [io] -- points to `val io`, [io.io] - points to `val io` */ +fun foo() {} ``` +That was an informal description of the mindset behind this change. +Let's systemize this information in the following chapters: -## Order of scopes +## Implementation details -However, there may be more than one declaration with the same priority if they come from different scopes. +All names should be treated according to the following three-step algorithm: + +1. [Perform self-link search](#step-1-perform-self-link-search) +2. [Perform scope traversal](#step-2-perform-scope-traversal) +3. [Perform package search](#step-3-perform-package-search) + +### Step 1. Perform self-link search +When the given name is short, the resolution should start by looking for suitable symbols in the context declaration. +It's vital to consider thew tag section the KDoc is contained in. + +It's also important to handle `[this]` receiver links in a proper way. + +### Step 2. Perform scope traversal + +#### Short names treatment +The resolver gathers all the scopes that are visible from the current declaration, +which can be done using `ContextCollector` from Kotlin Analysis API. +`ContextCollector` already provides scopes in the order the compiler processes them during the name resolution. +All kinds of provided scopes can be found in `KaScopeKind`. + +A general variety of scopes can be described as the following: +1. Type parameter scope +2. Local scope +3. Type scope +4. Static member scope +5. Explicit importing scope +6. Package scope +7. Explicit star importing scope +8. Default importing scope +9. Default star importing scope -For example, ```kotlin -// FILE: a. kt -package com.example.pkg1 +// Package scope +package myPackage -class A(p: Int) +import foo.bar.A // Explicit regular importing scope +import something.* // Explicit star importing scope -// FILE: b. kt -package com.example.pkg2 -import com.example.pkg1.A +class MyClass { + // Type scope -class A(p: String) + companion object { + // Static member scope + fun staticFoo() {} + } -/** - * [com.example.pkg1.A] and [com.example.pkg2.A] are both classes - * available here by short names according to the current proposal - */ -fun usage() { - A(1) // resolved to com.example.pkg1.A - A("") // resolved to com.example.pkg2.A + fun foo() { // Type parameter scope + // Local scope + } } ``` -For declarations with the same priority and the same name, selecting a target declaration should be based on the order of scopes as implemented in the compiler. +Then it iterates through all the scopes and gathers suitable symbols according to the following priorities: +1. Classifier +2. Function +3. Property -Thus, the order of declarations inside a single group of priorities is completely determined by the implementation of the compiler. The implemented order is described [here](https://github.com/JetBrains/kotlin/blob/4c3bbc5d4b8d8ada9f8738504b53c44019843d3b/spec-docs/NameResolution.adoc) (can be outdated). +If any symbols are found in the current scope, +all these symbols should be immediately returned in the order of their priorities. -Also, the order is defined by the Kotlin Language Specification: [Linked scopes](https://kotlinlang.org/spec/scopes-and-identifiers.html#linked-scopes), [Overload resolution](https://kotlinlang.org/spec/overload-resolution.html#overload-resolution) and other sections. +#### Multi-segment names treatment +The handling of multi-segment names here is a bit trickier. +A multi-segment name can be either a relative name or a global FQN. +And there is no way to tell which one we are looking at. +* [Relative names](#relative-names) +* [Global names](#global-names) -For example, +##### Relative names ```kotlin -// FILE: a. kt -package com.example.pkg1 +object Something { + class Foo { + companion object { + class Bar + } + + /** + * [Foo.Companion.Bar] -- Multi-segment, but not global FQN + */ + fun foo() { + Foo.Companion.Bar() + } + } +} +``` +To handle such cases, the scopes retrieved for the traversal should be slightly transformed. +If a given name has several segments, then it should be accessible by that name from some of these outer scopes. +In fact, the resolver should only pick and process just a single scope, +see the reasoning behind this in the [Restrictions of multi-segment names resolution](#restrictions-of-multi-segment-names-resolution). + +Imagine that we have a name `A.B.foo`. +We assume that this link is relative, so in each scope we have to find a class-like (i.e., a declaration container) `A`, +then if `A` is found, we should start looking for class-like `B` in its class scope and so on. +Then the rest of the logic is quite simple: retrieve symbols from this scope by the short name (`foo`). + +The resolver should iterate through scopes from local to global +and look for a chain of nested classifiers starting in the current scope. +If this chain is found, +the resolver just picks this classifier member scope and looks for symbols with the given short name. +However, if, at some point of this symbols-by-segment search, +the resolver comes across some non-function symbol from which +it's impossible to continue the chain, the resolver should stop and consider this link as unresolved. +Examples of such cases are classes with no nested classifiers matching the next segment and properties. +Again, see [following chapter](#restrictions-of-multi-segment-names-resolution) for more info. + +##### Global names +A multi-segment name can also be global, i.e., +located in some other package visible from the current one and not imported in the current file. +Then the containing scope of this declaration is not directly visible from the current position +(when using `ContextCollector` from AA), so this case should be handled manually. + +If we have some name `A.B.C.D.foo`, then we have to find the longest existing package, +which name is a segment prefix of this link (search for `A.B.C.D`, then for `A.B.C` and so on). +Let's imagine that some package with `A.B` name is found. +The only thing left to do is to resolve `C.D.foo` inside this package scope. +Now it's quite similar to the logic we had for relative and short names. +Firstly, the resolver should find all nested class-likes on its way to the short name (`C.D` in this case). +If this chain of nested classifiers is found, finding the shourt name `foo` in this classifier scope is trivial. -val a = 0 +```kotlin +package A.B -// FILE: b. kt -package com.example.pkg2 -import com.example.pkg1.a // (1) -import com.example.pkg1.* // (3) +class C { + object D { + fun foo() {} + } +} +``` -val a = 0 // (2) +This global name search should not be performed when there are conflicting non-function declarations +found in any of the local scopes, +see [Restrictions of multi-segment names resolution](#restrictions-of-multi-segment-names-resolution) for more info. + +##### Restrictions of multi-segment names resolution +It's important to remind that we would like this resolution to be as close as possible to the compiler. +So we should only resolve multi-segment names when the compiler does so. +Imagine that we have some name `A.B.C` that we would like to point to. +The location of this declaration relatively to the given position is not important. + +Here we will put a declaration in some other package +just that there are no conflicting overloads within the same package. +```kotlin +// FILE: A.B/C.kt +package A.B + +class C + +// FILE: Usage.kt -/* [a] leads to the imported [com.example.pkg1.a] (1) */ fun usage() { - a // resolved to the imported com.example.pkg1.a (1) + A.B.C() // RESOLVED } +``` -/* [a] leads to the imported [com.example.pkg1.a] (1) */ -val usage = a // resolved to the imported com.example.pkg1.a (1) +The reference is correctly resolved. +But then there might be other declarations in the usage file. +```kotlin +// FILE: Usage.kt + +fun A() = 5 + +fun usage() { + A.B.C() // RESOLVED +} ``` -In the example, top-level declarations are prioritized in the following order: - - (1) Explicit imports - - (2) Functions in the same package _Such function may be located in the other files in the same package._ - - (3) Star-imports - - (4) Implicitly imported functions (from stdlib) +```kotlin +// FILE: Usage.kt + +val A = 5 + +fun usage() { + A.B.C() // UNRESOLVED +} +``` -Here is yet another example. ```kotlin -fun ff() {} // (4) -open class B { - fun ff() {} // (2) -} -class A : B() { - public inner class C : B() { - fun ff() {} // (1) - - /** [ff] refers to (1)*/ - fun usage() {} - } - - companion object { - fun ff() {} // (3) - } +// FILE: Usage.kt + +class A + +fun usage() { + A.B.C() // UNRESOLVED +} +``` + +```kotlin +// FILE: Usage.kt + +class Something + +fun Something.A() {} + +fun Something.usage() { + A.B.C() // RESOLVED +} +``` + +```kotlin +// FILE: Usage.kt + +class Something + +val Something.A: Int + get() = 5 + +fun Something.usage() { + A.B.C() // UNRESOLVED } ``` -In this example, member declarations are ordered by scopes as follows: - - (1) Members directly declared in the same class - - (2) Inherited members - - (3) Members from companion objects - - (4) Outer scope -## Alternative -The problem of ambiguous KDoc links can be solved by tooling (Dokka and IDE). -Dokka can show all possible candidates *via a popup with an interactive list* in the same way as IDE does it for ambiguous resolving in Javadoc etc. +As we can see, the compiler only resolves this name when there are +no other non-function more local declarations that are resolvable by some prefix of the given name. +Otherwise, some prefix is resolved to this declaration and the rest of the chain is unresolved, +as this declaration doesn't have any suitable nested symbols. + +This should be also taken into account when resolving links in KDoc: +* Before performing a scope transformation for relative names, the resolver has to make sure that there are no +non-function declarations matching some prefix of the given name in a more local scope. +If such a declaration is found in some scope, then there is no need to process all further scopes, +as the compiler would resolve this prefix to the declaration in this scope. + +* The same is applied to the global name search: if such a conflicting declaration is already found in some local scope, +the resolver should not proceed to the global retrieval. +### Step 3. Perform package search +If no symbols were found on any of the previous steps, a global package search should be performed. +Note that the real package name should fully match the name specified in the link. +If a link is just `bar`, then it cannot be resolved to some package called `foo.bar`, +as it matches only the last segment but not the full package name. + + +### Result of the resolution +The result of the resolution is the set of all symbols found in self-links (for short names) +plus symbols from the first non-empty scope sorted by their priorities. + +Unfortunately, there is no disambiguation syntax +that would allow explicitly specifying the kind of the declaration the current link should point to. +To overcome this issue at the current moment, +the resolver should return all symbols that the compiler could resolve to the given name from the given position. + +```kotlin +/** + * @see Foo() function. - leads to the interface, + * no way to point to the function. + */ +interface Foo + +fun Foo(): Foo = object : Foo {} +``` + +It's important to preserve the priority of symbols in the resulting collection for purposes +specified in [Handling resolved symbols on the use-site](#handling-resolved-symbols-on-the-use-site). + +## Handling resolved symbols on the use-site +There are a number of various logic pieces that heavily rely on the KDoc name resolver: +* Import optimizer +* Usage finder (that also highights declarations that the link points to) +* Navigation actions +* Documentation renderers (Dokka, rendered HTMLs in the IDE) + +We would like all these usages to be as consistent as possible with each other. + +Currently, most usages handle all the symbols that are returned by the resolver (i.e., import optimizer). +However, navigation actions that are also provided to users only navigate to a single declaration from the returned set +(the first one to be precise). +This might be quite confusing to users, as in the following code both functions are highlighted as *used*, +but *Go To Declaration* action takes users just to the first declaration: + +```kotlin +fun foo() {} +fun foo(x: Int) {} + +/** + * [foo] + */ +fun usage() {} +``` + +The IDE should show a popup drop-down menu the same way it does with other ambiguous references: ![example](https://i.ibb.co/dKQkshh/image.png?) -It was rejected in favour of the current proposal. + + +However, tools that require just a single resolved symbol, e.g., +for HTML rendering purposes, should be able to pick just the first symbol from the resulting collection. +That's why it's important to construct this collection in such a way +that more local symbols with higher priority go first. +It makes the result consistent and reliable. # Appendix ## Other languages -All considered languages can be divided into two groups: - * ambiguous links are allowed (Java); - * ambiguous links are disallowed and cause a warning. In this case, a language provides a mechanism to disambiguate them. (Swift, C#, Rust, Golang) +All considered languages can be divided into two groups by the way they deal with ambigous links: + * Ambiguous links are allowed (Java); + * Ambiguous links are disallowed and cause a warning. In this case, a language provides a mechanism to disambiguate them. (Swift, *C#*, Rust, *Golang*) ### Javadoc -[JavaDoc Documentation Comment Specification: References](https://docs.oracle.com/en/java/javase/22/docs/specs/javadoc/doc-comment-spec.html#references) describes the specification of Javadoc references a little: +[Javadoc Documentation Comment Specification: References](https://docs.oracle.com/en/java/javase/22/docs/specs/javadoc/doc-comment-spec.html#references) describes the specification of Javadoc references a little: > the parameter types and parentheses can be omitted if the method or constructor is not overloaded and the name is not also that of a field or enum member in the same class or interface. -Homewer, [Javadoc's style guide](https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#styleguide) allows to omit parentheses for the general form of methods and constructors. In this case, an ambiguous reference will lead to: +Homewer, +[Javadoc's style guide](https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#styleguide) allows +omitting parentheses for the general form of methods and constructors. +In this case, an ambiguous reference will lead to: - a field if it exists - - otherwise, to the first occurrence of overload in the code. + - otherwise, to the first occurrence of overload in the code. For example, ```java /** @@ -568,13 +1007,14 @@ Meanwhile, a class always has a priority: public int JavaClassA2 = 0; } ``` -Also, Javadoc does not have references to function parameters. +Also, Javadoc does not provide a way to reference function parameters. ### JavaScript (JSDoc) -JSDoc does not have such problems with relative references since it has a unique identifier like a fully qualified path in Kotlin. -For `@link` tag ( https://jsdoc.app/tags-inline-link ) there is a namepath. +JSDoc does not have such problems with relative references +since it has a unique identifier like a fully qualified path in Kotlin. +For `@link` tag (https://jsdoc.app/tags-inline-link) there is a namepath. ```js /** * See {@link MyClass} and [MyClass's foo]{@link MyClass#foo} that just opens MyClass.html#foo @@ -600,7 +1040,8 @@ class MyClass { foo = "John"; } ``` -A namepath provides a way to do so and disambiguate between instance members, static members and inner variables. See [https://jsdoc.app/about-namepaths](https://jsdoc.app/about-namepaths) +A namepath provides a way to do so and disambiguate between instance members, static members, and inner variables. +See [https://jsdoc.app/about-namepaths](https://jsdoc.app/about-namepaths) ```js /** {@link Person#say} // the instance method @@ -621,11 +1062,12 @@ Person.say = function() { } ``` -Also, it does not allow to have a class and a function with the same name in a single scope. +Also, it does not allow having a class and a function with the same name in a single scope. ### Python (Sphinx) -The Python allows to have a cross-reference via the markup see [https://www.sphinx-doc.org/en/master/usage/domains/python.html#cross-referencing-python-objects](https://www.sphinx-doc.org/en/master/usage/domains/python.html#cross-referencing-python-objects) +Python allows having a cross-reference via the markup +(see [Cross-referencing Python objects](https://www.sphinx-doc.org/en/master/usage/domains/python.html#cross-referencing-python-objects)). There are some roles: `:py:class` `:py:func` `:py:meth:` `:py:attr:` and so on. > Normally, names in these roles are searched first without any further qualification, then with the current module name prepended, then with the current module and class name (if any) prepended. @@ -653,12 +1095,13 @@ Python does not support method overloading. ### Swift -Swift does not allow ambiguous links, although IDE suggests fixing them. If a reference is ambiguous, it is unresolved and will be displayed as plain text. +Swift does not allow ambiguous links, although the IDE suggests fixing them. +If a reference is ambiguous, it is unresolved and will be displayed as plain text. ![example](https://i.ibb.co/9s3pmyp/Screenshot-2024-07-13-at-12-38-09-AM.png) -Swift has some approaches to disambiguate links. +Swift also provides mechanisms for the links disambiguation. See [Navigate to a symbol](https://www.swift.org/documentation/docc/linking-to-symbols-and-other-content#Navigate-to-a-Symbol) #### 1. Suffixes of overloads @@ -686,9 +1129,12 @@ class FF { ### C# -It has [XML documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments). A generated XML file then passes to a Documentation generator, e. g. Sandcastle. +It has [XML documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments). +A generated XML file then passes to a Documentation generator, e.g., Sandcastle. -The `cref` attribute is used to provide a reference to a code element. The C# documentation does not describe the cases of overloads and ambiguous references. The support of such references depends on a Documentation generator. +The `cref` attribute is used to provide a reference to a code element. +The C# documentation does not describe the cases of overloads and ambiguous references. +The support of such references depends on a Documentation generator. The documentation generator must respect namespace visibility according to using statements appearing within the source code. @@ -703,7 +1149,7 @@ public class Utils { static void Foo(int a) { } } ``` -As Java, C# does not allow to have a nested class with the same name as enclosing class +Similar to Java, C# does not allow having a nested class with the same name as enclosing class. ### Rust In case of ambiguity, the rustdoc will warn about the ambiguity and suggest a disambiguator. @@ -726,19 +1172,15 @@ See [Go Doc Comments: Links](https://tip.golang.org/doc/comment#doclinks) However, the Godoc does not support links very well to check. - - - - ## Visibility -KDoc ignores visibility, i.e. all declarations are public for KDoc references. +KDoc ignores visibility, i.e., all declarations are public for KDoc references. Whether resolving KDoc references should take visibility into account is an open question. -Javadoc can take visibility into account for particular cases (not specified), but for most cases it works like KDoc. +Javadoc can take visibility into account for particular cases (not specified), but for most cases it works just like KDoc. ```java /** - * {@link JavaD} is resolved despite `private` and displayed as plain text + * {@link JavaC} is resolved despite `private` and displayed as plain text */ public class JavaB { private class JavaC {} From 20ac6e5317f9692dd0e4fb7cc375116ebfae7b7d Mon Sep 17 00:00:00 2001 From: Oleg Makeev Date: Thu, 18 Sep 2025 11:33:26 +0200 Subject: [PATCH 2/2] [KEEP-0389] Adjust the KEEP according to the second round of discussions See KT-76607 --- ...oc-streamline-KDoc-ambiguity-references.md | 889 +++++++++--------- 1 file changed, 468 insertions(+), 421 deletions(-) diff --git a/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md b/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md index bb9a6d040..9ae7070fa 100644 --- a/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md +++ b/proposals/KEEP-0389-kdoc-streamline-KDoc-ambiguity-references.md @@ -1,8 +1,9 @@ # Streamline KDoc ambiguity links * **Type**: Proposal -* **Author**: Vadim Mishenev -* **Status**: Submitted +* **Authors**: Vadim Mishenev, Oleg Makeev +* **Contributors**: Marco Pennekamp, Yan Zhulanow, Oleg Yukhnevich, Azat Abdullin +* **Status**: In progress * **Issues**: [dokka/#3451](https://github.com/Kotlin/dokka/issues/3451), [dokka/#3179](https://github.com/Kotlin/dokka/issues/3179), [dokka/#3334](https://github.com/Kotlin/dokka/issues/3334) * **Discussion**: [#389](https://github.com/Kotlin/KEEP/issues/389) @@ -17,15 +18,16 @@ It addresses the range of issues ([dokka/#3451](https://github.com/Kotlin/dokka/ [KDoc](https://kotlinlang.org/docs/kotlin-doc.html) is a language used to write Kotlin documentation. It's an equivalent of Javadoc from Java. -As well as Javadoc, it allows embedding navigatable references to code declarations right in the documentation using square brackets `[]`. +As well as Javadoc, it allows embedding navigatable links to code declarations +right in the documentation using square brackets `[]`. There are two types of KDoc links to a declaration: - Fully qualified ones, for example `[com.example.classA]`, starting with a full package name -- Relative ones (also known as short names), for example `[memberProperty]` / `[classA.myObject.method]`. +- Relative ones (also known as short names), for example `[memberProperty]` / `[ClassA.myObject.method]`. -Also, KDoc allows referring to: -- Functional parameters `[p]` or type parameters `[T]`. They can be not only in `@param [p]` or `@param p` -- A receiver via `[this]` +Besides regular top-level declarations, KDoc also allows referring to: +- Functional parameters, context parameters, or type parameters +- Extension receiver via `[this]` - Packages Here is an example for understating: @@ -59,9 +61,52 @@ class classA { fun classA.extension(p: Int) = 0 ``` +### Visibility +KDoc ignores visibility, i.e., all declarations are treated as visible from the KDoc position. +However, whether resolving KDoc links should take visibility into account is an open question, +which is out of the scope of this proposal. + +On the opposite, Javadoc can take visibility into account for particular cases (not specified), +but for most cases it works just like KDoc. + +```java +/** + * {@link JavaC} is resolved despite `private` and displayed as plain text + */ +public class JavaB { + private class JavaC {} + void f() {} +} +/** + * {@link JavaC} is unresolved + * since JavaB.JavaC is private + * but {@link #f} is resolved and displayed as plain text + */ + public class JavaA extends JavaB { + } +``` + +```kotlin +val a = 0 +internal fun somethingInternal() = 0 + +/** [somethingInternal] leads to the internal fun */ +fun usage() {} + +class A { + private val somethingPrivate = 0 + + class B { + /** [somethingPrivate] leads to the private property in A */ + fun usage() {} + } +} +``` + + ### What is an ambiguity? -An ambiguity in KDoc name resolution is a case when there are multiple symbols available for the same KDoc reference. +An ambiguity in KDoc name resolution is a case when there are multiple declarations available for the same KDoc link. Consider the following example: ```kotlin @@ -87,9 +132,9 @@ fun A(x: Int) {} fun A(x: String) {} ``` -It contains various declarations with the same name `A`: imports, packages, classifiers, variables, functions, etc. -To which symbol should the KDoc name `A` resolve to? -This is why KDoc resolution requires a set of strict rules and priorities, +It contains various declarations with the same name `A`: imports, packages, classes, variables, functions, etc. +To which declaration should the KDoc name `A` resolve to? +That's why KDoc resolution requires a set of strict rules and priorities, which can help to create a consistent and disambiguating resolution strategy. ## How does the current K1 / K2 implementation prevent ambiguity? @@ -98,8 +143,10 @@ There can be various ambiguous cases, which both K1 and K2 handle using the same mechanisms and rules to deal with them. ### Self-links -Javadoc and KDoc allow having links to the elements from the context declaration, it is a quite popular practice. -Such links are called `self-links`. +A self-link is a link that resolves to the documented declaration or its component. +Both Javadoc and KDoc give documented declarations the highest priority among all other declarations with the same name. +If no suitable documented declaration with a matching name was found, the link is not considered a self one. + Such links are widely used in the various libraries (e.g. Kotlinx) because of their visual consistency and simplicity. ```kotlin @@ -108,13 +155,14 @@ val x = 0 class T /** - * [foo], [x], [T] - self-links, point to symbols from the context declaration, - * i.e. `foo` function + * [foo], [x], [T] - self-links, point to declarations from the context declaration, + * i.e., `foo` function, `x` value parameter and `T` type parameter. + * + * All the other declarations are ignored, as they have lower priority. */ fun foo(x: Int) {} ``` -Such context symbols have the highest priority among all other symbols with the same name. Their principle is quite natural and convenient in use, as they cannot be broken by introducing some other declaration. For example, @@ -124,21 +172,34 @@ val A = 0 ``` After adding a function, the link is left the same. ```kotlin -fun A() = 0 /** [A] - to val A */ val A = 0 + +fun A() = 0 ``` ### Disambiguation by a kind of declaration and by order of scopes +When reasoning about existing resolution strategies, it's important to note +that the KDoc resolution was never a part of the Kotlin specification. +So the current K1 and K2 implementations differ from each other. +We will take a look at both of them while considering the K1 implementation +to be the default and "classic" one. +This KEEP will mainly refer to the K1 implementation unless specified otherwise. + +Also, keep in mind that both of the implementations are ad hoc and the following +descriptions will not cover all possible cases and just outline general approaches. + +#### K1 implementation + When there are no suitable self-links found, -the current resolution strategy handles symbol priorities based on two properties: +the current resolution strategy handles declaration priorities based on two properties: - Declaration kind - Scope of origin There are four main kinds of declarations (from higher priority to lower): -- Classifier +- Class - Package - Function - Property @@ -148,16 +209,16 @@ For each declaration kind (except for packages, as their search is global) the r an instance of this declaration kind with the given name `x` in every scope that is visible from the KDoc position. It starts from local scopes (function body scope, type scope, etc.) and proceeds to global scopes (package scope, explicit import scopes, star import scopes, scopes of other packages). -The result of such an approach is a single most local symbol of the highest priority kind possible. +The result of such an approach is a single most local declaration of the highest priority kind possible. -This strategy will be then referred to as the **declaration-kind-first approach**. +This strategy will be then referred to as the **kind-first approach**. Some examples: ```kotlin val x = 0 fun x() = 0 -/** here [x] refers to the function, as it has higher priority */ +/** Here [x] refers to the function, as it has higher priority */ fun usage() = 0 ``` @@ -167,7 +228,7 @@ class Something object A { fun Something() {} - /** here [Something] refers to the class, as classes have higher priority than functions */ + /** Here [Something] refers to the class, as classes have higher priority than functions */ fun usage() = 0 } ``` @@ -178,7 +239,8 @@ class Something object A { class Something - /** Here [Something] refers to the nested class, + /** + * Here [Something] refers to the nested class, * as the nested class is from a local scope, which is seen from this position */ fun usage() = 0 @@ -192,7 +254,7 @@ Please note again that this only applies to cases when no `self-links` (mentione class Something object A { - /** Here [Something] refers to the function, as it is our context declaration. + /** Here [Something] refers to the function, as it is our documented declaration. * This link is considered to be a so-called self-link */ fun Something() {} @@ -200,12 +262,38 @@ object A { ``` So the general priority order can be described as following: -- Self-links (symbols from the context declaration) -- Classifier (from local to global) +- Self-links (declarations from the documented declaration) +- Class (from local to global) - Packages (global search) - Functions (from local to global) - Properties (from local to global) +#### K2 implementation + +The K2 implementation mostly ignores declaration kinds and focuses on retrieving +declarations by scopes. + +Let's consider each scope: +1. Firstly the resolver checks whether the link is `[this]` and retrieves the extension receiver +from the documented extension callable. +2. Then the resolver considers the [lexical scope](https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scope_vs._dynamic_scope_2) +of the documented declaration, i.e., member scope of every outer declaration in the order of their +locality. +These scopes also contain context/type/value parameters of these outer declarations. +Then, in each scope, the resolver tries to acquire matching declarations contained in this member scope. +3. After the search through the lexical scope, the resolver moves on to other scopes: + * Explicit importing scope + * Package scope + * Default importing scope + * Explicit star importing scope + * Default star importing scope +4. After the local search, the algorithm retrieves global packages by the given name. +5. If no symbols were found on the previous steps, the link is considered to be fully qualified. +The last step for the resolver is to search for non-imported declarations from other packages. + +After the search is done, the resolver sorts the list of all the collected declarations prioritizing classes, +which is the only handling of declaration kinds. + ### Disambiguation by overloads In the case of overload functions, a KDoc link leads to the first occurrence in code. For example, @@ -241,31 +329,44 @@ class A { class A } ``` -The case (a nested class with the same name as the enclosing class) can be unpopular since it is banned in Java, C#. +The case (a nested class with the same name as the enclosing class) can be unpopular +since it is banned in such languages as Java and C#. -There is a more practical case in Kotlin with a factory function : +While preferring parameters / properties of the documented declaration over other symbols, +the K1 implementation doesn't prioritize the documented declaration itself: ```kotlin -/** [A] */ +/** + * [A] - K1/K2: Leads to the class + */ class A -/** [A] */ -fun A(p: Int) = A() + +/** + * [A] - K1: leads to the class, K2: leads to the documented function + */ +fun A() = A() + +/** + * [A] - K1/K2: leads to the parameter + */ +fun foo(A: Int) = A() ``` -In K1, both links lead to the class A. In K2, each of them leads to its context declaration. -This case can be applied to all possible pairs of declaration kinds + +The same problem applies to all possible pairs of declaration kinds in K1 (function and function, also known as the overload problem; property and class...). -Also note that constructors have the name of the corresponding class during the resolution. +During the resolution, constructors have the name as the corresponding class. +When KDoc is attached to some constructor, the K1 implementation still prefers the class: ```kotlin class A { /** - * [A] In K1, it leads to the class A. In K2 - to the constructor + * [A] - K1: leads to the class A, K2: leads to the constructor */ constructor(s: String) } ``` ### Related problem: Availability/Visibility of nested classes from base classes -At the moment, the resolution of references to "inherited" nested classes is different between two implementations, +At the moment, the resolution of links to "inherited" nested classes varies between the two implementations, as it's not supported by K1: ```kotlin open class DateBased { @@ -277,40 +378,51 @@ open class DateBased { */ class MonthBased : DateBased() ``` -K1 implementation -failing to determine what declarations the current context of KDoc contains leads to unresolved references. -However, inherited members are still seen and resolved. +However, inherited members are still seen and resolved in K1. ### 2. Ambiguity with links to constructor parameters -In a primary constructor, the same link can refer to parameters and properties. -This does not matter for the IDE. -Opposite, Dokka treats parameters and properties differently. +When properties are declared in primary constructors, there are +actually two declarations, even though they have the same code location. +One of them is the class property, and another one is the parameter of this constructor. + +When a link points to such a property, the K1 implementation prefers the property, +while in K2 it's resolved to the corresponding constructor parameter. ```kotlin /** * [abc] K1 refers to the property `abc`, K2 - to the parameter */ class A(val abc: String) ``` -From the point of IDE view, -the link `[abc]` leads to the same position in a source file independently of whether the `abc` is a parameter or property. +However, this does not matter for the IDE tooling, as they both have the same PSI. +The link `[abc]` leads to the same position in a source file independently of whether the `abc` is a parameter or property. + +#### Related problem: Availability/Visibility of constructor parameters + +The K1 implementation considers primary constructor parameters as unavailable in class bodies. +In K2, such parameters are available in all the nested scopes, even in scope of nested classes. -### Related problem: Availability/Visibility of constructor parameters -The availability of parameters inside a scope can result in ambiguous links (in case of other symbols with the same name inside the type scope). ```kotlin class A(a: Int) { /** - * [a] is unresolved in K1. In K2, it is resolved + * [a] - K1: Unresolved, K2: resolved to the parameter */ - fun usage() = 0 + fun usage() {} + + class B { + /** + * [a] - K1: Unresolved, K2: resolved to the parameter + */ + fun usage() {} + } } ``` -# Issues with the existing K2 implementation +## Issues with the existing K1 implementation -## Global declarations breaking the local resolution +### Global declarations breaking the local resolution -The main concern is the order of priorities in the **declaration-kind-first** approach. +The main concern is the order of priorities in the **kind-first** approach. With the current approach, global context can easily break the local resolution. Take a look at the following example: @@ -320,14 +432,14 @@ class bar fun foo() { fun bar() {} /** - * [bar] -- Points to the local function until a global class is introduced. + * [bar] -- Points to the local function until some class import is introduced. * No way to refer to the function afterward */ } ``` An introduction of a global declaration with a higher priority makes -all local references with the same name point to it. +all local links with the same name point to it. And it's not always that easy to track it down. Accidental introduction of an import with the same name also poses the same danger: @@ -349,30 +461,84 @@ This local shadowing issue is even harder to track down, as now it's not limited ```kotlin val io = 5 -/** [io] -- points to `val io` until some `io` package is introduced somewhere else */ +/** [io] -- points to `val io` until some `io` package is introduced + * in the current module or in some of its dependencies + */ fun foo() {} ``` -The **declaration-scope-first** approach is inconsistent by itself, +The **kind-first** approach is inconsistent by itself, as it initially assigns self-links (i.e., local resolution) with the highest priority -but then proceeds to prefer global symbols to local ones: +but then proceeds to prefer global declarations to local ones: ```kotlin class foo object Something { /** - * [foo] - POINTS TO THE FUNCTION + * [foo] - points to the function */ fun foo() {} /** - * [foo] - POINTS TO THE CLASS + * [foo] - points to the class */ fun bar() {} } ``` -## KDoc names pointing to a single symbol +## Issues with the existing K2 implementation + +### Misuses of tag sections +[Tag sections](https://kotlinlang.org/docs/kotlin-doc.html#block-tags) (or block tags) allow breaking the +KDoc into smaller sections for documenting various specific details of the declarations. + +Some KDoc tags might require a subject link. Such tags are: +* `@throws` +* `@exception` +* `@param` +* `@property` +* `@see` +* `@sample` + +A tag section requiring a subject considers the first word on the line to be a reference: +```kotlin +/** + * The following examples are equivalent. + * @param x is my Int parameter + * @param [x] is my Int parameter + */ +fun foo(x: Int) {} +``` + +And while some of them are generic and can be used for all kinds of declarations, e.g., `@see`, +most of them are clearly supposed to accept specific kinds of declarations. +* `@param` should be used for function / constructor parameters. +* `@property` should be used for links to properties of the primary constructor. +* `@exception` / `@throws` should be used for `Throwable`s. + +However, a lot of users are not familiar with block tags. +Additionally, the difference between parameter/property can easily be confusing in practice, +so there are a lot of, e.g, links to parameters that are put in `@property` sections. + +The most obvious solution for this issue is to restrict the use of tag sections to their corresponding declaration kinds. +In fact, the K1 implementation already does this, however, just for the `@param` tag. + +```kotlin +/** + * K1: + * @param x - unresolved + * K2: + * @param x - resolved + */ +fun x() {} +``` + +## KDoc names pointing to a single declaration +Currently, KDoc links can only be resolved to a single symbol. +Even though K1/K2 resolvers return multiple candidates for the same link, +the IDE only shows the first one. +However, the rest of the candidates are still highlighted as used in the code. + Since some declarations might have overloads, KDoc names being able to point just to a single declaration feel too restricting. Sometimes it’s crucial to support this behavior @@ -382,15 +548,16 @@ For use-cases when just a single declaration is required, users of the KDoc resolver could pick the first element from the resulting collection, which preserves the behavior. - # Proposal This document proposes that existing KDoc links should point to only one -(or multiple in case of overloads) specific location in the same way -as the compiler does for name resolution. +(or multiple in case of overloads and some ambiguities) specific location in the same way +the Kotlin language does for name resolution in the code. -Meanwhile, the ambiguity of KDoc links is inevitable. -Just a warning about ambiguity does not make sense as there is no disambiguator at this time. +Meanwhile, the ambiguity of KDoc links is still inevitable. +Some might suggest providing an IDE warning informing users that their link is ambiguous, i.e., +that there are multiple declarations available for the given name. +However, such an inspection would be useless and annoying, as there is no disambiguator at this time. Therefore, the proposed approach is to prioritize candidates for KDoc links to deterministically choose a subset of all suitable declarations. @@ -399,17 +566,19 @@ To provide this disambiguation mechanism, the following questions should be cove - If there is more than one candidate, what should be prioritized? - If there are candidates that have the same priority, which should be chosen? -The following sections addresses these three questions. +The following sections address these three questions. ## Context of KDoc -Here context is the set of all name bindings available by its short names in a current KDoc comment ([scope vs context](https://en.wikipedia.org/wiki/Scope_(computer_science))). -For example, the KDoc context of a class should correspond to the first line of a fake function. +The KDoc context is the set of all declarations that are resolvable at a specific, +imaginary place in the code of the documented property by the compiler. +Hence, KDoc resolution rules derive from the language's resolution rules. +For example, the KDoc context of a class is derived from the first line of a fake function body as such: ```kotlin /** - * [p] is resolved - * [param] ERROR: unresolved + * [p] is contained in the KDoc context + * [param] is not */ class A(param: Int) { companion object { @@ -420,177 +589,77 @@ class A(param: Int) { Here `[p]` is resolved since it is available on the first line of a fake function: ```kotlin /** - * [p] is resolved - * [param] ERROR: unresolved + * [p] is contained in the KDoc context + * [param] is not */ -class A { +class A(param: Int) { fun fake() { p // is available here param // ERROR: unresolved } + companion object { val p = 0 } } ``` -That means that the KDoc link should have the set of available names as the first line of a fake function. -Note the rule ignores the visibility of declarations. -Similarly, the KDoc context of functions/constructors corresponds to the beginning of the first line of a body, -and the KDoc context of properties corresponds to the beginning of property initializers. - -### KDoc tag sections -The KDoc tag sections (`@constructor`, `@param`, `@property`) should introduce their new scope in the documentation. -Previously, they've never affected the resolution. -All other tag sections must not affect the resolution in any sense. -#### @constructor section +We can see that the constructor parameter `param` is not accessible in this context. +It's not called "unresolved" as this link is a self-link, so it's handled separately. -The tag `@constructor` can be applied to classes and secondary constructors -and has context where parameters of the context constructor and the constructor itself are available. - +Declarations don't have to be resolvable in the code as-is. +It's contained in the KDoc context when there is a case this declaration is correctly referenced in the code. +It might be a direct call, might be a reference, +but there must be a case when this declaration could be resolved in the code. ```kotlin -/** - * [p] ERROR: unresolved - * @constructor [p] is resolved - */ -class A(p: Int, val abc: String) { +class A { + fun myFun() {} + /** - * [p] ERROR: unresolved - */ - fun f() { - p // ERROR: unresolved + * [myFun] is visible and resolved + */ + class B { + fun myFakeFun(x: A) { + x.myFun() // Valid + A::myFun // Valid + } } - - /** - * [p] is resolved - */ - val prop = p } ``` -There is another example for `@constructor`: -```kotlin -/** - * [A] - to the class, [abc] - to the property - * @constructor [A] - to the constructor, [abc] - to the parameter - */ -class A(var abc: String) -``` - -The idea behind this resolution is rather simple. -The constructor from the example above can be easily refactored to be a secondary constructor. -Here we clearly see that the `abc` parameter only belongs to this constructor and not seen from the class -(i.e., from the first line of a fake function). -However, the property is fully accessible. - +When KDoc is not attached to any declarations, an imaginary one can be inserted right below: ```kotlin -/** - * [A] - to the class, [abc] - to the property - */ -class A private constructor() { - /** - * [A] - to the constructor, [abc] - to the parameter - */ - constructor (abc: String): this() - lateinit var abc: String -} -``` - +fun foo() { + val bar = 1 -```kotlin -/** -* [A] - to the class, [a] - unresolved -* @constructor [A] - to the current constructor, [a] - to the parameter -*/ -class A(a: Int) { - /** - * [A] - to the current constructor, [a] - unresolved - * @constructor [A] - to the current constructor, [a] - to the current parameter - */ - constructor(a: String) : this(a.length) + * [bar] is visible and resolved + */ + // val myFakeVal = bar // Valid } ``` -Note that the constructor symbol is prioritized over parameter symbols: -```kotlin -/** - * [abc] - to the class - * @constructor [abc] - to the constructor - */ -class abc(var abc: String) -``` - -#### @param section - -`@param` section can be applied to classes and functions. -When applied to classes, it also makes the constructor and its parameters available the same way `@constructor` does; -however, parameters have higher priority. -Also `@param` doesn't affect the resolution for secondary constructors, as it doesn't make any sense to do so. - -```kotlin -/** - * [A] - to the class, [abc] - to the property - * @param [A] - to the constructor, [abc] - to the parameter - */ -class A(var abc: String) -``` - -```kotlin -/** - * [abc] - to the class - * @param [abc] - to the parameter - */ -class abc(var abc: String) -``` - -In the case of functions, all the parameters are available from a regular KDoc. -`@param` section can be used to refer to the parameter instead of the function in case of name clashes. -```kotlin -/** - * [abc] - to the function - * @param [abc] - to the parameter - */ -fun abc(var abc: String) {} -``` - -#### @property section - -`@property` section can be applied to classes to prioritize properties from this class. - -```kotlin -/** - * [abc] - to the class - * @property [abc] - to the property - */ -class abc(var abc: String) -``` - -A small summary example: -```kotlin -/** -* [abc] - to the class -* - * @constructor [abc] - to the constructor - * @param [abc] - to the parameter - * @property [abc] - to the property - */ -class abc(var abc: String) -``` +Similarly, the KDoc context of functions/constructors corresponds to the beginning of their bodies, +the KDoc context of properties corresponds to the beginning of property initializers, etc. ## Resolution Strategy We would like to make the resolution of KDoc names closer to what the compiler does when resolving references in code. -This approach is more natural, clear to the user, and convenient, as users don't have to -The base logic here is that if some name `X` is resolved to declaration `Y` in context `C` during the compilation, -then the same KDoc reference `X` located in some KDoc in the same context `C` should be resolved to `Y`. - -Generally speaking, -the compiler also prioritizes symbols during resolution based on declaration kinds and scopes of origin. +This approach is more natural, clear, and convenient to users, +as they are used to the way the compiler resolves references. +The base logic here is that some KDoc link `[X]` should be resolved to declarations from its [context](#context-of-kdoc) +with the same name `X`. +If there are multiple declarations available, the resolver should pick a set of them which are the closest semantically +to the position of this KDoc. + +Similarly to K1, the compiler prioritizes declarations during resolution +based on their kinds and scopes of origin. However, the priorities are switched: locality of scopes has a higher priority than declaration kinds. -The compiler also doesn't have a strict order of declaration kinds, -as it has more information about a required symbol from the use-site -(not just name as KDoc does). +These declaration priorities are not that strict, the compiler derives additional information +from the use-site of the reference (i.e., type/value parameters, implicit receivers, etc.), +while KDoc links are resolved without any additional information, just based on the link name. + +Take a look at various examples of how call-site information affects the compiler resolution: ```kotlin fun foo() {} @@ -631,10 +700,13 @@ fun main() { } ``` -Since the only information the KDoc resolver has is the name of the symbol it's looking for, -prioritization of declaration kinds is vital. -But the main point of this switch is to truly and always prefer local over global -by iterating through scopes from local to global and then looking for various symbols +Since the only information the KDoc resolver has is the name of the declaration it's looking for, +prioritization of declaration kinds during the resolution is vital. +However, locality of scopes should have higher priority than declaration kinds during the KDoc resolution. + +The main point of this priority switch is to truly and always prefer local to global +so that local links are not broken by the introduction of some global declarations. +This can be achieved by iterating through scopes from local to global and then looking for various declarations (w.r.t. their declaration kinds) just inside the current scope. This approach will be referred to as the **scope-first approach**. @@ -643,10 +715,36 @@ More info on the compiler resolution can be found on the following resources: * [Linked scopes | Kotlin Specification](https://kotlinlang.org/spec/scopes-and-identifiers.html#linked-scopes) * [Overload resolution | Kotlin Specification](https://kotlinlang.org/spec/overload-resolution.html#overload-resolution) -But now we have to deal with the global package search, as we cannot integrate it into this local scope traversal. +Now another problem arises: how can we deal with the global package search? +The K1 implementation prioritizes kinds over locality, so the algorithm in pseude code looks something like this: +```kotlin +for (scope in scopes) { + searchForClasses(name) +} +searchForPackages(name) +for (scope in scopes) { + searchForFunctions(name) +} +for (scope in scopes) { + searchForProperties(name) +} +``` + +Since we are switching the priorities to prioritize locality over kinds, the new algorithm looks like this: +```kotlin +// searchForPackages(name) +for (scope in scopes) { + searchForClasses(name) + searchForFunctions(name) + searchForProperties(name) +} +// searchForPackages(name) +``` + +Now we cannot integrate the global package search into this local scope traversal. So this search should either go first (have the highest priority) or go last. The first option obviously contradicts the idea of preferring local declarations as much as possible. -So the proposed idea is to assign it with the lowest priority. +The proposed idea is to assign it with the lowest priority. It makes it impossible to refer to packages in some cases, but it's inevitable when there is no any disambiguating syntax: ```kotlin @@ -661,7 +759,7 @@ fun foo() {} That was an informal description of the mindset behind this change. Let's systemize this information in the following chapters: -## Implementation details +## Resolution algorithm All names should be treated according to the following three-step algorithm: @@ -670,29 +768,56 @@ All names should be treated according to the following three-step algorithm: 3. [Perform package search](#step-3-perform-package-search) ### Step 1. Perform self-link search -When the given name is short, the resolution should start by looking for suitable symbols in the context declaration. -It's vital to consider thew tag section the KDoc is contained in. -It's also important to handle `[this]` receiver links in a proper way. +When the given name is short (i.e., contains just one segment), +the resolution should start by looking for declarations with the same name in the context declaration. +This includes: +* The documented declaration itself +* Value parameters +* Type parameters +* Context parameters +* Properties of the primary constructor + +Note that when the context declaration is a class, +the KDoc documents both the class and its primary constructor (since they have the same location). +So it's necessary to retrieve declarations from both of them. + +The resolver should also handle `[this]` receiver links in a proper way. +If the documented declaration is an extension callable, then `[this]` should be resolved +to the extension receiver of the current declaration. +In Kotlin, `this` is a special keyword, no declarations are allowed to be named this way, +so there won't be any ambiguity with such self-links. + +When the link is a subject of a tag section, the resolver +should only look for declarations relevant for the current tag section. + +- `@param x` must only be resolvable to value / type / context parameters of the documented declaration. + When the context declaration is a class, property parameters of its primary constructor are also considered + as parameters (`class Foo(val x: Int)`). +- `@property x` must only be applicable to class documentation and must be resolvable to all properties + of the documented class (both properties of the primary constructor and body properties). +- `@exception` / `@throws` must only be resolvable to `Throwable`s. ### Step 2. Perform scope traversal #### Short names treatment -The resolver gathers all the scopes that are visible from the current declaration, -which can be done using `ContextCollector` from Kotlin Analysis API. -`ContextCollector` already provides scopes in the order the compiler processes them during the name resolution. -All kinds of provided scopes can be found in `KaScopeKind`. - -A general variety of scopes can be described as the following: -1. Type parameter scope -2. Local scope -3. Type scope -4. Static member scope -5. Explicit importing scope -6. Package scope -7. Explicit star importing scope -8. Default importing scope -9. Default star importing scope + +If the name is short, i.e., contains just one segment, and the self-link search did not find any suitable declarations, +the resolution should start by looking for declarations in all the scopes that are visible from the current position. + +Firstly, the resolver should retrieve a list of scopes for the current position. +These scopes are: + +1. **Local scope**. + These are the most local declarations, + like variables declared inside functions when the KDoc is placed in their bodies. +2. **Scopes of outer classifiers**. + These scopes contain both non-static and static members of outer classes, including inherited members. +3. **Explicit importing scopes**. Declarations from explicit imports (`import A.B.myName`). +4. **Current package scope**. Global declarations from the current package. +5. **Explicit star importing scopes**. Declarations from star imports (`import A.B.*`). +6. **Default importing scope**. Default imports from the Kotlin language. +7. **Default star importing scope**. Default star imports from the Kotlin language. ```kotlin // Package scope @@ -705,33 +830,93 @@ class MyClass { // Type scope companion object { - // Static member scope + // Static type scope fun staticFoo() {} } - fun foo() { // Type parameter scope + fun foo() { // Local scope } } ``` -Then it iterates through all the scopes and gathers suitable symbols according to the following priorities: -1. Classifier +Then it should iterate through all the scopes +and gather declarations with the given name, according to the following priorities: +1. Class 2. Function 3. Property -If any symbols are found in the current scope, -all these symbols should be immediately returned in the order of their priorities. +If there are any declarations of the same kind found in the current scope, +these all of these declarations should be immediately returned. #### Multi-segment names treatment + The handling of multi-segment names here is a bit trickier. A multi-segment name can be either a relative name or a global FQN. -And there is no way to tell which one we are looking at. +And there is no way to tell which one we are looking at before the resolution is performed. +Such links are first considered as relative links, +and then they are treated as global links. -* [Relative names](#relative-names) -* [Global names](#global-names) +1. [Relative name resolution](#relative-name-resolution) +2. [Global name resolution](#global-name-resolution) + +Here it's crucial to understand the following algorithm that helps us to +transform multi-segment name search into a single-segment name search: + +##### Scope reduction algorithm + +This algorithm will be actively used in the following sections. +It helps us to apply the same logic as we used for short names to the multi-segment name search. + +Imagine that we have some relative name, e.g. `A.B.C` in the following case: +```kotlin +class Global { + class A { + class B { + class C { + companion object { + /** + * [A.B.C] + */ + fun foo() {} + } + } + } + } +} +``` + +A relative name is a multi-segment name that is relative to some position visible from the current context. +In this case, the relative name is `A.B.C` and it's relative to the `Global` class. + +The resolver has already calculated the list of visible scopes +when searching for short names in [Short names treatment](#short-names-treatment). +If a relative link is resolvable, then it must be relative to any of these visible scopes. + +To reduce this multi-segment search to a single-segment one, +all these scopes have to be slightly modified. + +`A.B.C` implies that somewhere there is a chain of two nested classifiers `A` and `B` with `B` +containing some member `C`. +The resolver should try to find a classifier `A` in each of these scopes and, if found, +replace the original scope with the member scope of this class. +If no suitable classes are found in some scope, this scope should be discarded. +Then the resolver should take all the modified scopes and repeat the search for nested classifiers (`B` in this case) +until just the short name `C` is left. +Then it's just a single-segment name search in the constructed list of scopes. + +Note that currently it's prohibited to have a variable as a link segment. +That's why each non-final segment of the name should be resolved to a classifier. +```kotlin +/** + * [name.length] - unresolved, prohibited + */ +fun foo(name: String) {} +``` +Whether such links should be resolvable is out of the scope of this proposal. + +##### Relative name resolution -##### Relative names ```kotlin object Something { class Foo { @@ -748,39 +933,22 @@ object Something { } } ``` -To handle such cases, the scopes retrieved for the traversal should be slightly transformed. -If a given name has several segments, then it should be accessible by that name from some of these outer scopes. -In fact, the resolver should only pick and process just a single scope, -see the reasoning behind this in the [Restrictions of multi-segment names resolution](#restrictions-of-multi-segment-names-resolution). - -Imagine that we have a name `A.B.foo`. -We assume that this link is relative, so in each scope we have to find a class-like (i.e., a declaration container) `A`, -then if `A` is found, we should start looking for class-like `B` in its class scope and so on. -Then the rest of the logic is quite simple: retrieve symbols from this scope by the short name (`foo`). - -The resolver should iterate through scopes from local to global -and look for a chain of nested classifiers starting in the current scope. -If this chain is found, -the resolver just picks this classifier member scope and looks for symbols with the given short name. -However, if, at some point of this symbols-by-segment search, -the resolver comes across some non-function symbol from which -it's impossible to continue the chain, the resolver should stop and consider this link as unresolved. -Examples of such cases are classes with no nested classifiers matching the next segment and properties. -Again, see [following chapter](#restrictions-of-multi-segment-names-resolution) for more info. - -##### Global names -A multi-segment name can also be global, i.e., -located in some other package visible from the current one and not imported in the current file. -Then the containing scope of this declaration is not directly visible from the current position -(when using `ContextCollector` from AA), so this case should be handled manually. +To handle such cases, we can use [Scope reduction algorithm](#scope-reduction-algorithm) +and then process the same way as for short names. + +##### Global name resolution + +A multi-segment name can also be global, i.e., start with a package name. + +If the search for relative names fails to resolve the first segment of the link, the resolver +should fall back to the global search. If we have some name `A.B.C.D.foo`, then we have to find the longest existing package, which name is a segment prefix of this link (search for `A.B.C.D`, then for `A.B.C` and so on). -Let's imagine that some package with `A.B` name is found. +Let's imagine that the search stopped on some package `A.B`. The only thing left to do is to resolve `C.D.foo` inside this package scope. -Now it's quite similar to the logic we had for relative and short names. -Firstly, the resolver should find all nested class-likes on its way to the short name (`C.D` in this case). -If this chain of nested classifiers is found, finding the shourt name `foo` in this classifier scope is trivial. +Now we should again use the [Scope reduction algorithm](#scope-reduction-algorithm) +and then just search for the short name `foo` in the resulting sequence. ```kotlin package A.B @@ -792,132 +960,35 @@ class C { } ``` -This global name search should not be performed when there are conflicting non-function declarations -found in any of the local scopes, -see [Restrictions of multi-segment names resolution](#restrictions-of-multi-segment-names-resolution) for more info. - -##### Restrictions of multi-segment names resolution -It's important to remind that we would like this resolution to be as close as possible to the compiler. -So we should only resolve multi-segment names when the compiler does so. -Imagine that we have some name `A.B.C` that we would like to point to. -The location of this declaration relatively to the given position is not important. - -Here we will put a declaration in some other package -just that there are no conflicting overloads within the same package. -```kotlin -// FILE: A.B/C.kt -package A.B - -class C - -// FILE: Usage.kt - -fun usage() { - A.B.C() // RESOLVED -} -``` - -The reference is correctly resolved. -But then there might be other declarations in the usage file. -```kotlin -// FILE: Usage.kt - -fun A() = 5 - -fun usage() { - A.B.C() // RESOLVED -} -``` - -```kotlin -// FILE: Usage.kt - -val A = 5 - -fun usage() { - A.B.C() // UNRESOLVED -} -``` - -```kotlin -// FILE: Usage.kt - -class A - -fun usage() { - A.B.C() // UNRESOLVED -} -``` - -```kotlin -// FILE: Usage.kt - -class Something - -fun Something.A() {} - -fun Something.usage() { - A.B.C() // RESOLVED -} -``` - -```kotlin -// FILE: Usage.kt - -class Something - -val Something.A: Int - get() = 5 - -fun Something.usage() { - A.B.C() // UNRESOLVED -} -``` - -As we can see, the compiler only resolves this name when there are -no other non-function more local declarations that are resolvable by some prefix of the given name. -Otherwise, some prefix is resolved to this declaration and the rest of the chain is unresolved, -as this declaration doesn't have any suitable nested symbols. - -This should be also taken into account when resolving links in KDoc: -* Before performing a scope transformation for relative names, the resolver has to make sure that there are no -non-function declarations matching some prefix of the given name in a more local scope. -If such a declaration is found in some scope, then there is no need to process all further scopes, -as the compiler would resolve this prefix to the declaration in this scope. - -* The same is applied to the global name search: if such a conflicting declaration is already found in some local scope, -the resolver should not proceed to the global retrieval. - ### Step 3. Perform package search -If no symbols were found on any of the previous steps, a global package search should be performed. + +If no declarations were found on any of the previous steps, a global package search should be performed. Note that the real package name should fully match the name specified in the link. If a link is just `bar`, then it cannot be resolved to some package called `foo.bar`, as it matches only the last segment but not the full package name. - ### Result of the resolution -The result of the resolution is the set of all symbols found in self-links (for short names) -plus symbols from the first non-empty scope sorted by their priorities. -Unfortunately, there is no disambiguation syntax -that would allow explicitly specifying the kind of the declaration the current link should point to. -To overcome this issue at the current moment, -the resolver should return all symbols that the compiler could resolve to the given name from the given position. +The result of the resolution is the set of all declarations found on the first successful algorithm step out of three. +It's important to preserve the priority of declarations in the resulting collection for purposes +specified in [Handling resolved declarations on the use-site](#handling-resolved-declarations-on-the-use-site). +Unfortunately, there is no disambiguation syntax +that would allow explicitly specifying the kind or other details of the declaration the current link should point to: ```kotlin /** - * @see Foo() function. - leads to the interface, + * [Foo] - leads to the interface, * no way to point to the function. */ interface Foo fun Foo(): Foo = object : Foo {} ``` +Whether the resolver should return all declarations from some scope or just declarations of some kind from this scope +is still a question. -It's important to preserve the priority of symbols in the resulting collection for purposes -specified in [Handling resolved symbols on the use-site](#handling-resolved-symbols-on-the-use-site). +## Handling resolved declarations on the use-site -## Handling resolved symbols on the use-site There are a number of various logic pieces that heavily rely on the KDoc name resolver: * Import optimizer * Usage finder (that also highights declarations that the link points to) @@ -926,11 +997,11 @@ There are a number of various logic pieces that heavily rely on the KDoc name re We would like all these usages to be as consistent as possible with each other. -Currently, most usages handle all the symbols that are returned by the resolver (i.e., import optimizer). +Currently, most usages handle all the declarations that are returned by the resolver (i.e., import optimizer). However, navigation actions that are also provided to users only navigate to a single declaration from the returned set (the first one to be precise). This might be quite confusing to users, as in the following code both functions are highlighted as *used*, -but *Go To Declaration* action takes users just to the first declaration: +but *Go To Declaration* action takes users just to the first found declaration: ```kotlin fun foo() {} @@ -942,14 +1013,13 @@ fun foo(x: Int) {} fun usage() {} ``` -The IDE should show a popup drop-down menu the same way it does with other ambiguous references: +The IDE should show a popup drop-down menu the same way it does with other ambiguous links: ![example](https://i.ibb.co/dKQkshh/image.png?) - -However, tools that require just a single resolved symbol, e.g., -for HTML rendering purposes, should be able to pick just the first symbol from the resulting collection. +However, tools that require just a single resolved declaration, e.g., +for HTML rendering purposes, should be able to pick just the first declaration from the resulting collection. That's why it's important to construct this collection in such a way -that more local symbols with higher priority go first. +that more local declarations with higher priority go first. It makes the result consistent and reliable. # Appendix @@ -961,6 +1031,7 @@ All considered languages can be divided into two groups by the way they deal wit * Ambiguous links are disallowed and cause a warning. In this case, a language provides a mechanism to disambiguate them. (Swift, *C#*, Rust, *Golang*) ### Javadoc + [Javadoc Documentation Comment Specification: References](https://docs.oracle.com/en/java/javase/22/docs/specs/javadoc/doc-comment-spec.html#references) describes the specification of Javadoc references a little: > the parameter types and parentheses can be omitted if the method or constructor is not overloaded and the name is not also that of a field or enum member in the same class or interface. @@ -1105,6 +1176,7 @@ Swift also provides mechanisms for the links disambiguation. See [Navigate to a symbol](https://www.swift.org/documentation/docc/linking-to-symbols-and-other-content#Navigate-to-a-Symbol) #### 1. Suffixes of overloads + ```swift /// ``update(_:)-6woox`` /// ``update(_:)-6wqkp`` @@ -1115,7 +1187,9 @@ func usage() {} func update(_ power: String) {} func update(_ energyLevel: Int) {} ``` + #### 2. Suffixes of symbol types + ```swift /// ``Color-property`` /// ``Color-class`` or ``Color-swift.class`` @@ -1152,6 +1226,7 @@ public class Utils { Similar to Java, C# does not allow having a nested class with the same name as enclosing class. ### Rust + In case of ambiguity, the rustdoc will warn about the ambiguity and suggest a disambiguator. See [Disambiguators](https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators) @@ -1167,36 +1242,8 @@ fn Foo() {} There are no overloads in Rust. ### Golang + See [Go Doc Comments: Links](https://tip.golang.org/doc/comment#doclinks) > If different source files in a package import different packages using the same name, then the shorthand is ambiguous and cannot be used. -However, the Godoc does not support links very well to check. - -## Visibility -KDoc ignores visibility, i.e., all declarations are public for KDoc references. -Whether resolving KDoc references should take visibility into account is an open question. - -Javadoc can take visibility into account for particular cases (not specified), but for most cases it works just like KDoc. - -```java -/** - * {@link JavaC} is resolved despite `private` and displayed as plain text - */ -public class JavaB { - private class JavaC {} - void f() {} -} -/** - * {@link JavaC} is unresolved - * since JavaB.JavaC is private - * but {@link #f} is resolved and displayed as plain text - */ - public class JavaA extends JavaB { - } -``` - -```kotlin -val a = 0 -internal fun a() = 0 -/** [a] leads to the internal fun and will be displayed in Dokka as plain text*/ -``` +However, the Godoc does not support links very well to check. \ No newline at end of file