Kotlin Current Function Name by Anonymous Object No Longer Works in Kotlin 1.3.x - kotlin

In Kotlin JVM 1.2.x, I use to be able to do the following:
inline fun <R> Logger.logStuff(
crossinline f: () -> R
): R {
val methodName = object {}.javaClass.enclosingMethod.name
try {
this.debug("$methodName : Begin")
f()
this.debug("$methodName : End")
} catch (ex: Exception) {
this.error("$methodName : Threw exception : $ex")
throw ex
}
}
class Foo {
fun doStuff() = log.logStuff {
1 + 3
}
}
This would give me logs like:
Foo : doStuff : Begin
Foo : doStuff : End
But, after upgrading to Kotlin 1.3.50 (from 1.2.x), I got logs like the following:
Foo : logStuff : Begin
Foo : logStuff : End
I am aware of currentThread().stackTrace[1].methodName to get the enclosing method name, but I was hoping to avoid that.
Is there another way to get the current function name?

You may convert your logStuff() fun to extension and make your R type reified, like this:
inline fun <reified R : Any> R.logStuff() {
val methodName = object {}.javaClass.enclosingMethod.name
}
Update 08/12/22:
What is the difference and what this solution gives us?
We may improve it just a little bit more to visualise it better:
inline fun <reified R : Any> R?.log(msg: String?) = this?.run {
val objectId: String = this::class.simpleName ?: this::class.hashCode().toString()
val methodName = object {}.javaClass.enclosingMethod?.name
Log.d("TAG", "$objectId.$methodName() {\n $msg\n}")
}
Now we may use it just like:
class ExampleClass {
fun exampleFun() {
log("test")
}
}
And it will give us an output like:
ExampleClass.exampleFun() {
test
}
Because log function is inlined, the code will be executed inside an "exampleFun()".
As you may see, there would be a "Current Function Name" in the output. Please also note, that in some cases class may not have a simpleName, that's why there is a fallback to hashCode.
Update 30/01/22:
If above doesn't work for you, please try this:
inline fun <reified R : Any> R?.log(msg: String?) = this?.run {
val objectId: String = this::class.simpleName ?: this::class.hashCode().toString()
val methodName = StackWalker.getInstance().walk { frames ->
frames.findFirst().map { it.methodName }.orElse(null)
}
println("$objectId.$methodName() {\n $msg\n}")
}

Related

How to a class with generics with an inline refied function

If I have a class like the following:
class SimpleClass {
var test: Int = 1
}
I can do something like this:
inline fun <reified T : SimpleClass> myFunction1(): T {
//Do something with T
}
But if I have a class with generics:
class ComplexClass<T> {
var test: T? = null
}
How can I do the same?
The following doesn't work:
inline fun <reified T : ComplexClass<C>> myFunction2(): T {
//Do something with T
//Do something with C
}
How can I do it?
Here is a more complex example, if needed.
Let's take a look at your example
class Mediator {
//It does't work! My question is about this function.
inline fun <reified T : Container<C>> mediate(string: String): T {
if (C::class == Child1::class)
//Do something
if (C::class == Child2::class)
//Do something else
}
}
class UseExample : Mediator() {
fun example1(): Container<Child1> {
return mediate("test1") // your mediate function does not take
// any parameters to determine the generic type
}
fun example2(): Container<Child2> {
return mediate("test2")
}
}
To perform something with the type C which is used to create Container<C> and perform something with the result type which you represent as T : Container<C> you only need to know the C. Since reified can only be used to keep the type if it is known during the compile time at the call site, rewrite your function signature like this.
inline fun <reified C> mediate(string: String): Container<C> {
// now you know the C and you know the T which is Container<C>
if (C::class == Child1::class) ...
// Since T depends on C, you can only check the C type to perform logic
}
Use it like following
fun example1(): Container<Child1> {
return mediate<Child1>("test") // Note that know your C
// type is known at the function call therefore compiler
// can substitute it with exact type
}
Here is my minimal example from Kotlin Playground
class Container<T>(t: T) {
val smth : T = t
}
class Mediator {
inline fun <reified C> mediate(string: String): Container<C> {
if (C::class == Int::class) {
println("Int")
return Container<Int>(1) as Container<C>
}
throw IllegalStateException("Yopta")
}
}
fun main() {
val m = Mediator()
m.mediate<Int>("ABC") // Output is "Int"
}

