There's a new Kotlin (v1.8.0) compiler plugin that can be used to provide operator loading.
It hasn't been announced yet, but it's available for use. It's in the Kotlin source code here. It's the same plugin that Gradle is using in the Kotlin DSL in Gradle version 8.1.
Support in IntelliJ might be limited - make sure you're on the latest versions.
Applying the Kotlin Assignment compiler plugin
The Kotlin Assignment plugin can be applied like other Kotlin compiler plugins.
In Gradle projects it can be applied using a simple Gradle plugin.
I'm not familiar with compiling Kotlin using Maven, Ant, or via the CLI - but look at the other Kotlin compiler plugin instructions for similar instructions.
ephemient has shared CLI instructions on Slack
$KOTLIN_HOME/bin/kotlinc-jvm -Xplugin=$KOTLIN_HOME/lib/assignment-compiler-plugin.jar -P plugin:org.jetbrains.kotlin.assignment:annotation=fqdn.SupportsKotlinAssignmentOverloading ...
(you can see all the strings in https://github.com/JetBrains/kotlin/blob/master/plugins/assign-plugin/assign-plugin.common/src/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginNames.kt)
Setting up assignment overloading
First create an annotation in your project
package my.project
/** Denotes types that will be processed by the Kotlin Assignment plugin */
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class KotlinAssignmentOverloadTarget
and then apply the plugin in Gradle, and configure it to use your annotation
plugins {
kotlin("plugin.assignment") version "1.8.10"
}
assignment {
annotation("my.project.KotlinAssignmentOverloadTarget")
}
Then in your code apply the annotation to StringWrapper.
@KotlinAssignmentOverloadTarget
class StringWrapper(
var value: String = ""
) {
override fun toString(): String = value
}
I recommend that the annotation should typically be applied to a 'wrapper' class that contains one (or possibly more) mutable values. It could also be applied to an interface which other similar classes implement (which is what Gradle does).
Writing assignment operators
Once the compiler plugin is applied and the annotation is set up, you can start writing overload operators.
@KotlinAssignmentOverloadTarget
class StringWrapper(
var value: String = ""
) {
// member function
/** Provides overloaded setter for setting the value of [value] using an assignment syntax */
fun assign(value: String) {
this.value = value
}
}
// extension function
/** Provides overloaded setter for setting the value of [value] using an assignment syntax */
fun StringWrapper.assign(value: StringWrapper) {
this.value = value.value
}
Using assignment operators
You can now directly assign strings to the name property
val dataAlpha = DataHolder()
dataAlpha.name = "alpha"
println(dataAlpha) // prints: Data(name=alpha)
And also, using the extension function, assign one StringWrapper to another.
val dataAlphaCopy = DataHolder()
dataAlphaCopy.name = dataAlpha.name
println(dataAlphaCopy) // prints: Data(name=alpha)
Limitations
Properties cannot be mutable
Operator overloading will not work when properties of type StringWrapper are vars. They must be vals.
class MutableDataHolder {
var name = StringWrapper()
}
fun main() {
val dataAlpha = MutableDataHolder()
// no overload operator is generated, because name is a 'var'
dataAlpha.name = "alpha" // ERROR Type mismatch.
}
Assignment overloading only works for member properties
Keep in mind that assignment overload only works when the properties are member properties.
So, when there's a StringWrapper value that is not a class property, the assignment overloader won't work.
val nameValue = StringWrapper()
nameValue = "123" // ERROR Val cannot be reassigned, and Type mismatch
Either manually call the assign() function, or create a class with a property. Using an object expression also works.
val nameValue = StringWrapper()
// manually call the 'assign' function
nameValue.assign("123")
val values = object {
val name = StringWrapper()
}
values.name = "123" // 'name' is a member property, so the assignment overload works
Assignment overloading breaks delayed property assignment
In Kotlin classes the values of properties can be assigned immediately or later, in an init {} block.
If the property also supports overload assignment then this can completely prevents the property from being initialised.
@KotlinAssignmentOverloadTarget
class StringWrapper(
var value: String = ""
) {
fun assign(value: String) {
this.value = value
}
fun assign(value: StringWrapper) {
this.value = value.value
}
}
class SomeOtherClass {
val stringWrapper: StringWrapper
init {
// RUNTIME ERROR - this uses the assignment operator,
// preventing the property from being initialised.
stringWrapper = StringWrapper()
}
}
There's no helpful compilation warning, and it will fail with an unfriendly runtime error.
Cannot invoke "StringWrapper.assign(StringWrapper)" because "this.stringWrapper" is null
It's possible to work around this in simple situations by immediately assigning the property, but in more complex situations it could be very difficult.
class SomeOtherClass {
// Because StringWrapper supports assignment overloading
// the property must be instantiated immediately.
val stringWrapper: StringWrapper = StringWrapper()
}