kotlin - type inference and type mismatch when updating kotlin version - kotlin

I'm having some difficulties trying to understand what is going on the following code:
fun helperMethodNameA(someId: String, rules: RulesObject) {
val content = JsonNodeFactory.instance.arrayNode().apply { // A & B
add(JsonNodeFactory.instance.objectNode().apply {
set("body", JsonNodeFactory.instance.objectNode().apply { // C
set("text", JsonNodeFactory.instance.objectNode().apply { // D
set("value", JsonNodeFactory.instance.textNode(mapper.writeValueAsString(rules))) // E
})
})
})
}
return helperMethodNameB(someId, content.toString())
}
This project has a dependency on another which set Kotlin v1.3.20. The dependency project had the Kotlin version bumped up to v1.3.60. This bit broke with the update as per the following:
A - [ERROR] <pathToFile> [line, position] Type inference failed: inline fun <T> T.apply(block: T.() -> Unit): T
[ERROR] cannot be applied to
[ERROR] receiver: ArrayNode! arguments: (ArrayNode!.() -> ArrayNode!)
B - [ERROR] <pathToFile> [line, position] Type mismatch: inferred type is ArrayNode!.() -> ArrayNode! but ArrayNode!.() -> Unit was expected
C - [ERROR] <pathToFile> [line, position] Type inference failed: Not enough information to infer parameter T in operator fun <T : JsonNode!> set(p0: String!, p1: JsonNode!): T!
[ERROR] Please specify it explicitly.
D - [ERROR] <pathToFile> [line, position] Type inference failed: Not enough information to infer parameter T in operator fun <T : JsonNode!> set(p0: String!, p1: JsonNode!): T!
[ERROR] Please specify it explicitly.
E - [ERROR] <pathToFile> [line, position] Type inference failed: Not enough information to infer parameter T in operator fun <T : JsonNode!> set(p0: String!, p1: JsonNode!): T!
[ERROR] Please specify it explicitly.
What am I missing here?

The solution was to specify the type as bellow:
fun helperMethodNameA(someId: String, rules: RulesObject) {
val content = JsonNodeFactory.instance.arrayNode().apply {
add(JsonNodeFactory.instance.objectNode().apply {
set<ObjectNode>("body", JsonNodeFactory.instance.objectNode().apply {
set<ObjectNode>("text", JsonNodeFactory.instance.objectNode().apply {
set<TextNode>("value", JsonNodeFactory.instance.textNode(mapper.writeValueAsString(rules)))
})
})
})
}
return helperMethodNameB(someId, content.toString())
}

Related

Kotlin `use` with AutoCloseable type and a lambda returning Boolean

This must be simple, but I've been banging my head against it for half an hour now... Here's my old, exception-unsafe code:
fun isReady(): Boolean {
try {
val cc: CommandChannel = getCommandChannel() // implements AutoCloseable
cc.writeMessage("blahblah")
val response = cc.readMessage() // might throw
cc.close()
if (response.error == null) {
return true
}
} catch (e: ChannelConnectionException) {
}
return false
}
I'd like to rewrite it to use use, something like this:
fun isReady(): Boolean {
getCommandChannel().use {
try {
it.writeMessage("blahblah")
if (it.readMessage().error == null) {
return true
}
} catch (e: ChannelConnectionException) {
}
return false
}
}
But Kotlin gives me an error:
[ERROR] X.kt:[108,29] Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
[ERROR] #InlineOnly public inline fun <T : Closeable?, R> ???.use(block: (???) -> ???): ??? defined in kotlin.io
[ERROR] X.kt:[110,17] Unresolved reference: it
[ERROR] X.kt:[111,21] Unresolved reference: it
[ERROR] X.kt:[112,21] 'return' is not allowed here
[ERROR] X.kt:[116,13] 'return' is not allowed here
[ERROR] X.kt:[118,5] A 'return' expression required in a function with a block body ('{...}')
[ERROR] -> [Help 1]
Or, I say "okay I don't understand terse lambda syntax, let me use fun," and I try this:
fun isReady(): Boolean {
return getCommandChannel().use(fun(cc: CommandChannel): Boolean {
try {
cc.writeMessage("blahblah")
if (cc.readMessage().error == null) {
return true
}
} catch (e: ChannelConnectionException) {
}
return false
})
}
Kotlin still complains:
[ERROR] X.kt:[108,36] Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
[ERROR] #InlineOnly public inline fun <T : Closeable?, R> ???.use(block: (???) -> Boolean): Boolean defined in kotlin.io
[ERROR]
[ERROR] -> [Help 1]
My current guess is that the problem is that the CommandChannel I'm trying to use implements AutoCloseable instead of Closeable... but I don't know what I'm supposed to do about that! (Is there some sort of adaptor I need to wrap it in, maybe?) Fundamentally, I just want to make sure that cc's close method gets called correctly at the end of cc's scope, so that I'm not leaking connections.
If you look at the Kotlin source,
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R
is available in kotlin-stdlib package (source), whereas
public inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R
is available in kotlin-stdlib-jdk7(source) package.
So, the error that you get :
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
[ERROR] #InlineOnly public inline fun <T : Closeable?, R> ???.use(block: (???) -> Boolean): Boolean defined in kotlin.io
indicates that since CommandChannel implements AutoCloseable and not Closeable, the use function appropriate to that is not available in your project. Try adding the dependency of kotlin-stdlib-jdk8 or kotlin-stdlib-jdk7 and you should then be able to use .use{} for CommandChannel.