why lambda function parameter's type is Nothing on generic type with asterisk in kotlin?

when i call some api, i wished use multiple callback with generic parameter.
so i defined CallBackData class
class CallBackData<T>(val func: (T?) -> Boolean, val params: T?)
it not data class. because it super class of other callbacks.
and i define Array<CallBackData<*>> variable for multiple callback.
val callbackDts : Array<CallBackData<*>> = arrayOf(
CallBackData(::sampleCallback1, SomeClass(1)),
CallBackData(::sampleCallback2, "hello"),
CallBackData(::sampleCallback3, -1),
)
but when i call func, it say error
Type mismatch.
Required: Nothing?
Found: Any?
i don't get it. why? isn't same it.params type T is same of it.func(param(T))? right? why is Nothing Type? why is not same?
this is full code
fun start(){
val callbackDts : Array<CallBackData<*>> = arrayOf(
CallBackData(::sampleCallback1, SomeClass(1)),
CallBackData(::sampleCallback2, "hello"),
CallBackData(::sampleCallback3, -1),
)
callApi(callbackDts)
}
fun callApi(callbacks : Array<CallBackData<*>>){
callbacks.forEach{
it.func(it.params)
}
}
fun sampleCallback1(params: SomeClass?) : Boolean {
println("sampleCallback1 ${params.toString()}")
return true
}
fun sampleCallback2(params: String?) : Boolean {
println("sampleCallback2 $params")
return true
}
fun sampleCallback3(params: Int?) : Boolean {
println("sampleCallback3 $params")
return true
}
data class SomeClass(val i:Int)
class CallBackData<T>(val func : (T?) -> Boolean, val params: T?)
i tried convert to like this (using out keyword), but it's failed same.(Lambda's parameter type is Nothing?)
fun start(){
val callbackDts : Array<CallBackData<out Any?>> = arrayOf(
CallBackData(::sampleCallback1, SomeClass(1)),
CallBackData(::sampleCallback2, "hello"),
CallBackData(::sampleCallback3, -1),
)
callApi(callbackDts)
}
fun callApi(callbacks : Array<CallBackData<out Any?>>){
callbacks.forEach{
it.func(it.params)
}
}
fun sampleCallback1(params: SomeClass?) : Boolean {
println("sampleCallback1 ${params.toString()}")
return true
}
fun sampleCallback2(params: String?) : Boolean {
println("sampleCallback2 $params")
return true
}
fun sampleCallback3(params: Int?) : Boolean {
println("sampleCallback3 $params")
return true
}
data class SomeClass(val i:Int)
class CallBackData<T>(val func : (T?) -> Boolean, val params: T?)
i look forward to your reply. thanks!
Unfortunately, the type information of T is gone once you projected a CallbackData<T> to CallbackData<*>. It is no longer known that it.func takes the same type as it.params.
But you do know that they are the same type in the CallBackData class itself, don't you? So you can just add a call method
class CallBackData<T>(val func : (T?) -> Boolean, var params: T?) {
fun call() = func(params)
}
and
callbacks.forEach{
it.call()
}
Or you can overload the invoke operator:
operator fun invoke() = func(params)
You would then be able to do it() directly.
Even if you don't have control over CallBackData, you can still add an extension function:
operator fun <T> CallBackData<T>.invoke() = func(params)
Adding to other answers: if this is the only reason why you defined the CallBackData, then you don't really need this class. Kotlin has support for closures, so we don't need to intercept functions and parameters separately:
fun start(){
val callbackDts = arrayOf<() -> Unit>(
{ sampleCallback1(SomeClass(1)) },
{ sampleCallback2("hello") },
{ sampleCallback3(-1) },
)
callApi(callbackDts)
}
fun callApi(callbacks : Array<() -> Unit>){
callbacks.forEach{
it()
}
}
You can define a function
fun <T> CallBackData<T>.call() = func(params)
and then callApi can be changed to:
fun callApi(callbacks : Array<CallBackData<*>>){
callbacks.forEach{ it.call() }
}
Then Kotlin does not have a problem to infer that the types of func and params match for each CallBackData.

