Check REPL snippet syntax before calling analysis

restores the behaviour of the GenericRepl implementation and fixes
issues then invalid code is being compiled
#KT-34888 fixed
This commit is contained in:
Ilya Chernikov
2020-01-10 09:30:59 +01:00
parent 8e07482862
commit d15d62a338
4 changed files with 80 additions and 40 deletions
@@ -44,6 +44,13 @@ class LegacyReplTest : TestCase() {
}
}
fun testReplSyntaxErrorsChecked() {
LegacyTestRepl().use { repl ->
val res = repl.compileAndEval(repl.nextCodeLine("data class Q(val x: Int, val: String)"))
TestCase.assertTrue("Expected compile error", res.first is ReplCompileResult.Error)
}
}
fun testReplCodeFormat() {
LegacyTestRepl().use { repl ->
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
@@ -10,6 +10,7 @@ import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerImpl
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.junit.Assert
import org.junit.Test
import kotlin.script.experimental.api.*
@@ -28,9 +29,7 @@ class ReplTest : TestCase() {
@Test
fun testCompileAndEval() {
val out = captureOut {
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
checkEvaluateInRepl(
sequenceOf(
"val x = 3",
"x + 4",
@@ -44,9 +43,7 @@ class ReplTest : TestCase() {
@Test
fun testEvalWithResult() {
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
checkEvaluateInRepl(
sequenceOf(
"val x = 5",
"x + 6",
@@ -58,9 +55,7 @@ class ReplTest : TestCase() {
@Test
fun testEvalWithIfResult() {
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
checkEvaluateInRepl(
sequenceOf(
"val x = 5",
"x + 6",
@@ -73,27 +68,25 @@ class ReplTest : TestCase() {
@Test
fun testImplicitReceiver() {
val receiver = TestReceiver()
chechEvaluateInRepl(
simpleScriptompilationConfiguration.with {
implicitReceivers(TestReceiver::class)
},
simpleScriptEvaluationConfiguration.with {
implicitReceivers(receiver)
},
checkEvaluateInRepl(
sequenceOf(
"val x = 4",
"x + prop1",
"res1 * 3"
),
sequenceOf(null, 7, 21)
sequenceOf(null, 7, 21),
simpleScriptompilationConfiguration.with {
implicitReceivers(TestReceiver::class)
},
simpleScriptEvaluationConfiguration.with {
implicitReceivers(receiver)
}
)
}
@Test
fun testEvalWithError() {
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
checkEvaluateInRepl(
sequenceOf(
"throw RuntimeException(\"abc\")",
"val x = 3",
@@ -102,12 +95,30 @@ class ReplTest : TestCase() {
sequenceOf(RuntimeException("abc"), null, 4)
)
}
@Test
fun testSyntaxErrors() {
checkEvaluateInReplDiags(
sequenceOf(
"data class Q(val x: Int, val: String)",
"fun g(): Unit { return }}",
"fun f() : Int { return 1",
"6*7"
),
sequenceOf(
makeFailureResult("Parameter name expected"),
makeFailureResult("Unexpected symbol"),
makeFailureResult("Expecting '}'"),
42.asSuccess()
)
)
}
}
fun evaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration?,
snippets: Sequence<String>
snippets: Sequence<String>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
val replCompilerProxy =
KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
@@ -139,18 +150,24 @@ fun evaluateInRepl(
}
}
fun chechEvaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration?,
fun checkEvaluateInReplDiags(
snippets: Sequence<String>,
expected: Sequence<Any?>
expected: Sequence<ResultWithDiagnostics<Any?>>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration
) {
val expectedIter = expected.iterator()
evaluateInRepl(compilationConfiguration, evaluationConfiguration, snippets).forEachIndexed { index, res ->
when (res) {
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
is ResultWithDiagnostics.Success -> {
val expectedVal = expectedIter.next()
evaluateInRepl(snippets, compilationConfiguration, evaluationConfiguration).forEachIndexed { index, res ->
val expectedRes = expectedIter.next()
when {
res is ResultWithDiagnostics.Failure && expectedRes is ResultWithDiagnostics.Failure -> {
Assert.assertTrue(
"#$index: Expected $expectedRes, got $res",
res.reports.map { it.message } == expectedRes.reports.map { it.message }
)
}
res is ResultWithDiagnostics.Success && expectedRes is ResultWithDiagnostics.Success -> {
val expectedVal = expectedRes.value
when (val resVal = res.value.returnValue) {
is ResultValue.Value -> Assert.assertEquals(
"#$index: Expected $expectedVal, got $resVal",
@@ -165,8 +182,21 @@ fun chechEvaluateInRepl(
else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null)
}
}
else -> {
Assert.fail("#$index: Expected $expectedRes, got $res")
}
}
}
}
fun checkEvaluateInRepl(
snippets: Sequence<String>,
expected: Sequence<Any?>,
compilationConfiguration: ScriptCompilationConfiguration = simpleScriptompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration? = simpleScriptEvaluationConfiguration
) = checkEvaluateInReplDiags(
snippets, expected.map { ResultWithDiagnostics.Success(it) }, compilationConfiguration,
evaluationConfiguration
)
class TestReceiver(val prop1: Int = 3)
@@ -60,15 +60,15 @@ class ResolveDependenciesTest : TestCase() {
@Test
fun testReplResolveFunAndValFromClassloader() {
chechEvaluateInRepl(
configurationWithDependenciesFromClassloader, null,
sequenceOf(funAndValAccessScriptText, funAndValAccessScriptText),
sequenceOf(42, 42)
checkEvaluateInRepl(
sequenceOf(funAndValAccessScriptText, funAndValAccessScriptText), sequenceOf(42, 42),
configurationWithDependenciesFromClassloader,
null
)
chechEvaluateInRepl(
configurationWithDependenciesFromClassloader, null,
funAndValImportScriptText.split('\n').asSequence(),
sequenceOf(null, null, 42)
checkEvaluateInRepl(
funAndValImportScriptText.split('\n').asSequence(), sequenceOf(null, null, 42),
configurationWithDependenciesFromClassloader,
null
)
runScriptAndCheckResult(funAndValImportScript, configurationWithDependenciesFromClassloader, null, 42)
}
@@ -96,6 +96,9 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
)
.valueOr { return it }
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(snippetKtFile, errorHolder)
if (syntaxErrorReport.isHasErrors) return failure(messageCollector)
val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(
context,
snippetKtFile,