Improve class name generation for scripts and REPL snippets
- allow to override default name/scheme - implement host/engine specific names for jsr223 to avoid classloading clashes (also fix "eval in eval" test)
This commit is contained in:
@@ -8,6 +8,7 @@ package kotlin.script.experimental.api
|
|||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import kotlin.script.experimental.util.PropertiesCollection
|
import kotlin.script.experimental.util.PropertiesCollection
|
||||||
|
|
||||||
|
// Warning: during the transition to the new REPL infrastructure, should be kept in sync with REPL_CODE_LINE_FIRST_NO/REPL_CODE_LINE_FIRST_GEN
|
||||||
const val REPL_SNIPPET_FIRST_NO = 1
|
const val REPL_SNIPPET_FIRST_NO = 1
|
||||||
const val REPL_SNIPPET_FIRST_GEN = 1
|
const val REPL_SNIPPET_FIRST_GEN = 1
|
||||||
|
|
||||||
@@ -48,3 +49,17 @@ val ScriptCompilationConfigurationKeys.repl
|
|||||||
*/
|
*/
|
||||||
val ReplScriptCompilationConfigurationKeys.resultFieldPrefix by PropertiesCollection.key<String>("res")
|
val ReplScriptCompilationConfigurationKeys.resultFieldPrefix by PropertiesCollection.key<String>("res")
|
||||||
|
|
||||||
|
typealias MakeSnippetIdentifier = (ScriptCompilationConfiguration, ReplSnippetId) -> String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The REPL snippet class identifier generation function
|
||||||
|
*/
|
||||||
|
val ReplScriptCompilationConfigurationKeys.makeSnippetIdentifier by PropertiesCollection.key<MakeSnippetIdentifier>(
|
||||||
|
{ _, snippetId ->
|
||||||
|
makeDefaultSnippetIdentifier(snippetId)
|
||||||
|
})
|
||||||
|
|
||||||
|
fun makeDefaultSnippetIdentifier(snippetId: ReplSnippetId) =
|
||||||
|
"Line_${snippetId.no}${if (snippetId.generation > REPL_SNIPPET_FIRST_GEN) "_gen_${snippetId.generation}" else ""}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ fun ScriptCompilationConfiguration?.with(body: ScriptCompilationConfiguration.Bu
|
|||||||
*/
|
*/
|
||||||
val ScriptCompilationConfigurationKeys.displayName by PropertiesCollection.key<String>()
|
val ScriptCompilationConfigurationKeys.displayName by PropertiesCollection.key<String>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default script class name
|
||||||
|
*/
|
||||||
|
val ScriptCompilationConfigurationKeys.defaultIdentifier by PropertiesCollection.key<String>("Script")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The script filename extension
|
* The script filename extension
|
||||||
* Used for the primary script definition selection as well as to assign a kotlin-specific file type to the files with the extension in Intellij IDEA
|
* Used for the primary script definition selection as well as to assign a kotlin-specific file type to the files with the extension in Intellij IDEA
|
||||||
|
|||||||
+13
-3
@@ -9,9 +9,7 @@ import org.jetbrains.kotlin.cli.common.repl.*
|
|||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
import javax.script.ScriptContext
|
import javax.script.ScriptContext
|
||||||
import javax.script.ScriptEngineFactory
|
import javax.script.ScriptEngineFactory
|
||||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
|
||||||
import kotlin.script.experimental.api.hostConfiguration
|
|
||||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||||
import kotlin.script.experimental.host.withDefaultsFrom
|
import kotlin.script.experimental.host.withDefaultsFrom
|
||||||
import kotlin.script.experimental.jvm.baseClassLoader
|
import kotlin.script.experimental.jvm.baseClassLoader
|
||||||
@@ -42,6 +40,18 @@ class KotlinJsr223ScriptEngineImpl(
|
|||||||
val compilationConfiguration by lazy {
|
val compilationConfiguration by lazy {
|
||||||
ScriptCompilationConfiguration(baseCompilationConfiguration) {
|
ScriptCompilationConfiguration(baseCompilationConfiguration) {
|
||||||
hostConfiguration.update { it.withDefaultsFrom(jsr223HostConfiguration) }
|
hostConfiguration.update { it.withDefaultsFrom(jsr223HostConfiguration) }
|
||||||
|
repl {
|
||||||
|
// Snippet classes should be named uniquely, to avoid classloading clashes in the "eval in eval" scenario
|
||||||
|
// TODO: consider applying the logic for any REPL, alternatively - develop other naming scheme to avoid clashes
|
||||||
|
makeSnippetIdentifier { configuration, snippetId ->
|
||||||
|
val scriptContext: ScriptContext? = configuration[ScriptCompilationConfiguration.jsr223.getScriptContext]?.invoke()
|
||||||
|
val engineState = scriptContext?.let {
|
||||||
|
it.getBindings(ScriptContext.ENGINE_SCOPE)?.get(KOTLIN_SCRIPT_STATE_BINDINGS_KEY)
|
||||||
|
}
|
||||||
|
if (engineState == null) makeDefaultSnippetIdentifier(snippetId)
|
||||||
|
else "ScriptingHost${System.identityHashCode(engineState).toString(16)}_${makeDefaultSnippetIdentifier(snippetId)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -74,7 +74,10 @@ internal class SourceCodeFromReplCodeLine(
|
|||||||
compilationConfiguration: ScriptCompilationConfiguration
|
compilationConfiguration: ScriptCompilationConfiguration
|
||||||
) : SourceCode {
|
) : SourceCode {
|
||||||
override val text: String get() = codeLine.code
|
override val text: String get() = codeLine.code
|
||||||
override val name: String = "${makeScriptBaseName(codeLine)}.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
|
override val name: String =
|
||||||
|
"${compilationConfiguration[ScriptCompilationConfiguration.repl.makeSnippetIdentifier]!!(
|
||||||
|
compilationConfiguration, ReplSnippetIdImpl(codeLine.no, codeLine.generation, 0)
|
||||||
|
)}.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
|
||||||
override val locationId: String? = null
|
override val locationId: String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -31,6 +31,7 @@ abstract class ScriptDefinition : UserDataHolderBase() {
|
|||||||
abstract fun isScript(file: File): Boolean
|
abstract fun isScript(file: File): Boolean
|
||||||
abstract val fileExtension: String
|
abstract val fileExtension: String
|
||||||
abstract val name: String
|
abstract val name: String
|
||||||
|
open val defaultClassName: String = "Script"
|
||||||
// TODO: used in settings, find out the reason and refactor accordingly
|
// TODO: used in settings, find out the reason and refactor accordingly
|
||||||
abstract val definitionId: String
|
abstract val definitionId: String
|
||||||
|
|
||||||
@@ -138,6 +139,9 @@ abstract class ScriptDefinition : UserDataHolderBase() {
|
|||||||
compilationConfiguration[ScriptCompilationConfiguration.displayName]?.takeIf { it.isNotBlank() }
|
compilationConfiguration[ScriptCompilationConfiguration.displayName]?.takeIf { it.isNotBlank() }
|
||||||
?: compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!.typeName.substringAfterLast('.')
|
?: compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!.typeName.substringAfterLast('.')
|
||||||
|
|
||||||
|
override val defaultClassName: String
|
||||||
|
get() = compilationConfiguration[ScriptCompilationConfiguration.defaultIdentifier] ?: super.defaultClassName
|
||||||
|
|
||||||
override val definitionId: String get() = compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!.typeName
|
override val definitionId: String get() = compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!.typeName
|
||||||
|
|
||||||
override val contextClassLoader: ClassLoader? by lazy {
|
override val contextClassLoader: ClassLoader? by lazy {
|
||||||
|
|||||||
+1
-1
@@ -291,7 +291,7 @@ fun SourceCode.getVirtualFile(definition: ScriptDefinition): VirtualFile {
|
|||||||
val vFile = LocalFileSystem.getInstance().findFileByIoFile(file)
|
val vFile = LocalFileSystem.getInstance().findFileByIoFile(file)
|
||||||
if (vFile != null) return vFile
|
if (vFile != null) return vFile
|
||||||
}
|
}
|
||||||
val scriptName = withCorrectExtension(name ?: "script", definition.fileExtension)
|
val scriptName = withCorrectExtension(name ?: definition.defaultClassName, definition.fileExtension)
|
||||||
val scriptPath = when (this) {
|
val scriptPath = when (this) {
|
||||||
is FileScriptSource -> file.path
|
is FileScriptSource -> file.path
|
||||||
is ExternalSourceCode -> externalLocation.toString()
|
is ExternalSourceCode -> externalLocation.toString()
|
||||||
|
|||||||
+1
-2
@@ -109,7 +109,6 @@ open class GenericReplCompiler(
|
|||||||
CompilationErrorHandler.THROW_EXCEPTION
|
CompilationErrorHandler.THROW_EXCEPTION
|
||||||
)
|
)
|
||||||
|
|
||||||
val generatedClassname = makeScriptBaseName(codeLine)
|
|
||||||
compilerState.history.push(LineId(codeLine), scriptDescriptor)
|
compilerState.history.push(LineId(codeLine), scriptDescriptor)
|
||||||
|
|
||||||
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
|
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
|
||||||
@@ -117,7 +116,7 @@ open class GenericReplCompiler(
|
|||||||
return ReplCompileResult.CompiledClasses(
|
return ReplCompileResult.CompiledClasses(
|
||||||
LineId(codeLine),
|
LineId(codeLine),
|
||||||
compilerState.history.map { it.id },
|
compilerState.history.map { it.id },
|
||||||
generatedClassname,
|
scriptDescriptor.name.identifier,
|
||||||
classes,
|
classes,
|
||||||
generationState.scriptSpecific.resultFieldName != null,
|
generationState.scriptSpecific.resultFieldName != null,
|
||||||
classpathAddendum ?: emptyList(),
|
classpathAddendum ?: emptyList(),
|
||||||
|
|||||||
Reference in New Issue
Block a user