dokka icon indicating copy to clipboard operation
dokka copied to clipboard

[K2] KMP: Expect/actual enums have `expect` modifiers but don't have `actual` for builtin functions

Open atyrin opened this issue 1 year ago • 10 comments

Declare an expect enum and actual counterparts

// commonMain
expect enum class EventType {
    ADDED, CHANGED, REMOVED
}

// platforms
actual enum class EventType {
    ADDED, CHANGED, REMOVED
}

In generated documentation. expect parts have the modifiers for valueOf/values functions but actual don't. It doesn't affects user-defined functions. image

K1 both functions are without expect-actual modifiers:

image

atyrin avatar Jul 22 '24 13:07 atyrin

expect should be removed

IgnatBeresnev avatar Aug 02 '24 12:08 IgnatBeresnev

A similar issue for Exception inheritors.

For example, the class declaration in commonMain:

// stdlib part
public expect open class RuntimeException : Exception {
    public constructor()
    public constructor(message: String?)
    public constructor(message: String?, cause: Throwable?)
    public constructor(cause: Throwable?)
}

// user code
public open class SdkBaseException : RuntimeException {
    public constructor() : super()
    public constructor(message: String?) : super(message)
    public constructor(message: String?, cause: Throwable?) : super(message, cause)
    public constructor(cause: Throwable?) : super(cause)
}

in K2 will have expect in properties signature

Image

atyrin avatar Mar 12 '25 12:03 atyrin

A similar issue for Exception inheritors.

In K2, it depends on the version of stdlib.
In stdlib, 2.2.0, Throwable.cause and Throwable.message are marked as expect . Meanwhile, in stdlib 2.0.0, they are not, and Dokka K2 generates them without the expect modifier.

Dokka K1 always omits expect, regardless of the stdlib.

vmishenev avatar Oct 14 '25 19:10 vmishenev

In K2, it depends on the version of stdlib.

I don't think that the issue pointed out above is. Look at NotImplementedError in Kotlin 2.2.0 - it has expect for Throwable.cause and Throwable.message. That's probably a Dokka issue, as we simply show inherited (without overrides) declarations as-is.

whyoleg avatar Oct 15 '25 08:10 whyoleg

I don't think that the issue pointed out above is

My previous comment just explains how it works in Dokka K1 and why it doesn't have expect.

Look at NotImplementedError in Kotlin 2.2.0 - it has expect for Throwable.cause and Throwable.message.

It is expected, the behavior is the same for SdkBaseException

That's probably a Dokka issue, as we simply show inherited (without overrides) declarations as-is.

What should Dokka show? Which overrides?

vmishenev avatar Oct 15 '25 10:10 vmishenev

Sorry, looks like I misunderstood the intention of the message :(

I wanted to point out that it's not Throwable (or built-in) related and will happen with any expect/actual class that doesn't override some expect declarations. AFAIK, it may happen not only with the expect modifier, but also if we have other modifiers, which we (and the IDE) show, for example, open:

Image

Dokka works the same. Here, IDE indicates that the method is open and notes that it originates from SomeInterface. While in Dokka, we just show this someOpenFunction method as is with open modifier, and even redirect to SomeInterface.someOpenFunction, while in reality it's not even declared in SomeClass and probably should not have open modifier at all.

The same is happening with expect val cause: Throwable?:

Image

So, this is me just badly trying to suggest that the issue with Throwable is a broader issue with how we represent inherited declarations and should be addressed separately (#3639 might also be related here). Leaving this issue to just solving the problem with enums, which is actually a different one, as those are special declarations.

whyoleg avatar Oct 15 '25 11:10 whyoleg

the issue with Throwable is a broader issue with how we represent inherited declarations and should be addressed separately

The current issue is more about the differences between K1 and K2. In the general case,

expect open class MyThrowable() {
    val cause: Throwable?
    val message: String
}

class MySdkBaseException : MyThrowable()

K1 and K2 render the same signatures with expect.

I would say the problem you mentioned is completely different. Moreover, Kotlin does not allow having expect for usual members in code, but Dokka renders the modifier. However, as far as I remember, expected and actual classes are still in Beta.

vmishenev avatar Oct 15 '25 12:10 vmishenev

Marking synthetic methods (e.g. Enum.valueOf, Enum.values) as expect /actual is a minor question.

It seems these methods are inside an expect declaration and, generally, everything in expect declarations should be marked as expect, so the general consensus on this issues is to add the actual modifier.

It depends on https://youtrack.jetbrains.com/issue/KT-81798/AA-Incorrect-value-of-isActual-isExpect-for-the-synthetic-methods since QDoc reuses it also, but probably will be fixed on the FIR generation side

vmishenev avatar Oct 20 '25 12:10 vmishenev

One more case, similar to Throwable, this time for Enum.ordinal and Enum.name. Enum is declared fully in common code, without expect/actual:

enum class SignatureFormat { RAW, DER }

K1 output:

Image

K2 output:

Image

whyoleg avatar Oct 29 '25 14:10 whyoleg

One more case, similar to Throwable, this time for Enum.ordinal and Enum.name.

This is the same situation for Throwable. Enum.ordinal and Enum.name are marked as expected in the stdlib 2.2, but are not in the stdlib 1.9 (K1)

I have created #4333 for such cases

vmishenev avatar Nov 03 '25 13:11 vmishenev