Disable componentN() generation in data classes

When writing libraries which will primarily be used from Java, I found that the the generated componentN() functions were irritating when accessing data classes from Java.


  • Is it possible to implement destructuring declarations without these methods?
  • (or) Is it possible to have an @NoDestructuring annotation which would disable this feature?

Could you specify a case where componentN would be a hindrance?

Well, the functions only make sense when using them from Kotlin. A Java/Groovy/(any other JVM language) user will just be confused when they see something like this when trying out a library:

It seems to be IDE problem, not language problem. The solution could be to introduce a special method annotation like @Generated which is added automatically to generated methods and force IDE not to list such methods in some cases.

1 Like

That would also work, even though I would prefer a @NoDestructuring annotation.
If the compiler could figure out if a class is a data class (the data modifier could be stored in the metadata of the class) destructuring could be supported without the componentN() functions.

bump

Please don’t bump. Especially not after such a short period of time with no response. The only reason to revive a topic is to ask how things are going with (possible) future features, and even then you have to be patient and wait at least a couple of weeks or months.

About your proposal: It won’t work because how would the Kotlin compiler know the order of the properties if they are not numbered?

I think @darksnake’s suggestion will solve the issue. Unfortunately the standard @Generated annotation is not retained in class files. There is already an issue for the standard annotation, but I guess it cannot be implemented.

The best thing you can do is create an issue for IntelliJ IDEA where you request that the code completion for Java stops showing certain Kotlin functions. I suppose they must only not be shown immediately, and that they will be shown when pressed harder as you might want to invoke them from Java too for some reason.

Well, for generated (synthetic) methods Java offer magical $ symbol, which hides generated (in the byte-code) methods from the normal Java-code.

I’d say Kotlin could generate methods component$1, component$2, which it still could use for destruction (those names are not supposed to be used directly). No problems here.

If user decides to provide such method (e.g. like it is done for Map.Entry), she still could write component1 manually - and it will be visible from Java (unless it’s an extension method, of course)

2 Likes

True, this could have been better names for the generated methods. I don’t know if this naming scheme has been considered. It is probably too late to switch to these names because of backward compatibility.

The $ symbol doesn’t actually hide anything from Java code. Its special meaning is only a convention; it doesn’t have any impact on the behavior of the compiler and IDE.

1 Like

I agree with you.

Annotate generated method should be helpful for test coverage and mutation test.

Yes, an @Generated annotation seems to be the cleanest solution! I’ll create an issue in the issue tracker this evening.

Edit: KT-20284

As to every Kotlin–generated method (not only componentN(), but also copy(...), etc.), why doesn’t the Kotlin compiler just annotate them as @JvmSynthetic, so that they’re considered as compiler–generated at the bytecode level and are not accessible from Java or any JVM–compliant language? I mean, they would still be accessible via reflection, but I don’t see it as a problem. Rather, they wouldn’t pop out of the suggestion box of any IDE, without the need of any modification to IntelliJ IDEA, which I think is not a good thing, as a Java method that has been accidentally named as component1() wouldn’t appear when it has nothing to do with Kotlin.

Below I just placed a poll, so to see whether there’s anyone that agrees with me.

  • Mark generated methods as @JvmSynthetic
  • Don’t agree

0 voters

Maybe this would work for componentN but functions like copy should be accessible from java so using JvmSynthetic wouldn’t work there. And if we’re talking about “every Kotlin-generated method” that would also include equals, hashCode for data classes. Using JvmSynthetic to mark generated methods in general just doesn’t work.

Using it for componentN is a good idea. No one really wants to call this from java anyways unless java adds their own destrucoring operation.

2 Likes

You may be right in regards to the copy() method, although I don’t understand what it’s meant for. However, when it comes to equals(), hashCode(), and toString(), these methods are declared in the java.lang.Object class, so the Kotlin compiler only generates its definition (implementation) and hence I wouldn’t say they’re compiler–generated, they’re more like “compiler–implemented.” And not considering them as compiler–generated means I wouldn’t, of course, annotate them as @JvmSynthetic.

There should be a third option in the poll along the lines of “something else”.

1 Like

I put getter and setter on the plate.

1 Like

If you don’t fully agree, you may want to post your opinion to discuss about it. My idea is not immutable — I’m willing to change my mind; it’s just that I can’t edit my original post, since it contains a poll and more than 5 minutes have elapsed since I posted it. But you see it, I’ve already clarified a few points due to some misunderstanding arising from my proposal.

Getters and setters wouldn’t obviously be annotated as @JvmSynthetic , since they exist for compatibility with Java code. Also, they’re emitted by the Kotlin compiler due to the JVM lack of properties, but they’re just the way properties are translated to JVM bytecode and not really compiler–generated.

Looking at the JVM specification synthetic for methods means: " The ACC_SYNTHETIC flag indicates that this method was generated by a compiler and does not appear in source code, unless it is one of the methods named in §4.7.8." This section provides further detail:

The Synthetic attribute is a fixed-length attribute in the attributes table of a ClassFile , field_info , or method_info structure (§4.1, §4.5, §4.6). A class member that does not appear in the source code must be marked using a Synthetic attribute, or else it must have its ACC_SYNTHETIC flag set. The only exceptions to this requirement are compiler-generated methods which are not considered implementation artifacts, namely the instance initialization method representing a default constructor of the Java programming language (§2.9.1), the class or interface initialization method (§2.9.2), and the Enum.values() and Enum.valueOf() methods.

In particular relevant here is the notion of implementation artefacts. For example they are used to support visibility where the language (Java) differs from the platform (JVM), but also to make covariant return types work. In the case of data classes, the generated methods are not so much artefacts, they are intended consequences of adding “data” in front of the class declaration. The component methods are valid accessors in Kotlin, not only accessible for structured decomposition. So from that perspective synthetic wouldn’t really be supported.

On the other hand, the Kotlin compiler can (and does) ignore Synthetic if it has further specific information. As such synthetic can be used to hide code from Java (due to how javac and Java editors treat synthetic). When the goal is to provide maximum interoperability this is something to do reluctantly. In other words, where possible this should be limited to cases where Java cannot use it correctly (or only very awkwardly - suspend functions), or has other issues - the method that supports default arguments with an additional mask parameter that is invisible in Kotlin.

2 Likes