Kotlin detect ProtoNumber collisions compile time - kotlin

I want to use kotlin.serialization ProtoBuf mode in android app. How do I check compile time that #ProtoNumber annotations don't collide? Here's an example, both annotation specify 1 as number, this is broken but gives no runtime error:
#Serializable
data class TestOverlap(
#ProtoNumber(1)
val a: Int?,
#ProtoNumber(1)
val b: Int?,
)

Related

Kotlin - reflection and type safety

I am writing a small library for programatically generating SQL queries.
The goal is that the API of this library can be used like that:
myQuery.where(
MyClass::id equal "foo",
MyClass::age notEqual 55
).findAll()
The signatures of both the where function, and the infix operators is under my control. Here are the relevant definitions that I have at the moment:
interface KpaFilter<BASE, FIELD>
infix fun <BASE: Any, FIELD: Any> KProperty1<BASE, FIELD>.equal(value: FIELD): KpaFilter<BASE, FIELD> { ... }
fun <BASE: Any> where(vararg filters: KpaFilter<BASE, *>?): KpaQuery<BASE>
Still, I cannot find a proper way to make this type safe. For example, I would like this to raise a compilation error, but unfortunately, it compiles:
val someFilter = MyClass::id equal 55 // id is a string
Is it possible to somehow modify the signatures of the declarations above and achieve this kind of type safety, without making the API more cumbersome than its current form?
If you don't mind using Kotlin internal APIs, there is a hidden feature in the compiler which does exactly this. The feature is enabled by annotating a parameter with #kotlin.internal.Exact:
#Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
infix fun <BASE: Any, FIELD: Any> KProperty1<BASE, #kotlin.internal.Exact FIELD>.equal(value: FIELD): KpaFilter<BASE, FIELD> { ... }
This works pretty well and can be used in regular Kotlin projects. Of course, we don't have guarantees it will be supported in the future.

Kotlin Problem Delegating to Map with DefaultValue - Language Bug?

In the following code, where MyMap trivially implements Map by delegation to impl:
foo#host:/tmp$ cat Foo.kt
class MyMap <K, V> (val impl : Map <K, V>) : Map<K, V> by impl {
fun myGetValue (k: K) = impl.getValue(k)
}
fun main() {
val my_map = MyMap(mapOf('a' to 1, 'b' to 2).withDefault { 42 })
println(my_map.myGetValue('c')) // OK
println(my_map.getValue('c')) // ERROR
}
Why do I get the following error on the second println?
foo#host:/tmp$ /path/to/kotlinc Foo.kt
foo#host:/tmp$ /path/to/kotlin FooKt
42
Exception in thread "main" java.util.NoSuchElementException: Key c is missing in the map.
at kotlin.collections.MapsKt__MapWithDefaultKt.getOrImplicitDefaultNullable(MapWithDefault.kt:24)
at kotlin.collections.MapsKt__MapsKt.getValue(Maps.kt:344)
at FooKt.main(Foo.kt:8)
at FooKt.main(Foo.kt)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.jetbrains.kotlin.runner.AbstractRunner.run(runners.kt:64)
at org.jetbrains.kotlin.runner.Main.run(Main.kt:176)
at org.jetbrains.kotlin.runner.Main.main(Main.kt:186)
foo#bigdev:/tmp$
Update: The compiler and runtime version outputs are:
foo#host:/tmp$ kotlinc -version
info: kotlinc-jvm 1.6.10 (JRE 17.0.1+12-LTS)
foo#host:/tmp$ kotlin -version
Kotlin version 1.6.10-release-923 (JRE 17.0.1+12-LTS)
foo#host:/tmp$ javac -version
javac 17.0.1
foo#host:/tmp$ java -version
openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment Corretto-17.0.1.12.1 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.1.12.1 (build 17.0.1+12-LTS, mixed mode, sharing)
This is occurring because of the slightly unexpected way in which withDefault is implemented. The wrapper that withDefault produces doesn't override getValue() as this is impossible because getValue() is an extension function. So unfortunately, what we have instead is a classic OOP anti-pattern: getValue() does an is check to see if it's being called on the internal MapWithDefault interface, and only uses the default value if that is the case. I don't see any way they could have avoided this situation without breaking the Map contract.
myGetValue calls getValue on the underlying delegate, which is a MapWithDefault, so it works fine.
getValue called on your MyMap instance will fail the internal is MapWithDefault check because MyMap is not a MapWithDefault, even though its delegate is. The delegates other types are not propagated up to the class that delegates to it, which makes sense. Like if we delegated to a MutableMap, we might want the class to be considered only a read-only Map.
Though I would have expected your code to work to be honest, this could be bug but we'd have to look at the produced bytecode.
In the documentation it says (emphasis mine):
This implicit default value is used when the original map doesn't contain a value for the key specified and a value is obtained with Map.getValue function, for example when properties are delegated to the map.
The conflict of "contracts" comes from the actual Map interface, which says:
Returns the value corresponding to the given [key], or null if such a key is not present in the map.
The maps default contract must fulfill this, so it can "only" return null when a key is non-existent.
I have found one discussion about this in the Kotlin forums.

