IntelliJ compiler warning on spreading empty array for varargs - kotlin

I have this class that takes a a varargs parameter and a key in the primary constructor, and a secondary constructor that only requires the key, then, when calling the primary constructor,
I just pass an empty Array and spread it *emptyArray<String>.
class AppendableParameter(val key: String, vararg parameters: String) {
constructor(key: String) : this(key, *emptyArray<String>())
}
The code works fine, but IntelliJ gives me a warning:
Remove redundant spread operator
If i do remove it though, it will not work as it'd be expecting a String, and the "suggested fix"
from IntelliJ just deletes the parameter, making the constructor call invalid.
Is this possibly a bug in IntelliJ's system, or am I missing something trivial?

I can't speak to the warning emitted by IntelliJ, but your secondary constructor is unnecessary. It's possible to call methods with a varargs parameter without passing any arguments for said varargs parameter, which is the same as passing an empty array. In other words, having:
class Foo(val key: String, vararg params: String)
Already let's you call the primary constructor like so:
val foo = Foo("key")
If you want a non-empty array to be used when no argument is given then you can specify a default argument:
class Foo(
val key: String,
vararg params: String = arrayOf("default", "params")
)

Related

Easiest way to modify value passed to inline class constructor

I'm trying to use inline classes in Kotlin to create a class inlining the String class, such that if I have an instance of my class that it will always be true for the contained string that s == s.trim().
I was initially expecting there to be a straightforward way to do this, like perhaps:
#JvmInline
value class Trimmed private constructor(val str: String) : {
constructor(s : String) : super(s.trim())
}
but that doesn't work, and neither do the other direct approaches I considered ("this(s.trim())", etc.).
This problem has turned out to be surprisingly tricky:
Kotlin seems to provide no easy way to have the primary constructor filter or modify the data that is passed to the constructor of the contained String object.
Even if I make the primary constructor private, I can't declare another constructor with the same signature (taking a single String as a parameter).
If this were a normal (non-inlined) class, I could just set the value after superclass class construction (e.g. "init { str = str.trim() }", but since it's an inline class, I can't do that. ("this=this.trim()" doesn't work either, and String objects themselves are immutable so I can't change the contents of 'str'.)
I tried making the class constructor private and creating a factory function in the same file with the same name as the class, but then I couldn't call the class constructor from within the factory function due to access restrictions.
I then tried making the factory function within the class's companion object, but then Kotlin tried to make that function call itself recursively instead of calling the class's constructor. I wasn't able to find a way to syntactially disambiguate this. I managed to work around this by creating a file-private typealias to give another name for the class so I could call the constructor from within the factory function. (Annoyingly, I couldn't declare the typealias in the companion object next to the factory function: I had to declare it outside.)
This worked, but seemed ugly:
typealias Trimmed2 = Trimmed
#JvmInline
value class Trimmed private constructor(val str: String) {
init { assert(str == str.trim()) }
companion object {
// Kotlin won't let me put the typealias here. :-(
fun Trimmed(s: String): Trimmed = Trimmed2(s.trim()) // Don't want recursion here!
}
}
Another working solution is here, using a private constructor with a dummy argument. Of course Kotlin complained that the dummy argument was unused and so I had to put in a big (why is it so big?) annotation suppressing the warning, which is, again, ugly:
#JvmInline
value class Trimmed private constructor(val str: String) {
private constructor (untrimmed: String, #Suppress("UNUSED_PARAMETER") dummy: Unit) : this(untrimmed.trim())
init { assert(str == str.trim()) }
companion object {
fun Trimmed(s: String): Trimmed = Trimmed(s, Unit)
}
}
Is there a simpler, cleaner way to do this? For instance, a syntactic way to clarify to Kotlin that the companion function is trying to call the class constructor and not itself and so avoid the need for a dummy parameter?
Goals:
Code to construct instances of the class from outside this file should look like constructing an instance of a normal class: 'Trimmed("abc")', not using some factory function with a different name (e.g. "of" or "trimmedOf") or other alternate syntax.
It should be impossible to construct the object containing an untrimmed string. Outside code, and the Trimmed class itself, should be able to trust that if a Trimmed instance exists, that its contained str will be a trimmed string.

Kotlin data class secondary constructor init block

Let's imagine that we have data class with two properties and we need secondary constructor for some reasons. Problem is that i need recalculate each argument in primary constructor call instead of using some cached value of raw.split("_"):
data class Id(
val arg1: String,
val arg2: String
) {
constructor(raw: String) : this(raw.split("_")[0], raw.split("_")[1])
}
I can do this in Java but how I can do this in Kotlin?
You can do it this way:
data class Id(
val arg1: String,
val arg2: String
) {
private constructor(splitted: List<String>) : this(splitted[0], splitted[1])
constructor(raw: String) : this(raw.split("_"))
}
It's a good and idiomatic way to solve your problem. Since all secondary constructors must delegate to primary constructor (data class always has it), you can't do what you want in constructor body. In Java it works because there are no primary constructors and no data classes at language level - in Kotlin you can do it like in Java too if you remove data modifier and move properties outside of constructor, but it's a really bad way.

KCallable doesn't work in Kotlin when one of the parameters is vararg

I have an issue where I have a class that stores a KCallable<Any> and calls it using parameters passed in by an interpreter that's running a custom scripting language. This works for most functions when I use KCallable.call(), but it doesn't seem to properly handle functions with a vararg parameter, instead assuming that the parameter is an array of the given type. Is there any way I can work around this issue using some sort of reflection method to convert the input for the parameters? Here's my current code:
class KotlinFunction(function: KCallable<Any>) {
fun call(args: List<Any>) {
function.call(*args.toTypedArray())
}
}
given the function as class member:
fun concat(vararg xs: String) = xs.reduce{l,r -> l + r}
Using 3 parameters, I get the following error:
java.lang.IllegalArgumentException: Callable expects 1 arguments, but 3 were provided.
The way that the vararg argument works is by collecting the multiple values into an Array, and passing that into the function. Therefore, your function concat is actually a function that takes an Array<String> as its single argument, and a reference to it is of type KFunction1<Array<out String>, String>.
So to call it with your setup, what you need to do is pass in the Array representing the vararg arguments inside a List, making this Array the only argument going into the KCallable#call method after spreading the list:
val kf = KotlinFunction(::concat)
kf.call(listOf(arrayOf("a", "b", "c")))
class KotlinFunction(val function: KCallable<Any>) {
fun call(args: List<Any>) {
// Here, `call` will be invoked with one parameter, the Array,
// as that's the only element in the List
function.call(*args.toTypedArray())
}
}

Cannot access expected class constructor parameters in kotlin multi-platform

I'm currently working on a multi-platform module using kotlin. To do so, I rely on the expect/actual mechanism.
I declare a simple class in Common.kt:
expect class Bar constructor(
name: String
)
I'd like to use the defined class in a common method (also present in Common.kt):
fun hello(bar: Bar) {
print("Hello, my name is ${bar.name}")
}
The actual implementation is defined in Jvm.kt:
actual data class Bar actual constructor(
val name: String
)
The problem is I got the following error inside my hello function
Unresolved reference: name
What am I doing wrong?
Expected classes constructor cannot have a property parameter
Therefore it is necessary to describe the property as a class member with val name: String
Actual constructor of 'Bar' has no corresponding expected declaration
However, for the actual constructor to match the expected declaration the number of parameters has to be the same. That is why the parameter is also added name: String in the constructor in addition to the existence of the property.
expect class Bar(name: String) {
val name: String
}
actual class Bar actual constructor(actual val name: String)
Note: If we leave the constructor empty of the expected class we see how the IDE complains when adding a constructor in the current class for the incompatibility.
GL
It should be val name in the expect part as well, either in the constructor parameter list or as a member property.

Error: None of the following functions can be called with the arguments supplied

My class:
class Manager (var name: String, var nationality: String) {
constructor(agent: String): this() {}
}
returns the following error:
None of the following functions can be called with the arguments supplied.
<init>(String) defined in Manager
<init>(String, String) defined in Manager
Any idea why?
Your class has a primary constructor that takes two arguments, and then you define a secondary constructor that takes one argument.
Now, as per the Kotlin documentation:
If the class has a primary constructor, each secondary constructor
needs to delegate to the primary constructor, either directly or
indirectly through another secondary constructor(s).
You're trying to do that by calling this(), but since you don't have a zero-argument constructor (primary or secondary), this results in a compilation error.
To fix, for example, you can call your primary constructor from your secondary constructor as follows:
class Manager (var name: String, var nationality: String) {
constructor(agent: String): this(agent, "") {}
}