Kotlin DSL - type inference on returned value

I'm trying to introduce the following (simplified) DSL:
fun <T> myDsl(specFn: DslSpec<T>.() -> Unit) {
val value = DslSpec<T>().apply(specFn).fn!!()
println("value is: $value")
}
class DslSpec<T> {
internal var fn: (() -> T)? = null
fun getValue(fn: () -> T) {
this.fn = fn
}
}
fun testCase() {
myDsl {
getValue {
"abc"
}
}
}
But it fails to infer T based just on the returned type of getValue ("Not enough information to infer type variable T"). I kind of see how it could be a very hard task to do for a compiler, but thought maybe there are already some tricks to make constructs like this work?
If you're using a version of Kotlin < 1.6.0, you should add #BuilderInference to the specFn argument:
fun <T> myDsl(#BuilderInference specFn: DslSpec<T>.() -> Unit) {
...
}
https://pl.kotl.in/__xy04j88
If you're using a version >= 1.6.0, you should either use the annotation as well, or both your declarations and their usages must be compiled with the compiler argument -Xenable-builder-inference.

Unexpected reified type inference

I expected the below code to be failed to compile at the last line. because it missed some type expression for type inference.
test.kts:
import kotlin.reflect.KClass
sealed class Test {
object A : Test()
object B : Test()
companion object {
fun <T : Test> getByClass(c: KClass<T>): T =
#Suppress("UNCHECKED_CAST")
when (c) {
A::class -> A as T
B::class -> B as T
else -> throw IllegalArgumentException("Unexpected class: $c")
}
inline fun <reified T : Test> get(): T = getByClass(T::class)
}
}
fun f1(a: Test.A) {
println(a::class)
}
fun f2(a: Any) {
println(a::class)
}
// Works right
f1(Test.get())
// Just invocation get raises compile error
// Test.get()
// Providing type raises compile error
// Test.get<Any>()
// f2(Test.get<Any>())
// This does not throw compile error
f2(Test.get())
Outputs and versions in my terminal:
$ kotlinc -version
info: kotlinc-jvm 1.4.21 (JRE 11.0.5+10)
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.intellij.util.ReflectionUtil to method java.util.ResourceBundle.setParent(java.util.ResourceBundle)
WARNING: Please consider reporting this to the maintainers of com.intellij.util.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
$ kotlinc -script test.kts
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.intellij.util.ReflectionUtil to method java.util.ResourceBundle.setParent(java.util.ResourceBundle)
WARNING: Please consider reporting this to the maintainers of com.intellij.util.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
class Test$Test$A
java.lang.IllegalArgumentException: Unexpected class: class Test$Test
at Test$Test$Companion.getByClass(test.kts:13)
at Test.<init>(test.kts:32)
This script raises java.lang.IllegalArgumentException: Unexpected class: class Test$Test at 13th line in getByClass. The get() seems to get Test class as a reified class.
How I should write get() as type safe? I want the code to be failed at compile, not at runtime.
This is an alternative way of getting a type from a sealed class. You can use the sealedSubclasses from KClass:
sealed class Service {
object A : Service()
object B : Service()
object C : Service()
companion object {
val all: List<Service> = Service::class.sealedSubclasses
.map { it.objectInstance as Service }
fun findByClass(kClass: KClass<out Service>): Service = all.first { it::class == kClass }
}
You could then look for a concrete sub type using findByClass():
val serviceC: ServiceC = Service.findByClass(Service.C::class)
Note
findByClass() can safely return a non null value, though first can throw an exception when the element is not present, but because of parameter kClass: KClass<out Service> we can only pass a sub type of Service. So that class type must be available.
the reason is you are doing this in the static block of class Test, so it is passing the function the main class type, not the instance type(it's a class method not an instance method). you need to change it so that it could be only called on the instances like A and B (non-static). something like this can work:
sealed class Test {
object A : Test()
object B : Test()
companion object {
fun <T : Test> getByClass(c: KClass<T>): T =
#Suppress("UNCHECKED_CAST")
when (c) {
A::class -> A as T
B::class -> B as T
else -> throw IllegalArgumentException("Unexpected class: $c")
}
}
fun get(): Test {
return getByClass(this::class)
}
}
fun main() {
val b = Test.B
println(b.get())
}

scalamock: how to mock curried method with repeated paramter

I am trying to mock the awscala.dynamodbv2.DynamoDB.putConditionalMethod
How would one define an expects for a method which is curried and includes a repeated parameter:
putConditional(tableName: String, attributes: (String, Any)*)(cond: Seq[(String, aws.model.ExpectedAttributeValue)]): Unit
Here's what I've got working:
(mockClient.putConditional(_: String, _: (String, Any))(_: Seq[(String, ExpectedAttributeValue)]))
.expects("Data-Identity-Partitions",
*,
Seq(
"DatacenterId" -> exp.isNull,
"InstanceId" -> exp.isNull,
"TTL" -> exp.isNull
))
But this:
(mockClient.putConditional(_: String, _: (String, Any))(_: Seq[(String, ExpectedAttributeValue)]))
.expects("Data-Identity-Partitions",
Seq("DatacenterId" -> 1,
"InstanceId" -> 0,
"TTL" -> System.currentTimeMillis()),
Seq(
"DatacenterId" -> exp.isNull,
"InstanceId" -> exp.isNull,
"TTL" -> exp.isNull
))
results in the following compiler error:
[error] AwsPartitionActorSpec.scala:76: type mismatch;
[error] found : Seq[(String, Any)]
[error] required: org.scalamock.matchers.MockParameter[(String, Any)]
[error] Seq[(String, Any)]("DatacenterId" -> 1,
[error] ^
better late than never i suppose, here's my suggestion:
trait testtrait {
def foo(t: String, a: (String, Any) *): Int
}
"foo" should "work" in {
val m = mock[testtrait]
m.foo _ expects where {
case ("foo", Seq(("bar", 42L), ("baz", "mango"))) => true
case _ => false
} returns 5
m.foo("foo", ("bar", 42L), ("baz", "mango")) should be (5)
}

Kotlin Platform Types and Generics

I am stuck at the last Kotlin Koans task 28 where I get these error messages when I try to call my partitionTo function:
Error:(25, 12) Kotlin: Type inference failed. Expected type mismatch: found: kotlin.Pair<kotlin.Collection<kotlin.String!>, kotlin.Collection<kotlin.String!>> required: kotlin.Pair<kotlin.List<kotlin.String>, kotlin.List<kotlin.String>>
Error:(30, 12) Kotlin: Type inference failed. Expected type mismatch: found: kotlin.Pair<kotlin.Collection<kotlin.Char!>, kotlin.Collection<kotlin.Char!>> required: kotlin.Pair<kotlin.Set<kotlin.Char>, kotlin.Set<kotlin.Char>>
I read that the exclamation mark at the end of a type marks a platform type. But then I would have expected the type java.lang.String! and not kotlin.String!. Do I have to enforce null checks somewhere? Maybe someone can help me with this last task. I am using IntelliJs Kotlin Plugin version 0.10.195.
This is my code:
fun List<String>.partitionWordsAndLines(): Pair<List<String>, List<String>> {
return partitionTo(ArrayList<String>(), ArrayList<String>()) { s -> !s.contains(" ") }
}
fun Set<Char>.partitionLettersAndOtherSymbols(): Pair<Set<Char>, Set<Char>> {
return partitionTo(HashSet<Char>(), HashSet<Char>()) { c -> c in 'a'..'z' || c in 'A'..'Z'}
}
inline fun <reified T> Collection<T>.partitionTo(first: MutableCollection<T>, second: MutableCollection<T>, predicate: (T) -> Boolean): Pair<Collection<T>, Collection<T>> {
for (element in this) {
if (predicate(element)) {
first.add(element)
} else {
second.add(element)
}
}
return Pair(first, second)
}
The problem is that you promised to return a pair of Lists:
fun List<String>.partitionWordsAndLines(): Pair<List<String>, List<String>> {
But in fact returned a pair of Collections:
inline fun <reified T> Collection<T>.partitionTo(...): Pair<Collection<T>, Collection<T>> {
A useful hint from the task:
The signature of the function 'toCollection()' from standard library may help you.
See it here: https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/generated/_Snapshots.kt#L207
P.S. Why did you want to use inline and reified on partitionTo?
Check you return types partitionWordsAndLines(): **Pair, List>**, extensions require List or Set where partitionTo returns Collection
Here is fixed version
inline fun <reified T, C: MutableCollection<T>> Collection<T>.partitionTo(first: C, second: C, predicate: (T) -> Boolean): Pair<C, C>