[REPL] Report script codegen errors in advance

This commit is contained in:
Ilya Muradyan
2022-05-17 17:41:10 +03:00
parent eb1d7110ec
commit 00819c421f
3 changed files with 126 additions and 31 deletions
@@ -201,6 +201,31 @@ class ReplTest : TestCase() {
) )
} }
@Test
fun testCodegenErrors() {
checkEvaluateInReplDiags(
sequenceOf(
"""
val x = 1
class C {
companion object {
fun f() = x
}
}
""".trimIndent()
),
sequenceOf(
makeFailureResult(
"Object Companion captures the script class instance. Try to use class or anonymous object instead",
location = SourceCode.Location(
SourceCode.Position(3, 15),
SourceCode.Position(3, 21)
)
),
)
)
}
@Test @Test
// TODO: make it covering more cases // TODO: make it covering more cases
fun testIrReceiverOvewrite() { fun testIrReceiverOvewrite() {
@@ -217,7 +242,11 @@ class ReplTest : TestCase() {
} }
@Test @Test
fun testNoEvaluationError() { fun testNoErrorAfterBrokenCodegenSnippet() {
val errorMessage = "Platform declaration clash: The following declarations have the same JVM signature (getX()I):\n" +
" fun `<get-X>`(): Int defined in Line_0_simplescript\n" +
" fun `<get-x>`(): Int defined in Line_0_simplescript"
checkEvaluateInReplDiags( checkEvaluateInReplDiags(
sequenceOf( sequenceOf(
""" """
@@ -225,19 +254,27 @@ class ReplTest : TestCase() {
val X = 1 val X = 1
val x = stack(1, X) val x = stack(1, X)
""".trimIndent(), """.trimIndent(),
"val y = 42" """
val y = 42
y
""".trimIndent()
), ),
sequenceOf( sequenceOf(
ResultValue.NotEvaluated.asSuccess( ResultWithDiagnostics.Failure(
listOf( errorMessage.asErrorDiagnostics(
ScriptDiagnostic( location = SourceCode.Location(
ScriptDiagnostic.unspecifiedError, SourceCode.Position(2, 1),
"Unable to instantiate class Line_0_simplescript: java.lang.ClassFormatError: " + SourceCode.Position(2, 6)
"Duplicate method name \"getX\" with signature \"()I\" in class file Line_0_simplescript" )
),
errorMessage.asErrorDiagnostics(
location = SourceCode.Location(
SourceCode.Position(3, 1),
SourceCode.Position(3, 6)
) )
) )
), ),
makeFailureResult("Snippet cannot be evaluated due to history mismatch") 42.asSuccess()
) )
) )
} }
@@ -333,6 +370,20 @@ class ReplTest : TestCase() {
} }
companion object { companion object {
private fun positionsEqual(a: SourceCode.Position?, b: SourceCode.Position?): Boolean {
if (a == null || b == null) {
return a == null && b == null
}
return a.col == b.col && a.line == b.line
}
private fun locationsEqual(a: SourceCode.Location?, b: SourceCode.Location?): Boolean {
if (a == null || b == null) {
return a == null && b == null
}
return positionsEqual(a.start, b.start) && positionsEqual(a.end, b.end)
}
private fun evaluateInRepl( private fun evaluateInRepl(
snippets: Sequence<String>, snippets: Sequence<String>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration, compilationConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
@@ -389,7 +440,7 @@ class ReplTest : TestCase() {
Assert.assertTrue( Assert.assertTrue(
"#$index: Expected $expectedRes, got $res. Locations are different", "#$index: Expected $expectedRes, got $res. Locations are different",
resReports.map { it.location }.zip(expectedRes.reports.map { it.location }).all { resReports.map { it.location }.zip(expectedRes.reports.map { it.location }).all {
it.second == null || it.second == it.first it.second == null || locationsEqual(it.first, it.second)
} }
) )
} }
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.diagnostics.impl.SimpleDiagnosticsCollector
import org.jetbrains.kotlin.idea.MainFunctionDetector import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmDescriptorMangler import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmDescriptorMangler
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
@@ -60,6 +61,7 @@ open class KJvmReplCompilerBase<AnalyzerT : ReplCodeAnalyzerBase>(
configuration: ScriptCompilationConfiguration configuration: ScriptCompilationConfiguration
): ResultWithDiagnostics<LinkedSnippet<KJvmCompiledScript>> = ): ResultWithDiagnostics<LinkedSnippet<KJvmCompiledScript>> =
snippets.map { snippet -> snippets.map { snippet ->
// TODO: get rid of messageCollector to avoid creation of additional entities
withMessageCollector(snippet) { messageCollector -> withMessageCollector(snippet) { messageCollector ->
val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr { val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr {
return it return it
@@ -124,10 +126,25 @@ open class KJvmReplCompilerBase<AnalyzerT : ReplCodeAnalyzerBase>(
val isIr = context.environment.configuration.getBoolean(JVMConfigurationKeys.IR) val isIr = context.environment.configuration.getBoolean(JVMConfigurationKeys.IR)
val codegenDiagnosticsCollector = SimpleDiagnosticsCollector()
val genStateBuilder = GenerationState.Builder(
snippetKtFile.project,
ClassBuilderFactories.BINARIES,
compilationState.analyzerEngine.module,
compilationState.analyzerEngine.trace.bindingContext,
compilationState.environment.configuration
).diagnosticReporter(codegenDiagnosticsCollector)
val generationState = if (isIr) { val generationState = if (isIr) {
generateWithBackendIr(compilationState, snippetKtFile, sourceFiles) generateWithBackendIr(compilationState, sourceFiles, genStateBuilder)
} else { } else {
generateWithOldBackend(compilationState, snippetKtFile, sourceFiles) generateWithOldBackend(snippetKtFile, sourceFiles, genStateBuilder)
}
if (codegenDiagnosticsCollector.hasErrors) {
val scriptDiagnostics = codegenDiagnosticsCollector.scriptDiagnostics(snippet)
return failure(messageCollector, *scriptDiagnostics.toTypedArray())
} }
state.history.push(LineId(snippetNo, 0, snippet.hashCode()), scriptDescriptor) state.history.push(LineId(snippetNo, 0, snippet.hashCode()), scriptDescriptor)
@@ -156,17 +173,11 @@ open class KJvmReplCompilerBase<AnalyzerT : ReplCodeAnalyzerBase>(
}.last() }.last()
private fun generateWithOldBackend( private fun generateWithOldBackend(
compilationState: ReplCompilationState<AnalyzerT>,
snippetKtFile: KtFile, snippetKtFile: KtFile,
sourceFiles: List<KtFile> sourceFiles: List<KtFile>,
prebuiltState: GenerationState.Builder,
): GenerationState { ): GenerationState {
val generationState = GenerationState.Builder( val generationState = prebuiltState.build().also { generationState ->
snippetKtFile.project,
ClassBuilderFactories.BINARIES,
compilationState.analyzerEngine.module,
compilationState.analyzerEngine.trace.bindingContext,
compilationState.environment.configuration
).build().also { generationState ->
generationState.scriptSpecific.earlierScriptsForReplInterpreter = state.history.map { it.item } generationState.scriptSpecific.earlierScriptsForReplInterpreter = state.history.map { it.item }
generationState.beforeCompile() generationState.beforeCompile()
generationState.oldBEInitTrace(sourceFiles) generationState.oldBEInitTrace(sourceFiles)
@@ -178,8 +189,8 @@ open class KJvmReplCompilerBase<AnalyzerT : ReplCodeAnalyzerBase>(
private fun generateWithBackendIr( private fun generateWithBackendIr(
compilationState: ReplCompilationState<AnalyzerT>, compilationState: ReplCompilationState<AnalyzerT>,
snippetKtFile: KtFile, sourceFiles: List<KtFile>,
sourceFiles: List<KtFile> prebuiltState: GenerationState.Builder,
): GenerationState { ): GenerationState {
val generatorExtensions = object : JvmGeneratorExtensionsImpl(compilationState.environment.configuration) { val generatorExtensions = object : JvmGeneratorExtensionsImpl(compilationState.environment.configuration) {
override fun getPreviousScripts() = state.history.map { compilationState.symbolTable.referenceScript(it.item) } override fun getPreviousScripts() = state.history.map { compilationState.symbolTable.referenceScript(it.item) }
@@ -189,14 +200,7 @@ open class KJvmReplCompilerBase<AnalyzerT : ReplCodeAnalyzerBase>(
compilationState.environment.configuration.get(CLIConfigurationKeys.PHASE_CONFIG), compilationState.environment.configuration.get(CLIConfigurationKeys.PHASE_CONFIG),
compilationState.mangler, compilationState.symbolTable, generatorExtensions compilationState.mangler, compilationState.symbolTable, generatorExtensions
) )
val generationState = GenerationState.Builder( val generationState = prebuiltState.build()
snippetKtFile.project,
ClassBuilderFactories.BINARIES,
compilationState.analyzerEngine.module,
compilationState.analyzerEngine.trace.bindingContext,
compilationState.environment.configuration
)
.build()
codegenFactory.generateModule( codegenFactory.generateModule(
generationState, generationState,
@@ -11,6 +11,11 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.psi
import org.jetbrains.kotlin.scripting.definitions.MessageReporter import org.jetbrains.kotlin.scripting.definitions.MessageReporter
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
@@ -18,6 +23,7 @@ import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptDiagnostic import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asErrorDiagnostics import kotlin.script.experimental.api.asErrorDiagnostics
import kotlin.script.experimental.jvm.util.toSourceCodePosition
class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector { class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector {
@@ -205,3 +211,37 @@ val MessageCollector.reporter: MessageReporter
this.report(severity.toCompilerMessageSeverity(), message) this.report(severity.toCompilerMessageSeverity(), message)
} }
fun KtDiagnostic.asScriptDiagnostic(sourceCode: SourceCode): ScriptDiagnostic {
val (diagnosticCode, scriptSeverity) = when (severity) {
Severity.INFO -> ScriptDiagnostic.unspecifiedInfo to ScriptDiagnostic.Severity.INFO
Severity.ERROR -> ScriptDiagnostic.unspecifiedError to ScriptDiagnostic.Severity.ERROR
Severity.WARNING -> ScriptDiagnostic.unspecifiedInfo to ScriptDiagnostic.Severity.WARNING
}
val renderer = RootDiagnosticRendererFactory(this)
val location = if (textRanges.isEmpty()) {
null
} else {
val firstRange = textRanges.first()
val lastRange = textRanges.last()
SourceCode.LocationWithId(
element.psi?.containingFile?.virtualFile?.path.orEmpty(),
SourceCode.Location(
firstRange.startOffset.toSourceCodePosition(sourceCode),
lastRange.endOffset.toSourceCodePosition(sourceCode)
)
)
}
return ScriptDiagnostic(
diagnosticCode,
renderer.render(this),
scriptSeverity,
location
)
}
fun BaseDiagnosticsCollector.scriptDiagnostics(sourceCode: SourceCode) = diagnostics.map {
it.asScriptDiagnostic(sourceCode)
}