Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Throws has no effect when the target is a property

While taking a look at this question, I noticed that applying @Throws to a get or set use-site has no effect.

Additionally, the only valid targets for @Throws are AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, and AnnotationTarget.CONSTRUCTOR.

Other annotations, such as the JPA annotations and Deprecated work fine and are properly applied to the method!

This is strange behavior.

To demonstrate, I created a simple abstract class in Java, with one constructor, one method and one get method.

public abstract class JavaAbstractClass {

    @Deprecated
    @NotNull public abstract String getString() throws IOException;
    public abstract void setString(@NotNull String string) throws IOException;

    public abstract void throwsFunction() throws IOException;

    public JavaAbstractClass() throws IOException {
    }

}

As you can see, every method/constructor is marked as throwing IOException.

However, when I try to write an equivalent class in Kotlin, and mark the appropriate methods with throws for interop, the generated getString and setString methods have no throws clause.

abstract class KotlinAbstractClass @Throws(IOException::class) constructor() {

    @get:Deprecated("Deprecated")
    @get:Throws(IOException::class)
    @set:Throws(IOException::class)
    abstract var string: String

    @Throws(IOException::class)
    abstract fun throwsFunction()

}

The decompiled code:

@Metadata(Some metadata here)
public abstract class KotlinAbstractClass {
   /** @deprecated */
   @Deprecated(
      message = "Deprecated"
   ) // @Deprecated made it through!
   @NotNull
   public abstract String getString(); // Nothing here!

   public abstract void setString(@NotNull String var1); // Nothing here!

   public abstract void throwsFunction() throws IOException;

   public KotlinAbstractClass() throws IOException {
   }
}

To me, it seems to be because these internal annotations must be handled specially by the compiler, instead of being applied directly to the method.

Additionally, applying it to the getter of a non-abstract property:

val string: String
@Throws(IOException::class) get() = "Foo"

does generate a method with the signature public final String getString() throws IOException!

Perhaps this case wasn't handled properly?

Is this a bug?


Note: This doesn't have anything to do with whether the method actually throws this exception.

If I do:

@get:Throws(IOException::class)
val string: String
    get() = BufferedReader(FileReader("file.txt")).readText()

The compiled code is still

@NotNull
public final String getString() {
    return TextStreamsKt.readText((Reader)(new BufferedReader((Reader)(new FileReader("file.txt")))));
}

despite the fact that the FileReader constructor throws a FileNotFoundException.

Additionally, this shouldn't matter for abstract methods anyway, as they can't have an implementation and still can have a throws clause.

If I do as @tynn suggests and add a concrete implementation:

class ConcreteClass : KotlinAbstractClass() {

    override val string: String
        get() = BufferedReader(FileReader("file.txt")).readText()

    ...

}

I still get the same result.

like image 242
Salem Avatar asked Oct 29 '22 22:10

Salem


1 Answers

I believe @tynn suggests you do the following:

override val string: String
        @Throws(FileNotFoundException::class) get() = BufferedReader(FileReader("file.txt")).readText()

This should give you the proper Java version with throws in the signature. I guess the reasoning is that if you just do this:

@get:Throws(IOException::class)
val foo: String = "foo"

the compiler is smart enough to see that there's nothing in the getter that would throw an IOException, since you've never overridden it, so it won't produce the throws section. When the getter is overridden, the compiler has no way to know if the code you've supplied can throw, so it obeys the annotation and will always output the throws part.

UPDATE

The following seems to generate correct bytecode:

abstract class KotlinAbstractClass {

    abstract var string: String
        @Throws(IOException::class) get
        @Throws(IOException::class) set
}

After taking a closer look at it, I see no reason for @get:Throws(IOException::class) not to work in this case. You might file an issue on the YouTrack for Kotlin and see what the team members have to say about it.

like image 161
Egor Avatar answered Nov 07 '22 20:11

Egor