diff --git a/compiler/testData/codegen/box/reflection/classes/createInstance.kt b/compiler/testData/codegen/box/reflection/classes/createInstance.kt new file mode 100644 index 00000000000..3b8e7db7a28 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/classes/createInstance.kt @@ -0,0 +1,73 @@ +// WITH_REFLECT + +import kotlin.reflect.createInstance +import kotlin.test.assertTrue +import kotlin.test.fail + +// Good classes + +class Simple +class PrimaryWithDefaults(val d1: String = "d1", val d2: Int = 2) +class Secondary(val s: String) { + constructor() : this("s") +} +class SecondaryWithDefaults(val s: String) { + constructor(x: Int = 0) : this(x.toString()) +} +class SecondaryWithDefaultsNoPrimary { + constructor(x: Int) {} + constructor(s: String = "") {} +} + +// Bad classes + +class NoNoArgConstructor(val s: String) { + constructor(x: Int) : this(x.toString()) +} +class NoArgAndDefault() { + constructor(x: Int = 0) : this() +} +class DefaultPrimaryAndDefaultSecondary(val s: String = "") { + constructor(x: Int = 0) : this(x.toString()) +} +class SeveralDefaultSecondaries { + constructor(x: Int = 0) {} + constructor(s: String = "") {} + constructor(d: Double = 3.14) {} +} +class PrivateConstructor private constructor() +object Object + +// ----------- + +inline fun test() { + val instance = T::class.createInstance() + assertTrue(instance is T) +} + +inline fun testFail() { + try { + T::class.createInstance() + fail("createInstance should have failed on ${T::class}") + } catch (e: Exception) { + // OK + } +} + +fun box(): String { + test() + test() + test() + test() + test() + test() + + testFail() + testFail() + testFail() + testFail() + testFail() + testFail() + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 8820408fca1..ff3cc0b8826 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -11892,6 +11892,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("createInstance.kt") + public void testCreateInstance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/classes/createInstance.kt"); + doTest(fileName); + } + @TestMetadata("declaredMembers.kt") public void testDeclaredMembers() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/classes/declaredMembers.kt"); diff --git a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt index 4053d531907..7abad005ccd 100644 --- a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt +++ b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt @@ -272,3 +272,16 @@ fun KClass.cast(value: Any?): T { fun KClass.safeCast(value: Any?): T? { return if (isInstance(value)) value as T else null } + + +/** + * Creates a new instance of the class, calling a constructor which either has no parameters or all parameters of which are optional + * (see [KParameter.isOptional]). If there are no or many such constructors, an exception is thrown. + */ +fun KClass.createInstance(): T { + // TODO: throw a meaningful exception + val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) } + ?: throw IllegalArgumentException("Class should have a single no-arg constructor: $this") + + return noArgsConstructor.callBy(emptyMap()) +}