From 89006f16cd97cfd1ede6e352f3b6e9d161d22a7b Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Sun, 28 Apr 2019 18:38:24 +0200 Subject: [PATCH] Prepare scripting infrastructure for REPL: - refactor script compiler to simplify extending it for repl - add repl snippet compilation functions to the new scripting compiler - extract util functions into appropriate files - extract repl part into separate class - extract bridge definition and related definitions into separate file --- .../kotlin/cli/common/repl/ReplState.kt | 4 + .../script/experimental/api/errorHandling.kt | 8 + .../script/experimental/api/replData.kt | 31 + .../jvmhost/impl/BridgeScriptDefinition.kt | 145 +++++ .../jvmhost/impl/KJvmCompilerImpl.kt | 575 ++---------------- .../jvmhost/impl/KJvmReplCompilerImpl.kt | 136 +++++ .../jvmhost/impl/compilationContext.kt | 223 +++++++ .../jvmhost/impl/errorReporting.kt | 135 ++++ .../jvmhost/impl/jvmCompilationUtil.kt | 156 +++++ .../jvmhost/repl/jvmReplCompilation.kt | 116 ++++ .../kotlin/scripting/repl/ReplCodeAnalyzer.kt | 15 +- 11 files changed, 1010 insertions(+), 534 deletions(-) create mode 100644 libraries/scripting/common/src/kotlin/script/experimental/api/replData.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/BridgeScriptDefinition.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmReplCompilerImpl.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/compilationContext.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/errorReporting.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/jvmCompilationUtil.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/jvmReplCompilation.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt index 45218024c67..4646f3ec741 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplState.kt @@ -61,6 +61,10 @@ interface IReplStageState { fun > asState(target: Class): StateT = if (target.isAssignableFrom(this::class.java)) this as StateT else throw IllegalArgumentException("$this is not an expected instance of IReplStageState") + + fun dispose() { + history.reset() + } } diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt index 71e4dfe7f2a..c0a0c468ac5 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt @@ -161,3 +161,11 @@ fun ResultWithDiagnostics.resultOrNull(): R? = when (this) { is ResultWithDiagnostics.Success -> value else -> null } + +/** + * Extracts the result value from the receiver wrapper or run non-returning lambda if receiver represents a Failure + */ +inline fun ResultWithDiagnostics.resultOr(body: (ResultWithDiagnostics.Failure) -> Nothing): R = when (this) { + is ResultWithDiagnostics.Success -> value + else -> body(this as ResultWithDiagnostics.Failure) +} diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/replData.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/replData.kt new file mode 100644 index 00000000000..95918719256 --- /dev/null +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/replData.kt @@ -0,0 +1,31 @@ +/* + * 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 kotlin.script.experimental.api + +import java.io.Serializable + +const val REPL_SNIPPET_FIRST_NO = 1 +const val REPL_Snippet_FIRST_GEN = 1 + +interface ReplSnippetId : Serializable, Comparable { + val no: Int + val generation: Int +} + +data class ReplSnippetIdImpl(override val no: Int, override val generation: Int, private val codeHash: Int) : ReplSnippetId, Serializable { + + constructor(no: Int, generation: Int, code: SourceCode) : this(no, generation, code.text.hashCode()) + + override fun compareTo(other: ReplSnippetId): Int = (other as? ReplSnippetIdImpl)?.let { otherId -> + no.compareTo(otherId.no).takeIf { it != 0 } + ?: generation.compareTo(otherId.generation).takeIf { it != 0 } + ?: codeHash.compareTo(otherId.codeHash) + } ?: -1 + + companion object { + private val serialVersionUID: Long = 1L + } +} diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/BridgeScriptDefinition.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/BridgeScriptDefinition.kt new file mode 100644 index 00000000000..953fb64e733 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/BridgeScriptDefinition.kt @@ -0,0 +1,145 @@ +/* + * 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 kotlin.script.experimental.jvmhost.impl + +import com.intellij.openapi.fileTypes.LanguageFileType +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.NameUtils +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.full.starProjectedType +import kotlin.script.dependencies.ScriptContents +import kotlin.script.experimental.api.* +import kotlin.script.experimental.dependencies.DependenciesResolver +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.host.getScriptingClass +import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver +import kotlin.script.experimental.util.getOrError + +// A bridge to the current scripting +// mostly copies functionality from KotlinScriptDefinitionAdapterFromNewAPI[Base] +// reusing it requires structural changes that doesn't seem justified now, since the internals of the scripting should be reworked soon anyway +// TODO: either finish refactoring of the scripting internals or reuse KotlinScriptDefinitionAdapterFromNewAPI[Base] here +// NOTE: since KotlinScriptDefinition is not designed to separate static (script definition related) and dynamic (actual script compilation +// configuration) parameters, the implementation is quite hacky, especially for cases as REPL +// TODO: finish refactoring and replace KotlinScriptDefinition with right abstractions +internal class BridgeScriptDefinition( + val scriptCompilationConfiguration: ScriptCompilationConfiguration, + val hostConfiguration: ScriptingHostConfiguration, + dynamicState: BridgeScriptDefinitionDynamicState +) : KotlinScriptDefinition(Any::class) { + + val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) { + getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass)) + } + + override val template: KClass<*> get() = baseClass + + override val name: String + get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: "Kotlin Script" + + override val fileType: LanguageFileType = KotlinFileType.INSTANCE + + override fun isScript(fileName: String): Boolean = + fileName.endsWith(".$fileExtension") + + override fun getScriptName(script: KtScript): Name { + val fileBasedName = NameUtils.getScriptNameForFile(script.containingKtFile.name) + return Name.identifier(fileBasedName.identifier.removeSuffix(".$fileExtension")) + } + + override val fileExtension: String + get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension + + override val acceptedAnnotations = run { + val cl = this::class.java.classLoader + scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations + ?.map { (cl.loadClass(it.typeName) as Class).kotlin } + ?: emptyList() + } + + override val implicitReceivers: List by lazy(LazyThreadSafetyMode.PUBLICATION) { + scriptCompilationConfiguration[ScriptCompilationConfiguration.implicitReceivers] + .orEmpty() + .map { getScriptingClass(it).starProjectedType } + } + + override val providedProperties: List> by lazy(LazyThreadSafetyMode.PUBLICATION) { + scriptCompilationConfiguration[ScriptCompilationConfiguration.providedProperties] + ?.map { (k, v) -> k to getScriptingClass(v).starProjectedType }.orEmpty() + } + + override val additionalCompilerArguments: List + get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] + .orEmpty() + + override val dependencyResolver: DependenciesResolver = + BridgeDependenciesResolver( + scriptCompilationConfiguration, + dynamicState::updateConfiguration, + dynamicState::getScriptSource + ) + + private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) { + hostConfiguration[ScriptingHostConfiguration.getScriptingClass] + ?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment") + } + + private fun getScriptingClass(type: KotlinType) = + scriptingClassGetter( + type, + KotlinScriptDefinition::class, // Assuming that the KotlinScriptDefinition class is loaded in the proper classloader + hostConfiguration + ) +} + +// TODO: consider synchronization, since it is mutable (or finish the refactoring after all) +internal class BridgeScriptDefinitionDynamicState { + + private val _sources = linkedSetOf() + private val _configurations = hashMapOf() + private var _baseScriptCompilationConfiguration: ScriptCompilationConfiguration? = null + + val sources: Set get() = _sources + + val configurations: Map get() = _configurations + + val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = _baseScriptCompilationConfiguration!! + + val mainScript: SourceCode get() = sources.first() + + val mainScriptCompilationConfiguration: ScriptCompilationConfiguration + get() = configurations[mainScript] ?: baseScriptCompilationConfiguration + + fun configureFor(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) { + _sources.clear() + _sources.add(script) + _configurations.clear() + _baseScriptCompilationConfiguration = scriptCompilationConfiguration + } + + fun updateConfiguration(script: SourceCode, updatedConfiguration: ScriptCompilationConfiguration) { + _configurations[script] = updatedConfiguration + updatedConfiguration[ScriptCompilationConfiguration.importScripts]?.let { + _sources.addAll(it) + } + } + + fun getScriptSource(scriptContents: ScriptContents): SourceCode? { + val name = scriptContents.file?.name + return sources.find { + // TODO: consider using merged text (likely should be cached) + // on the other hand it may become obsolete when scripting internals will be redesigned properly + (name != null && name == it.scriptFileName( + sources.first(), + mainScriptCompilationConfiguration + )) || it.text == scriptContents.text + } + } +} \ No newline at end of file diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt index 694f39c0597..d49e5d9cb6b 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt @@ -4,575 +4,88 @@ */ package kotlin.script.experimental.jvmhost.impl -import com.intellij.openapi.fileTypes.LanguageFileType -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.text.StringUtil -import com.intellij.openapi.vfs.CharsetToolkit -import com.intellij.psi.PsiFileFactory -import com.intellij.psi.impl.PsiFileFactoryImpl -import com.intellij.testFramework.LightVirtualFile import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys -import org.jetbrains.kotlin.cli.common.arguments.Argument -import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments -import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageCollector -import org.jetbrains.kotlin.cli.common.setupCommonArguments -import org.jetbrains.kotlin.cli.jvm.* -import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles 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.cli.jvm.config.JvmClasspathRoot -import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots -import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.CompilationErrorHandler import org.jetbrains.kotlin.codegen.KotlinCodegenFacade import org.jetbrains.kotlin.codegen.state.GenerationState -import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar -import org.jetbrains.kotlin.config.* -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.name.NameUtils +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtScript -import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar -import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys -import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition -import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies -import org.jetbrains.kotlin.scripting.dependencies.collectScriptsCompilationDependencies -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -import java.util.* -import kotlin.reflect.KClass -import kotlin.reflect.KMutableProperty1 -import kotlin.reflect.KType -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.starProjectedType -import kotlin.script.dependencies.ScriptContents import kotlin.script.experimental.api.* -import kotlin.script.experimental.dependencies.DependenciesResolver -import kotlin.script.experimental.host.FileScriptSource import kotlin.script.experimental.host.ScriptingHostConfiguration -import kotlin.script.experimental.host.getMergedScriptText -import kotlin.script.experimental.host.getScriptingClass -import kotlin.script.experimental.jvm.JvmDependency -import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver -import kotlin.script.experimental.jvm.impl.KJvmCompiledScript -import kotlin.script.experimental.jvm.jdkHome -import kotlin.script.experimental.jvm.jvm -import kotlin.script.experimental.jvm.util.KotlinJars -import kotlin.script.experimental.jvm.withUpdatedClasspath import kotlin.script.experimental.jvmhost.KJvmCompilerProxy -import kotlin.script.experimental.util.getOrError class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvmCompilerProxy { override fun compile( script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration - ): ResultWithDiagnostics> { - val messageCollector = ScriptDiagnosticsMessageCollector() - val reportingState = ReportingState() + ): ResultWithDiagnostics> = + withMessageCollectorAndDisposable(locationId = script.locationId) { messageCollector, disposable -> - fun failure(vararg diagnostics: ScriptDiagnostic): ResultWithDiagnostics.Failure = - ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics) + val context = createSharedCompilationContext(scriptCompilationConfiguration, hostConfiguration, messageCollector, disposable) - fun failure(message: String): ResultWithDiagnostics.Failure = - failure(message.asErrorDiagnostics(path = script.locationId)) + val mainKtFile = + getScriptKtFile(script, context.baseScriptCompilationConfiguration, context.environment.project, messageCollector) + .resultOr { return it } - val disposable = Disposer.newDisposable() + context.scriptCompilationState.configureFor(script, context.baseScriptCompilationConfiguration) - try { - setIdeaIoUseFallback() + val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(context, mainKtFile, messageCollector) - // TODO: refactor/cleanup when the internal resolving API will allow easier info passing between resolver and compiler + val analysisResult = analyze(sourceFiles, context.environment) - val kotlinCompilerConfiguration = - createInitialCompilerConfiguration(scriptCompilationConfiguration, messageCollector, reportingState) + if (!analysisResult.shouldGenerateCode) return failure(script, messageCollector, "no code to generate") + if (analysisResult.isError() || messageCollector.hasErrors()) return failure(messageCollector) - val initialScriptCompilationConfiguration = - scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration) - - val sourcesWithRefinementsState = SourcesWithRefinedConfigurations(script) - - kotlinCompilerConfiguration.add( - ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, - makeScriptDefinition(initialScriptCompilationConfiguration, script, sourcesWithRefinementsState) - ) - - val environment = KotlinCoreEnvironment.createForProduction( - disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES - ) - - val mainKtFile = getMainKtFile(script, initialScriptCompilationConfiguration, environment) - ?: return failure("Unable to make PSI file from script") - val ktScript = mainKtFile.declarations.firstIsInstanceOrNull() - ?: return failure("Not a script file") - - val sourceFiles = arrayListOf(mainKtFile) - val (classpath, newSources, sourceDependencies) = - collectScriptsCompilationDependencies( - kotlinCompilerConfiguration, - environment.project, - sourceFiles - ) - - // TODO: consider removing, it is probably redundant: the actual index update is performed with environment.updateClasspath - kotlinCompilerConfiguration.addJvmClasspathRoots(classpath) - environment.updateClasspath(classpath.map(::JvmClasspathRoot)) - - sourceFiles.addAll(newSources) - - // collectScriptsCompilationDependencies calls resolver for every file, so at this point all updated configurations are collected - environment.configuration.updateWithRefinedConfigurations( - initialScriptCompilationConfiguration, sourcesWithRefinementsState.refinedConfigurations, messageCollector, reportingState - ) - - val analysisResult = analyze(sourceFiles, environment) - - if (!analysisResult.shouldGenerateCode) return failure("no code to generate") - if (analysisResult.isError() || messageCollector.hasErrors()) return failure() - - val generationState = generate(analysisResult, sourceFiles, kotlinCompilerConfiguration) + val generationState = generate(analysisResult, sourceFiles, context.environment.configuration) val compiledScript = - makeCompiledScript(generationState, script, ktScript, sourceDependencies) { ktFile -> - sourcesWithRefinementsState.refinedConfigurations.entries.find { ktFile.name == it.key.name }?.value - ?: initialScriptCompilationConfiguration + makeCompiledScript(generationState, script, sourceFiles.first(), sourceDependencies) { ktFile -> + context.scriptCompilationState.configurations.entries.find { ktFile.name == it.key.name }?.value + ?: context.baseScriptCompilationConfiguration } - return ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics) - } catch (ex: Throwable) { - return failure(ex.asDiagnostics(path = script.locationId)) - } finally { - disposable.dispose() + ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics) } - } +} - private class SourcesWithRefinedConfigurations(rootScript: SourceCode) { - val knownSources = hashSetOf(rootScript) - val refinedConfigurations = hashMapOf() - } +private fun analyze(sourceFiles: Collection, environment: KotlinCoreEnvironment): AnalysisResult { + val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]!! - private class ReportingState { - var currentArguments = K2JVMCompilerArguments() - } + val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings) - private fun makeScriptDefinition( - scriptCompilationConfiguration: ScriptCompilationConfiguration, - mainScript: SourceCode, - sourcsesWithConfigurationsState: SourcesWithRefinedConfigurations - ): BridgeScriptDefinition = - BridgeScriptDefinition( - scriptCompilationConfiguration, - hostConfiguration, - { script, updatedConfiguration -> - sourcsesWithConfigurationsState.refinedConfigurations[script] = updatedConfiguration - updatedConfiguration[ScriptCompilationConfiguration.importScripts]?.let { - sourcsesWithConfigurationsState.knownSources.addAll(it) - } - }, - { scriptContents -> - val name = scriptContents.file?.name - sourcsesWithConfigurationsState.knownSources.find { - // TODO: consider using merged text (likely should be cached) - // on the other hand it may become obsolete when scripting internals will be redesigned properly - (name != null && name == it.scriptFileName( - mainScript, - scriptCompilationConfiguration - )) || it.text == scriptContents.text - } - } + analyzerWithCompilerReport.analyzeAndReport(sourceFiles) { + val project = environment.project + TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( + project, + sourceFiles, + NoScopeRecordCliBindingTrace(), + environment.configuration, + environment::createPackagePartProvider ) - - private fun ScriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration: CompilerConfiguration) = - withUpdatedClasspath(kotlinCompilerConfiguration.jvmClasspathRoots) - - private fun createInitialCompilerConfiguration( - scriptCompilationConfiguration: ScriptCompilationConfiguration, - messageCollector: MessageCollector, - reportingState: ReportingState - ): CompilerConfiguration { - - val baseArguments = K2JVMCompilerArguments() - parseCommandLineArguments( - scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: emptyList(), - baseArguments - ) - - reportArgumentsIgnoredGenerally(baseArguments, messageCollector, reportingState) - reportingState.currentArguments = baseArguments - - return org.jetbrains.kotlin.config.CompilerConfiguration().apply { - put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) - setupCommonArguments(baseArguments) - - setupJvmSpecificArguments(baseArguments) - - // Default value differs from the argument's default (see #KT-29405 and #KT-29319) - put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8) - - val jdkHomeFromConfigurations = scriptCompilationConfiguration.getNoDefault(ScriptCompilationConfiguration.jvm.jdkHome) - ?: hostConfiguration[ScriptingHostConfiguration.jvm.jdkHome] - if (jdkHomeFromConfigurations != null) { - messageCollector.report(CompilerMessageSeverity.LOGGING, "Using JDK home directory $jdkHomeFromConfigurations") - put(JVMConfigurationKeys.JDK_HOME, jdkHomeFromConfigurations) - } else { - configureJdkHome(baseArguments) - } - - put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true) - - val isModularJava = isModularJava() - - scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.let { dependencies -> - addJvmClasspathRoots( - dependencies.flatMap { - (it as JvmDependency).classpath - } - ) - } - - add(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, ScriptingCompilerConfigurationComponentRegistrar()) - - configureExplicitContentRoots(baseArguments) - - if (!baseArguments.noStdlib) { - addModularRootIfNotNull(isModularJava, "kotlin.stdlib", KotlinJars.stdlib) - addModularRootIfNotNull(isModularJava, "kotlin.script.runtime", KotlinJars.scriptRuntimeOrNull) - } - // see comments about logic in CompilerConfiguration.configureStandardLibs - if (!baseArguments.noReflect && !baseArguments.noStdlib) { - addModularRootIfNotNull(isModularJava, "kotlin.reflect", KotlinJars.reflectOrNull) - } - - put(CommonConfigurationKeys.MODULE_NAME, baseArguments.moduleName ?: "kotlin-script") - - configureAdvancedJvmOptions(baseArguments) - } - } - - companion object { - - private fun SourceCode.scriptFileName( - mainScript: SourceCode, - scriptCompilationConfiguration: ScriptCompilationConfiguration - ): String = - when { - name != null -> name!! - mainScript == this -> "script.${scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension]}" - else -> throw Exception("Unexpected script without name: $this") - } - - private fun getMainKtFile( - mainScript: SourceCode, - scriptCompilationConfiguration: ScriptCompilationConfiguration, - environment: KotlinCoreEnvironment - ): KtFile? { - val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl - val scriptText = getMergedScriptText(mainScript, scriptCompilationConfiguration) - val virtualFile = ScriptLightVirtualFile( - mainScript.scriptFileName(mainScript, scriptCompilationConfiguration), - (mainScript as? FileScriptSource)?.file?.path, - scriptText - ) - return psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile? - } - - private fun CompilerConfiguration.updateWithRefinedConfigurations( - initialScriptCompilationConfiguration: ScriptCompilationConfiguration, - refinedScriptCompilationConfigurations: HashMap, - messageCollector: ScriptDiagnosticsMessageCollector, - reportingState: ReportingState - ) { - val updatedCompilerOptions = refinedScriptCompilationConfigurations.flatMap { - it.value[ScriptCompilationConfiguration.compilerOptions] ?: emptyList() - } - if (updatedCompilerOptions.isNotEmpty() && - updatedCompilerOptions != initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] - ) { - - val updatedArguments = K2JVMCompilerArguments() - parseCommandLineArguments(updatedCompilerOptions, updatedArguments) - - reportArgumentsIgnoredGenerally(updatedArguments, messageCollector, reportingState) - reportArgumentsIgnoredFromRefinement(updatedArguments, messageCollector, reportingState) - - setupCommonArguments(updatedArguments) - - setupJvmSpecificArguments(updatedArguments) - - configureAdvancedJvmOptions(updatedArguments) - } - } - - private fun analyze(sourceFiles: Collection, environment: KotlinCoreEnvironment): AnalysisResult { - val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]!! - - val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings) - - analyzerWithCompilerReport.analyzeAndReport(sourceFiles) { - val project = environment.project - TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( - project, - sourceFiles, - NoScopeRecordCliBindingTrace(), - environment.configuration, - environment::createPackagePartProvider - ) - } - return analyzerWithCompilerReport.analysisResult - } - - private fun generate( - analysisResult: AnalysisResult, sourceFiles: List, kotlinCompilerConfiguration: CompilerConfiguration - ): GenerationState { - val generationState = GenerationState.Builder( - sourceFiles.first().project, - ClassBuilderFactories.BINARIES, - analysisResult.moduleDescriptor, - analysisResult.bindingContext, - sourceFiles, - kotlinCompilerConfiguration - ).build() - - KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION) - return generationState - } - - private fun makeCompiledScript( - generationState: GenerationState, - script: SourceCode, - ktScript: KtScript, - sourceDependencies: List, - getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration - ): KJvmCompiledScript { - val scriptDependenciesStack = ArrayDeque() - - fun makeOtherScripts(script: KtScript): List> { - - // TODO: ensure that it is caught earlier (as well) since it would be more economical - if (scriptDependenciesStack.contains(script)) - throw IllegalArgumentException("Unable to handle recursive script dependencies") - scriptDependenciesStack.push(script) - - val containingKtFile = script.containingKtFile - val otherScripts: List> = - sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile -> - sourceFile.declarations.firstIsInstanceOrNull()?.let { - KJvmCompiledScript( - containingKtFile.virtualFile?.path, - getScriptConfiguration(sourceFile), - it.fqName.asString(), - makeOtherScripts(it), - null - ) - } - } ?: emptyList() - - scriptDependenciesStack.pop() - return otherScripts - } - - val module = makeCompiledModule(generationState) - return KJvmCompiledScript( - script.locationId, - getScriptConfiguration(ktScript.containingKtFile), - ktScript.fqName.asString(), - makeOtherScripts(ktScript), - module - ) - } - - private fun reportArgumentsIgnoredGenerally( - arguments: K2JVMCompilerArguments, - messageCollector: MessageCollector, - reportingState: ReportingState - ) { - - reportIgnoredArguments( - arguments, - "The following compiler arguments are ignored on script compilation: ", - messageCollector, - reportingState, - K2JVMCompilerArguments::version, - K2JVMCompilerArguments::destination, - K2JVMCompilerArguments::buildFile, - K2JVMCompilerArguments::commonSources, - K2JVMCompilerArguments::allWarningsAsErrors, - K2JVMCompilerArguments::script, - K2JVMCompilerArguments::scriptTemplates, - K2JVMCompilerArguments::scriptResolverEnvironment, - K2JVMCompilerArguments::disableStandardScript, - K2JVMCompilerArguments::disableDefaultScriptingPlugin, - K2JVMCompilerArguments::pluginClasspaths, - K2JVMCompilerArguments::pluginOptions, - K2JVMCompilerArguments::useJavac, - K2JVMCompilerArguments::compileJava, - K2JVMCompilerArguments::reportPerf, - K2JVMCompilerArguments::dumpPerf - ) - } - - private fun reportArgumentsIgnoredFromRefinement( - arguments: K2JVMCompilerArguments, messageCollector: MessageCollector, reportingState: ReportingState - ) { - reportIgnoredArguments( - arguments, - "The following compiler arguments are ignored when configured from refinement callbacks: ", - messageCollector, - reportingState, - K2JVMCompilerArguments::noJdk, - K2JVMCompilerArguments::jdkHome, - K2JVMCompilerArguments::javaModulePath, - K2JVMCompilerArguments::classpath, - K2JVMCompilerArguments::noStdlib, - K2JVMCompilerArguments::noReflect - ) - } - - private fun reportIgnoredArguments( - arguments: K2JVMCompilerArguments, message: String, - messageCollector: MessageCollector, reportingState: ReportingState, - vararg toIgnore: KMutableProperty1 - ) { - val ignoredArgKeys = toIgnore.mapNotNull { argProperty -> - if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) { - argProperty.findAnnotation()?.value - ?: throw IllegalStateException("unknown compiler argument property: $argProperty: no Argument annotation found") - } else null - } - - if (ignoredArgKeys.isNotEmpty()) { - messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, "$message${ignoredArgKeys.joinToString(", ")}") - } - } } + return analyzerWithCompilerReport.analysisResult } -internal class ScriptDiagnosticsMessageCollector : MessageCollector { +private fun generate( + analysisResult: AnalysisResult, sourceFiles: List, kotlinCompilerConfiguration: CompilerConfiguration +): GenerationState { + val generationState = GenerationState.Builder( + sourceFiles.first().project, + ClassBuilderFactories.BINARIES, + analysisResult.moduleDescriptor, + analysisResult.bindingContext, + sourceFiles, + kotlinCompilerConfiguration + ).build() - private val _diagnostics = arrayListOf() - - val diagnostics: List get() = _diagnostics - - override fun clear() { - _diagnostics.clear() - } - - override fun hasErrors(): Boolean = - _diagnostics.any { it.severity == ScriptDiagnostic.Severity.ERROR } - - - override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) { - val mappedSeverity = when (severity) { - CompilerMessageSeverity.EXCEPTION, - CompilerMessageSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR - CompilerMessageSeverity.STRONG_WARNING, - CompilerMessageSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING - CompilerMessageSeverity.INFO -> ScriptDiagnostic.Severity.INFO - CompilerMessageSeverity.LOGGING -> ScriptDiagnostic.Severity.DEBUG - else -> null - } - if (mappedSeverity != null) { - val mappedLocation = location?.let { - if (it.line < 0 && it.column < 0) null // special location created by CompilerMessageLocation.create - else SourceCode.Location(SourceCode.Position(it.line, it.column)) - } - _diagnostics.add(ScriptDiagnostic(message, mappedSeverity, location?.path, mappedLocation)) - } - } + KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION) + return generationState } - -// A bridge to the current scripting -// mostly copies functionality from KotlinScriptDefinitionAdapterFromNewAPI[Base] -// reusing it requires structural changes that doesn't seem justified now, since the internals of the scripting should be reworked soon anyway -// TODO: either finish refactoring of the scripting internals or reuse KotlinScriptDefinitionAdapterFromNewAPI[BAse] here -internal class BridgeScriptDefinition( - val scriptCompilationConfiguration: ScriptCompilationConfiguration, - val hostConfiguration: ScriptingHostConfiguration, - updateConfiguration: (SourceCode, ScriptCompilationConfiguration) -> Unit, - getScriptSource: (ScriptContents) -> SourceCode? -) : KotlinScriptDefinition(Any::class) { - - val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) { - getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass)) - } - - override val template: KClass<*> get() = baseClass - - override val name: String - get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: "Kotlin Script" - - override val fileType: LanguageFileType = KotlinFileType.INSTANCE - - override fun isScript(fileName: String): Boolean = - fileName.endsWith(".$fileExtension") - - override fun getScriptName(script: KtScript): Name { - val fileBasedName = NameUtils.getScriptNameForFile(script.containingKtFile.name) - return Name.identifier(fileBasedName.identifier.removeSuffix(".$fileExtension")) - } - - override val fileExtension: String - get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension - - override val acceptedAnnotations = run { - val cl = this::class.java.classLoader - scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations - ?.map { (cl.loadClass(it.typeName) as Class).kotlin } - ?: emptyList() - } - - override val implicitReceivers: List by lazy(LazyThreadSafetyMode.PUBLICATION) { - scriptCompilationConfiguration[ScriptCompilationConfiguration.implicitReceivers] - .orEmpty() - .map { getScriptingClass(it).starProjectedType } - } - - override val providedProperties: List> by lazy(LazyThreadSafetyMode.PUBLICATION) { - scriptCompilationConfiguration[ScriptCompilationConfiguration.providedProperties] - ?.map { (k, v) -> k to getScriptingClass(v).starProjectedType }.orEmpty() - } - - override val additionalCompilerArguments: List - get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] - .orEmpty() - - override val dependencyResolver: DependenciesResolver = - BridgeDependenciesResolver(scriptCompilationConfiguration, updateConfiguration, getScriptSource) - - private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) { - hostConfiguration[ScriptingHostConfiguration.getScriptingClass] - ?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment") - } - - private fun getScriptingClass(type: KotlinType) = - scriptingClassGetter( - type, - KotlinScriptDefinition::class, // Assuming that the KotlinScriptDefinition class is loaded in the proper classloader - hostConfiguration - ) -} - -internal class ScriptLightVirtualFile(name: String, private val _path: String?, text: String) : - LightVirtualFile(name, KotlinLanguage.INSTANCE, StringUtil.convertLineSeparators(text)) { - - init { - charset = CharsetToolkit.UTF8_CHARSET - } - - override fun getPath(): String = _path ?: super.getPath() - override fun getCanonicalPath(): String? = path -} - -private fun makeCompiledModule(generationState: GenerationState) = KJvmCompiledModuleInMemory( - generationState.factory.asList() - .associateTo(sortedMapOf()) { it.relativePath to it.asByteArray() } -) diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmReplCompilerImpl.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmReplCompilerImpl.kt new file mode 100644 index 00000000000..b5de8fcc820 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmReplCompilerImpl.kt @@ -0,0 +1,136 @@ +/* + * 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 kotlin.script.experimental.jvmhost.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.* +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.codegen.ClassBuilderFactories +import org.jetbrains.kotlin.codegen.CompilationErrorHandler +import org.jetbrains.kotlin.codegen.KotlinCodegenFacade +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.ScriptDescriptor +import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.jvmhost.repl.JvmReplCompilerState +import kotlin.script.experimental.jvmhost.repl.KJvmReplCompilerProxy + +class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvmReplCompilerProxy { + + override fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation { + val context = withMessageCollectorAndDisposable(disposeOnSuccess = false) { messageCollector, disposable -> + createSharedCompilationContext(scriptCompilationConfiguration, hostConfiguration, messageCollector, disposable).asSuccess() + }.resultOr { throw IllegalStateException("Unable to initialize repl compiler:\n ${it.reports.joinToString("\n ")}") } + return ReplCompilationState(context) + } + + fun checkSyntax( + script: SourceCode, + scriptCompilationConfiguration: ScriptCompilationConfiguration, + project: Project + ): ResultWithDiagnostics = + withMessageCollector(script) { messageCollector -> + val ktFile = getScriptKtFile(script, scriptCompilationConfiguration, project, messageCollector) + .resultOr { return it } + val errorHolder = object : MessageCollectorBasedReporter { + override val messageCollector = messageCollector + } + val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder) + if (syntaxErrorReport.isHasErrors) failure(messageCollector) else true.asSuccess() + } + + fun compileReplSnippet( + compilationState: JvmReplCompilerState.Compilation, + snippet: SourceCode, + snippetId: ReplSnippetId, + history: IReplStageHistory + ): ResultWithDiagnostics> = + 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) + .resultOr { return it } + + val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(context, snippetKtFile, messageCollector) + + 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 + else -> return failure(snippet, messageCollector, "Unexpected result ${analysisResult::class.java}") + } + + val type = (scriptDescriptor as ScriptDescriptor).resultValue?.returnType + + val generationState = GenerationState.Builder( + snippetKtFile.project, + ClassBuilderFactories.BINARIES, + compilationState.analyzerEngine.module, + compilationState.analyzerEngine.trace.bindingContext, + sourceFiles, + compilationState.environment.configuration + ).build().apply { + replSpecific.resultType = type + replSpecific.scriptResultFieldName = scriptResultFieldName(codeLine.no) + replSpecific.earlierScriptsForReplInterpreter = history.map { it.item } + beforeCompile() + } + KotlinCodegenFacade.generatePackage( + generationState, + snippetKtFile.script!!.containingKtFile.packageFqName, + setOf(snippetKtFile.script!!.containingKtFile), + CompilationErrorHandler.THROW_EXCEPTION + ) + + val generatedClassname = makeScriptBaseName(codeLine) + history.push(LineId(codeLine), scriptDescriptor) + + val compiledScript = + makeCompiledScript(generationState, snippet, sourceFiles.first(), sourceDependencies) { ktFile -> + context.scriptCompilationState.configurations.entries.find { ktFile.name == it.key.name }?.value + ?: context.baseScriptCompilationConfiguration + } + + ResultWithDiagnostics.Success(compiledScript, 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) + + diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/compilationContext.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/compilationContext.kt new file mode 100644 index 00000000000..15ea4d39b70 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/compilationContext.kt @@ -0,0 +1,223 @@ +/* + * 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 kotlin.script.experimental.jvmhost.impl + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.setupCommonArguments +import org.jetbrains.kotlin.cli.jvm.* +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar +import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys +import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies +import org.jetbrains.kotlin.scripting.dependencies.collectScriptsCompilationDependencies +import kotlin.script.experimental.api.ScriptCompilationConfiguration +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.api.compilerOptions +import kotlin.script.experimental.api.dependencies +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.jvm.JvmDependency +import kotlin.script.experimental.jvm.jdkHome +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.util.KotlinJars +import kotlin.script.experimental.jvm.withUpdatedClasspath + +internal class SharedScriptCompilationContext( + val disposable: Disposable, + val baseScriptCompilationConfiguration: ScriptCompilationConfiguration, + val environment: KotlinCoreEnvironment, + val ignoredOptionsReportingState: IgnoredOptionsReportingState, + val scriptCompilationState: BridgeScriptDefinitionDynamicState +) + +internal fun createSharedCompilationContext( + scriptCompilationConfiguration: ScriptCompilationConfiguration, + hostConfiguration: ScriptingHostConfiguration, + messageCollector: ScriptDiagnosticsMessageCollector, + disposable: Disposable +): SharedScriptCompilationContext { + val ignoredOptionsReportingState = IgnoredOptionsReportingState() + + val scriptCompilationState = BridgeScriptDefinitionDynamicState() + + val (initialScriptCompilationConfiguration, kotlinCompilerConfiguration) = + createInitialConfigurations( + scriptCompilationConfiguration, hostConfiguration, scriptCompilationState, messageCollector, ignoredOptionsReportingState + ) + val environment = KotlinCoreEnvironment.createForProduction( + disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES + ) + + return SharedScriptCompilationContext( + disposable, initialScriptCompilationConfiguration, environment, ignoredOptionsReportingState, scriptCompilationState + ) +} + +internal fun createInitialConfigurations( + scriptCompilationConfiguration: ScriptCompilationConfiguration, + hostConfiguration: ScriptingHostConfiguration, + scriptCompilationState: BridgeScriptDefinitionDynamicState, + messageCollector: ScriptDiagnosticsMessageCollector, + ignoredOptionsReportingState: IgnoredOptionsReportingState +): Pair { + val kotlinCompilerConfiguration = + createInitialCompilerConfiguration( + scriptCompilationConfiguration, hostConfiguration, messageCollector, ignoredOptionsReportingState + ) + + val initialScriptCompilationConfiguration = + scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration) + + kotlinCompilerConfiguration.add( + ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, + BridgeScriptDefinition( + scriptCompilationConfiguration, + hostConfiguration, + scriptCompilationState + ) + ) + + return Pair(initialScriptCompilationConfiguration, kotlinCompilerConfiguration) +} + +private fun ScriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration: CompilerConfiguration) = + withUpdatedClasspath(kotlinCompilerConfiguration.jvmClasspathRoots) + +private fun createInitialCompilerConfiguration( + scriptCompilationConfiguration: ScriptCompilationConfiguration, + hostConfiguration: ScriptingHostConfiguration, + messageCollector: MessageCollector, + reportingState: IgnoredOptionsReportingState +): CompilerConfiguration { + + val baseArguments = K2JVMCompilerArguments() + parseCommandLineArguments( + scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: emptyList(), + baseArguments + ) + + reportArgumentsIgnoredGenerally(baseArguments, messageCollector, reportingState) + reportingState.currentArguments = baseArguments + + return CompilerConfiguration().apply { + put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) + setupCommonArguments(baseArguments) + + setupJvmSpecificArguments(baseArguments) + + // Default value differs from the argument's default (see #KT-29405 and #KT-29319) + put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8) + + val jdkHomeFromConfigurations = scriptCompilationConfiguration.getNoDefault(ScriptCompilationConfiguration.jvm.jdkHome) + ?: hostConfiguration[ScriptingHostConfiguration.jvm.jdkHome] + if (jdkHomeFromConfigurations != null) { + messageCollector.report(CompilerMessageSeverity.LOGGING, "Using JDK home directory $jdkHomeFromConfigurations") + put(JVMConfigurationKeys.JDK_HOME, jdkHomeFromConfigurations) + } else { + configureJdkHome(baseArguments) + } + + put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true) + + val isModularJava = isModularJava() + + scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.let { dependencies -> + addJvmClasspathRoots( + dependencies.flatMap { + (it as JvmDependency).classpath + } + ) + } + + add( + ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, + ScriptingCompilerConfigurationComponentRegistrar() + ) + + configureExplicitContentRoots(baseArguments) + + if (!baseArguments.noStdlib) { + addModularRootIfNotNull(isModularJava, "kotlin.stdlib", KotlinJars.stdlib) + addModularRootIfNotNull(isModularJava, "kotlin.script.runtime", KotlinJars.scriptRuntimeOrNull) + } + // see comments about logic in CompilerConfiguration.configureStandardLibs + if (!baseArguments.noReflect && !baseArguments.noStdlib) { + addModularRootIfNotNull(isModularJava, "kotlin.reflect", KotlinJars.reflectOrNull) + } + + put(CommonConfigurationKeys.MODULE_NAME, baseArguments.moduleName ?: "kotlin-script") + + configureAdvancedJvmOptions(baseArguments) + } +} + +internal fun collectRefinedSourcesAndUpdateEnvironment( + context: SharedScriptCompilationContext, + mainKtFile: KtFile, + messageCollector: ScriptDiagnosticsMessageCollector +): Pair, List> { + val sourceFiles = arrayListOf(mainKtFile) + val (classpath, newSources, sourceDependencies) = + collectScriptsCompilationDependencies( + context.environment.configuration, + context.environment.project, + sourceFiles + ) + + // TODO: consider removing, it is probably redundant: the actual index update is performed with environment.updateClasspath + context.environment.configuration.addJvmClasspathRoots(classpath) + context.environment.updateClasspath(classpath.map(::JvmClasspathRoot)) + + sourceFiles.addAll(newSources) + + // collectScriptsCompilationDependencies calls resolver for every file, so at this point all updated configurations are collected + context.environment.configuration.updateWithRefinedConfigurations( + context.baseScriptCompilationConfiguration, context.scriptCompilationState.configurations, + messageCollector, context.ignoredOptionsReportingState + ) + return sourceFiles to sourceDependencies +} + +private fun CompilerConfiguration.updateWithRefinedConfigurations( + initialScriptCompilationConfiguration: ScriptCompilationConfiguration, + refinedScriptCompilationConfigurations: Map, + messageCollector: ScriptDiagnosticsMessageCollector, + reportingState: IgnoredOptionsReportingState +) { + val updatedCompilerOptions = refinedScriptCompilationConfigurations.flatMap { + it.value[ScriptCompilationConfiguration.compilerOptions] ?: emptyList() + } + if (updatedCompilerOptions.isNotEmpty() && + updatedCompilerOptions != initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] + ) { + + val updatedArguments = K2JVMCompilerArguments() + parseCommandLineArguments(updatedCompilerOptions, updatedArguments) + + reportArgumentsIgnoredGenerally(updatedArguments, messageCollector, reportingState) + reportArgumentsIgnoredFromRefinement(updatedArguments, messageCollector, reportingState) + + setupCommonArguments(updatedArguments) + + setupJvmSpecificArguments(updatedArguments) + + configureAdvancedJvmOptions(updatedArguments) + } +} \ No newline at end of file diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/errorReporting.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/errorReporting.kt new file mode 100644 index 00000000000..685dc838a2a --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/errorReporting.kt @@ -0,0 +1,135 @@ +/* + * 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 kotlin.script.experimental.jvmhost.impl + +import org.jetbrains.kotlin.cli.common.arguments.Argument +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.findAnnotation +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.ScriptDiagnostic +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.api.asErrorDiagnostics + +internal class ScriptDiagnosticsMessageCollector : MessageCollector { + + private val _diagnostics = arrayListOf() + + val diagnostics: List get() = _diagnostics + + override fun clear() { + _diagnostics.clear() + } + + override fun hasErrors(): Boolean = + _diagnostics.any { it.severity == ScriptDiagnostic.Severity.ERROR } + + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) { + val mappedSeverity = when (severity) { + CompilerMessageSeverity.EXCEPTION, + CompilerMessageSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR + CompilerMessageSeverity.STRONG_WARNING, + CompilerMessageSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING + CompilerMessageSeverity.INFO -> ScriptDiagnostic.Severity.INFO + CompilerMessageSeverity.LOGGING -> ScriptDiagnostic.Severity.DEBUG + else -> null + } + if (mappedSeverity != null) { + val mappedLocation = location?.let { + if (it.line < 0 && it.column < 0) null // special location created by CompilerMessageLocation.create + else SourceCode.Location( + SourceCode.Position( + it.line, + it.column + ) + ) + } + _diagnostics.add(ScriptDiagnostic(message, mappedSeverity, location?.path, mappedLocation)) + } + } +} + +internal fun failure( + messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic +): ResultWithDiagnostics.Failure = + ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics) + +internal fun failure( + script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String +): ResultWithDiagnostics.Failure = + failure(messageCollector, message.asErrorDiagnostics(path = script.locationId)) + +internal class IgnoredOptionsReportingState { + var currentArguments = K2JVMCompilerArguments() +} + +internal fun reportArgumentsIgnoredGenerally( + arguments: K2JVMCompilerArguments, + messageCollector: MessageCollector, + reportingState: IgnoredOptionsReportingState +) { + + reportIgnoredArguments( + arguments, + "The following compiler arguments are ignored on script compilation: ", + messageCollector, + reportingState, + K2JVMCompilerArguments::version, + K2JVMCompilerArguments::destination, + K2JVMCompilerArguments::buildFile, + K2JVMCompilerArguments::commonSources, + K2JVMCompilerArguments::allWarningsAsErrors, + K2JVMCompilerArguments::script, + K2JVMCompilerArguments::scriptTemplates, + K2JVMCompilerArguments::scriptResolverEnvironment, + K2JVMCompilerArguments::disableStandardScript, + K2JVMCompilerArguments::disableDefaultScriptingPlugin, + K2JVMCompilerArguments::pluginClasspaths, + K2JVMCompilerArguments::pluginOptions, + K2JVMCompilerArguments::useJavac, + K2JVMCompilerArguments::compileJava, + K2JVMCompilerArguments::reportPerf, + K2JVMCompilerArguments::dumpPerf + ) +} + +internal fun reportArgumentsIgnoredFromRefinement( + arguments: K2JVMCompilerArguments, messageCollector: MessageCollector, reportingState: IgnoredOptionsReportingState +) { + reportIgnoredArguments( + arguments, + "The following compiler arguments are ignored when configured from refinement callbacks: ", + messageCollector, + reportingState, + K2JVMCompilerArguments::noJdk, + K2JVMCompilerArguments::jdkHome, + K2JVMCompilerArguments::javaModulePath, + K2JVMCompilerArguments::classpath, + K2JVMCompilerArguments::noStdlib, + K2JVMCompilerArguments::noReflect + ) +} + +private fun reportIgnoredArguments( + arguments: K2JVMCompilerArguments, message: String, + messageCollector: MessageCollector, reportingState: IgnoredOptionsReportingState, + vararg toIgnore: KMutableProperty1 +) { + val ignoredArgKeys = toIgnore.mapNotNull { argProperty -> + if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) { + argProperty.findAnnotation()?.value + ?: throw IllegalStateException("unknown compiler argument property: $argProperty: no Argument annotation found") + } else null + } + + if (ignoredArgKeys.isNotEmpty()) { + messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, "$message${ignoredArgKeys.joinToString(", ")}") + } +} \ No newline at end of file diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/jvmCompilationUtil.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/jvmCompilationUtil.kt new file mode 100644 index 00000000000..ae697dc8fdc --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/jvmCompilationUtil.kt @@ -0,0 +1,156 @@ +/* + * 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 kotlin.script.experimental.jvmhost.impl + +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.CharsetToolkit +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.impl.PsiFileFactoryImpl +import com.intellij.testFramework.LightVirtualFile +import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.util.* +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.FileScriptSource +import kotlin.script.experimental.host.getMergedScriptText +import kotlin.script.experimental.jvm.impl.KJvmCompiledScript + +internal class ScriptLightVirtualFile(name: String, private val _path: String?, text: String) : + LightVirtualFile(name, KotlinLanguage.INSTANCE, StringUtil.convertLineSeparators(text)) { + + init { + charset = CharsetToolkit.UTF8_CHARSET + } + + override fun getPath(): String = _path ?: super.getPath() + override fun getCanonicalPath(): String? = path +} + +internal fun makeCompiledModule(generationState: GenerationState) = + KJvmCompiledModuleInMemory( + generationState.factory.asList() + .associateTo(sortedMapOf()) { it.relativePath to it.asByteArray() } + ) + +internal fun SourceCode.scriptFileName( + mainScript: SourceCode, + scriptCompilationConfiguration: ScriptCompilationConfiguration +): String = + when { + name != null -> name!! + mainScript == this -> "script.${scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension]}" + else -> throw Exception("Unexpected script without name: $this") + } + +internal inline fun withMessageCollectorAndDisposable( + messageCollector: ScriptDiagnosticsMessageCollector = ScriptDiagnosticsMessageCollector(), + disposable: Disposable = Disposer.newDisposable(), + disposeOnSuccess: Boolean = true, + locationId: String? = null, + body: (ScriptDiagnosticsMessageCollector, Disposable) -> ResultWithDiagnostics +): ResultWithDiagnostics { + var failed = false + return try { + setIdeaIoUseFallback() + body(messageCollector, disposable).also { + failed = it is ResultWithDiagnostics.Failure + } + } catch (ex: Throwable) { + failed = true + failure(messageCollector, ex.asDiagnostics(path = locationId)) + } finally { + if (disposeOnSuccess || failed) { + disposable.dispose() + } + } +} + +internal inline fun withMessageCollector( + script: SourceCode, + messageCollector: ScriptDiagnosticsMessageCollector = ScriptDiagnosticsMessageCollector(), + body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics +): ResultWithDiagnostics = + try { + body(messageCollector) + } catch (ex: Throwable) { + failure(messageCollector, ex.asDiagnostics(path = script.locationId)) + } + +internal fun getScriptKtFile( + script: SourceCode, + scriptCompilationConfiguration: ScriptCompilationConfiguration, + project: Project, + messageCollector: ScriptDiagnosticsMessageCollector +): ResultWithDiagnostics { + val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl + val scriptText = getMergedScriptText(script, scriptCompilationConfiguration) + val virtualFile = ScriptLightVirtualFile( + script.scriptFileName(script, scriptCompilationConfiguration), + (script as? FileScriptSource)?.file?.path, + scriptText + ) + val ktFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile? + return when { + ktFile == null -> failure(script, messageCollector, "Unable to make PSI file from script") + ktFile.declarations.firstIsInstanceOrNull() == null -> failure(script, messageCollector, "Not a script file") + else -> ktFile.asSuccess() + } +} + +internal fun makeCompiledScript( + generationState: GenerationState, + script: SourceCode, + ktFile: KtFile, + sourceDependencies: List, + getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration +): KJvmCompiledScript { + val scriptDependenciesStack = ArrayDeque() + val ktScript = ktFile.declarations.firstIsInstanceOrNull() + ?: throw IllegalStateException("Expecting script file: KtScript is not found in ${ktFile.name}") + + fun makeOtherScripts(script: KtScript): List> { + + // TODO: ensure that it is caught earlier (as well) since it would be more economical + if (scriptDependenciesStack.contains(script)) + throw IllegalArgumentException("Unable to handle recursive script dependencies") + scriptDependenciesStack.push(script) + + val containingKtFile = script.containingKtFile + val otherScripts: List> = + sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile -> + sourceFile.declarations.firstIsInstanceOrNull()?.let { + KJvmCompiledScript( + containingKtFile.virtualFile?.path, + getScriptConfiguration(sourceFile), + it.fqName.asString(), + makeOtherScripts(it), + null + ) + } + } ?: emptyList() + + scriptDependenciesStack.pop() + return otherScripts + } + + val module = makeCompiledModule(generationState) + return KJvmCompiledScript( + script.locationId, + getScriptConfiguration(ktScript.containingKtFile), + ktScript.fqName.asString(), + makeOtherScripts(ktScript), + module + ) +} + diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/jvmReplCompilation.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/jvmReplCompilation.kt new file mode 100644 index 00000000000..705ab282791 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/repl/jvmReplCompilation.kt @@ -0,0 +1,116 @@ +/* + * 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 kotlin.script.experimental.jvmhost.repl + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.ScriptDescriptor +import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write +import kotlin.script.experimental.api.ScriptCompilationConfiguration +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.jvmhost.impl.KJvmCompilerImpl +import kotlin.script.experimental.jvmhost.impl.withDefaults + +class JvmReplCompiler( + val scriptCompilationConfiguration: ScriptCompilationConfiguration, + val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, + val replCompilerProxy: KJvmReplCompilerProxy = KJvmCompilerImpl(hostConfiguration.withDefaults()) +) : ReplCompiler { + + override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock) + + override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + +} + +interface KJvmReplCompilerProxy { + fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation +} + +class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) : + BasicReplStageHistory(state.lock) { + + override fun reset(): Iterable { + val removedCompiledLines = super.reset() + val removedAnalyzedLines = state.compilation.analyzerEngine.reset() + + checkConsistent(removedCompiledLines, removedAnalyzedLines) + return removedCompiledLines + } + + override fun resetTo(id: ILineId): Iterable { + val removedCompiledLines = super.resetTo(id) + val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id) + + checkConsistent(removedCompiledLines, removedAnalyzedLines) + return removedCompiledLines + } + + private fun checkConsistent(removedCompiledLines: Iterable, removedAnalyzedLines: List) { + 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, + override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock() +) : IReplStageState { + + override val history = JvmReplCompilerStageHistory(this) + + override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get() + + override fun dispose() { + lock.write { + _compilation?.disposable?.dispose() + _compilation = null + super.dispose() + } + } + + fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write { + if (_compilation == null) { + initializeCompilation(scriptCompilationConfiguration) + } + _compilation!! + } + + internal val compilation: Compilation + get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed") + + private var _compilation: Compilation? = 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) + } + + interface Compilation { + val disposable: Disposable + val baseScriptCompilationConfiguration: ScriptCompilationConfiguration + val environment: KotlinCoreEnvironment + val analyzerEngine: ReplCodeAnalyzer + } +} diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/repl/ReplCodeAnalyzer.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/repl/ReplCodeAnalyzer.kt index 75226fa7042..9c02c63a13c 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/repl/ReplCodeAnalyzer.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/repl/ReplCodeAnalyzer.kt @@ -90,14 +90,23 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) { psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) - return doAnalyze(psiFile, codeLine) + return doAnalyze(psiFile, emptyList(), codeLine) } - private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult { + fun analyzeReplLineWithImportedScripts(psiFile: KtFile, importedScripts: List, codeLine: ReplCodeLine): ReplLineAnalysisResult { + topDownAnalysisContext.scripts.clear() + trace.clearDiagnostics() + + psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no) + + return doAnalyze(psiFile, importedScripts, codeLine) + } + + private fun doAnalyze(linePsi: KtFile, importedScripts: List, codeLine: ReplCodeLine): ReplLineAnalysisResult { scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi))) replState.submitLine(linePsi, codeLine) - val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi)) + val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts) val diagnostics = trace.bindingContext.diagnostics val hasErrors = diagnostics.any { it.severity == Severity.ERROR }