Kotlin - TypeReference<T> Cannot obtain Class<*> For Type Arguments

I've created a Kotlin equivalent of TypeReference<T> like so:
abstract class TypeReference<T> : Comparable<T> {
val type: Type get() = getGenericType()
val arguments: List<Type> get() = getTypeArguments()
final override fun compareTo(other: T): Int {
return 0
}
private fun getGenericType(): Type {
val superClass = javaClass.genericSuperclass
check(superClass !is Class<*>) {
"TypeReference constructed without actual type information."
}
return (superClass as ParameterizedType).actualTypeArguments[0]
}
private fun getTypeArguments(): List<Type> {
val type = getGenericType()
return if (type is ParameterizedType) {
type.actualTypeArguments.toList()
} else emptyList()
}
}
In order to obtain Class<*> of the generic type and its arguments, I've also created the following extension function (and this is where I believe the problem lies, since this is where the stack trace fails).
fun Type.toClass(): Class<*> = when (this) {
is ParameterizedType -> rawType.toClass()
is Class<*> -> this
else -> Class.forName(typeName)
}
I'm unit testing this like so:
#Test
fun `TypeReference should correctly identify the List of BigDecimal type`() {
// Arrange
val expected = List::class.java
val expectedParameter1 = BigDecimal::class.java
val typeReference = object : TypeReference<List<BigDecimal>>() {}
// Act
val actual = typeReference.type.toClass()
val actualParameter1 = typeReference.arguments[0].toClass()
// Assert
assertEquals(expected, actual)
assertEquals(expectedParameter1, actualParameter1)
}
The problem I think, lies in the extension function else -> Class.forName(typeName) as it throws:
java.lang.ClassNotFoundException: ? extends java.math.BigDecimal
Is there a better way to obtain the Class<*> of a Type, even when they're generic type parameters?
You need to add is WildcardType -> ... branch to your when-expression to handle types like ? extends java.math.BigDecimal (Kotlin equivalent is out java.math.BigDecimal), ?(Kotlin equivalent is *), ? super Integer(Kotlin equivalent is in java.math.Integer):
fun Type.toClass(): Class<*> = when (this) {
is ParameterizedType -> rawType.toClass()
is Class<*> -> this
is WildcardType -> upperBounds.singleOrNull()?.toClass() ?: Any::class.java
else -> Class.forName(typeName)
}
Note that in this implementation single upper bound types will be resolved as its upper bound, but all other wildcard types (including multiple upper bounds types) will be resolved as Class<Object>
https://github.com/pluses/ktypes
val typeReference = object : TypeReference<List<BigDecimal>>() {}
val superType = typeReference::class.createType().findSuperType(TypeReference::class)!!
println(superType.arguments.first())// List<java.math.BigDecimal>
println(superType.arguments.first().type?.arguments?.first())// java.math.BigDecimal

GSON-based DSL causing a NPE

I've been trying to create a Kotlin DSL for creating GSON JsonObjects with a JSON-like syntax. My builder looks like this
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
class JsonBuilder(builder: JsonBuilder.() -> Unit) {
init {
builder()
}
val result = JsonObject()
infix fun String.to(property: Number) = result.addProperty(this, property)
infix fun String.to(property: Char) = result.addProperty(this, property)
infix fun String.to(property: Boolean) = result.addProperty(this, property)
infix fun String.to(property: String) = result.addProperty(this, property)
infix fun String.to(property: JsonElement) = result.add(this, property)
infix fun String.to(properties: Collection<JsonElement>) {
val arr = JsonArray()
properties.forEach(arr::add)
result.add(this, arr)
}
operator fun String.invoke(builder: JsonObject.() -> Unit) {
val obj = JsonObject()
obj.builder()
result.add(this, obj)
}
}
fun json(builder: JsonBuilder.() -> Unit) = JsonBuilder(builder).result
And my test looks like this
fun main() {
val json = json {
"name" to "value"
"obj" {
"int" to 1
}
"true" to true
}
println(json)
}
However, upon execution it causes a NullPointerException pointing to the first String extension function used, which I don't find very descriptive as I don't see anything being nullable up to that point. Moreover, I don't see how it really differs from the regular execution which of course doesn't cause a NPE.
val json = JsonObject()
json.addProperty("name", "value")
val obj = JsonObject()
obj.addProperty("int", 1)
json.add("obj", obj)
json.addProperty("true", true)
My question is what's exactly causing the exception (and how to prevent it).
The issue is that you've specified the initialiser block earlier than the result object, causing it to be null when you come to use it - this can be visualised by the following (decompiled output of your code).
public JsonBuilder(#NotNull Function1 builder) {
Intrinsics.checkParameterIsNotNull(builder, "builder");
super();
builder.invoke(this);
this.result = new JsonObject();
}
Therefore, the solution is to move the declaration and initialisation of result earlier than the initialiser block.
class JsonBuilder(builder: JsonBuilder.() -> Unit) {
val result = JsonObject()
init {
builder()
}
// ...
}
And the result is now...
{"name":"value","int":1,"obj":{},"true":true}
EDIT: You'll also want to allow chaining with your DSL, and fix a bug you currently have.
operator fun String.invoke(builder: JsonBuilder.() -> Unit) {
val obj = JsonBuilder(builder).result
result.add(this, obj)
}
Which produces the correct result of
{"name":"value","obj":{"int":1},"true":true}