Kotlin compiler shows compilation error and suggests to "change type from 'T' to 'T"' while there is only one T in the context

I tried to implement some type classes from Haskell but confronted the issue that is probably a bug in the Kotlin compiler.
interface Semigroup<Instance> {
infix fun Instance.assocOp(oother: Instance): Instance
}
inline fun <reified T: Semigroup<T>> Iterable<T>.concat() = this.reduce<T, T> { acc: T, t: T -> acc.assocOp(t) }
The error message is "Expected parameter of type T".
IDEA suggests to "Change type from 'T' to 'T'" (does nothing).
I expect acc to belong to the type T mentioned in generics. But because of some reason compiler tries to find some other type T. I tried to
specify the type explicitly/implicitly
build ignoring IDEA message
change used version of Kotlin compiler (I have tried 1.4.20, 1.4.10, 1.3.72).
Nothing worked.
I suppose that writing the function without reduce (manually) may help to deal with it. Also, writing java code doing the same may help to mitigate the problem. But these solutions are only workarounds for the problem. Is the issue my fault or the compiler bug?
The compiler error clearly is not helpful here. However, it is correct that the code should not compile IMO.
You're defining the method assocOp as a member extension function. The extension applies to any type T, but it's a member of the interface Semigroup<T>.
To call that extension, you need both a receiver or type T and a receiver of type Semigroup<T> (acting as a context).
In your case, the type T both plays the role of the generic type parameter and of the Semigroup<T>, but you still need to have 2 "receivers" for your extension, even if both are the same instance.
Maybe try this:
inline fun <reified T : Semigroup<T>> Iterable<T>.concat(): T =
reduce<T, T> { t1: T, t2: T -> with(t1) { t1.assocOp(t2) } }
The with(t1) { ... } provides a context of type Semigroup<T>, while the t1 used in t1.assocOp(t2) acts as the T receiver.

Overload resolution ambiguity on Int.toBigDecimal in Kotlin

I want to convert integers to bigDecimals. However, it seems that IntelliJ'Idea sees twice the definition for the toBigDecimal().
when trying on the REPL I get this error:
12.toBigDecimal()
error: overload resolution ambiguity:
#SinceKotlin #InlineOnly public inline fun Int.toBigDecimal(): BigDecimal defined in kotlin
#SinceKotlin #InlineOnly public inline fun Int.toBigDecimal(): BigDecimal defined in kotlin
Meanwhile it is fine using Strings:
"12".toBigDecimal()
res1: java.math.BigDecimal = 12
I am using Kotlin version 1.3.61 (JRE 1.8.0_232-b09)
I am very confused and I don't know waht to look after. any pointers?
I have just experienced the same issue and managed to solve it.
Just right click on the error and select ignore in compile and it will compile and run fine.

Annotation class does not validate input as enum in Kotlin

I'd like to create annotation instead of enum use it for when statement
#Retention(AnnotationRetention.SOURCE)
#IntDef(SELECT.toLong(), WEAR.toLong(), BAND.toLong())
annotation class CurrentState
companion object {
const val SELECT = 0
const val WEAR = 1
const val BAND = 2
}
private fun handleFragment(#CurrentState state:Int) {
val fragment:Fragment =
when(state){
SELECT -> SelectDeviceFragment.newInstance()
WEAR -> ConnectWatchFragment.newInstance()
BAND -> SelectDeviceFragment.newInstance()
}
From my understanding, this code should prevent me from performing following code:
handleFragment(5)
and when statement should not ask me to add else statement, as expected from enum
What I'm doing wrong or misunderstand?
From https://discuss.kotlinlang.org/t/intdef-and-stringdef-not-being-checked-at-compile-time/7029:
This checking doesn’t come from the compiler, but from Android lint. Work to make android lint language independent is being done, but if I’m not mistaken you’ll need a newer version of Android Studio for it.
And Android Studio 3.1 blog post mentions lint checks for Kotlin as a feature (though it doesn't say whether this check specifically is supported).
The #IntDef annotation is part of the Android framework. The compiler is not aware of any specific semantics of this annotation, and is not able to use it for checking the exhaustiveness of when statements.
Moreover, even with the annotation, you can call handleFragment(5). Such code will not be a compiler error, it will only be reported as a lint warning.