Skip to content

Commit 0a27340

Browse files
committed
Adds manual chapter about Kotlin
1 parent ae69db4 commit 0a27340

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

docs/_manual/16-kotlin.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
title: "Kotlin support"
3+
permalink: /manual/kotlin/
4+
---
5+
EqualsVerifier has some support for Kotlin classes. In principle, Kotlin classes are the same as Java classes on the bytecode level, so EqualsVerifier can read them in the same way. However, the Kotlin compiler does some tricks in order to be able to represent its more advanced language features. This means that in some cases, EqualsVerifier needs to treat Kotlin classes a little differently. This is possible in many, but unfortunately not all, cases.
6+
7+
## Simple classes
8+
9+
Since EqualsVerifier works primarily with Java reflection, you'll have to provide EqualsVerifier with Java-style reflection types:
10+
11+
```kotlin
12+
EqualsVerifier.forClass(Foo::class.java)
13+
.verify()
14+
```
15+
16+
For most simple classes and data classes, that's enough.
17+
18+
## Delegates
19+
20+
Kotlin supports various forms of delegate fields. In a Kotlin class definition, delegates may look like this:
21+
22+
```kotlin
23+
data class StringContainer(s: String)
24+
25+
class Foo(container: StringContainer) {
26+
private val bar: String by container::s // object delegation
27+
private val baz: String by lazy { ... } // lazy delegation
28+
}
29+
```
30+
31+
But EqualsVerifier works with Java reflection, which looks at the fields as they exist in the compiled bytecode. And at that level, these fields don't exist! When decompiled to Java, it looks like this:
32+
33+
```java
34+
class Foo {
35+
private final StringContainer bar$receiver;
36+
private final kotlin.Lazy<String> baz$delegate;
37+
}
38+
```
39+
40+
EqualsVerifier can deal with most forms of Kotlin delegation, but in order to do so, it does require the `org.jetbrains.kotlin:kotlin-reflect` library to be available. You might have to add it to your `pom.xml` or your Gradle scripts. In some cases, EqualsVerifier can fall back to using bytecode names, but not in all cases. If EqualsVerifier needs it, it will let you know if it's not available.
41+
42+
EqualsVerifier can detect when a field is a Kotlin delegate, and it can translate between the Kotlin names and the bytecode names. So, in error messages, instead of `bar$receiver`, you will see `bar`. And instead of having to call `withIgnoredFields("baz$delegate")`, you can simply call `withIgnoredFields("baz")`. You can still call `withIgnoredFields("baz$delegate")` if you prefer, for example because you don't want to add `kotlin-reflect` to the project.
43+
44+
Unfortunately, when adding [prefab values](/equalsverifier/manual/prefab-values), you still have to provide values of the underlying type:
45+
46+
```kotlin
47+
EqualsVerifier.forClass(Foo::class.java)
48+
.withPrefabValuesForField(Foo::bar.name, StringContainer("a"), StringContainer("b"))
49+
.withPrefabValuesForField(Foo::baz.name, lazy { "a" }, lazy { "b" })
50+
.verify()
51+
```
52+
53+
Note also that some of the checks that EqualsVerifier normally does, like reflexivity checks, don't work for delegates. For example, the use of the triple-equals operator `===` will not be detected in this case:
54+
55+
```kotlin
56+
class Foo(container: StringContainer) {
57+
private val foo: String by container::s
58+
59+
override fun equals(other: Any?): Boolean =
60+
(other is Foo) && foo === other.foo
61+
}
62+
```
63+
64+
EqualsVerifier will generate two non-equal instances of `StringContainer`, but the Kotlin compiler generates bytecode that directly compares its `s` field in the `equals` method, without calling `equals` on `StringContainer` itself. Because of architectural decisions made before Kotlin even existed and which are hard to change now, EqualsVerifier gives both `StringContainers` the same String instance for their `s` fields, and because of that EqualsVerifier can't detect the difference between `==` or `===` (or in Java terms, the difference between `equals()` and `==`.)
65+
66+
## Interface delegation
67+
68+
A special case of delegation in Kotlin is interface delegation:
69+
70+
```kotlin
71+
interface Foo {
72+
val foo: Int
73+
}
74+
75+
data class FooImpl(override val foo: Int) : Foo
76+
77+
class InterfaceDelegation(fooValue: Int) : Foo by FooImpl(fooValue) {
78+
79+
override fun equals(other: Any?): Boolean =
80+
(other is InterfaceDelegation) && foo == other.foo
81+
82+
override fun hashCode(): Int = foo
83+
}
84+
```
85+
86+
In this case, the `FooImpl` instance will be stored in a field named `$$delegate_0`. Unfortunately, there is no way to reliably translate between this bytecode field and its Kotlin-level corresponding `foo` property. As a result, error messages may be more cryptic. Also, EqualsVerifier might ask more quickly for prefab values for the type that is delegated to (in this case: `FooImpl`).

docs/_pages/manual.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ These pages will quickly get you up and running with EqualsVerifier, and help yo
2020
* [Dealing with legacy systems](/equalsverifier/manual/legacy-systems)
2121
* [The Java Platform Module System](/equalsverifier/manual/jpms)
2222
* [What are these prefab values?](/equalsverifier/manual/prefab-values)
23+
* [Kotlin support](/equalsverifier/manual/kotlin)
2324
* [Additional resources](/equalsverifier/resources)

0 commit comments

Comments
 (0)