Implement class finder for customized scripts resolving - not yet completely functional

This commit is contained in:
Ilya Chernikov
2016-06-01 14:07:13 +02:00
parent b63ed0f4aa
commit 3e17724e58
11 changed files with 190 additions and 35 deletions
@@ -226,7 +226,8 @@ object KotlinToJVMBytecodeCompiler {
try {
try {
tryConstructClass(scriptClass.kotlin, scriptArgs)
?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs")
?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs\n" +
"\tconstructors: \n\t\t${scriptClass.kotlin.constructors.joinToString("\n\t\t", "(") { it.parameters.joinToString { it.type.toString() } }}")
}
finally {
// NB: these lines are required (see KT-9546) but aren't covered by tests
@@ -291,16 +292,19 @@ object KotlinToJVMBytecodeCompiler {
else -> null
}
fun foldingFunc(state: Pair<List<Any>, List<String>>, par: KParameter): Pair<List<Any>, List<String>> {
if (state.second.isNotEmpty()) {
fun foldingFunc(state: Pair<List<Any>, List<String>?>, par: KParameter): Pair<List<Any>, List<String>?> {
state.second?.let { scriptArgsLeft ->
try {
val primArgCandidate = convertPrimitive(par.type, state.second.first())
if (primArgCandidate != null)
return Pair(state.first + primArgCandidate, state.second.drop(1))
if (scriptArgsLeft.isNotEmpty()) {
val primArgCandidate = convertPrimitive(par.type, scriptArgsLeft.first())
if (primArgCandidate != null)
return@foldingFunc Pair(state.first + primArgCandidate, scriptArgsLeft.drop(1))
}
val arrayArgCandidate = convertArray((par.type.javaType as? Class<*>)?.componentType?.kotlin?.defaultType, state.second)
val arrCompType = (par.type.javaType as? Class<*>)?.componentType?.kotlin?.defaultType
val arrayArgCandidate = convertArray(arrCompType, scriptArgsLeft)
if (arrayArgCandidate != null)
return Pair(state.first + arrayArgCandidate, emptyList<String>())
return@foldingFunc Pair(state.first + arrayArgCandidate, null)
}
catch (e: NumberFormatException) {
} // just skips to return below
@@ -310,7 +314,7 @@ object KotlinToJVMBytecodeCompiler {
for (ctor in scriptClass.constructors) {
val (ctorArgs, scriptArgsLeft) = ctor.parameters.fold(Pair(emptyList<Any>(), scriptArgs), ::foldingFunc)
if (ctorArgs.size == ctor.parameters.size && scriptArgsLeft.isEmpty())
if (ctorArgs.size == ctor.parameters.size && (scriptArgsLeft == null || scriptArgsLeft.isEmpty()))
return ctor.call(*ctorArgs.toTypedArray())
}
return null
@@ -50,20 +50,16 @@ object StandardScriptDefinition : KotlinScriptDefinition {
override val name = "Kotlin Script"
override fun getScriptName(script: KtScript): Name {
return ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
}
override fun getScriptName(script: KtScript): Name =
ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
override fun isScript(file: VirtualFile): Boolean {
return PathUtil.getFileExtension(file.name) == KotlinParserDefinition.STD_SCRIPT_SUFFIX
}
override fun isScript(file: VirtualFile): Boolean =
PathUtil.getFileExtension(file.name) == KotlinParserDefinition.STD_SCRIPT_SUFFIX
// NOTE: for now we treat .kts files as if they have 'args: Array<String>' parameter
// this is not supposed to be final design
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> {
val kc: KClass<StandardScriptDefinition> = StandardScriptDefinition::class
return makeStringListScriptParameters(scriptDescriptor, ARGS_NAME)
}
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
makeStringListScriptParameters(scriptDescriptor, ARGS_NAME)
}
fun makeStringListScriptParameters(scriptDescriptor: ScriptDescriptor, propertyName: Name): List<ScriptParameter> {
@@ -33,3 +33,11 @@ fun getScriptExtraImports(psiFile: PsiFile): List<KotlinScriptExtraImport> =
psiFile.virtualFile?.let { file ->
KotlinScriptExtraImportsProvider.getInstance(psiFile.project)?.getExtraImports(file)
} ?: emptyList()
fun getScriptCombinedClasspath(file: VirtualFile, project: Project): List<String> =
(getScriptDefinition(file, project)?.getScriptDependenciesClasspath() ?: emptyList()) +
getScriptExtraImports(file, project).flatMap { it.classpath }
fun getScriptCombinedClasspath(psiFile: PsiFile): List<String> =
(getScriptDefinition(psiFile) ?.getScriptDependenciesClasspath() ?: emptyList()) +
getScriptExtraImports(psiFile).flatMap { it.classpath }
+6 -3
View File
@@ -1,4 +1,4 @@
// Expecting two string parameters
// Expecting two string parameters or nothing
fun fib(n: Int): Int {
val v = if(n < 2) 1 else fib(n-1) + fib(n-2)
@@ -6,5 +6,8 @@ fun fib(n: Int): Int {
return v
}
System.out.println("num: ${args[0]} (${args[1]})")
val result = fib(java.lang.Integer.parseInt(args[0]))
val num = if (args.size > 0) java.lang.Integer.parseInt(args[0]) else 4
val comment = if (args.size > 1) args[1] else "none"
System.out.println("num: $num ($comment)")
val result = fib(num)
@@ -67,6 +67,13 @@ class ReflectedSuperclassWithParamsTestScriptDefinition(extension: String,
superclassParameters.map { it.name }
}
class StandardWithClasspathScriptDefinition(extension: String, classpath: List<String>? = null)
: BaseScriptDefinition(extension, classpath)
{
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) =
StandardScriptDefinition.getScriptParameters(scriptDescriptor)
}
fun classpathFromProperty(): List<String> =
System.getProperty("java.class.path")?.let {
it.split(String.format("\\%s", File.pathSeparatorChar).toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
@@ -39,11 +39,10 @@ import org.jetbrains.kotlin.utils.PathUtil
import org.junit.Assert
import org.junit.Test
import java.io.File
import kotlin.reflect.defaultType
import java.lang.reflect.InvocationTargetException
class ScriptTest {
@Test
@Throws(Exception::class)
fun testScriptWithParam() {
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass)
@@ -51,8 +50,7 @@ class ScriptTest {
}
@Test
@Throws(Exception::class)
fun testStandardScript() {
fun testStandardScriptWithParams() {
val aClass = compileScript("fib_std.kts", StandardScriptDefinition)
Assert.assertNotNull(aClass)
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!.kotlin, listOf("4", "comment"))
@@ -60,7 +58,14 @@ class ScriptTest {
}
@Test
@Throws(Exception::class)
fun testStandardScriptWithoutParams() {
val aClass = compileScript("fib_std.kts", StandardScriptDefinition)
Assert.assertNotNull(aClass)
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!.kotlin, emptyList())
Assert.assertNotNull(anObj)
}
@Test
fun testScriptWithParamConversion() {
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass)
@@ -69,7 +74,6 @@ class ScriptTest {
}
@Test
@Throws(Exception::class)
fun testScriptWithPackage() {
val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass)
@@ -77,7 +81,6 @@ class ScriptTest {
}
@Test
@Throws(Exception::class)
fun testScriptWithScriptDefinition() {
val aClass = compileScript("fib.fib.kt", SimpleParamsTestScriptDefinition(".fib.kt", numIntParam()))
Assert.assertNotNull(aClass)
@@ -124,11 +127,45 @@ class ScriptTest {
Assert.assertNotNull(aClass2)
}
@Test
fun testSmokeScriptException() {
val aClass = compileSmokeTestScript(
"scriptException/script.kts",
StandardWithClasspathScriptDefinition(
".kts",
listOf("dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-runtime.jar",
"dependencies/bootstrap-compiler/Kotlin/kotlinc/lib/kotlin-reflect.jar")))
Assert.assertNotNull(aClass)
var exceptionThrown = false
try {
KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!.kotlin, emptyList())
}
catch (e: InvocationTargetException) {
Assert.assertTrue(e.cause is IllegalStateException)
exceptionThrown = true
}
Assert.assertTrue(exceptionThrown)
}
private fun compileScript(
scriptPath: String,
scriptDefinition: KotlinScriptDefinition,
runIsolated: Boolean = true,
suppressOutput: Boolean = false): Class<*>?
suppressOutput: Boolean = false): Class<*>? =
compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, runIsolated, suppressOutput)
private fun compileSmokeTestScript(
scriptPath: String,
scriptDefinition: KotlinScriptDefinition,
runIsolated: Boolean = true,
suppressOutput: Boolean = false): Class<*>? =
compileScriptImpl("compiler/testData/integration/smoke/" + scriptPath, scriptDefinition, runIsolated, suppressOutput)
private fun compileScriptImpl(
scriptPath: String,
scriptDefinition: KotlinScriptDefinition,
runIsolated: Boolean,
suppressOutput: Boolean): Class<*>?
{
val paths = PathUtil.getKotlinPathsForDistDirectory()
val messageCollector =
@@ -139,7 +176,7 @@ class ScriptTest {
try {
val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.JDK_ONLY, TestJdkKind.FULL_JDK)
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
configuration.addKotlinSourceRoot("compiler/testData/script/" + scriptPath)
configuration.addKotlinSourceRoot(scriptPath)
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
scriptDefinition.getScriptDependenciesClasspath().forEach { configuration.addJvmClasspathRoot(File(it)) }