We're facing an interesting challenge when it come...
# library-development
p
We're facing an interesting challenge when it comes to the visibility of
copy
and the change related to it that is going to be made in Kotlin 2.1.0 (ref). We do have some thoughts, but I wanted to consult it with wider group of people before we decide on something. In the library github-workflows-kt, we generate certain data classes on the server side, and the users consume them via Kotlin Scripting. Here's a simple example of such generated class: SimpleActionWithRequiredStringInputs.kt. Notice what is done with the constructors: there are two, and the primary constructor is made private. It's because we want to enforce using named arguments when instantiating the class. To achieve it, we use a kind of hack with
vararg pleaseUseNamedArguments: Unit
as the first arg, and it's done in the secondary constructor because
Primary constructor vararg parameters are prohibited for data classes.
- that's why we also make the primary one private. Now, the whole problem revolves around the fact that our users may already depend on the existence of the public
copy
method. It's going to become private starting Kotlin 2.1.0. We want to keep providing this API for the users. These ideas came to our minds, among others: 1. implement
copy
ourselves ◦ won't work since there would be conflicting overloads 2. ensure that named arguments are used in a different way, e.g. in runtime via reflection (performance-wise, it's OK), then we could make the primary constructor public ◦ is it even possible? named/positional arguments distinction exists probably only in compile time 3. make the classes regular (non-
data
) and implement everything that data classes provide on our own, with the ultimate control over visibility ◦ well, it would probably work, but it's complex and I'm trying to see if there's another way 😄 So basically, the problem boils down to this: data classes (or data-like classes) with enforcing named arguments upon creation, working in Kotlin 2.1.0+. Our discussion so far: https://kotlinlang.slack.com/archives/C02UUATR7RC/p1724584851609139 Any novel take on this problem is greatly appreciated! CC @Vampire
p
Yeah, I've already linked it
e
I don't see what your issue is then. add the annotation to your class and you retain the old behavior (API for callers)
but if you want arguments to be named, doesn't the generated copy bypass that anyway? this seems like maybe they shouldn't have been
data class
to begin with
p
I'm confused by what's written after "When you apply ExposedCopyVisibility annotation to your data class:". Will @ExposedCopyVisibility remain as a way to preserve the current behavior? "But illegal usages of the 'copy' method will become inaccessible anyway." - I think I need an example of what it means
Regarding the copy method, good point - I somehow assumed it already enforces named args, but it doesn't
e
illegal usages are `@ConsistentCopyVisibility data class private constructor(...)`'s `copy`; that's the upcoming default. the current behavior is
@ExposedCopyVisibility
p
got it, thanks, looks obvious now
@Vampire so I think I don't understand what you wrote in the other thread: >
@ExposedCopyVisibility
does not solve it. > It only ensures binary compatibility for already compiled code that uses the method according to its documentation > but will not allow the consumer code to compile. + > As a quick fix the annotation is fine of course. I was under the impression you meant it as final solution. But from 2.1 on it will turn into an unsuppressible error.
e
as far as the docs say, the effective API for non-annotated classes will change in step 2 and the ABI will change in step 3, but using the annotation explicitly opts you into one or the other now (making
copy
legal or illegal)
v
illegal usages are `@ConsistentCopyVisibility data class private constructor(...)`'s `copy`; that's the upcoming default. the current behavior is
@ExposedCopyVisibility
That's not what I read. There are two things. There is the declaration of
copy
done by the data class mechanism. There is the call to the
copy
function from consumer code. This call in consumer code is, what is called "illegal *usage*". Further down it also says, that as soon as the illegal usages are fixed the annotation should be dropped again. Besides that, the intention of generating the methods we want was not only due to the
copy
visibility. The classes should never have been data classes (yes I know that I suggested it back then, mainly to get the
copy
function), you can for example read more at https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html. The data classes undermine several points of why we do the named-argument forcing. For example for
copy
you can use non-named arguments and thus any change in inputs except adding new in the end will be a source-breaking change and using decomposition also is a positional operation unless the feature for named decomposition is added to Kotlin, so if used has the same problem, which also is the reason I did not generate the
componentX
methods in the PR. I don't think there is any real alternative to generating the code to achieve the original goal, because any way that uses data classes includes the bad stuff we actually do not want.
👍 1
p
I don't think there is any real alternative to generating the code to achieve the original goal, because any way that uses data classes includes the bad stuff we actually do not want.
yeah, ok - I was trying to clarify whether we're in a rush preparing the library (the bindings server) for newer versions of Kotlin, or it's basically an independent change to move away from data classes. For me it looks like it's the latter
v
It is the latter with the former as side-effect and initiator
👍 1
Or at least that's my intention :-)
p
got it, thanks! so the only thing left to clarify in this thread is if the docs around copy's visibility are clear enough - from our discussion it looks like they are ambiguous
v
Tbh imho they are not. It clearly says "illegal usage of the copy function". And also explains further and so on. Even after this discussion I don't see how the doc can be interpreted differently if you fully read it and not just have a cursory look. :-)
👍 1