KEMBAR78
Require a finding on a contravariant parameter type? [working decision: no] · Issue #49 · jspecify/jspecify · GitHub
Skip to content

Require a finding on a contravariant parameter type? [working decision: no] #49

@dzharkov

Description

@dzharkov

In the draft proposal, there's a part that allows for overrides to have value parameters with contravariant nullability:

The method’s parameter types may have wider nullability than the corresponding superparameters' types, meaning, it suffices for superparameters to be type-convertible to the overriding method’s parameter types (while base types have to follow the normal rules of the compiler so overriding works as expected).

That might be a problem for the Kotlin compiler since in pure Kotlin value parameters are not contravariant in overrides and we apply the same logic when looking at the annotated Java code.

The reason we don't allow contravariant parameters in override is basically the same as in Java: they might be considered as overloads. In Java, one can not override with a wider-typed parameter (types should be equal). The same applies for Kotlin, but we have nullability as a built-in part of the type system.

interface A {
    fun foo(x: String)
}

class B : A {
    override fun foo(x: String) {
        
    }
    
    @JvmName("fooNullable")
    fun foo(x: String?) {} // is a different overload for `foo`
}

fun main() {
    B().foo("") // resolved to the not-nullable
    B().foo(null) // resolved to nullable
}

That means that when we see contravariant nullability in Java annotations, such code is assumed to be illegal thus we ignore annotations info for such parts completely as inconsistent:

interface A {
    void foo(@NotNull String x);
}

class B implements A {
    @Override
    public void foo(@Nullable String x) {

    }
}

B::foo has signature like fun foo(x: String!), i.e. it has unknown nullability from Kotlin point-of-view.

But at the same time it's fine transforming from unknown nullness to both NotNull and Nullable:

interface A {
    void foo(String x, String y);
}

class B implements A {
    @Override
    public void foo(@Nullable String x, @NotNull String y) {

    }
}

In the above example B::foo is perceived by Kotlin as fun foo(x: String?, y: String)

Metadata

Metadata

Assignees

No one assigned

    Labels

    designAn issue that is resolved by making a decision, about whether and how something should work.nullnessFor issues specific to nullness analysis.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions