5

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.

Salem
  • 13,516
  • 4
  • 51
  • 70
  • Try to implement a getter or setter actually throwing this exception. If your code is safe, you do not have to redeclare the `throws` declarations of what you override. Maybe _Kotlin_ is just clever with its `@Throws` annotation. – tynn Dec 10 '17 at 10:59
  • @tynn I will edit my question. – Salem Dec 10 '17 at 11:06

1 Answers1

2

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.

Egor
  • 39,695
  • 10
  • 113
  • 130
  • No, I addressed both parts in my question, putting `@Throws` directly onto _any_ getter, even if it does not throw, adds a `throws` clause. Putting `@get:Throws` onto _any_ property (even if it _does_ throw) has no effect. If I do as you suggest and put `val string: String @Throws(IOException::class) get() = "Foo"`, "the compiler is smart enough to see that there's nothing in the getter that would throw an IOException" is impossible, because a throws clause is still generated. – Salem Dec 11 '17 at 08:46
  • 1
    Ooh, I had forgotten about that possibility. I agree that it seems like the use-site annotation should work, I'll do that when I can. Thanks! – Salem Dec 11 '17 at 15:42