From 8a1b44cc2b9df7b3bb05adabc045a539a2d28c29 Mon Sep 17 00:00:00 2001 From: Ilya Muradyan Date: Wed, 6 May 2020 04:54:22 +0300 Subject: [PATCH] Add snippet result type to analysis output of REPL IDE services --- .../script/experimental/api/replAnalysis.kt | 34 +++- .../KJvmReplCompilerWithIdeServices.kt | 162 ++++++++++-------- .../compiler/impl/IdeLikeReplCodeAnalyzer.kt | 12 +- 3 files changed, 133 insertions(+), 75 deletions(-) diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/replAnalysis.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/replAnalysis.kt index be912ec9972..0c7535e82ad 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/replAnalysis.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/replAnalysis.kt @@ -5,16 +5,13 @@ package kotlin.script.experimental.api +import kotlin.script.experimental.util.PropertiesCollection + /** * Type for [ReplCompleter.complete] return value */ typealias ReplCompletionResult = Sequence -/** - * Type for [ReplCodeAnalyzer.analyze] return value - */ -typealias ReplAnalyzerResult = Sequence - /** * Single code completion result */ @@ -25,6 +22,33 @@ data class SourceCodeCompletionVariant( val icon: String ) +interface ReplAnalyzerResultKeys + +/** + * The container for analysis results data returned from the [ReplCodeAnalyzer.analyze] + */ +class ReplAnalyzerResult(baseConfigurations: Iterable, body: Builder.() -> Unit = {}) : + PropertiesCollection(Builder(baseConfigurations).apply(body).data) { + + constructor(body: Builder.() -> Unit = {}) : this(emptyList(), body) + + class Builder internal constructor(baseConfigurations: Iterable) : + ReplAnalyzerResultKeys, + PropertiesCollection.Builder(baseConfigurations) + + companion object : ReplAnalyzerResultKeys +} + +/** + * Script compile-time errors and warning with their locations + */ +val ReplAnalyzerResultKeys.analysisDiagnostics by PropertiesCollection.key>(emptySequence()) + +/** + * String representing snippet return value, or null, if script returns nothing + */ +val ReplAnalyzerResultKeys.renderedResultType by PropertiesCollection.key() + /** * Interface for REPL context completion */ diff --git a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt index 60e1fb4346d..669f8ebcc36 100644 --- a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt +++ b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/KJvmReplCompilerWithIdeServices.kt @@ -6,12 +6,16 @@ package org.jetbrains.kotlin.scripting.ide_services.compiler import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptDiagnosticsMessageCollector 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 org.jetbrains.kotlin.scripting.ide_services.compiler.impl.* import kotlin.script.experimental.api.* import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration @@ -29,58 +33,33 @@ class KJvmReplCompilerWithIdeServices(hostConfiguration: ScriptingHostConfigurat configuration: ScriptCompilationConfiguration ): ResultWithDiagnostics = 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 + val analyzeResult = analyzeWithCursor( + messageCollector, snippet, configuration, cursor + ) { snippet, cursorAbs -> + val newText = + prepareCodeForCompletion(snippet.text, cursorAbs) + object : SourceCode { + override val text: String + get() = newText + override val name: String? + get() = snippet.name + override val locationId: String? + get() = snippet.locationId } - else -> return failure( - newSnippet, - messageCollector, - "Unexpected result ${analysisResult::class.java}" - ) } - return getKJvmCompletion( - snippetKtFile, - bindingContext, - resolutionFacade, - moduleDescriptor, - cursorAbs - ).asSuccess(messageCollector.diagnostics) + with(analyzeResult.valueOr { return it }) { + return getKJvmCompletion( + ktScript, + bindingContext, + resolutionFacade, + moduleDescriptor, + cursorAbs + ).asSuccess(messageCollector.diagnostics) + } } - private fun List.toAnalyzeResult() = (filter { + private fun List.toAnalyzeResultSequence() = (filter { when (it.severity) { ScriptDiagnostic.Severity.FATAL, ScriptDiagnostic.Severity.ERROR, @@ -96,24 +75,73 @@ class KJvmReplCompilerWithIdeServices(hostConfiguration: ScriptingHostConfigurat configuration: ScriptCompilationConfiguration ): ResultWithDiagnostics { return withMessageCollector(snippet) { messageCollector -> - val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr { - return it + val analyzeResult = analyzeWithCursor( + messageCollector, snippet, configuration + ) + + with(analyzeResult.valueOr { return it }) { + val resultRenderedType = resultProperty?.let { + DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it.type) + } + return ReplAnalyzerResult { + analysisDiagnostics(messageCollector.diagnostics.toAnalyzeResultSequence()) + renderedResultType(resultRenderedType) + }.asSuccess() } - - 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() } } + + private fun analyzeWithCursor( + messageCollector: ScriptDiagnosticsMessageCollector, + snippet: SourceCode, + configuration: ScriptCompilationConfiguration, + cursor: SourceCode.Position? = null, + getNewSnippet: (SourceCode, Int) -> SourceCode = { code, _ -> code } + ): ResultWithDiagnostics { + val initialConfiguration = configuration.refineBeforeParsing(snippet).valueOr { + return it + } + + val cursorAbs = cursor?.calcAbsolute(snippet) ?: -1 + val newSnippet = if (cursorAbs == -1) snippet else getNewSnippet(snippet, cursorAbs) + + val compilationState = state.getCompilationState(initialConfiguration) + + val (_, errorHolder, snippetKtFile) = prepareForAnalyze( + newSnippet, + messageCollector, + compilationState, + checkSyntaxErrors = false + ).valueOr { return it } + + val analysisResult = + compilationState.analyzerEngine.statelessAnalyzeWithImportedScripts(snippetKtFile, emptyList(), scriptPriority.get() + 1) + AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder) + + val (_, bindingContext, resolutionFacade, moduleDescriptor, resultProperty) = when (analysisResult) { + is IdeLikeReplCodeAnalyzer.ReplLineAnalysisResultWithStateless.Stateless -> { + analysisResult + } + else -> return failure( + newSnippet, + messageCollector, + "Unexpected result ${analysisResult::class.java}" + ) + } + + return AnalyzeWithCursorResult( + snippetKtFile, bindingContext, resolutionFacade, moduleDescriptor, cursorAbs, resultProperty + ).asSuccess() + } + + companion object { + data class AnalyzeWithCursorResult( + val ktScript: KtFile, + val bindingContext: BindingContext, + val resolutionFacade: KotlinResolutionFacadeForRepl, + val moduleDescriptor: ModuleDescriptor, + val cursorAbs: Int, + val resultProperty: PropertyDescriptor?, + ) + } } diff --git a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt index 6099b6c2433..a3879b52098 100644 --- a/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt +++ b/plugins/scripting/scripting-ide-services/src/org/jetbrains/kotlin/scripting/ide_services/compiler/impl/IdeLikeReplCodeAnalyzer.kt @@ -10,6 +10,8 @@ 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.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics @@ -25,7 +27,8 @@ class IdeLikeReplCodeAnalyzer(private val environment: KotlinCoreEnvironment) : override val diagnostics: Diagnostics, val bindingContext: BindingContext, val resolutionFacade: KotlinResolutionFacadeForRepl, - val moduleDescriptor: ModuleDescriptor + val moduleDescriptor: ModuleDescriptor, + val resultProperty: PropertyDescriptor?, ) : ReplLineAnalysisResultWithStateless { override val scriptDescriptor: ClassDescriptorWithResolutionScopes? get() = null @@ -51,7 +54,9 @@ class IdeLikeReplCodeAnalyzer(private val environment: KotlinCoreEnvironment) : ) replState.submitLine(linePsi) - topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts) + val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts) + + val resultPropertyDescriptor = (context.scripts[linePsi.script] as? ScriptDescriptor)?.resultValue val moduleDescriptor = container.getService(ModuleDescriptor::class.java) val resolutionFacade = @@ -61,7 +66,8 @@ class IdeLikeReplCodeAnalyzer(private val environment: KotlinCoreEnvironment) : diagnostics, trace.bindingContext, resolutionFacade, - moduleDescriptor + moduleDescriptor, + resultPropertyDescriptor, ) }