User1291
User1291

Reputation: 8182

Kotlin fails to get the right enum instance

So we are using Vaadin. Vaadin comes with a Key interface. I copied it and deleted all entries except the CONTROL key (for demonstration purposes here, we don't copy the classes in our actual code):

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

@FunctionalInterface
public interface VaadinKey extends Serializable {

    VaadinKey CONTROL = VaadinKey.of("Control", "ControlLeft", "ControlRight");

    //...

    static VaadinKey of(String key, String... additionalKeys) {
        Objects.requireNonNull(key);
        if ("".equals(key)) {
            throw new IllegalArgumentException("'key' cannot be empty");
        }
        List<String> keys = new ArrayList<>();
        keys.add(key);
        keys.addAll(Arrays.asList(additionalKeys));
        return () -> keys;
    }


    List<String> getKeys();


    default boolean matches(String key) {
        return getKeys().contains(key);
    }


    static boolean isModifier(com.vaadin.flow.component.Key key) {
        return Stream.of(VaadinKeyModifier.values())
            .anyMatch(k -> k.matches(key.getKeys().get(0)));
    }

}

The interface is implemented by a KeyModifier class, that I've also copied out and stripped down:

import java.util.List;
import java.util.stream.Stream;

public enum VaadinKeyModifier implements VaadinKey {

    /**
     * KeyModifier for "{@code Control}" key.
     */
    CONTROL(VaadinKey.CONTROL)

    //...
    ;

    private final VaadinKey key;

    VaadinKeyModifier(VaadinKey key) {
        this.key = key;
    }


    @Override
    public List<String> getKeys() {
        return key.getKeys();
    }


    public static VaadinKeyModifier of(String key) {
        return Stream.of(values()).filter(k -> k.matches(key)).findFirst()
            .orElseThrow(IllegalArgumentException::new);
    }
}

Now let's try to use it from Kotlin:

fun foo() {
    val ctrl: VaadinKeyModifier = VaadinKeyModifier.CONTROL
}

This will fail to compile because

Type mismatch: inferred type is VaadinKey! but VaadinKeyModifier was expected

(And if I ctrl+click it in IntelliJ, it will also take me to the VaadinKey interface.)

I also tried

import com.[...].VaadinKeyModifier.CONTROL as CONTROL_MODIFIER

fun foo() {
    val ctrl: VaadinKeyModifier = CONTROL_MODIFIER
}

With the same result.

Now interestingly, the direct use of VaadinKeyModifier.CONTROL will not register any usage of VaadinKeyModifier::CONTROL, whereas the import variant registers one usage in the import (which however does not prevent the compilation error).

What gives?

Why does this happen?

And how do I get this to work?

Upvotes: 2

Views: 429

Answers (1)

Name of your enum instance (CONTROL) clashes with its self-titled field inherited from the interface.

In Java this ambiguity resolves in favor of enum instance, in Kotlin (during Java interop) for some reason it resolves in favor of field. However if VaadinKeyModifier class was defined in Kotlin, it would have been resolved in favor of enum:

enum class VaadinKeyModifierKT(private val key: VaadinKey) : VaadinKey {
    CONTROL(VaadinKey.CONTROL);
}

val ctrl: VaadinKeyModifierKT = VaadinKeyModifierKT.CONTROL //Would be compiled

If changing given code to avoid this ambiguity (for instance, via renaming fields/enums or separating interface and these fields) is not an option, then you can access instances of VaadinKeyModifier via its name:

val ctrl = enumValueOf<VaadinKeyModifier>("CONTROL")

As a less error-prone option, I'd suggest creating auxilary enum, duplicating values of VaadinKeyModifier, with converter to VaadinKeyModifier type:

enum class Modifier {
    SHIFT,
    CONTROL,
    ALT,
    ALT_GRAPH,
    META;

    fun toVaadinKeyModifier() : VaadinKeyModifier = enumValueOf(name)
}

//Usage:
val ctrl: VaadinKeyModifier = Modifier.CONTROL.toVaadinKeyModifier()

Upvotes: 2

Related Questions