Improve script and REPL result handling:

- implement error result
- refactor other result classes
- implement handling in the script evaluation extension - also restores
  previous script error reporting functionality
- add possibility to customize result fileds in script and REPL
- refactor result calculation in the backend: cleanup, rename (since
  it is not only about REPL now)
This commit is contained in:
Ilya Chernikov
2019-07-04 13:33:15 +02:00
parent dc4370ff08
commit 9ae0ff03fa
23 changed files with 421 additions and 127 deletions
@@ -364,23 +364,27 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
private void putStackValue(@Nullable KtElement expr, @NotNull Type type, @Nullable KotlinType kotlinType, @NotNull StackValue value) {
// for repl store the result of the last line into special field
if (value.type != Type.VOID_TYPE && state.getReplSpecific().getShouldGenerateScriptResultValue()) {
if (value.type != Type.VOID_TYPE) {
ScriptContext context = getScriptContext();
if (expr == context.getLastStatement()) {
StackValue.Field resultValue = StackValue.field(context.getResultFieldInfo(), StackValue.LOCAL_0);
resultValue.store(value, v);
state.getReplSpecific().setHasResult(true);
return;
if (context != null && expr == context.getLastStatement()) {
FieldInfo resultFieldInfo = context.getResultFieldInfo();
if (resultFieldInfo != null) {
StackValue.Field resultValue = StackValue.field(resultFieldInfo, StackValue.LOCAL_0);
resultValue.store(value, v);
state.getScriptSpecific().setResultType(resultFieldInfo.getFieldKotlinType());
state.getScriptSpecific().setResultFieldName(resultFieldInfo.getFieldName());
return;
}
}
}
value.put(type, kotlinType, v);
}
@NotNull
@Nullable
private ScriptContext getScriptContext() {
CodegenContext context = getContext();
while (!(context instanceof ScriptContext)) {
while (context != null && !(context instanceof ScriptContext)) {
context = context.getParentContext();
}
return (ScriptContext) context;
@@ -23,7 +23,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
import org.jetbrains.kotlin.serialization.DescriptorSerializer
@@ -86,14 +85,14 @@ class ScriptCodegen private constructor(
)
val asmMethod = jvmSignature.asmMethod
if (state.replSpecific.shouldGenerateScriptResultValue) {
val resultFieldInfo = scriptContext.resultFieldInfo
scriptContext.resultFieldInfo?.let { resultFieldInfo ->
classBuilder.newField(
JvmDeclarationOrigin.NO_ORIGIN,
ACC_PUBLIC or ACC_FINAL,
resultFieldInfo.fieldName,
resultFieldInfo.fieldType.descriptor,
null, null)
NO_ORIGIN,
ACC_PUBLIC or ACC_FINAL,
resultFieldInfo.fieldName,
resultFieldInfo.fieldType.descriptor,
null, null
)
}
val mv = classBuilder.newMethod(
@@ -277,7 +276,7 @@ class ScriptCodegen private constructor(
val builder = state.factory.newVisitor(
OtherOrigin(declaration, scriptDescriptor), classType, declaration.containingFile)
val earlierScripts = state.replSpecific.earlierScriptsForReplInterpreter
val earlierScripts = state.scriptSpecific.earlierScriptsForReplInterpreter
val scriptContext = parentContext.intoScript(
scriptDescriptor,
@@ -42,15 +42,15 @@ class ScriptContext(
) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) {
val lastStatement: KtExpression?
val resultFieldInfo: FieldInfo
val resultFieldInfo: FieldInfo?
get() {
assert(state.replSpecific.shouldGenerateScriptResultValue) { "Should not be called unless 'resultFieldName' is set" }
val scriptResultFieldName = state.replSpecific.scriptResultFieldName!!
val fieldType = state.replSpecific.resultType?.let { state.typeMapper.mapType(it) } ?: AsmTypes.OBJECT_TYPE
val resultValue = scriptDescriptor.resultValue ?: return null
val scriptResultFieldName = resultValue.name.identifier
val fieldType = resultValue.returnType?.let { state.typeMapper.mapType(it) } ?: AsmTypes.OBJECT_TYPE
return FieldInfo.createForHiddenField(
state.typeMapper.mapClass(scriptDescriptor),
fieldType,
state.replSpecific.resultType,
resultValue.returnType,
scriptResultFieldName
)
}
@@ -391,7 +391,7 @@ class PsiSourceCompilerForInline(private val codegen: ExpressionCodegen, overrid
return when (descriptor) {
is ScriptDescriptor -> {
val earlierScripts = state.replSpecific.earlierScriptsForReplInterpreter
val earlierScripts = state.scriptSpecific.earlierScriptsForReplInterpreter
containerContext.intoScript(
descriptor,
earlierScripts ?: emptyList(),
@@ -214,15 +214,15 @@ class GenerationState private constructor(
val factory: ClassFileFactory
private lateinit var duplicateSignatureFactory: BuilderFactoryForDuplicateSignatureDiagnostics
val replSpecific = ForRepl()
val scriptSpecific = ForScript()
//TODO: should be refactored out
class ForRepl {
// TODO: review usages and consider replace mutability with explicit passing of input and output
class ForScript {
// quite a mess, this one is an input from repl interpreter
var earlierScriptsForReplInterpreter: List<ScriptDescriptor>? = null
var scriptResultFieldName: String? = null
val shouldGenerateScriptResultValue: Boolean get() = scriptResultFieldName != null
// and the rest is an output from the codegen
var resultFieldName: String? = null
var resultType: KotlinType? = null
var hasResult: Boolean = false
}
val isCallAssertionsDisabled: Boolean = configuration.getBoolean(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS)
@@ -108,12 +108,16 @@ open class GenericReplEvaluator(
historyActor.addFinal(compileResult.lineId, EvalClassWithInstanceAndLoader(scriptClass.kotlin, scriptInstance, classLoader, invokeWrapper))
val resultFieldName = scriptResultFieldName(compileResult.lineId.no)
val resultField = scriptClass.getDeclaredField(resultFieldName).apply { isAccessible = true }
val resultValue: Any? = resultField.get(scriptInstance)
return if (compileResult.hasResult) {
val resultFieldName = scriptResultFieldName(compileResult.lineId.no)
val resultField = scriptClass.declaredFields.find { it.name == resultFieldName }?.apply { isAccessible = true }
assert(resultField != null) { "compileResult.hasResult == true but resultField is null" }
val resultValue: Any? = resultField!!.get(scriptInstance)
return if (compileResult.hasResult) ReplEvalResult.ValueResult(resultFieldName, resultValue, compileResult.type)
else ReplEvalResult.UnitResult()
ReplEvalResult.ValueResult(resultFieldName, resultValue, compileResult.type)
} else {
ReplEvalResult.UnitResult()
}
}
}
}