Open jlink opened 3 years ago
One more item: declaration-site variance.
Currently, Kotlin treats Java's Arbitrary<T>
as invariant.
In other words, if I declare interface ArbitraryAction<T> : Arbitrary<MyActionType<T>>
(e.g. to reduce nesting everywhere), then Kotlin does not allow returning sub-types without casts.
The idea of Kotlin generics is that you can specify "usage type" once at type parameter declaration, and then it would be reused in all the usages.
In other words, in Kotlin, the following would mean "the interface only produces objects of T
, and it never consumes them":
interface Arbitrary<out T> {
...
Sample:
interface RegularArbitrary<T>
interface OutArbitrary<out T>
class Test {
lateinit var regularString: RegularArbitrary<String>
lateinit var regularAny: RegularArbitrary<Any>
lateinit var outString: OutArbitrary<String>
lateinit var outAny: OutArbitrary<Any>
fun withRegularArbitrary(regularAny: RegularArbitrary<Any>) {
}
fun withOutArbitrary(regularAny: OutArbitrary<Any>) {
}
fun test() {
// Type mismatch. Required: RegularArbitrary<Any> Found: RegularArbitrary<String>
withRegularArbitrary(regularString)
// Compiles ok
withOutArbitrary(outString)
}
}
In the ideal world, there should be an annotation (or something else) that could clarify Kotlin compiler that given interface declaration Arbitrary<T>
means Arbitrary<out T>
(here's the issue for Kotlin compiler: https://youtrack.jetbrains.com/issue/KT-41062 )
However, as a middle ground, it might be great if Arbitrary
interface could be converted to Kotlin so generic variance could be declared explicitly. It would enable clients to have just Arbitrary<String>
in declarations and Kotlin compiler would be able to pass Arbitrary<String>
into methods that receive Arbitrary<Any>
.
However, as a middle ground, it might be great if
Arbitrary
interface could be converted to Kotlin so generic variance could be declared explicitly. It would enable clients to have justArbitrary<String>
in declarations and Kotlin compiler would be able to passArbitrary<String>
into methods that receiveArbitrary<Any>
.
Moving Arbitrary
to the Kotlin side would make all jqwik code dependent on Kotlin, right?
Moving Arbitrary to the Kotlin side would make all jqwik code dependent on Kotlin, right?
That depends.
There's an option to skip dependency on kotlin-stdlib
, so it would look pretty much the same for existing Java clients.
In other words, kotlin-stdlib
is optional, and it is needed only if the code uses kotlin stdlib
in the implementation.
You could add something like the following to gradle.properties
, and the compilation would fail if you accidentally use something from stdlib:
# See https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library
kotlin.stdlib.default.dependency=false
However, if you convert the interface to Kotlin, it would indeed require Kotlin compiler for compiling the interface.
I think it should not be a problem since you already have jqwik-kotlin
module which requires Kotlin compiler.
As a side-effect, Kotlin compiler could produce nullability annotations based on Kotlin nullability types, so the generated bytecode is better even when using from Java.
@jlink , there's an update: https://youtrack.jetbrains.com/issue/KT-41062#focus=Comments-27-5752006.0-0
They suggest two options: a) Migrate code to Kotlin b) Write metadata explicitly. For instance, copy-paste @Metadata annotation from a Kotlin-generated bytecode, or generate the metadata via kotlinx.metadata
b) sounds much more realistic to me than a) At least short- or mid-term.
What's wrong with converting a single (or several) interface to Kotlin? I guess it would be easier than hand-crafting the metadata and praying for it to work.
Cross Java/Kotlin build is slow IME. And will probably lead to unknown complications.
Just in case, :api
compilation takes ~1-2 seconds in total.
@vlsi Moving core stuff over to Kotlin is an option for Version 2.0. There are a couple of features that could benefit from it. But I'd rather do it right than do it fast in this case.
I assume that as Arbitrary<Any>
is a workaround, right?
No, I do not do as Arbitrary<Any>
.
Sometimes specifying variance via Arbitrary<out ...>
helps.
Sometimes typealias Arbitrary<T> = net.jqwik.api.Arbitrary<out T>
is good enough.
I ran into variance issues when I tried to introduce an interface for deduplicating signatures: interface ArbitraryAction<T> : Arbitrary<MyActionType<T>>
For now, I just ignore the idea and write Arbitrary<out MyActionType<...>>
everywhere.
For now, I just ignore the idea and write
Arbitrary<out MyActionType<...>>
everywhere.
That could be a type alias then.
Typealias does not work when more nesting is needed.
For instance, an interface can't extend typealias
Testing Problem
With version 1.6.0 jqwik offers an optional kotlin module. Not everything from the initial issue https://github.com/jlink/jqwik/issues/151 has been implemented yet.
Suggested Solution
Add the following Kotlin-related features:
@ForAll list: List<String?>
. This will require that jqwik uses Kotlin reflection for creating theTypeUsage
values for parameters. Currently only Java reflection is used with a bit of enhancement throughTypeUsage.Enhancer