How to get around Type mismatch Required: Foo<Type>, Found: Foo<Type?>

Given the following Kotlin code:
class Foo<T>(val t : T?)
fun <T : Any, R : Any> Foo<T?>.transform(transformer : (T) -> R) : Foo<R?> {
return when (t) {
null -> Foo(null)
else -> Foo(transformer(t))
}
}
fun main(args : Array<String>) {
val foo = Foo(args.firstOrNull())
val bar = foo.transform<String, Int> { t -> t.length }
val baz = bar.transform<Int, IntRange> { t -> t..(t + 1) }
}
Why do I get the following error:
Type mismatch. Required: Foo<String?> Found: Foo<String>
If I remove the ? from the extension function to be Foo<T>.transform I instead get the following error:
Type mismatch. Required: Foo<Int> Found: Foo<Int?>
I can understand the second error, because you cannot assign Int? to Int, but the first doesn't make any sense, as you can assign String to String?
EDIT:
I have modified the class Foo<T> to be class Foo<out T> and this works for me as the value t will only ever be read after the initial assignment. With this option I do not need to define the type parameters at the call site of transform.
Another option I have found that I think is a bit messy (and not sure why it makes a difference) is adding a third type parameter to the extension function as follows:
fun <T : Any, U : T?, R : Any> Foo<U>.transform(transformer : (T) -> R) : Foo<R?>
The call site of this on the other hand I find a bit odd. Looking at the above code the call of foo.transform MUST NOT include the type parameters, but the call of bar.transform<Int, Int?, IntRange> MUST include the type parameters in order to work.
This option allows setting the value t at some later point if it were a var instead of val. But it also removes the smart casting on t in the transform function. Although that can be gotten around with a !! if you are not worried about race conditions or (with some additional effort) ?: or ?. if you are worried about race conditions.
You can change your Foo<T> class to be not invariant (see https://kotlinlang.org/docs/reference/generics.html):
class Foo<out T>(val t : T?)
fun <T : Any, R : Any> Foo<T?>.transform(transformer : (T) -> R) : Foo<R?> {
return when (t) {
null -> Foo(null)
else -> Foo(transformer(t))
}
}
fun main(args : Array<String>) {
val foo = Foo(args.firstOrNull())
val bar = foo.transform<String, Int> { t -> t.length }
val baz = bar.transform<Int, IntRange> { t -> t..(t + 1) }
}
The out T specifies precisely the behavior you want.
Since you specify the property t in the constructor as T? you don't need to specify Foo<T?> as receiver and Foo<R?> as return type. Instead use Foo<T> and Foo<R> and it will work.
class Foo<T>(val t : T?)
fun <T: Any, R: Any> Foo<T>.transform(transformer : (T) -> R) : Foo<R> {
return when (t) {
null -> Foo(null)
else -> Foo(transformer(t))
}
}
fun main(args : Array<String>) {
val foo = Foo(args.firstOrNull())
val bar = foo.transform { t -> t.length }
val baz = bar.transform { t -> t..(t + 1) }
}
Note: You don't need to specify the generic types for transform because they can be inferred (at least in this example).