[REPL] Report script codegen errors in advance
This commit is contained in:
+61
-10
@@ -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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-21
@@ -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,
|
||||||
|
|||||||
+40
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user