0

I'm trying to write a Java library that has a non-nullable API, and I'd like Kotlin to properly infer this non-nullability. I'm using JSR-305 annotations and its @TypeQualifierDefault to achieve the effect for the entire package, as per Kotlin's Java interop reference.

I build it all using Gradle 4.10.2 and I supply -Xjsr305=strict to the Kotlin compiler, as instructed. Yet, when I reflectively inspect the types from Kotlin, they are reported as platform types, e.g.:

fun sample.Foo.bar(T!): kotlin.collections.(Mutable)List<T!>!

What am I doing wrong that I don't get the following output?

fun sample.Foo.bar(T): kotlin.collections.(Mutable)List<T>

Note that Spring Framework 5 has a similar annotation named @NonNullApi.

I'm using OpenJDK 11.

PS. I know I can just annotate every method and parameter with @Nonnull, but I'm after global behavior and converting Kotlin's List<T!> to List<T>.


Here's the MCVE:

  • src/main/java/sampleAnnotation/NonNullPackage.java

    package sampleAnnotation;
    
    import javax.annotation.Nonnull;
    import javax.annotation.meta.TypeQualifierDefault;
    import java.lang.annotation.*;
    
    @Nonnull
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    @TypeQualifierDefault({
            ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE
    })
    public @interface NonNullPackage {
    }
    
  • src/main/java/foo/package-info.java

    @NonNullPackage
    package foo;
    
    import sampleAnnotation.NonNullPackage;
    
  • src/main/java/foo/Foo.java

    package foo;
    
    import java.util.List;
    
    public interface Foo {
        <T> List<T> bar(T t);
    }
    
  • src/main/kotlin/sampleKotlin/Main.kt

    package sampleKotlin
    
    import foo.Foo
    import kotlin.reflect.full.memberFunctions
    
    fun main(args : Array<String>) {
        println(Foo::class.memberFunctions.first())
    }
    
  • build.gradle

    plugins {
        id 'java'
        id 'org.jetbrains.kotlin.jvm' version '1.3.0'
    }
    
    wrapper {
        gradleVersion = '4.10.2'
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
    
        compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib'
        compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect'
    }
    
    compileKotlin {
        kotlinOptions.freeCompilerArgs = ['-Xjsr305=strict']
    }
    
Tomasz Linkowski
  • 4,386
  • 23
  • 38
  • 1
    What happens when you actually use your Java class and, for example, try to pass null as argument or check a retrn value for null? Does it behave as expected, or not? – JB Nizet Nov 04 '18 at 13:33
  • 1
    @JBNizet Sorry for late reply. Indeed, when I try to compile method like `fun baz(foo: Foo) { foo.bar(null) }` I get "Null can not be a value of a non-null type Nothing". Thanks! As far as I understand, it means there's some bug in Kotlin reflection, right? And I have to think of another way of checking programatically if my API is seen as expected in Kotlin. – Tomasz Linkowski Nov 04 '18 at 16:01
  • I'm not sure it's a bug. To me, it just shows that they're still platform types, but that the compiler use their annotations to help you use them. – JB Nizet Nov 04 '18 at 16:11
  • 1
    @JBNizet Well, it feels like a bug to me :) For example, if I define `class FooImpl implements Foo { public List bar(T t) { return null; } }` (for which I get a warning in IntelliJ), and then I write `val list = FooImpl().bar("")` in Kotlin, it compiles without errors but at runtime, it throws "FooImpl().bar("") must not be null". In other words, the return type is *not* considered by Kotlin to be a platform type neither at compile time nor at runtime. I'll report it to JetBrains to see how they view it :) – Tomasz Linkowski Nov 04 '18 at 20:28
  • 1
    Reported as [KT-28009](https://youtrack.jetbrains.com/issue/KT-28009). – Tomasz Linkowski Nov 04 '18 at 20:49

0 Answers0