Introduce script names and location ids, use them in diagnostics and...

for virtual file names. Also fix compiled script serialization.
This commit is contained in:
Ilya Chernikov
2018-12-19 14:54:07 +01:00
parent 77095cb895
commit 44ea2bf1d4
13 changed files with 133 additions and 33 deletions
@@ -31,22 +31,33 @@ class KJvmCompiledModule(
}
class KJvmCompiledScript<out ScriptBase : Any>(
sourceLocationId: String?,
compilationConfiguration: ScriptCompilationConfiguration,
private var scriptClassFQName: String,
override val otherScripts: List<CompiledScript<*>> = emptyList(),
otherScripts: List<CompiledScript<*>> = emptyList(),
private var compiledModule: KJvmCompiledModule? = null
) : CompiledScript<ScriptBase>, Serializable {
private var _sourceLocationId: String? = sourceLocationId
override val sourceLocationId: String?
get() = _sourceLocationId
private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration
override val compilationConfiguration: ScriptCompilationConfiguration
get() = _compilationConfiguration!!
private var _otherScripts: List<CompiledScript<*>> = otherScripts
override val otherScripts: List<CompiledScript<*>>
get() = _otherScripts
override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>> = try {
val classLoader = scriptEvaluationConfiguration?.get(JvmScriptEvaluationConfiguration.actualClassLoader)
?: run {
if (compiledModule == null)
return ResultWithDiagnostics.Failure("Unable to load class $scriptClassFQName: no compiled module is provided".asErrorDiagnostics())
return ResultWithDiagnostics.Failure("Unable to load class $scriptClassFQName: no compiled module is provided".asErrorDiagnostics(path = sourceLocationId))
val baseClassLoader = scriptEvaluationConfiguration?.get(JvmScriptEvaluationConfiguration.baseClassLoader)
?: Thread.currentThread().contextClassLoader
val dependencies = compilationConfiguration[ScriptCompilationConfiguration.dependencies]
@@ -64,6 +75,7 @@ class KJvmCompiledScript<out ScriptBase : Any>(
ResultWithDiagnostics.Failure(
ScriptDiagnostic(
"Unable to instantiate class $scriptClassFQName",
sourcePath = sourceLocationId,
exception = e
)
)
@@ -77,18 +89,23 @@ class KJvmCompiledScript<out ScriptBase : Any>(
}
private fun writeObject(outputStream: ObjectOutputStream) {
outputStream.writeObject(sourceLocationId)
outputStream.writeObject(otherScripts)
outputStream.writeObject(compiledModule)
outputStream.writeObject(scriptClassFQName)
}
@Suppress("UNCHECKED_CAST")
private fun readObject(inputStream: ObjectInputStream) {
_compilationConfiguration = null
_sourceLocationId = inputStream.readObject() as String?
_otherScripts = inputStream.readObject() as List<CompiledScript<*>>
compiledModule = inputStream.readObject() as KJvmCompiledModule
scriptClassFQName = inputStream.readObject() as String
}
companion object {
@JvmStatic
private val serialVersionUID = 0L
private val serialVersionUID = 1L
}
}
@@ -31,7 +31,6 @@ import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.script.KotlinScriptDefinition
@@ -65,6 +64,12 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
fun failure(vararg diagnostics: ScriptDiagnostic): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
fun failure(message: String): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(
*messageCollector.diagnostics.toTypedArray(),
message.asErrorDiagnostics(path = script.locationId)
)
try {
setIdeaIoUseFallback()
@@ -130,19 +135,19 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
val scriptText = getMergedScriptText(script, updatedConfiguration)
val scriptFileName = "script" // TODO: extract from file/url if available
val scriptFileName = script.name ?: "script.${updatedConfiguration[ScriptCompilationConfiguration.fileExtension]}"
val virtualFile = LightVirtualFile(
"$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}",
scriptFileName,
KotlinLanguage.INSTANCE,
StringUtil.convertLineSeparators(scriptText)
).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: return failure("Unable to make PSI file from script".asErrorDiagnostics())
?: return failure("Unable to make PSI file from script")
val ktScript = psiFile.declarations.firstIsInstanceOrNull<KtScript>()
?: return failure("Not a script file".asErrorDiagnostics())
?: return failure("Not a script file")
val sourceFiles = arrayListOf(psiFile)
val (classpath, newSources, sourceDependencies) =
@@ -162,7 +167,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
}
val analysisResult = analyzerWithCompilerReport.analysisResult
if (!analysisResult.shouldGenerateCode) return failure("no code to generate".asErrorDiagnostics())
if (!analysisResult.shouldGenerateCode) return failure("no code to generate")
if (analysisResult.isError() || messageCollector.hasErrors()) return failure()
val generationState = GenerationState.Builder(
@@ -189,7 +194,9 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
val otherScripts: List<KJvmCompiledScript<*>> =
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile ->
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
KJvmCompiledScript<Any>(updatedConfiguration, it.fqName.asString(), makeOtherScripts(it))
KJvmCompiledScript<Any>(
containingKtFile.virtualFile?.path, updatedConfiguration, it.fqName.asString(), makeOtherScripts(it)
)
}
} ?: emptyList()
@@ -198,6 +205,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
}
val compiledScript = KJvmCompiledScript<Any>(
script.locationId,
updatedConfiguration,
ktScript.fqName.asString(),
makeOtherScripts(ktScript),
@@ -206,7 +214,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
return ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics)
} catch (ex: Throwable) {
return failure(ex.asDiagnostics())
return failure(ex.asDiagnostics(path = script.locationId))
}
}
}
@@ -237,9 +245,10 @@ internal class ScriptDiagnosticsMessageCollector : MessageCollector {
}
if (mappedSeverity != null) {
val mappedLocation = location?.let {
SourceCode.Location(SourceCode.Position(it.line, it.column))
if (it.line < 0 && it.column < 0) null // special location created by CompilerMessageLocation.create
else SourceCode.Location(SourceCode.Position(it.line, it.column))
}
_diagnostics.add(ScriptDiagnostic(message, mappedSeverity, mappedLocation))
_diagnostics.add(ScriptDiagnostic(message, mappedSeverity, location?.path, mappedLocation))
}
}
}
@@ -92,6 +92,6 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
}
}
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(e.asDiagnostics("Error evaluating script"))
ResultWithDiagnostics.Failure(e.asDiagnostics("Error evaluating script", path = compiledScript.sourceLocationId))
}
}
@@ -75,6 +75,25 @@ class ScriptingHostTest : TestCase() {
Assert.assertEquals(greeting, output)
}
@Test
fun testImportError() {
val script = "println(\"Hello from imported \$helloScriptName script!\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
refineConfiguration {
beforeCompiling { ctx ->
ScriptCompilationConfiguration(ctx.compilationConfiguration) {
importScripts(File(TEST_DATA_DIR, "missing_script.kts").toScriptSource())
}.asSuccess()
}
}
}
val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null)
assertTrue(res is ResultWithDiagnostics.Failure)
val report = res.reports.find { it.message.startsWith("Source file or directory not found") }
assertNotNull(report)
assertEquals("/script.kts", report?.sourcePath)
}
@Test
fun testMemoryCache() {