Add new REPL API JVM implementation

This commit is contained in:
Ilya Muradyan
2020-02-28 19:45:20 +03:00
committed by Ilya Chernikov
parent 4c2c44b106
commit d2fec96f38
51 changed files with 2557 additions and 503 deletions
@@ -0,0 +1,248 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.impl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.repl.LineId
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerStageHistory
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import java.util.concurrent.atomic.AtomicInteger
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.script.experimental.util.LinkedSnippetImpl
import kotlin.script.experimental.util.add
open class KJvmReplCompilerBase<AnalyzerT : ReplCodeAnalyzerBase> protected constructor(
protected val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val initAnalyzer: (SharedScriptCompilationContext) -> AnalyzerT
) : ReplCompiler<KJvmCompiledScript>, ScriptCompiler {
val state = JvmReplCompilerState({ createReplCompilationState(it, initAnalyzer) })
val history = JvmReplCompilerStageHistory(state)
protected val scriptPriority = AtomicInteger()
override var lastCompiledSnippet: LinkedSnippetImpl<KJvmCompiledScript>? = null
protected set
fun createReplCompilationState(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
initAnalyzer: (SharedScriptCompilationContext) -> AnalyzerT /* = { ReplCodeAnalyzer1(it.environment) } */
): ReplCompilationState<AnalyzerT> {
val context = withMessageCollectorAndDisposable(disposeOnSuccess = false) { messageCollector, disposable ->
createIsolatedCompilationContext(
scriptCompilationConfiguration,
hostConfiguration,
messageCollector,
disposable
).asSuccess()
}.valueOr { throw IllegalStateException("Unable to initialize repl compiler:\n ${it.reports.joinToString("\n ")}") }
return ReplCompilationState(context, initAnalyzer)
}
override suspend fun compile(
snippets: Iterable<SourceCode>,
configuration: ScriptCompilationConfiguration
): ResultWithDiagnostics<LinkedSnippet<KJvmCompiledScript>> =
snippets.map { snippet ->
withMessageCollector(snippet) { messageCollector ->
val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr {
return it
}
val compilationState = state.getCompilationState(initialConfiguration)
val (context, errorHolder, snippetKtFile) = prepareForAnalyze(
snippet,
messageCollector,
compilationState,
checkSyntaxErrors = true
).valueOr { return@withMessageCollector it }
val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(
context,
snippetKtFile,
messageCollector
)
val firstFailure = sourceDependencies.firstOrNull { it.sourceDependencies is ResultWithDiagnostics.Failure }
?.let { it.sourceDependencies as ResultWithDiagnostics.Failure }
if (firstFailure != null)
return firstFailure
if (history.isEmpty()) {
val updatedConfiguration = ScriptDependenciesProvider.getInstance(context.environment.project)
?.getScriptConfiguration(snippetKtFile)?.configuration
?: context.baseScriptCompilationConfiguration
registerPackageFragmentProvidersIfNeeded(
updatedConfiguration,
context.environment
)
}
val no = scriptPriority.getAndIncrement()
val analysisResult =
compilationState.analyzerEngine.analyzeReplLineWithImportedScripts(snippetKtFile, sourceFiles.drop(1), snippet, no)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> return failure(
messageCollector
)
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> {
(analysisResult.scriptDescriptor as? ScriptDescriptor)
?: return failure(
snippet,
messageCollector,
"Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}"
)
}
else -> return failure(
snippet,
messageCollector,
"Unexpected result ${analysisResult::class.java}"
)
}
val generationState = GenerationState.Builder(
snippetKtFile.project,
ClassBuilderFactories.BINARIES,
compilationState.analyzerEngine.module,
compilationState.analyzerEngine.trace.bindingContext,
sourceFiles,
compilationState.environment.configuration
).build().apply {
scriptSpecific.earlierScriptsForReplInterpreter = history.map { it.item }
beforeCompile()
}
KotlinCodegenFacade.generatePackage(generationState, snippetKtFile.script!!.containingKtFile.packageFqName, sourceFiles)
history.push(LineId(no, 0, snippet.hashCode()), scriptDescriptor)
val dependenciesProvider = ScriptDependenciesProvider.getInstance(context.environment.project)
val compiledScript =
makeCompiledScript(
generationState,
snippet,
sourceFiles.first(),
sourceDependencies
) { ktFile ->
dependenciesProvider?.getScriptConfiguration(ktFile)?.configuration
?: context.baseScriptCompilationConfiguration
}
lastCompiledSnippet = lastCompiledSnippet.add(compiledScript)
lastCompiledSnippet?.asSuccess(messageCollector.diagnostics)
?: failure(
snippet,
messageCollector,
"last compiled snippet should not be null"
)
}
}.last()
override suspend fun invoke(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript> {
return when (val res = compile(script, scriptCompilationConfiguration)) {
is ResultWithDiagnostics.Success -> res.value.get().asSuccess(res.reports)
is ResultWithDiagnostics.Failure -> res
}
}
protected data class AnalyzePreparationResult(
val context: SharedScriptCompilationContext,
val errorHolder: MessageCollectorBasedReporter,
val snippetKtFile: KtFile
)
protected fun prepareForAnalyze(
snippet: SourceCode,
parentMessageCollector: MessageCollector,
compilationState: JvmReplCompilerState.Compilation,
checkSyntaxErrors: Boolean
): ResultWithDiagnostics<AnalyzePreparationResult> =
withMessageCollector(
snippet,
parentMessageCollector
) { messageCollector ->
val context =
(compilationState as? ReplCompilationState<*>)?.context
?: return failure(
snippet, messageCollector, "Internal error: unknown parameter passed as compilationState: $compilationState"
)
setIdeaIoUseFallback()
val errorHolder = object : MessageCollectorBasedReporter {
override val messageCollector = messageCollector
}
val snippetKtFile =
getScriptKtFile(
snippet,
context.baseScriptCompilationConfiguration,
context.environment.project,
messageCollector
)
.valueOr { return it }
if (checkSyntaxErrors) {
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(snippetKtFile, errorHolder)
if (syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof) return failure(
messageCollector, ScriptDiagnostic(ScriptDiagnostic.incompleteCode, "Incomplete code")
)
if (syntaxErrorReport.isHasErrors) return failure(
messageCollector
)
}
return AnalyzePreparationResult(
context,
errorHolder,
snippetKtFile
).asSuccess()
}
companion object {
fun create(hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration) =
KJvmReplCompilerBase(hostConfiguration) {
ReplCodeAnalyzerBase(it.environment)
}
}
}
class ReplCompilationState<AnalyzerT : ReplCodeAnalyzerBase>(
val context: SharedScriptCompilationContext,
val analyzerInit: (context: SharedScriptCompilationContext) -> AnalyzerT
) : JvmReplCompilerState.Compilation {
override val disposable: Disposable? get() = context.disposable
override val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = context.baseScriptCompilationConfiguration
override val environment: KotlinCoreEnvironment get() = context.environment
override val analyzerEngine: AnalyzerT by lazy {
// ReplCodeAnalyzer1(context.environment)
analyzerInit(context)
}
}
@@ -1,186 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.impl
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
import org.jetbrains.kotlin.cli.common.repl.LineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.KJvmReplCompilerProxy
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvmReplCompilerProxy {
override fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation {
val context = withMessageCollectorAndDisposable(disposeOnSuccess = false) { messageCollector, disposable ->
createIsolatedCompilationContext(
scriptCompilationConfiguration,
hostConfiguration,
messageCollector,
disposable
).asSuccess()
}.valueOr { throw IllegalStateException("Unable to initialize repl compiler:\n ${it.reports.joinToString("\n ")}") }
return ReplCompilationState(context)
}
override fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
): ResultWithDiagnostics<Boolean> =
withMessageCollector(script) { messageCollector ->
val ktFile = getScriptKtFile(
script,
scriptCompilationConfiguration,
project,
messageCollector
)
.valueOr { return it }
val errorHolder = object : MessageCollectorBasedReporter {
override val messageCollector = messageCollector
}
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder)
when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> false.asSuccess(messageCollector.diagnostics)
syntaxErrorReport.isHasErrors -> failure(messageCollector)
else -> true.asSuccess()
}
}
override fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
// TODO: replace history with some interface based on CompiledScript
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript> =
withMessageCollector(snippet) { messageCollector ->
val context = (compilationState as? ReplCompilationState)?.context
?: return failure(
snippet, messageCollector, "Internal error: unknown parameter passed as compilationState: $compilationState"
)
setIdeaIoUseFallback()
// NOTE: converting between REPL entities from compiler and "new" scripting entities
// TODO: (big) move REPL API from compiler to the new scripting infrastructure and streamline ops
val codeLine = makeReplCodeLine(snippetId, snippet)
val errorHolder = object : MessageCollectorBasedReporter {
override val messageCollector = messageCollector
}
val snippetKtFile =
getScriptKtFile(
snippet,
context.baseScriptCompilationConfiguration,
context.environment.project,
messageCollector
)
.valueOr { return it }
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(snippetKtFile, errorHolder)
if (syntaxErrorReport.isHasErrors) return failure(messageCollector)
val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(
context,
snippetKtFile,
messageCollector
)
val firstFailure = sourceDependencies.firstOrNull { it.sourceDependencies is ResultWithDiagnostics.Failure }
?.let { it.sourceDependencies as ResultWithDiagnostics.Failure }
if (firstFailure != null)
return firstFailure
if (history.isEmpty()) {
val updatedConfiguration = ScriptDependenciesProvider.getInstance(context.environment.project)
?.getScriptConfiguration(snippetKtFile)?.configuration
?: context.baseScriptCompilationConfiguration
registerPackageFragmentProvidersIfNeeded(updatedConfiguration, context.environment)
}
val analysisResult =
compilationState.analyzerEngine.analyzeReplLineWithImportedScripts(snippetKtFile, sourceFiles.drop(1), codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return failure(
messageCollector
)
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> {
(analysisResult.scriptDescriptor as? ScriptDescriptor)
?: return failure(
snippet,
messageCollector,
"Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}"
)
}
else -> return failure(
snippet,
messageCollector,
"Unexpected result ${analysisResult::class.java}"
)
}
val generationState = GenerationState.Builder(
snippetKtFile.project,
ClassBuilderFactories.BINARIES,
compilationState.analyzerEngine.module,
compilationState.analyzerEngine.trace.bindingContext,
sourceFiles,
compilationState.environment.configuration
).build().apply {
scriptSpecific.earlierScriptsForReplInterpreter = history.map { it.item }
beforeCompile()
}
KotlinCodegenFacade.generatePackage(generationState, snippetKtFile.script!!.containingKtFile.packageFqName, sourceFiles)
history.push(LineId(codeLine), scriptDescriptor)
val dependenciesProvider = ScriptDependenciesProvider.getInstance(context.environment.project)
val compiledScript =
makeCompiledScript(
generationState,
snippet,
sourceFiles.first(),
sourceDependencies
) { ktFile ->
dependenciesProvider?.getScriptConfiguration(ktFile)?.configuration
?: context.baseScriptCompilationConfiguration
}
compiledScript.asSuccess(messageCollector.diagnostics)
}
}
internal class ReplCompilationState(val context: SharedScriptCompilationContext) : JvmReplCompilerState.Compilation {
override val disposable: Disposable? get() = context.disposable
override val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = context.baseScriptCompilationConfiguration
override val environment: KotlinCoreEnvironment get() = context.environment
override val analyzerEngine: ReplCodeAnalyzer by lazy {
ReplCodeAnalyzer(context.environment)
}
}
internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine =
ReplCodeLine(id.no, id.generation, code.text)
@@ -52,14 +52,14 @@ import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.withUpdatedClasspath
internal class SharedScriptCompilationContext(
class SharedScriptCompilationContext(
val disposable: Disposable?,
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
val environment: KotlinCoreEnvironment,
val ignoredOptionsReportingState: IgnoredOptionsReportingState
)
internal fun createIsolatedCompilationContext(
fun createIsolatedCompilationContext(
baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
hostConfiguration: ScriptingHostConfiguration,
messageCollector: ScriptDiagnosticsMessageCollector,
@@ -18,7 +18,7 @@ import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asErrorDiagnostics
internal class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector {
class ScriptDiagnosticsMessageCollector(private val parentMessageCollector: MessageCollector?) : MessageCollector {
private val _diagnostics = arrayListOf<ScriptDiagnostic>()
@@ -78,17 +78,17 @@ private fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessa
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
}
internal fun failure(
fun failure(
messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic
): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
internal fun failure(
fun failure(
script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String
): ResultWithDiagnostics.Failure =
failure(messageCollector, message.asErrorDiagnostics(path = script.locationId))
internal class IgnoredOptionsReportingState {
class IgnoredOptionsReportingState {
var currentArguments = K2JVMCompilerArguments()
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.scripting.compiler.plugin.dependencies.ScriptsCompil
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
import org.jetbrains.kotlin.scripting.scriptFileName
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.Serializable
import java.util.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.FileBasedScriptSource
@@ -33,7 +34,7 @@ internal fun makeCompiledModule(generationState: GenerationState) =
.associateTo(sortedMapOf<String, ByteArray>()) { it.relativePath to it.asByteArray() }
)
internal inline fun <T> withMessageCollectorAndDisposable(
inline fun <T> withMessageCollectorAndDisposable(
script: SourceCode? = null,
parentMessageCollector: MessageCollector? = null,
disposable: Disposable = Disposer.newDisposable(),
@@ -57,7 +58,7 @@ internal inline fun <T> withMessageCollectorAndDisposable(
}
}
internal inline fun <T> withMessageCollector(
inline fun <T> withMessageCollector(
script: SourceCode? = null,
parentMessageCollector: MessageCollector? = null,
body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics<T>
@@ -99,6 +100,16 @@ internal fun getScriptKtFile(
}
}
class SourceCodeImpl(file: KtFile) : SourceCode, Serializable {
override val text: String = file.text
override val name: String? = file.name
override val locationId: String? = file.virtualFilePath
companion object {
private const val serialVersionUID = 1L
}
}
internal fun makeCompiledScript(
generationState: GenerationState,
script: SourceCode,
@@ -122,7 +133,7 @@ internal fun makeCompiledScript(
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.valueOrThrow()?.mapNotNull { sourceFile ->
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
KJvmCompiledScript(
containingKtFile.virtualFile?.path,
containingKtFile.virtualFilePath,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
null,
@@ -11,29 +11,35 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages.DiagnosticMessageHolder
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.dependencies.ScriptDependencies
class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.analyzerEngine.reset()
lock.write {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
lock.write {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<SourceCodeByReplLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
if (removedCompiledLine.no != removedAnalyzedLine.no) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
@@ -59,7 +65,7 @@ class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val analyzerEngine = ReplCodeAnalyzer(environment)
val analyzerEngine = ReplCodeAnalyzerBase(environment)
var lastDependencies: ScriptDependencies? = null
}
@@ -59,7 +59,7 @@ open class GenericReplCompiler(
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
val res = checker.check(state, codeLine)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete()
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete("Code is incomplete")
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
is ReplCheckResult.Ok -> {
} // continue
@@ -81,10 +81,10 @@ open class GenericReplCompiler(
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> {
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> {
return ReplCompileResult.Error(errorHolder.renderMessage())
}
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> {
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> {
(analysisResult.scriptDescriptor as? ScriptDescriptor)
?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}")
}
@@ -108,12 +108,12 @@ open class GenericReplCompiler(
setOf(psiFile.script!!.containingKtFile)
)
compilerState.history.push(LineId(codeLine), scriptDescriptor)
compilerState.history.push(LineId(codeLine.no, 0, codeLine.hashCode()), scriptDescriptor)
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
return ReplCompileResult.CompiledClasses(
LineId(codeLine),
LineId(codeLine.no, 0, codeLine.hashCode()),
compilerState.history.map { it.id },
scriptDescriptor.name.identifier,
classes,
@@ -7,13 +7,12 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
@@ -33,23 +32,30 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.jvm.util.CompiledHistoryItem
import kotlin.script.experimental.jvm.util.CompiledHistoryList
import kotlin.script.experimental.jvm.util.SnippetsHistory
class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableAnalyzerState()
open class ReplCodeAnalyzerBase(
environment: KotlinCoreEnvironment,
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
) {
protected val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
protected val container: ComponentProvider
protected val topDownAnalysisContext: TopDownAnalysisContext
protected val topDownAnalyzer: LazyTopDownAnalyzer
protected val resolveSession: ResolveSession
protected val replState = ResettableAnalyzerState()
val module: ModuleDescriptorImpl
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
init {
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
// to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true
// because no symbol declared in the REPL session can be used from Java)
val container = TopDownAnalyzerFacadeForJVM.createContainer(
container = TopDownAnalyzerFacadeForJVM.createContainer(
environment.project,
emptyList(),
trace,
@@ -71,7 +77,10 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
val scriptDescriptor: ClassDescriptorWithResolutionScopes?
val diagnostics: Diagnostics
data class Successful(override val scriptDescriptor: ClassDescriptorWithResolutionScopes, override val diagnostics: Diagnostics) :
data class Successful(
override val scriptDescriptor: ClassDescriptorWithResolutionScopes,
override val diagnostics: Diagnostics
) :
ReplLineAnalysisResult
data class WithErrors(override val diagnostics: Diagnostics) :
@@ -80,9 +89,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
fun resetToLine(lineId: ILineId): List<ReplCodeLine> = replState.resetToLine(lineId)
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> = replState.resetToLine(lineId)
fun reset(): List<ReplCodeLine> = replState.reset()
fun reset(): List<SourceCodeByReplLine> = replState.reset()
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
@@ -90,31 +99,38 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, emptyList(), codeLine)
return doAnalyze(psiFile, emptyList(), codeLine.toSourceCode())
}
fun analyzeReplLineWithImportedScripts(psiFile: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
fun analyzeReplLineWithImportedScripts(
psiFile: KtFile,
importedScripts: List<KtFile>,
codeLine: SourceCode,
priority: Int
): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority)
return doAnalyze(psiFile, importedScripts, codeLine)
return doAnalyze(psiFile, importedScripts, codeLine.addNo(priority))
}
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, codeLine: SourceCodeByReplLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(
FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts)
)
replState.submitLine(linePsi, codeLine)
replState.submitLine(linePsi)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
return if (hasErrors) {
replState.lineFailure(linePsi, codeLine)
ReplLineAnalysisResult.WithErrors(diagnostics)
replState.lineFailure(linePsi)
ReplLineAnalysisResult.WithErrors(
diagnostics
)
} else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
@@ -123,10 +139,9 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
diagnostics
)
}
}
private class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
protected class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
private lateinit var delegateFactory: DeclarationProviderFactory
private lateinit var rootPackageProvider: AdaptablePackageMemberDeclarationProvider
@@ -175,28 +190,31 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
data class CompiledCode(val className: String, val source: SourceCodeByReplLine)
// TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastructure everywhere
// TODO: review its place in the extracted state infrastructure (now the analyzer itself is a part of the state)
class ResettableAnalyzerState {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val successfulLines = ResettableSnippetsHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineId.no)
fun resetToLine(lineId: ILineId): List<SourceCodeByReplLine> {
val removed = successfulLines.resetToLine(lineId)
removed.forEach { submittedLines.remove(it.second.linePsi) }
return removed.map { it.first }
}
fun reset(): List<ReplCodeLine> {
fun reset(): List<SourceCodeByReplLine> {
submittedLines.clear()
return successfulLines.reset().map { it.first }
}
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(
ktFile,
successfulLines.lastValue()
)
fun submitLine(ktFile: KtFile) {
val line =
LineInfo.SubmittedLine(
ktFile,
successfulLines.lastValue()
)
submittedLines[ktFile] = line
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
@@ -205,7 +223,7 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) {
fun lineSuccess(ktFile: KtFile, codeLine: SourceCodeByReplLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) {
val successfulLine =
LineInfo.SuccessfulLine(
ktFile,
@@ -213,10 +231,15 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
scriptDescriptor
)
submittedLines[ktFile] = successfulLine
successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine)
successfulLines.add(
CompiledCode(
ktFile.name,
codeLine
), successfulLine
)
}
fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) {
fun lineFailure(ktFile: KtFile) {
submittedLines[ktFile] =
LineInfo.FailedLine(
ktFile,
@@ -251,3 +274,34 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
}
}
}
fun ReplCodeLine.toSourceCode() = SourceCodeByReplLine(code, no)
internal fun SourceCode.addNo(no: Int) = SourceCodeByReplLine(text, no, name, locationId)
data class SourceCodeByReplLine(
override val text: String,
val no: Int,
override val name: String? = null,
override val locationId: String? = null
) : SourceCode
private typealias ReplSourceHistoryList<ResultT> = List<CompiledHistoryItem<SourceCodeByReplLine, ResultT>>
@Deprecated("This functionality is left for backwards compatibility only", ReplaceWith("SnippetsHistory"))
private class ResettableSnippetsHistory<ResultT>(startingHistory: CompiledHistoryList<ReplCodeAnalyzerBase.CompiledCode, ResultT> = emptyList()) :
SnippetsHistory<ReplCodeAnalyzerBase.CompiledCode, ResultT>(startingHistory) {
fun resetToLine(line: ILineId): ReplSourceHistoryList<ResultT> {
val removed = arrayListOf<Pair<SourceCodeByReplLine, ResultT>>()
while ((history.lastOrNull()?.first?.source?.no ?: -1) > line.no) {
removed.add(history.removeAt(history.size - 1).let { Pair(it.first.source, it.second) })
}
return removed.reversed()
}
fun reset(): ReplSourceHistoryList<ResultT> {
val removed = history.map { Pair(it.first.source, it.second) }
history.clear()
return removed
}
}
@@ -76,7 +76,7 @@ class ReplInterpreter(
}
// TODO: add script definition with project-based resolving for IDEA repl
private val scriptCompiler: ReplCompiler by lazy {
private val scriptCompiler: ReplCompilerWithoutCheck by lazy {
GenericReplCompiler(
disposable,
REPL_LINE_AS_SCRIPT_DEFINITION,
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
@@ -15,54 +14,11 @@ import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
interface KJvmReplCompilerProxy {
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
class JvmReplCompilerStageHistory<CompilationT : JvmReplCompilerState.Compilation>(private val state: JvmReplCompilerState<CompilationT>) :
BasicReplStageHistory<ScriptDescriptor>(state.lock)
fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
): ResultWithDiagnostics<Boolean>
fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript>
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
class JvmReplCompilerState<CompilationT : JvmReplCompilerState.Compilation>(
val createCompilation: (ScriptCompilationConfiguration) -> CompilationT,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
@@ -80,29 +36,29 @@ class JvmReplCompilerState(
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): CompilationT = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
internal val compilation: CompilationT
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
private var _compilation: CompilationT? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
_compilation = createCompilation(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable?
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
val analyzerEngine: ReplCodeAnalyzerBase
}
}
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.generateTypicalIrProviderList
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.serialization.js.ModuleKind
import kotlin.script.experimental.api.valueOr
import kotlin.script.experimental.host.StringScriptSource
@@ -35,7 +35,7 @@ class JsCoreScriptingCompiler(
private val nameTables: NameTables,
private val symbolTable: SymbolTable,
private val dependencyDescriptors: List<ModuleDescriptor>,
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState = ReplCodeAnalyzer.ResettableAnalyzerState()
private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState = ReplCodeAnalyzerBase.ResettableAnalyzerState()
) {
fun compile(codeLine: ReplCodeLine): ReplCompileResult {
val snippet = codeLine.code
@@ -91,6 +91,6 @@ class JsCoreScriptingCompiler(
val code = generateJsCode(context, irModuleFragment, nameTables)
return createCompileResult(LineId(codeLine), code)
return createCompileResult(LineId(codeLine.no, 0, codeLine.hashCode()), code)
}
}
@@ -11,26 +11,27 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.AbstractJsScriptlikeCodeAnalyser
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.toSourceCode
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
class JsReplCodeAnalyzer(
environment: KotlinCoreEnvironment,
dependencies: List<ModuleDescriptor>,
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState
private val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState
) : AbstractJsScriptlikeCodeAnalyser(environment, dependencies) {
fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): AnalysisResult {
linePsi.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
replState.submitLine(linePsi, codeLine)
replState.submitLine(linePsi)
val result = analysisImpl(linePsi)
return if (result.isSuccess) {
replState.lineSuccess(linePsi, codeLine, result.script)
replState.lineSuccess(linePsi, codeLine.toSourceCode(), result.script)
AnalysisResult.success(result.bindingContext, result.moduleDescriptor)
} else {
replState.lineFailure(linePsi, codeLine)
replState.lineFailure(linePsi)
AnalysisResult.compilationError(result.bindingContext)
}
}
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import java.util.concurrent.locks.ReentrantReadWriteLock
// Used to compile REPL code lines
@@ -22,7 +22,7 @@ class JsReplCompiler(private val environment: KotlinCoreEnvironment) : ReplCompi
lock,
NameTables(emptyList()),
readLibrariesFromConfiguration(environment.configuration),
ReplCodeAnalyzer.ResettableAnalyzerState(),
ReplCodeAnalyzerBase.ResettableAnalyzerState(),
SymbolTable(IdSignatureDescriptor(JsManglerDesc))
)
}
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
import org.jetbrains.kotlin.util.Logger
@@ -132,7 +132,7 @@ fun readLibrariesFromConfiguration(configuration: CompilerConfiguration): List<M
.map { descriptorMap.getOrPut(it.libraryName) { getModuleDescriptorByLibrary(it, descriptorMap) } }
}
fun createCompileResult(code: String) = createCompileResult(LineId(ReplCodeLine(0, 0, "")), code)
fun createCompileResult(code: String) = createCompileResult(LineId(0, 0, 0), code)
fun createCompileResult(lineId: LineId, code: String): ReplCompileResult.CompiledClasses {
return ReplCompileResult.CompiledClasses(
@@ -219,7 +219,7 @@ class JsReplCompilationState(
lock: ReentrantReadWriteLock,
nameTables: NameTables,
dependencies: List<ModuleDescriptor>,
val replState: ReplCodeAnalyzer.ResettableAnalyzerState,
val replState: ReplCodeAnalyzerBase.ResettableAnalyzerState,
val symbolTable: SymbolTable
) : JsCompilationState(lock, nameTables, dependencies)