Fix some issues in script constructor param/arg matching
This commit is contained in:
@@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
package org.jetbrains.kotlin.util
|
package org.jetbrains.kotlin.util
|
||||||
|
|
||||||
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromNamedArgs
|
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromNamedArgs
|
||||||
@@ -23,7 +25,6 @@ import org.junit.Test
|
|||||||
import kotlin.reflect.KParameter
|
import kotlin.reflect.KParameter
|
||||||
|
|
||||||
class ArgsToParamsMatchingTest {
|
class ArgsToParamsMatchingTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMatchFromStrings() {
|
fun testMatchFromStrings() {
|
||||||
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf()))
|
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf()))
|
||||||
@@ -38,6 +39,13 @@ class ArgsToParamsMatchingTest {
|
|||||||
|
|
||||||
assertParamMapsEquals(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "2", "s", "0.1", "abc", "true", "1", "2", "3")),
|
assertParamMapsEquals(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "2", "s", "0.1", "abc", "true", "1", "2", "3")),
|
||||||
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1, "s" to "abc", "t" to true, "v" to arrayOf(1L, 2L, 3L))
|
"i" to 1, "b" to 2.toByte(), "c" to 's', "d" to 0.1, "s" to "abc", "t" to true, "v" to arrayOf(1L, 2L, 3L))
|
||||||
|
|
||||||
|
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf("i", "b", "c")))
|
||||||
|
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::foo, listOf("1", "2", "s", "0.1", "abc", "true", "not-a-long")))
|
||||||
|
Assert.assertNull(tryCreateCallableMappingFromStringArgs(::charArray, listOf("")))
|
||||||
|
|
||||||
|
assertParamMapsEquals(tryCreateCallableMappingFromStringArgs(::varargStrings, listOf("a", "b", "c")),
|
||||||
|
"s" to arrayOf("a", "b", "c"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -59,6 +67,13 @@ class ArgsToParamsMatchingTest {
|
|||||||
|
|
||||||
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), null to 's', "x" to 0.1))) // wrong name
|
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), null to 's', "x" to 0.1))) // wrong name
|
||||||
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), "c" to 's', null to 0.1))) // unnamed after named
|
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::foo, listOf(null to 1, null to 2.toByte(), "c" to 's', null to 0.1))) // unnamed after named
|
||||||
|
|
||||||
|
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::notNullNumber, listOf(null to null)))
|
||||||
|
assertParamMapsEquals(tryCreateCallableMappingFromNamedArgs(::nullableNumber, listOf(null to null)),
|
||||||
|
"n" to null)
|
||||||
|
assertParamMapsEquals(tryCreateCallableMappingFromNamedArgs(::notNullNumber, listOf(null to 42)),
|
||||||
|
"n" to 42)
|
||||||
|
Assert.assertNull(tryCreateCallableMappingFromNamedArgs(::notNullNumber, listOf(null to "42")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,5 +98,12 @@ private fun assertParamMapsEquals(actuals: Map<KParameter, Any?>?, vararg expect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
private fun foo(i: Int, b: Byte, c: Char, d: Double = 0.0, s: String = "", t: Boolean = true, vararg v: Long) {}
|
private fun foo(i: Int, b: Byte, c: Char, d: Double = 0.0, s: String = "", t: Boolean = true, vararg v: Long) {}
|
||||||
|
|
||||||
|
private fun charArray(c: CharArray) {}
|
||||||
|
|
||||||
|
private fun varargStrings(vararg s: String) {}
|
||||||
|
|
||||||
|
private fun notNullNumber(n: Number) {}
|
||||||
|
|
||||||
|
private fun nullableNumber(n: Number?) {}
|
||||||
|
|||||||
@@ -17,10 +17,9 @@
|
|||||||
package org.jetbrains.kotlin.utils
|
package org.jetbrains.kotlin.utils
|
||||||
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||||
import kotlin.reflect.KCallable
|
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.*
|
||||||
import kotlin.reflect.KParameter
|
import kotlin.reflect.full.isSubclassOf
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlin.reflect.jvm.jvmErasure
|
import kotlin.reflect.jvm.jvmErasure
|
||||||
|
|
||||||
fun tryConstructClassFromStringArgs(clazz: Class<*>, args: List<String>): Any? {
|
fun tryConstructClassFromStringArgs(clazz: Class<*>, args: List<String>): Any? {
|
||||||
@@ -89,13 +88,13 @@ private fun <T> tryCreateCallableMapping(callable: KCallable<*>, args: Iterator<
|
|||||||
// try single argument first
|
// try single argument first
|
||||||
val cvtRes = converter.tryConvertSingle(par, arg)
|
val cvtRes = converter.tryConvertSingle(par, arg)
|
||||||
if (cvtRes is ArgsConverter.Result.Success) {
|
if (cvtRes is ArgsConverter.Result.Success) {
|
||||||
if (cvtRes.v == null && !par.type.isMarkedNullable) { // TODO: this is not precise check - see comments to the property; consider better approach
|
if (cvtRes.v == null && !par.type.allowsNulls()) {
|
||||||
// or if we do not allow to overload on nullability, drop this check
|
// if we do not allow to overload on nullability, drop this check
|
||||||
return null // failed to match: null for a non-nullable value
|
return null // failed to match: null for a non-nullable value
|
||||||
}
|
}
|
||||||
res.put(par, cvtRes.v)
|
res.put(par, cvtRes.v)
|
||||||
}
|
}
|
||||||
else if ((par.type.classifier as? KClass<*>)?.java?.isArray ?: false) {
|
else if (par.type.jvmErasure.java.isArray) {
|
||||||
// try vararg
|
// try vararg
|
||||||
val cvtVRes = converter.tryConvertVararg(par, arg, argIt)
|
val cvtVRes = converter.tryConvertVararg(par, arg, argIt)
|
||||||
if (cvtVRes is ArgsConverter.Result.Success) {
|
if (cvtVRes is ArgsConverter.Result.Success) {
|
||||||
@@ -107,7 +106,7 @@ private fun <T> tryCreateCallableMapping(callable: KCallable<*>, args: Iterator<
|
|||||||
}
|
}
|
||||||
ArgsTraversalState.NAMED -> {
|
ArgsTraversalState.NAMED -> {
|
||||||
assert(arg.name != null)
|
assert(arg.name != null)
|
||||||
val parIdx = unboundParams.indexOfFirst { it.name != null && it.name == arg.name }.check { it >= 0 }
|
val parIdx = unboundParams.indexOfFirst { it.name == arg.name }.check { it >= 0 }
|
||||||
?: return null // failed to match: no matching named parameter found
|
?: return null // failed to match: no matching named parameter found
|
||||||
val par = unboundParams.removeAt(parIdx)
|
val par = unboundParams.removeAt(parIdx)
|
||||||
val cvtRes = converter.tryConvertSingle(par, arg)
|
val cvtRes = converter.tryConvertSingle(par, arg)
|
||||||
@@ -134,80 +133,70 @@ private fun <T> tryCreateCallableMapping(callable: KCallable<*>, args: Iterator<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KType.allowsNulls(): Boolean =
|
||||||
|
isMarkedNullable ||
|
||||||
|
classifier.let { it is KTypeParameter && it.upperBounds.any(KType::allowsNulls) }
|
||||||
|
|
||||||
|
|
||||||
private class StringArgsConverter : ArgsConverter<String> {
|
private class StringArgsConverter : ArgsConverter<String> {
|
||||||
|
|
||||||
override fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<String>): ArgsConverter.Result {
|
override fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<String>): ArgsConverter.Result {
|
||||||
|
val value = arg.value ?: return ArgsConverter.Result.Success(null)
|
||||||
|
|
||||||
fun convertPrimitive(type: KType?, arg: String): Any? =
|
val primitive: Any? = when (parameter.type.classifier) {
|
||||||
when (type?.classifier) {
|
String::class -> value
|
||||||
String::class -> arg
|
Int::class -> value.toIntOrNull()
|
||||||
Int::class -> arg.toInt()
|
Long::class -> value.toLongOrNull()
|
||||||
Long::class -> arg.toLong()
|
Short::class -> value.toShortOrNull()
|
||||||
Short::class -> arg.toShort()
|
Byte::class -> value.toByteOrNull()
|
||||||
Byte::class -> arg.toByte()
|
Char::class -> value.singleOrNull()
|
||||||
Char::class -> if (arg.length != 1) null else arg[0]
|
Float::class -> value.toFloatOrNull()
|
||||||
Float::class -> arg.toFloat()
|
Double::class -> value.toDoubleOrNull()
|
||||||
Double::class -> arg.toDouble()
|
Boolean::class -> value.toBoolean()
|
||||||
Boolean::class -> arg.toBoolean()
|
else -> null
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (arg.value != null) {
|
|
||||||
val primArgCandidate = convertPrimitive(parameter.type, arg.value)
|
|
||||||
if (primArgCandidate != null)
|
|
||||||
return ArgsConverter.Result.Success(primArgCandidate)
|
|
||||||
}
|
|
||||||
else return ArgsConverter.Result.Success(null)
|
|
||||||
}
|
}
|
||||||
catch (e: NumberFormatException) {}
|
|
||||||
|
|
||||||
return ArgsConverter.Result.Failure
|
return if (primitive != null) ArgsConverter.Result.Success(primitive) else ArgsConverter.Result.Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tryConvertVararg(parameter: KParameter, firstArg: NamedArgument<String>, restArgsIt: Iterator<NamedArgument<String>>): ArgsConverter.Result {
|
override fun tryConvertVararg(parameter: KParameter, firstArg: NamedArgument<String>, restArgsIt: Iterator<NamedArgument<String>>): ArgsConverter.Result {
|
||||||
|
|
||||||
fun convertAnyArray(type: KType?, args: Sequence<String?>): Any? =
|
fun convertAnyArray(classifier: KClassifier?, args: Sequence<String?>): Any? =
|
||||||
when (type?.classifier) {
|
when (classifier) {
|
||||||
String::class -> args.toList().toTypedArray()
|
String::class -> args.toList().toTypedArray()
|
||||||
else -> {
|
is KClass<*> -> classifier.constructors.firstNotNullResult { ctor ->
|
||||||
(type?.classifier as? KClass<*>)?.constructors?.forEach { ctor ->
|
try {
|
||||||
try {
|
args.map { ctor.call(it) }.toList().toTypedArray()
|
||||||
return@convertAnyArray args.map { ctor.call(it) }.toList().toTypedArray()
|
|
||||||
}
|
|
||||||
catch (e: Exception) {}
|
|
||||||
}
|
}
|
||||||
null
|
catch (e: Exception) { null }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun convertPrimitivesArray(type: KType?, args: Sequence<String?>): Any? =
|
|
||||||
when (type?.classifier) {
|
|
||||||
IntArray::class -> args.map { it?.toInt() }.toList().toTypedArray()
|
|
||||||
LongArray::class -> args.map { it?.toLong() }.toList().toTypedArray()
|
|
||||||
ShortArray::class -> args.map { it?.toShort() }.toList().toTypedArray()
|
|
||||||
ByteArray::class -> args.map { it?.toByte() }.toList().toTypedArray()
|
|
||||||
CharArray::class -> args.map { it?.get(0) }.toList().toTypedArray()
|
|
||||||
FloatArray::class -> args.map { it?.toFloat() }.toList().toTypedArray()
|
|
||||||
DoubleArray::class -> args.map { it?.toDouble() }.toList().toTypedArray()
|
|
||||||
BooleanArray::class -> args.map { it?.toBoolean() }.toList().toTypedArray()
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
fun convertPrimitivesArray(type: KType, args: Sequence<String?>): Any? =
|
||||||
if ((parameter.type.classifier as? KClass<*>)?.java?.isArray ?: false) {
|
when (type.classifier) {
|
||||||
val argsSequence = sequenceOf(firstArg.value) + restArgsIt.asSequence().map { it.value }
|
IntArray::class -> args.map { it?.toIntOrNull() }
|
||||||
val primArrayArgCandidate = convertPrimitivesArray(parameter.type, argsSequence)
|
LongArray::class -> args.map { it?.toLongOrNull() }
|
||||||
if (primArrayArgCandidate != null)
|
ShortArray::class -> args.map { it?.toShortOrNull() }
|
||||||
return ArgsConverter.Result.Success(primArrayArgCandidate)
|
ByteArray::class -> args.map { it?.toByteOrNull() }
|
||||||
val arrCompType = parameter.type.arguments.getOrNull(0)?.type
|
CharArray::class -> args.map { it?.singleOrNull() }
|
||||||
val arrayArgCandidate = convertAnyArray(arrCompType, argsSequence)
|
FloatArray::class -> args.map { it?.toFloatOrNull() }
|
||||||
if (arrayArgCandidate != null)
|
DoubleArray::class -> args.map { it?.toDoubleOrNull() }
|
||||||
return ArgsConverter.Result.Success(arrayArgCandidate)
|
BooleanArray::class -> args.map { it?.toBoolean() }
|
||||||
}
|
else -> null
|
||||||
|
}?.toList()?.check { list -> list.none { it == null } }?.toTypedArray()
|
||||||
|
|
||||||
|
val parameterType = parameter.type
|
||||||
|
if (parameterType.jvmErasure.java.isArray) {
|
||||||
|
val argsSequence = sequenceOf(firstArg.value) + restArgsIt.asSequence().map { it.value }
|
||||||
|
val primArrayArgCandidate = convertPrimitivesArray(parameterType, argsSequence)
|
||||||
|
if (primArrayArgCandidate != null)
|
||||||
|
return ArgsConverter.Result.Success(primArrayArgCandidate)
|
||||||
|
val arrayElementType = parameterType.arguments.firstOrNull()?.type
|
||||||
|
val arrayArgCandidate = convertAnyArray(arrayElementType?.classifier, argsSequence)
|
||||||
|
if (arrayArgCandidate != null)
|
||||||
|
return ArgsConverter.Result.Success(arrayArgCandidate)
|
||||||
}
|
}
|
||||||
catch (e: NumberFormatException) {}
|
|
||||||
|
|
||||||
return ArgsConverter.Result.Failure
|
return ArgsConverter.Result.Failure
|
||||||
}
|
}
|
||||||
@@ -218,7 +207,9 @@ private class StringArgsConverter : ArgsConverter<String> {
|
|||||||
|
|
||||||
private class AnyArgsConverter : ArgsConverter<Any> {
|
private class AnyArgsConverter : ArgsConverter<Any> {
|
||||||
override fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<Any>): ArgsConverter.Result {
|
override fun tryConvertSingle(parameter: KParameter, arg: NamedArgument<Any>): ArgsConverter.Result {
|
||||||
|
val value = arg.value ?: return ArgsConverter.Result.Success(null)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun convertPrimitivesArray(type: KType?, arg: Any?): Any? =
|
fun convertPrimitivesArray(type: KType?, arg: Any?): Any? =
|
||||||
when (type?.classifier) {
|
when (type?.classifier) {
|
||||||
IntArray::class -> (arg as? Array<Int>)?.toIntArray()
|
IntArray::class -> (arg as? Array<Int>)?.toIntArray()
|
||||||
@@ -232,14 +223,9 @@ private class AnyArgsConverter : ArgsConverter<Any> {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertSingle(type: KType, arg: Any?): Any? = when {
|
if (value::class.isSubclassOf(parameter.type.jvmErasure)) return ArgsConverter.Result.Success(value)
|
||||||
type.classifier == arg?.javaClass?.kotlin -> arg
|
|
||||||
type.jvmErasure.java.isAssignableFrom(arg?.javaClass) -> arg
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (convertSingle(parameter.type, arg.value) ?: convertPrimitivesArray(parameter.type, arg.value))
|
return convertPrimitivesArray(parameter.type, value)?.let { ArgsConverter.Result.Success(it) }
|
||||||
?.let { ArgsConverter.Result.Success(it) }
|
|
||||||
?: ArgsConverter.Result.Failure
|
?: ArgsConverter.Result.Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,4 +235,3 @@ private class AnyArgsConverter : ArgsConverter<Any> {
|
|||||||
override fun tryConvertTail(parameter: KParameter, firstArg: NamedArgument<Any>, restArgsIt: Iterator<NamedArgument<Any>>): ArgsConverter.Result =
|
override fun tryConvertTail(parameter: KParameter, firstArg: NamedArgument<Any>, restArgsIt: Iterator<NamedArgument<Any>>): ArgsConverter.Result =
|
||||||
tryConvertSingle(parameter, firstArg)
|
tryConvertSingle(parameter, firstArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user