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)
@@ -0,0 +1,27 @@
description = "Kotlin Scripting Compiler extension providing code completion and static analysis (for using in embeddable mode)"
plugins { java }
dependencies {
embedded(project(":kotlin-scripting-ide-services")) { isTransitive = false }
embedded(project(":idea:ide-common")) { isTransitive = false }
runtime(project(":kotlin-script-runtime"))
runtime(kotlinStdlib())
runtime(project(":kotlin-scripting-common"))
runtime(project(":kotlin-scripting-jvm"))
runtime(project(":kotlin-compiler-embeddable"))
runtime(project(":kotlin-scripting-compiler-embeddable"))
}
sourceSets {
"main" {}
"test" {}
}
publish()
noDefaultJar()
runtimeJar(rewriteDefaultJarDepsToShadedCompiler())
sourcesJar()
javadocJar()
@@ -0,0 +1,50 @@
plugins {
kotlin("jvm")
}
jvmTarget = "1.8"
val allTestsRuntime by configurations.creating
val testCompile by configurations
testCompile.extendsFrom(allTestsRuntime)
val embeddableTestRuntime by configurations.creating {
extendsFrom(allTestsRuntime)
}
dependencies {
allTestsRuntime(commonDep("junit"))
testCompile(project(":kotlin-scripting-ide-services"))
testCompile(project(":kotlin-scripting-compiler"))
testCompile(project(":compiler:cli-common"))
testRuntimeOnly(project(":kotlin-compiler"))
testRuntimeOnly(commonDep("org.jetbrains.intellij.deps", "trove4j"))
testRuntimeOnly(project(":idea:ide-common")) { isTransitive = false }
embeddableTestRuntime(project(":kotlin-scripting-ide-services-embeddable"))
embeddableTestRuntime(project(":kotlin-compiler-embeddable"))
embeddableTestRuntime(testSourceSet.output)
}
sourceSets {
"main" {}
"test" { projectDefault() }
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions.freeCompilerArgs += "-Xallow-kotlin-package"
}
projectTest(parallel = true) {
dependsOn(":dist")
workingDir = rootDir
}
// This doesn;t work now due to conflicts between embeddable compiler contents and intellij sdk modules
// To make it work, the dependencies to the intellij sdk should be eliminated
projectTest(taskName = "embeddableTest", parallel = true) {
workingDir = rootDir
dependsOn(embeddableTestRuntime)
classpath = embeddableTestRuntime
}
@@ -0,0 +1,426 @@
/*
* 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.ide_services
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.scripting.ide_services.test_util.JvmTestRepl
import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.script.experimental.util.get
import kotlin.script.experimental.jvm.util.isError
import kotlin.script.experimental.jvm.util.isIncomplete
// Adapted form GenericReplTest
// Artificial split into several testsuites, to speed up parallel testing
class JvmIdeServicesTest : TestCase() {
fun testReplBasics() {
JvmTestRepl()
.use { repl ->
val res1 = repl.compile(
SourceCodeTestImpl(
0,
"val x ="
)
)
assertTrue("Unexpected check results: $res1", res1.isIncomplete())
assertEvalResult(
repl,
"val l1 = listOf(1 + 2)\nl1.first()",
3
)
assertEvalUnit(
repl,
"val x = 5"
)
assertEvalResult(
repl,
"x + 2",
7
)
}
}
fun testReplErrors() {
JvmTestRepl()
.use { repl ->
repl.compileAndEval(repl.nextCodeLine("val x = 10"))
val res = repl.compileAndEval(repl.nextCodeLine("java.util.fish"))
assertTrue("Expected compile error", res.first.isError())
val result = repl.compileAndEval(repl.nextCodeLine("x"))
assertEquals(res.second.toString(), 10, (result.second?.result as ResultValue.Value?)?.value)
}
}
fun testReplErrorsWithLocations() {
JvmTestRepl()
.use { repl ->
val (compileResult, evalResult) = repl.compileAndEval(
repl.nextCodeLine(
"""
val foobar = 78
val foobaz = "dsdsda"
val ddd = ppp
val ooo = foobar
""".trimIndent()
)
)
if (compileResult.isError() && evalResult == null) {
val errors = compileResult.getErrors()
val loc = errors.location
if (loc == null) {
fail("Location shouldn't be null")
} else {
assertEquals(3, loc.line)
assertEquals(11, loc.column)
assertEquals(3, loc.lineEnd)
assertEquals(14, loc.columnEnd)
}
} else {
fail("Result should be an error")
}
}
}
fun testReplErrorsAndWarningsWithLocations() {
JvmTestRepl()
.use { repl ->
val (compileResult, evalResult) = repl.compileAndEval(
repl.nextCodeLine(
"""
fun f() {
val x = 3
val y = ooo
return y
}
""".trimIndent()
)
)
if (compileResult.isError() && evalResult == null) {
val errors = compileResult.getErrors()
val loc = errors.location
if (loc == null) {
fail("Location shouldn't be null")
} else {
assertEquals(3, loc.line)
assertEquals(13, loc.column)
assertEquals(3, loc.lineEnd)
assertEquals(16, loc.columnEnd)
}
} else {
fail("Result should be an error")
}
}
}
fun testReplSyntaxErrorsChecked() {
JvmTestRepl()
.use { repl ->
val res = repl.compileAndEval(repl.nextCodeLine("data class Q(val x: Int, val: String)"))
assertTrue("Expected compile error", res.first.isError())
}
}
private fun checkContains(actual: Sequence<SourceCodeCompletionVariant>, expected: Set<String>) {
val variants = actual.map { it.displayText }.toHashSet()
for (displayText in expected) {
if (!variants.contains(displayText)) {
fail("Element $displayText should be in $variants")
}
}
}
private fun checkDoesntContain(actual: Sequence<SourceCodeCompletionVariant>, expected: Set<String>) {
val variants = actual.map { it.displayText }.toHashSet()
for (displayText in expected) {
if (variants.contains(displayText)) {
fail("Element $displayText should NOT be in $variants")
}
}
}
fun testCompletion() = JvmTestRepl().use { repl ->
repl.compileAndEval(
repl.nextCodeLine(
"""
class AClass(val prop_x: Int) {
fun filter(xxx: (AClass).(AClass) -> Boolean): AClass {
return if(this.xxx(this))
this
else
this
}
}
val AClass.prop_y: Int
get() = prop_x * prop_x
val df = AClass(10)
val pro = "some string"
""".trimIndent()
)
)
val codeLine1 = repl.nextCodeLine(
"""
df.filter{pr}
""".trimIndent()
)
val completionList1 = repl.complete(codeLine1, 12)
checkContains(completionList1.valueOrThrow(), setOf("prop_x", "prop_y", "pro", "println(Double)"))
}
fun testPackageCompletion() = JvmTestRepl().use { repl ->
val codeLine1 = repl.nextCodeLine(
"""
import java.
val xval = 3
""".trimIndent()
)
val completionList1 = repl.complete(codeLine1, 12)
checkContains(completionList1.valueOrThrow(), setOf("lang", "math"))
checkDoesntContain(completionList1.valueOrThrow(), setOf("xval"))
}
fun testFileCompletion() = JvmTestRepl().use { repl ->
val codeLine1 = repl.nextCodeLine(
"""
val fname = "
""".trimIndent()
)
val completionList1 = repl.complete(codeLine1, 13)
val files = File(".").listFiles()?.map { it.name }
assertFalse("There should be files in current dir", files.isNullOrEmpty())
checkContains(completionList1.valueOrThrow(), files!!.toSet())
}
fun testReplCodeFormat() {
JvmTestRepl()
.use { repl ->
val codeLine0 =
SourceCodeTestImpl(0, "val l1 = 1\r\nl1\r\n")
val res = repl.compile(codeLine0)
assertTrue("Unexpected compile result: $res", res is ResultWithDiagnostics.Success<*>)
}
}
fun testRepPackage() {
JvmTestRepl()
.use { repl ->
assertEvalResult(
repl,
"package mypackage\n\nval x = 1\nx+2",
3
)
assertEvalResult(
repl,
"x+4",
5
)
}
}
fun testReplResultFieldWithFunction() {
JvmTestRepl()
.use { repl ->
assertEvalResultIs<Function0<Int>>(
repl,
"{ 1 + 2 }"
)
assertEvalResultIs<Function0<Int>>(
repl,
"res0"
)
assertEvalResult(
repl,
"res0()",
3
)
}
}
fun testReplResultField() {
JvmTestRepl()
.use { repl ->
assertEvalResult(
repl,
"5 * 4",
20
)
assertEvalResult(
repl,
"res0 + 3",
23
)
}
}
}
// Artificial split into several testsuites, to speed up parallel testing
class LegacyReplTestLong1 : TestCase() {
fun test256Evals() {
JvmTestRepl()
.use { repl ->
repl.compileAndEval(
SourceCodeTestImpl(
0,
"val x0 = 0"
)
)
val evals = 256
for (i in 1..evals) {
repl.compileAndEval(
SourceCodeTestImpl(
i,
"val x$i = x${i - 1} + 1"
)
)
}
val (_, evaluated) = repl.compileAndEval(
SourceCodeTestImpl(
evals + 1,
"x$evals"
)
)
assertEquals(evaluated.toString(), evals, (evaluated?.result as ResultValue.Value?)?.value)
}
}
}
// Artificial split into several testsuites, to speed up parallel testing
class LegacyReplTestLong2 : TestCase() {
fun testReplSlowdownKt22740() {
JvmTestRepl()
.use { repl ->
repl.compileAndEval(
SourceCodeTestImpl(
0,
"class Test<T>(val x: T) { fun <R> map(f: (T) -> R): R = f(x) }".trimIndent()
)
)
// We expect that analysis time is not exponential
for (i in 1..60) {
repl.compileAndEval(
SourceCodeTestImpl(
i,
"fun <T> Test<T>.map(f: (T) -> Double): List<Double> = listOf(f(this.x))"
)
)
}
}
}
}
private fun JvmTestRepl.compileAndEval(codeLine: SourceCode): Pair<ResultWithDiagnostics<LinkedSnippet<out CompiledSnippet>>, EvaluatedSnippet?> {
val compRes = compile(codeLine)
val evalRes = compRes.valueOrNull()?.let {
eval(it)
}
return compRes to evalRes?.valueOrNull().get()
}
private fun assertEvalUnit(
repl: JvmTestRepl,
@Suppress("SameParameterValue")
line: String
) {
val compiledSnippet =
checkCompile(repl, line)
val evalResult = repl.eval(compiledSnippet!!)
val valueResult = evalResult.valueOrNull().get()
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertTrue(valueResult!!.result is ResultValue.Unit)
}
private fun <R> assertEvalResult(repl: JvmTestRepl, line: String, expectedResult: R) {
val compiledSnippet =
checkCompile(repl, line)
val evalResult = repl.eval(compiledSnippet!!)
val valueResult = evalResult.valueOrNull().get()
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertTrue(valueResult!!.result is ResultValue.Value)
TestCase.assertEquals(expectedResult, (valueResult.result as ResultValue.Value).value)
}
private inline fun <reified R> assertEvalResultIs(repl: JvmTestRepl, line: String) {
val compiledSnippet =
checkCompile(repl, line)
val evalResult = repl.eval(compiledSnippet!!)
val valueResult = evalResult.valueOrNull().get()
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertTrue(valueResult!!.result is ResultValue.Value)
TestCase.assertTrue((valueResult.result as ResultValue.Value).value is R)
}
private fun checkCompile(repl: JvmTestRepl, line: String): LinkedSnippet<KJvmCompiledScript>? {
val codeLine = repl.nextCodeLine(line)
val compileResult = repl.compile(codeLine)
return compileResult.valueOrNull()
}
private data class CompilationErrors(
val message: String,
val location: CompilerMessageLocation?
)
private fun <T> ResultWithDiagnostics<T>.getErrors(): CompilationErrors =
CompilationErrors(
reports.joinToString("\n") { report ->
report.location?.let { loc ->
CompilerMessageLocation.create(
report.sourcePath,
loc.start.line,
loc.start.col,
loc.end?.line,
loc.end?.col,
null
)?.toString()?.let {
"$it "
}
}.orEmpty() + report.message
},
reports.firstOrNull {
when (it.severity) {
ScriptDiagnostic.Severity.ERROR -> true
ScriptDiagnostic.Severity.FATAL -> true
else -> false
}
}?.let {
val loc = it.location ?: return@let null
CompilerMessageLocation.create(
it.sourcePath,
loc.start.line,
loc.start.col,
loc.end?.line,
loc.end?.col,
null
)
}
)
@@ -0,0 +1,379 @@
/*
* 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.ide_services
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl
import org.jetbrains.kotlin.scripting.ide_services.test_util.simpleScriptCompilationConfiguration
import org.jetbrains.kotlin.scripting.ide_services.test_util.toList
import org.junit.Assert
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.script.experimental.api.*
class ReplCompletionAndErrorsAnalysisTest : TestCase() {
@Test
fun testTrivial() = test {
run {
doCompile
code = """
data class AClass(val memx: Int, val memy: String)
data class BClass(val memz: String, val mema: AClass)
val foobar = 42
var foobaz = "string"
val v = BClass("KKK", AClass(5, "25"))
""".trimIndent()
}
run {
code = "foo"
cursor = 3
expect {
addCompletion("foobar", "foobar", "Int", "property")
addCompletion("foobaz", "foobaz", "String", "property")
}
}
run {
code = "v.mema."
cursor = 7
expect {
completionsMode(ComparisonType.INCLUDES)
addCompletion("memx", "memx", "Int", "property")
addCompletion("memy", "memy", "String", "property")
}
}
run {
code = "listO"
cursor = 5
expect {
addCompletion("listOf(", "listOf(T)", "List<T>", "method")
addCompletion("listOf()", "listOf()", "List<T>", "method")
addCompletion("listOf(", "listOf(vararg T)", "List<T>", "method")
addCompletion("listOfNotNull(", "listOfNotNull(T?)", "List<T>", "method")
addCompletion("listOfNotNull(", "listOfNotNull(vararg T?)", "List<T>", "method")
}
}
}
@Test
fun testPackagesImport() = test {
run {
cursor = 17
code = "import java.lang."
expect {
completionsMode(ComparisonType.INCLUDES)
addCompletion("Process", "Process", " (java.lang)", "class")
}
}
}
@Test
fun testExtensionMethods() = test {
run {
doCompile
code = """
class AClass(val c_prop_x: Int) {
fun filter(xxx: (AClass).() -> Boolean): AClass {
return this
}
}
val AClass.c_prop_y: Int
get() = c_prop_x * c_prop_x
fun AClass.c_meth_z(v: Int) = v * c_prop_y
val df = AClass(10)
val c_zzz = "some string"
""".trimIndent()
}
run {
code = "df.filter{ c_ }"
cursor = 13
expect {
addCompletion("c_prop_x", "c_prop_x", "Int", "property")
addCompletion("c_zzz", "c_zzz", "String", "property")
addCompletion("c_prop_y", "c_prop_y", "Int", "property")
addCompletion("c_meth_z(", "c_meth_z(Int)", "Int", "method")
}
}
run {
code = "df.fil"
cursor = 6
expect {
addCompletion("filter { ", "filter(Line_1_simplescript.AClass.() -> ...", "Line_1_simplescript.AClass", "method")
}
}
}
@Test
fun testBacktickedFields() = test {
run {
doCompile
code = """
class AClass(val `c_prop x y z`: Int)
val df = AClass(33)
""".trimIndent()
}
run {
code = "df.c_"
cursor = 5
expect {
addCompletion("`c_prop x y z`", "`c_prop x y z`", "Int", "property")
}
}
}
@Test
fun testListErrors() = test {
run {
doCompile
code = """
data class AClass(val memx: Int, val memy: String)
data class BClass(val memz: String, val mema: AClass)
val foobar = 42
var foobaz = "string"
val v = BClass("KKK", AClass(5, "25"))
""".trimIndent()
expect {
errorsMode(ComparisonType.EQUALS)
}
}
run {
code = """
val a = AClass("42", 3.14)
val b: Int = "str"
val c = foob
""".trimIndent()
expect {
addError(1, 16, 1, 20, "Type mismatch: inferred type is String but Int was expected", "ERROR")
addError(1, 22, 1, 26, "The floating-point literal does not conform to the expected type String", "ERROR")
addError(2, 14, 2, 19, "Type mismatch: inferred type is String but Int was expected", "ERROR")
addError(3, 9, 3, 13, "Unresolved reference: foob", "ERROR")
}
}
}
@Test
fun testCompletionDuplication() = test {
for (i in 1..6) {
run {
if (i == 5) doCompile
if (i % 2 == 1) doErrorCheck
val value = "a".repeat(i)
code = "val ddddd = \"$value\""
cursor = 13 + i
}
}
run {
code = "dd"
cursor = 2
expect {
addCompletion("ddddd", "ddddd", "String", "property")
}
}
}
private class TestConf {
private val runs = mutableListOf<Run>()
fun run(setup: (Run).() -> Unit) {
val r = Run()
r.setup()
runs.add(r)
}
fun collect() = runs.map { it.collect() }
class Run {
private var _doCompile = false
val doCompile: Unit
get() {
_doCompile = true
}
private var _doComplete = false
val doComplete: Unit
get() {
_doComplete = true
}
private var _doErrorCheck = false
val doErrorCheck: Unit
get() {
_doErrorCheck = true
}
var cursor: Int? = null
var code: String = ""
private var _expected: Expected = Expected(this)
fun expect(setup: (Expected).() -> Unit) {
_expected = Expected(this)
_expected.setup()
}
fun collect(): Pair<RunRequest, ExpectedResult> {
return RunRequest(cursor, code, _doCompile, _doComplete, _doErrorCheck) to _expected.collect()
}
class Expected(private val run: Run) {
private val variants = mutableListOf<SourceCodeCompletionVariant>()
private var _completionsMode: ComparisonType = ComparisonType.DONT_CHECK
fun completionsMode(mode: ComparisonType) {
_completionsMode = mode
}
fun addCompletion(text: String, displayText: String, tail: String, icon: String) {
if (_completionsMode == ComparisonType.DONT_CHECK)
_completionsMode = ComparisonType.EQUALS
run.doComplete
variants.add(SourceCodeCompletionVariant(text, displayText, tail, icon))
}
private val errors = mutableListOf<ScriptDiagnostic>()
private var _errorsMode: ComparisonType = ComparisonType.DONT_CHECK
fun errorsMode(mode: ComparisonType) {
_errorsMode = mode
}
fun addError(startLine: Int, startCol: Int, endLine: Int, endCol: Int, message: String, severity: String) {
if (_errorsMode == ComparisonType.DONT_CHECK)
_errorsMode = ComparisonType.EQUALS
run.doErrorCheck
errors.add(
ScriptDiagnostic(
ScriptDiagnostic.unspecifiedError,
message,
ScriptDiagnostic.Severity.valueOf(severity),
location = SourceCode.Location(
SourceCode.Position(startLine, startCol),
SourceCode.Position(endLine, endCol)
)
)
)
}
fun collect(): ExpectedResult {
return ExpectedResult(variants, _completionsMode, errors, _errorsMode)
}
}
}
}
private fun test(setup: (TestConf).() -> Unit) {
val test = TestConf()
test.setup()
runBlocking { checkEvaluateInRepl(simpleScriptCompilationConfiguration, test.collect()) }
}
enum class ComparisonType {
INCLUDES, EQUALS, DONT_CHECK
}
data class RunRequest(
val cursor: Int?,
val code: String,
val doCompile: Boolean,
val doComplete: Boolean,
val doErrorCheck: Boolean
)
data class ExpectedResult(
val completions: List<SourceCodeCompletionVariant>, val completionsCompType: ComparisonType,
val errors: List<ScriptDiagnostic>, val errorsCompType: ComparisonType
)
private val currentLineCounter = AtomicInteger()
private fun nextCodeLine(code: String): SourceCode =
SourceCodeTestImpl(
currentLineCounter.getAndIncrement(),
code
)
private suspend fun evaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
snippets: List<RunRequest>
): List<ResultWithDiagnostics<Pair<List<SourceCodeCompletionVariant>, List<ScriptDiagnostic>>>> {
val compiler = KJvmReplCompilerWithIdeServices()
return snippets.map { (cursor, snippetText, doComplile, doComplete, doErrorCheck) ->
val pos = SourceCode.Position(0, 0, cursor)
val codeLine = nextCodeLine(snippetText)
val completionRes = if (doComplete && cursor != null) {
val res = compiler.complete(codeLine, pos, compilationConfiguration)
res.toList().filter { it.tail != "keyword" }
} else {
emptyList()
}
val errorsRes = if (doErrorCheck) {
val codeLineForErrorCheck = nextCodeLine(snippetText)
compiler.analyze(codeLineForErrorCheck, SourceCode.Position(0, 0), compilationConfiguration).toList()
} else {
emptyList()
}
if (doComplile) {
val codeLineForCompilation = nextCodeLine(snippetText)
compiler.compile(codeLineForCompilation, compilationConfiguration)
}
Pair(completionRes, errorsRes).asSuccess()
}
}
private fun <T> checkLists(index: Int, checkName: String, expected: List<T>, actual: List<T>, compType: ComparisonType) {
when (compType) {
ComparisonType.EQUALS -> Assert.assertEquals(
"#$index ($checkName): Expected $expected, got $actual",
expected,
actual
)
ComparisonType.INCLUDES -> Assert.assertTrue(
"#$index ($checkName): Expected $actual to include $expected",
actual.containsAll(expected)
)
ComparisonType.DONT_CHECK -> {
}
}
}
private suspend fun checkEvaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
testData: List<Pair<RunRequest, ExpectedResult>>
) {
val (snippets, expected) = testData.unzip()
val expectedIter = expected.iterator()
evaluateInRepl(compilationConfiguration, snippets).forEachIndexed { index, res ->
when (res) {
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
is ResultWithDiagnostics.Success -> {
val (expectedCompletions, completionsCompType, expectedErrors, errorsCompType) = expectedIter.next()
val (completionsRes, errorsRes) = res.value
checkLists(index, "completions", expectedCompletions, completionsRes, completionsCompType)
val expectedErrorsWithPath = expectedErrors.map {
it.copy(sourcePath = errorsRes.firstOrNull()?.sourcePath)
}
checkLists(index, "errors", expectedErrorsWithPath, errorsRes, errorsCompType)
}
}
}
}
}
@@ -0,0 +1,76 @@
/*
* 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.ide_services.test_util
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.jvm.util.classpathFromClass
@KotlinScript(fileExtension = "simplescript.kts")
abstract class SimpleScript
val simpleScriptCompilationConfiguration =
createJvmCompilationConfigurationFromTemplate<SimpleScript> {
updateClasspath(classpathFromClass<SimpleScript>())
}
val simpleScriptEvaluationConfiguration = ScriptEvaluationConfiguration()
@KotlinScript(fileExtension = "withboth.kts", compilationConfiguration = ReceiverAndPropertiesConfiguration::class)
abstract class ScriptWithBoth
@KotlinScript(fileExtension = "withproperties.kts", compilationConfiguration = ProvidedPropertiesConfiguration::class)
abstract class ScriptWithProvidedProperties
@KotlinScript(fileExtension = "withreceiver.kts", compilationConfiguration = ImplicitReceiverConfiguration::class)
abstract class ScriptWithImplicitReceiver
object ReceiverAndPropertiesConfiguration : ScriptCompilationConfiguration(
{
updateClasspath(classpathFromClass<ScriptWithBoth>())
providedProperties("providedString" to String::class)
implicitReceivers(ImplicitReceiverClass::class)
}
)
object ProvidedPropertiesConfiguration : ScriptCompilationConfiguration(
{
updateClasspath(classpathFromClass<ScriptWithProvidedProperties>())
providedProperties("providedString" to String::class)
}
)
object ImplicitReceiverConfiguration : ScriptCompilationConfiguration(
{
updateClasspath(classpathFromClass<ScriptWithImplicitReceiver>())
implicitReceivers(ImplicitReceiverClass::class)
}
)
class ImplicitReceiverClass(
@Suppress("unused")
val receiverString: String
)
inline fun <reified T : Any> createJvmCompilationConfigurationFromTemplate(
hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ScriptCompilationConfiguration =
createCompilationConfigurationFromTemplate(
KotlinType(T::class),
hostConfiguration,
ScriptCompilationConfiguration::class,
body
)
@@ -0,0 +1,60 @@
/*
* 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.ide_services.test_util
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
import java.io.Closeable
import java.util.concurrent.atomic.AtomicInteger
import kotlin.script.experimental.api.CompiledSnippet
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.script.experimental.jvm.util.toSourceCodePosition
internal class JvmTestRepl : Closeable {
private val currentLineCounter = AtomicInteger(0)
private val compileConfiguration = simpleScriptCompilationConfiguration
private val evalConfiguration = simpleScriptEvaluationConfiguration
fun nextCodeLine(code: String): SourceCode =
SourceCodeTestImpl(
currentLineCounter.getAndIncrement(),
code
)
private val replCompiler: KJvmReplCompilerWithIdeServices by lazy {
KJvmReplCompilerWithIdeServices()
}
private val compiledEvaluator: BasicJvmReplEvaluator by lazy {
BasicJvmReplEvaluator()
}
fun compile(code: SourceCode) = runBlocking { replCompiler.compile(code, compileConfiguration) }
fun complete(code: SourceCode, cursor: Int) = runBlocking { replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConfiguration) }
fun eval(snippet: LinkedSnippet<out CompiledSnippet>) = runBlocking { compiledEvaluator.eval(snippet, evalConfiguration) }
override fun close() {
}
}
internal class SourceCodeTestImpl(number: Int, override val text: String) : SourceCode {
override val name: String? = "Line_$number"
override val locationId: String? = "location_$number"
}
@JvmName("iterableToList")
fun <T> ResultWithDiagnostics<Iterable<T>>.toList() = this.valueOrNull()?.toList().orEmpty()
@JvmName("sequenceToList")
fun <T> ResultWithDiagnostics<Sequence<T>>.toList() = this.valueOrNull()?.toList().orEmpty()
@@ -0,0 +1,41 @@
description = "Kotlin Scripting Compiler extension providing code completion and static analysis"
plugins {
kotlin("jvm")
id("jps-compatible")
}
jvmTarget = "1.8"
publish()
dependencies {
compile(project(":kotlin-script-runtime"))
compile(kotlinStdlib())
compileOnly(project(":idea:ide-common"))
compile(project(":kotlin-scripting-common"))
compile(project(":kotlin-scripting-jvm"))
compileOnly(project(":kotlin-scripting-compiler"))
compileOnly(project(":compiler:cli"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
publishedRuntime(project(":kotlin-compiler"))
publishedRuntime(project(":kotlin-scripting-compiler"))
publishedRuntime(project(":kotlin-reflect"))
publishedRuntime(commonDep("org.jetbrains.intellij.deps", "trove4j"))
}
sourceSets {
"main" { projectDefault() }
"test" { }
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions {
freeCompilerArgs += "-Xskip-metadata-version-check"
freeCompilerArgs += "-Xallow-kotlin-package"
}
}
standardPublicJars()
@@ -0,0 +1,119 @@
/*
* 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.ide_services.compiler
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.failure
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.withMessageCollector
import org.jetbrains.kotlin.scripting.ide_services.compiler.impl.IdeLikeReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.ide_services.compiler.impl.getKJvmCompletion
import org.jetbrains.kotlin.scripting.ide_services.compiler.impl.prepareCodeForCompletion
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.calcAbsolute
class KJvmReplCompilerWithIdeServices(hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration) :
KJvmReplCompilerBase<IdeLikeReplCodeAnalyzer>(hostConfiguration, {
IdeLikeReplCodeAnalyzer(it.environment)
}),
ReplCompleter, ReplCodeAnalyzer {
override suspend fun complete(
snippet: SourceCode,
cursor: SourceCode.Position,
configuration: ScriptCompilationConfiguration
): ResultWithDiagnostics<ReplCompletionResult> =
withMessageCollector(snippet) { messageCollector ->
val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr {
return it
}
val cursorAbs = cursor.calcAbsolute(snippet)
val newText =
prepareCodeForCompletion(snippet.text, cursorAbs)
val newSnippet = object : SourceCode {
override val text: String
get() = newText
override val name: String?
get() = snippet.name
override val locationId: String?
get() = snippet.locationId
}
val compilationState = state.getCompilationState(initialConfiguration)
val (_, errorHolder, snippetKtFile) = prepareForAnalyze(
newSnippet,
messageCollector,
compilationState,
checkSyntaxErrors = false
).valueOr { return@withMessageCollector it }
val analysisResult =
compilationState.analyzerEngine.statelessAnalyzeWithImportedScripts(snippetKtFile, emptyList(), scriptPriority.get() + 1)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val (_, bindingContext, resolutionFacade, moduleDescriptor) = when (analysisResult) {
is IdeLikeReplCodeAnalyzer.ReplLineAnalysisResultWithStateless.Stateless -> {
analysisResult
}
else -> return failure(
newSnippet,
messageCollector,
"Unexpected result ${analysisResult::class.java}"
)
}
return getKJvmCompletion(
snippetKtFile,
bindingContext,
resolutionFacade,
moduleDescriptor,
cursorAbs
).asSuccess(messageCollector.diagnostics)
}
private fun List<ScriptDiagnostic>.toAnalyzeResult() = (filter {
when (it.severity) {
ScriptDiagnostic.Severity.FATAL,
ScriptDiagnostic.Severity.ERROR,
ScriptDiagnostic.Severity.WARNING
-> true
else -> false
}
}).asSequence()
override suspend fun analyze(
snippet: SourceCode,
cursor: SourceCode.Position,
configuration: ScriptCompilationConfiguration
): ResultWithDiagnostics<ReplAnalyzerResult> {
return withMessageCollector(snippet) { messageCollector ->
val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr {
return it
}
val compilationState = state.getCompilationState(initialConfiguration)
val (_, errorHolder, snippetKtFile) = prepareForAnalyze(
snippet,
messageCollector,
compilationState,
checkSyntaxErrors = true
).valueOr { return@withMessageCollector messageCollector.diagnostics.toAnalyzeResult().asSuccess() }
val analysisResult =
compilationState.analyzerEngine.statelessAnalyzeWithImportedScripts(snippetKtFile, emptyList(), scriptPriority.get() + 1)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
messageCollector.diagnostics.toAnalyzeResult().asSuccess()
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.ide_services.compiler.impl
import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.container.getService
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
class IdeLikeReplCodeAnalyzer(private val environment: KotlinCoreEnvironment) : ReplCodeAnalyzerBase(environment, CliBindingTrace()) {
interface ReplLineAnalysisResultWithStateless : ReplLineAnalysisResult {
// Result of stateless analyse, which may be used for reporting errors
// without code generation
data class Stateless(
override val diagnostics: Diagnostics,
val bindingContext: BindingContext,
val resolutionFacade: KotlinResolutionFacadeForRepl,
val moduleDescriptor: ModuleDescriptor
) :
ReplLineAnalysisResultWithStateless {
override val scriptDescriptor: ClassDescriptorWithResolutionScopes? get() = null
}
}
fun statelessAnalyzeWithImportedScripts(
psiFile: KtFile,
importedScripts: List<KtFile>,
priority: Int
): ReplLineAnalysisResultWithStateless {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, priority)
return doStatelessAnalyze(psiFile, importedScripts)
}
private fun doStatelessAnalyze(linePsi: KtFile, importedScripts: List<KtFile>): ReplLineAnalysisResultWithStateless {
scriptDeclarationFactory.setDelegateFactory(
FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi) + importedScripts)
)
replState.submitLine(linePsi)
topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
val moduleDescriptor = container.getService(ModuleDescriptor::class.java)
val resolutionFacade =
KotlinResolutionFacadeForRepl(environment, container)
val diagnostics = trace.bindingContext.diagnostics
return ReplLineAnalysisResultWithStateless.Stateless(
diagnostics,
trace.bindingContext,
resolutionFacade,
moduleDescriptor
)
}
}
@@ -0,0 +1,406 @@
/*
* 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.ide_services.compiler.impl
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.backend.common.onlyIf
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope.Companion.ALL_NAME_FILTER
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.asFlexibleType
import org.jetbrains.kotlin.types.isFlexible
import java.io.File
import java.util.*
import kotlin.script.experimental.api.SourceCodeCompletionVariant
fun getKJvmCompletion(
ktScript: KtFile,
bindingContext: BindingContext,
resolutionFacade: KotlinResolutionFacadeForRepl,
moduleDescriptor: ModuleDescriptor,
cursor: Int
) = KJvmReplCompleter(ktScript, bindingContext, resolutionFacade, moduleDescriptor, cursor).getCompletion()
// Insert a constant string right after a cursor position to make this identifiable as a simple reference
// For example, code line
// import java.
// ^
// is converted to
// import java.ABCDEF
// and it makes token after dot (for which reference variants are looked) discoverable in PSI
fun prepareCodeForCompletion(code: String, cursor: Int) =
code.substring(0, cursor) + KJvmReplCompleter.INSERTED_STRING + code.substring(cursor)
private class KJvmReplCompleter(
private val ktScript: KtFile,
private val bindingContext: BindingContext,
private val resolutionFacade: KotlinResolutionFacadeForRepl,
private val moduleDescriptor: ModuleDescriptor,
private val cursor: Int
) {
private fun getElementAt(cursorPos: Int): PsiElement? {
var element: PsiElement? = ktScript.findElementAt(cursorPos)
while (element !is KtExpression && element != null) {
element = element.parent
}
return element
}
fun getCompletion() = sequence<SourceCodeCompletionVariant> gen@{
val element = getElementAt(cursor)
var descriptors: Collection<DeclarationDescriptor>? = null
var isTipsManagerCompletion = true
var isSortNeeded = true
if (element == null)
return@gen
val simpleExpression = when {
element is KtSimpleNameExpression -> element
element.parent is KtSimpleNameExpression -> element.parent as KtSimpleNameExpression
else -> null
}
if (simpleExpression != null) {
val inDescriptor: DeclarationDescriptor = simpleExpression.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor
val prefix = element.text.substring(0, cursor - element.startOffset)
isSortNeeded = false
descriptors = ReferenceVariantsHelper(
bindingContext,
resolutionFacade,
moduleDescriptor,
VisibilityFilter(inDescriptor)
).getReferenceVariants(
simpleExpression,
DescriptorKindFilter.ALL,
{ name: Name -> !name.isSpecial && name.identifier.startsWith(prefix) },
filterOutJavaGettersAndSetters = true,
filterOutShadowed = false, // setting to true makes it slower up to 4 times
excludeNonInitializedVariable = true,
useReceiverType = null
)
} else if (element is KtStringTemplateExpression) {
if (element.hasInterpolation()) {
return@gen
}
val stringVal = element.entries.joinToString("") {
val t = it.text
if (it.startOffset <= cursor && cursor <= it.endOffset) {
val s = cursor - it.startOffset
val e = s + INSERTED_STRING.length
t.substring(0, s) + t.substring(e)
} else t
}
val separatorIndex = stringVal.lastIndexOfAny(charArrayOf('/', '\\'))
val dir = if (separatorIndex != -1) {
stringVal.substring(0, separatorIndex + 1)
} else {
"."
}
val namePrefix = stringVal.substring(separatorIndex + 1)
val file = File(dir)
file.listFiles { p, f -> p == file && f.startsWith(namePrefix, true) }?.forEach {
yield(SourceCodeCompletionVariant(it.name, it.name, "file", "file"))
}
return@gen
} else {
isTipsManagerCompletion = false
val resolutionScope: LexicalScope?
val parent = element.parent
val qualifiedExpression = when {
element is KtQualifiedExpression -> {
isTipsManagerCompletion = true
element
}
parent is KtQualifiedExpression -> parent
else -> null
}
if (qualifiedExpression != null) {
val receiverExpression = qualifiedExpression.receiverExpression
val expressionType = bindingContext.get(
BindingContext.EXPRESSION_TYPE_INFO,
receiverExpression
)?.type
if (expressionType != null) {
isSortNeeded = false
descriptors = ReferenceVariantsHelper(
bindingContext,
resolutionFacade,
moduleDescriptor,
{ true }
).getReferenceVariants(
receiverExpression,
CallTypeAndReceiver.DOT(receiverExpression),
DescriptorKindFilter.ALL,
ALL_NAME_FILTER
)
}
} else {
resolutionScope = bindingContext.get(
BindingContext.LEXICAL_SCOPE,
element as KtExpression?
)
descriptors = (resolutionScope?.getContributedDescriptors(
DescriptorKindFilter.ALL,
ALL_NAME_FILTER
)
?: return@gen)
}
}
if (descriptors != null) {
val targetElement = if (isTipsManagerCompletion) element else element.parent
val prefixEnd = cursor - targetElement.startOffset
var prefix = targetElement.text.substring(0, prefixEnd)
val cursorWithinElement = cursor - element.startOffset
val dotIndex = prefix.lastIndexOf('.', cursorWithinElement)
prefix = if (dotIndex >= 0) {
prefix.substring(dotIndex + 1, cursorWithinElement)
} else {
prefix.substring(0, cursorWithinElement)
}
if (descriptors !is ArrayList<*>) {
descriptors = ArrayList(descriptors)
}
(descriptors as ArrayList<DeclarationDescriptor>)
.map {
val presentation =
getPresentation(
it
)
Triple(it, presentation, (presentation.presentableText + presentation.tailText).toLowerCase())
}
.onlyIf({ isSortNeeded }) { it.sortedBy { descTriple -> descTriple.third } }
.forEach {
val descriptor = it.first
val (rawName, presentableText, tailText, completionText) = it.second
if (rawName.startsWith(prefix)) {
val fullName: String =
formatName(
presentableText
)
yield(
SourceCodeCompletionVariant(
completionText,
fullName,
tailText,
getIconFromDescriptor(
descriptor
)
)
)
}
}
yieldAll(
keywordsCompletionVariants(
KtTokens.KEYWORDS,
prefix
)
)
yieldAll(
keywordsCompletionVariants(
KtTokens.SOFT_KEYWORDS,
prefix
)
)
}
}
private inner class VisibilityFilter(
private val inDescriptor: DeclarationDescriptor
) : (DeclarationDescriptor) -> Boolean {
override fun invoke(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is TypeParameterDescriptor && !isTypeParameterVisible(descriptor)) return false
if (descriptor is DeclarationDescriptorWithVisibility) {
return try {
descriptor.visibility.isVisible(null, descriptor, inDescriptor)
} catch (e: IllegalStateException) {
true
}
}
return true
}
private fun isTypeParameterVisible(typeParameter: TypeParameterDescriptor): Boolean {
val owner = typeParameter.containingDeclaration
var parent: DeclarationDescriptor? = inDescriptor
while (parent != null) {
if (parent == owner) return true
if (parent is ClassDescriptor && !parent.isInner) return false
parent = parent.containingDeclaration
}
return true
}
}
companion object {
const val INSERTED_STRING = "ABCDEF"
private const val NUMBER_OF_CHAR_IN_COMPLETION_NAME = 40
private fun keywordsCompletionVariants(
keywords: TokenSet,
prefix: String
) = sequence {
keywords.types.forEach {
val token = (it as KtKeywordToken).value
if (token.startsWith(prefix)) yield(
SourceCodeCompletionVariant(
token,
token,
"keyword",
"keyword"
)
)
}
}
private val RENDERER =
IdeDescriptorRenderers.SOURCE_CODE.withOptions {
this.classifierNamePolicy =
ClassifierNamePolicy.SHORT
this.typeNormalizer =
IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES
this.parameterNameRenderingPolicy =
ParameterNameRenderingPolicy.NONE
this.renderDefaultAnnotationArguments = false
this.typeNormalizer = lambda@{ kotlinType: KotlinType ->
if (kotlinType.isFlexible()) {
return@lambda kotlinType.asFlexibleType().upperBound
}
kotlinType
}
}
private fun getIconFromDescriptor(descriptor: DeclarationDescriptor): String = when (descriptor) {
is FunctionDescriptor -> "method"
is PropertyDescriptor -> "property"
is LocalVariableDescriptor -> "property"
is ClassDescriptor -> "class"
is PackageFragmentDescriptor -> "package"
is PackageViewDescriptor -> "package"
is ValueParameterDescriptor -> "genericValue"
is TypeParameterDescriptorImpl -> "class"
else -> ""
}
private fun formatName(builder: String, symbols: Int = NUMBER_OF_CHAR_IN_COMPLETION_NAME): String {
return if (builder.length > symbols) {
builder.substring(0, symbols) + "..."
} else builder
}
data class DescriptorPresentation(
val rawName: String,
val presentableText: String,
val tailText: String,
val completionText: String
)
fun getPresentation(descriptor: DeclarationDescriptor): DescriptorPresentation {
val rawDescriptorName = descriptor.name.asString()
val descriptorName = rawDescriptorName.quoteIfNeeded()
var presentableText = descriptorName
var typeText = ""
var tailText = ""
var completionText = ""
if (descriptor is FunctionDescriptor) {
val returnType = descriptor.returnType
typeText =
if (returnType != null) RENDERER.renderType(returnType) else ""
presentableText += RENDERER.renderFunctionParameters(
descriptor
)
val parameters = descriptor.valueParameters
if (parameters.size == 1 && parameters.first().type.isFunctionType)
completionText = "$descriptorName { "
val extensionFunction = descriptor.extensionReceiverParameter != null
val containingDeclaration = descriptor.containingDeclaration
if (extensionFunction) {
tailText += " for " + RENDERER.renderType(
descriptor.extensionReceiverParameter!!.type
)
tailText += " in " + DescriptorUtils.getFqName(containingDeclaration)
}
} else if (descriptor is VariableDescriptor) {
val outType =
descriptor.type
typeText = RENDERER.renderType(outType)
} else if (descriptor is ClassDescriptor) {
val declaredIn = descriptor.containingDeclaration
tailText = " (" + DescriptorUtils.getFqName(declaredIn) + ")"
} else {
typeText = RENDERER.render(descriptor)
}
tailText = if (typeText.isEmpty()) tailText else typeText
if (completionText.isEmpty()) {
completionText = presentableText
var position = completionText.indexOf('(')
if (position != -1) { //If this is a string with a package after
if (completionText[position - 1] == ' ') {
position -= 2
}
//if this is a method without args
if (completionText[position + 1] == ')') {
position++
}
completionText = completionText.substring(0, position + 1)
}
position = completionText.indexOf(":")
if (position != -1) {
completionText = completionText.substring(0, position - 1)
}
}
return DescriptorPresentation(
rawDescriptorName,
presentableText,
tailText,
completionText
)
}
}
}
@@ -0,0 +1,73 @@
/*
* 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.ide_services.compiler.impl
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinResolutionFacadeForRepl(
private val environment: KotlinCoreEnvironment,
private val provider: ComponentProvider
) :
ResolutionFacade {
override val project: Project
get() = environment.project
override fun analyze(
element: KtElement,
bodyResolveMode: BodyResolveMode
): BindingContext {
throw UnsupportedOperationException()
}
override val moduleDescriptor: ModuleDescriptor
get() {
throw UnsupportedOperationException()
}
override fun <T : Any> getFrontendService(serviceClass: Class<T>): T {
return provider.resolve(serviceClass)!!.getValue() as T
}
override fun <T : Any> getIdeService(serviceClass: Class<T>): T {
throw UnsupportedOperationException()
}
override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? {
throw UnsupportedOperationException()
}
override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T {
throw UnsupportedOperationException()
}
override fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T {
throw UnsupportedOperationException()
}
override fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext {
throw UnsupportedOperationException()
}
override fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>): AnalysisResult {
throw UnsupportedOperationException()
}
override fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode): DeclarationDescriptor {
throw UnsupportedOperationException()
}
}