diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 41836997efd..ce08146a870 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -89,6 +89,7 @@ import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFact import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService import org.jetbrains.kotlin.script.* import org.jetbrains.kotlin.utils.PathUtil +import org.jetbrains.kotlin.utils.ifEmpty import java.io.File import java.util.* @@ -136,22 +137,14 @@ class KotlinCoreEnvironment private constructor( KotlinScriptDefinitionProvider.getInstance(project).let { scriptDefinitionProvider -> scriptDefinitionProvider.scriptDefinitions = - configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS).let { - if (it.isNotEmpty()) it - else { - val kotlinEnvVars = generateKotlinScriptClasspathEnvVarsForCompiler(project) - loadScriptConfigsFromProjectRoot(File(project.basePath ?: ".")).map { - KotlinConfigurableScriptDefinition(it, kotlinEnvVars) - } + StandardScriptDefinition - } - } - KotlinScriptExtraImportsProvider.getInstance(project).let { scriptExtraImportsProvider -> - configuration.addJvmClasspathRoots( - sourceFiles - .filter { it.originalFile.virtualFile != null && scriptDefinitionProvider.isScript(it.originalFile.virtualFile) } - .flatMap { scriptExtraImportsProvider.getExtraImports(it.originalFile.virtualFile).flatMap { it.classpath.map { File(it).canonicalFile } } } - .distinct()) - } + configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS) + .ifEmpty { listOf(StandardScriptDefinition) } + + configuration.addJvmClasspathRoots( + sourceFiles + .mapNotNull { src -> src.virtualFile?.let { scriptDefinitionProvider.findScriptDefinition(it) } } + .flatMap { it.getScriptDependenciesClasspath().map { File(it).canonicalFile } } + .distinct()) } fillClasspath(configuration) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt index 17a36f9e4b2..72c0a36889a 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt @@ -17,10 +17,12 @@ package org.jetbrains.kotlin.cli.jvm.compiler import com.intellij.openapi.util.io.JarUtil +import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics import org.jetbrains.kotlin.backend.common.output.OutputFileCollection import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.messages.* @@ -49,6 +51,7 @@ import org.jetbrains.kotlin.name.isSubpackageOf import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.script.getScriptDefinition import org.jetbrains.kotlin.util.PerformanceCounter import org.jetbrains.kotlin.utils.KotlinPaths import org.jetbrains.kotlin.utils.PathUtil @@ -59,6 +62,12 @@ import java.lang.reflect.InvocationTargetException import java.net.URLClassLoader import java.util.concurrent.TimeUnit import java.util.jar.Attributes +import kotlin.reflect.KClass +import kotlin.reflect.KParameter +import kotlin.reflect.KType +import kotlin.reflect.defaultType +import kotlin.reflect.jvm.javaMethod +import kotlin.reflect.jvm.javaType object KotlinToJVMBytecodeCompiler { @@ -213,11 +222,11 @@ object KotlinToJVMBytecodeCompiler { scriptArgs: List): ExitCode { val scriptClass = compileScript(environment, paths) ?: return ExitCode.COMPILATION_ERROR - val scriptConstructor = getScriptConstructor(scriptClass) try { try { - scriptConstructor.newInstance(*arrayOf(scriptArgs.toTypedArray())) + tryConstructClass(scriptClass.kotlin, scriptArgs) + ?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs") } finally { // NB: these lines are required (see KT-9546) but aren't covered by tests @@ -249,8 +258,63 @@ object KotlinToJVMBytecodeCompiler { } } - private fun getScriptConstructor(scriptClass: Class<*>): Constructor<*> = - scriptClass.getConstructor(Array::class.java) + @TestOnly + fun tryConstructClassPub(scriptClass: KClass, scriptArgs: List): Any? = tryConstructClass(scriptClass, scriptArgs) + + private fun tryConstructClass(scriptClass: KClass, scriptArgs: List): Any? { + + fun convertPrimitive(type: KType?, arg: String): Any? = + when (type) { + String::class.defaultType -> arg + Int::class.defaultType -> arg.toInt() + Long::class.defaultType -> arg.toLong() + Short::class.defaultType -> arg.toShort() + Byte::class.defaultType -> arg.toByte() + Char::class.defaultType -> arg[0] + Float::class.defaultType -> arg.toFloat() + Double::class.defaultType -> arg.toDouble() + Boolean::class.defaultType -> arg.toBoolean() + else -> null + } + + fun convertArray(type: KType?, args: List): Any? = + when (type) { + String::class.defaultType -> args.toTypedArray() + Int::class.defaultType -> args.map { it.toInt() }.toTypedArray() + Long::class.defaultType -> args.map { it.toLong() }.toTypedArray() + Short::class.defaultType -> args.map { it.toShort() }.toTypedArray() + Byte::class.defaultType -> args.map { it.toByte() }.toTypedArray() + Char::class.defaultType -> args.map { it[0] }.toTypedArray() + Float::class.defaultType -> args.map { it.toFloat() }.toTypedArray() + Double::class.defaultType -> args.map { it.toDouble() }.toTypedArray() + Boolean::class.defaultType -> args.map { it.toBoolean() }.toTypedArray() + else -> null + } + + fun foldingFunc(state: Pair, List>, par: KParameter): Pair, List> { + if (state.second.isNotEmpty()) { + try { + val primArgCandidate = convertPrimitive(par.type, state.second.first()) + if (primArgCandidate != null) + return Pair(state.first + primArgCandidate, state.second.drop(1)) + + val arrayArgCandidate = convertArray((par.type.javaType as? Class<*>)?.componentType?.kotlin?.defaultType, state.second) + if (arrayArgCandidate != null) + return Pair(state.first + arrayArgCandidate, emptyList()) + } + catch (e: NumberFormatException) { + } // just skips to return below + } + return state + } + + for (ctor in scriptClass.constructors) { + val (ctorArgs, scriptArgsLeft) = ctor.parameters.fold(Pair(emptyList(), scriptArgs), ::foldingFunc) + if (ctorArgs.size == ctor.parameters.size && scriptArgsLeft.isEmpty()) + return ctor.call(*ctorArgs.toTypedArray()) + } + return null + } fun compileScript(environment: KotlinCoreEnvironment, paths: KotlinPaths): Class<*>? = compileScript(environment, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt index 2095ba0f9d3..517d144db16 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt @@ -49,43 +49,27 @@ class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, val env override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) - protected val evaluatedClasspath by lazy { config.classpath.evalDistinctWith(environmentVars) } + private val evaluatedClasspath by lazy { config.classpath.evalWithVars(environmentVars).distinct() } override fun getScriptDependenciesClasspath(): List = evaluatedClasspath } -class KotlinDelegatingScriptDefinitionWithExtraImports internal constructor ( - val delegate: KotlinScriptDefinition, - val extraImports: List -) : KotlinScriptDefinition { - override val name: String get() = delegate.name - override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = delegate.getScriptParameters(scriptDescriptor) - override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List = delegate.getScriptSupertypes(scriptDescriptor) - override fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List = - delegate.getScriptParametersToPassToSuperclass(scriptDescriptor) - override fun isScript(file: VirtualFile): Boolean = delegate.isScript(file) - override fun getScriptName(script: KtScript): Name = delegate.getScriptName(script) - - protected val evaluatedExtraClasspath by lazy { - (delegate.getScriptDependenciesClasspath() + extraImports.flatMap { it.classpath }).distinct() - } - - override fun getScriptDependenciesClasspath(): List = evaluatedExtraClasspath -} - // return all combination of replacements of vars in the strings // if corresponding list of replacements is empty, all strings containing the reference to the var are removed // TODO: fix and tests // TODO: move to some utils -internal fun List.evalDistinctWith(varsMap: Map>?): List = +internal fun List.evalWithVars(varsMap: Map>?): List = if (varsMap == null || varsMap.isEmpty()) this else this.flatMap { cpentry -> varsMap.entries.fold(listOf(cpentry)) { p, v -> - if (v.value.isEmpty() && cpentry.contains("\${${v.key}}")) emptyList() - else v.value.flatMap { valListElement -> - p.map { it.replace("\${${v.key}}", valListElement) } + if (cpentry.contains("\${${v.key}}")) { + if (v.value.isEmpty()) emptyList() + else v.value.flatMap { valListElement -> + p.map { it.replace("\${${v.key}}", valListElement) } + } } + else p } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt index fe171c6c5c7..9a079083ef4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt @@ -31,12 +31,14 @@ class KotlinScriptExtraImportsProvider(val project: Project) { fun isExtraImportsConfig(file: VirtualFile): Boolean = file.name.endsWith(IMPORTSFILEEXTENSION) fun getExtraImports(file: VirtualFile): List = lock.read { - cache.getOrPut(file) { - file.parent.findFileByRelativePath(file.name + IMPORTSFILEEXTENSION)?.let { - loadScriptExtraImportConfigs(it.inputStream).map { KotlinScriptExtraImportFromConfig(it, envVars) } + if (file.isInLocalFileSystem) + cache.getOrPut(file) { + file.parent.findFileByRelativePath(file.name + IMPORTSFILEEXTENSION)?.let { + loadScriptExtraImportConfigs(it.inputStream).map { KotlinScriptExtraImportFromConfig(it, envVars) } + } + ?: emptyList() } - ?: listOf() - } + else emptyList() } fun invalidateExtraImports(importsFile: VirtualFile) { @@ -49,7 +51,7 @@ class KotlinScriptExtraImportsProvider(val project: Project) { companion object { @JvmStatic - fun getInstance(project: Project): KotlinScriptExtraImportsProvider = + fun getInstance(project: Project): KotlinScriptExtraImportsProvider? = ServiceManager.getService(project, KotlinScriptExtraImportsProvider::class.java) val IMPORTSFILEEXTENSION = ".ktsimports.xml" diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt index c8ae844aff0..06a1c1fbe65 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt @@ -21,19 +21,15 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? = - KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)?.let { kindDef -> - val extraImports = KotlinScriptExtraImportsProvider.getInstance(project).getExtraImports(file) - if (extraImports.isEmpty()) kindDef - else KotlinDelegatingScriptDefinitionWithExtraImports(kindDef, extraImports) - } + KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file) fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? = - KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile.originalFile.virtualFile)?.let { kindDef -> - (psiFile.originalFile.virtualFile ?: psiFile.virtualFile)?.let { file -> - val extraImports = KotlinScriptExtraImportsProvider.getInstance(psiFile.project).getExtraImports(file) - if (extraImports.isEmpty()) kindDef - else KotlinDelegatingScriptDefinitionWithExtraImports(kindDef, extraImports) - } - ?: kindDef - } + KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile.originalFile.virtualFile) +fun getScriptExtraImports(file: VirtualFile, project: Project): List = + KotlinScriptExtraImportsProvider.getInstance(project)?.getExtraImports(file) ?: emptyList() + +fun getScriptExtraImports(psiFile: PsiFile): List = + psiFile.virtualFile?.let { file -> + KotlinScriptExtraImportsProvider.getInstance(psiFile.project)?.getExtraImports(file) + } ?: emptyList() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt index 68192967836..d5a355f7473 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt @@ -56,7 +56,7 @@ fun loadScriptExtraImportConfigs(configStream: InputStream): List>) : KotlinScriptExtraImport { - override val classpath: List by lazy { config.classpath.evalDistinctWith(envVars) } + override val classpath: List by lazy { config.classpath.evalWithVars(envVars).distinct() } override val names: List get() = config.names } diff --git a/compiler/testData/script/fib_std.kts b/compiler/testData/script/fib_std.kts new file mode 100644 index 00000000000..42d1dcf67ef --- /dev/null +++ b/compiler/testData/script/fib_std.kts @@ -0,0 +1,10 @@ +// Expecting two string parameters + +fun fib(n: Int): Int { + val v = if(n < 2) 1 else fib(n-1) + fib(n-2) + System.out.println("fib($n)=$v") + return v +} + +System.out.println("num: ${args[0]} (${args[1]})") +val result = fib(java.lang.Integer.parseInt(args[0])) diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt index d440b706b17..b593d461cc4 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.config.addKotlinSourceRoot import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.ScriptParameter +import org.jetbrains.kotlin.script.StandardScriptDefinition import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.TestJdkKind @@ -38,16 +39,35 @@ import org.jetbrains.kotlin.utils.PathUtil import org.junit.Assert import org.junit.Test import java.io.File +import kotlin.reflect.defaultType class ScriptTest { @Test @Throws(Exception::class) - fun testScript() { + fun testScriptWithParam() { val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam())) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE).newInstance(4) } + @Test + @Throws(Exception::class) + fun testStandardScript() { + val aClass = compileScript("fib_std.kts", StandardScriptDefinition) + Assert.assertNotNull(aClass) + val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!.kotlin, listOf("4", "comment")) + Assert.assertNotNull(anObj) + } + + @Test + @Throws(Exception::class) + fun testScriptWithParamConversion() { + val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam())) + Assert.assertNotNull(aClass) + val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!.kotlin, listOf("4")) + Assert.assertNotNull(anObj) + } + @Test @Throws(Exception::class) fun testScriptWithPackage() { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt index 481c63d1fac..878466beffb 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.KotlinScriptExtraImport import org.jetbrains.kotlin.utils.alwaysNull import org.jetbrains.kotlin.utils.emptyOrSingletonList import java.io.File @@ -289,7 +290,8 @@ internal object NotUnderContentRootModuleInfo : IdeaModuleInfo { } internal data class CustomizedScriptModuleInfo(val project: Project, val module: Module?, val virtualFile: VirtualFile, - val scriptDefinition: KotlinScriptDefinition) : IdeaModuleInfo { + val scriptDefinition: KotlinScriptDefinition, + val scriptExtraImports: List) : IdeaModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.OTHER @@ -301,7 +303,7 @@ internal data class CustomizedScriptModuleInfo(val project: Project, val module: // TODO: find out whether it should be cashed (some changes listener should be implemented for the cached roots) val virtualFileManager = VirtualFileManager.getInstance() val jarfs = StandardFileSystems.jar() - return (scriptDefinition.getScriptDependenciesClasspath()) + return (scriptDefinition.getScriptDependenciesClasspath() + scriptExtraImports.flatMap { it.classpath }) .map { File(it).canonicalFile } .distinct() .map { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt index 93fa2b71266..7e4f803426c 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider import org.jetbrains.kotlin.script.getScriptDefinition +import org.jetbrains.kotlin.script.getScriptExtraImports import org.jetbrains.kotlin.utils.sure fun PsiElement.getModuleInfo(): IdeaModuleInfo = this.getModuleInfo { reason -> @@ -125,7 +126,7 @@ private fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFil val scriptDefinition = getScriptDefinition(virtualFile, project) if (scriptDefinition != null) - return CustomizedScriptModuleInfo(project, module, virtualFile, scriptDefinition) + return CustomizedScriptModuleInfo(project, module, virtualFile, scriptDefinition, getScriptExtraImports(virtualFile, project)) return NotUnderContentRootModuleInfo } diff --git a/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt b/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt index 45d9b217ce4..25f2644919f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt @@ -27,7 +27,7 @@ import java.io.File @Suppress("unused") // project component class KotlinScriptConfigurationManager(project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider, - private val scriptExtraImportsProvider: KotlinScriptExtraImportsProvider + scriptExtraImportsProvider: KotlinScriptExtraImportsProvider? ) : AbstractProjectComponent(project) { private val kotlinEnvVars: Map> by lazy { generateKotlinScriptClasspathEnvVarsForIdea(myProject) } @@ -43,8 +43,10 @@ class KotlinScriptConfigurationManager(project: Project, if (!anyScriptDefinitionChanged && isScriptDefinitionConfigFile(it)) { anyScriptDefinitionChanged = true } - if (scriptExtraImportsProvider.isExtraImportsConfig(it)) { - scriptExtraImportsProvider.invalidateExtraImports(it) + scriptExtraImportsProvider?.run { + if (isExtraImportsConfig(it)) { + invalidateExtraImports(it) + } } } }