[JS SCRIPT] refactor js script infrastructure

- Implement proper script compiler proxy to correctly handle script and its closed-world dependencies
 - Clean up zoo of JsScriptCompilers
This commit is contained in:
Roman Artemev
2019-09-25 20:50:23 +03:00
committed by romanart
parent f59e393e37
commit c0f279811e
25 changed files with 404 additions and 357 deletions
@@ -31,12 +31,7 @@ abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
): KotlinCoreEnvironment
abstract fun createScriptEvaluator(): ScriptEvaluator
abstract suspend fun compilerInvoke(
environment: KotlinCoreEnvironment,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>>
abstract fun createScriptCompiler(environment: KotlinCoreEnvironment): ScriptCompilerProxy
protected abstract fun ScriptEvaluationConfiguration.Builder.platformEvaluationConfiguration()
@@ -82,22 +77,17 @@ abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
}
val scriptCompilationConfiguration = definition.compilationConfiguration
val scriptEvaluator = createScriptEvaluator()
val scriptCompiler = createScriptCompiler(environment)
return runBlocking {
val compiledScript = compilerInvoke(
environment,
script,
scriptCompilationConfiguration
).valueOr {
val compiledScript = scriptCompiler.compile(script, scriptCompilationConfiguration).valueOr {
for (report in it.reports) {
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
}
return@runBlocking ExitCode.COMPILATION_ERROR
}
val evalResult = scriptEvaluator.invoke(compiledScript, evaluationConfiguration).valueOr {
val evalResult = createScriptEvaluator().invoke(compiledScript, evaluationConfiguration).valueOr {
for (report in it.reports) {
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
}
@@ -6,22 +6,28 @@
package org.jetbrains.kotlin.scripting.compiler.plugin
import com.intellij.core.JavaCoreProjectEnvironment
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.JsScriptCompilerWithDependenciesProxy
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.withMessageCollector
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.platform
import org.jetbrains.kotlin.scripting.repl.js.CompiledToJsScript
import org.jetbrains.kotlin.scripting.repl.js.JsScriptCompiler
import org.jetbrains.kotlin.scripting.repl.js.JsScriptDependencyCompiler
import org.jetbrains.kotlin.scripting.repl.js.JsScriptEvaluator
import org.jetbrains.kotlin.scripting.repl.js.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.JsDependency
// TODO: the code below has to be considered as temporary hack and removed ASAP.
// Actual ScriptCompilationConfiguration should be set up from CompilerConfiguration.
fun loadScriptConfiguration(configuration: CompilerConfiguration) {
val scriptConfiguration = ScriptCompilationConfiguration {
baseClass("kotlin.Any")
@@ -55,37 +61,13 @@ class JsScriptEvaluationExtension : AbstractScriptEvaluationExtension() {
return JsScriptEvaluator()
}
private var environment: KotlinCoreEnvironment? = null
private var dependencyJsCode: String? = null
private val scriptCompiler: JsScriptCompiler by lazy {
val env = environment ?: error("Expected environment is initialized prior to compiler instantiation")
JsScriptCompiler(env).apply {
dependencyJsCode = JsScriptDependencyCompiler(env.configuration, nameTables, symbolTable).compile(dependencies)
}
private var scriptCompilerProxy: ScriptCompilerProxy? = null
override fun createScriptCompiler(environment: KotlinCoreEnvironment): ScriptCompilerProxy {
return scriptCompilerProxy ?: JsScriptCompilerWithDependenciesProxy(environment).also { scriptCompilerProxy = it }
}
override suspend fun compilerInvoke(
environment: KotlinCoreEnvironment,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
this.environment = environment
return scriptCompiler.invoke(script, scriptCompilationConfiguration).onSuccess {
val compiledResult = it as CompiledToJsScript
val actualResult = dependencyJsCode?.let { d ->
dependencyJsCode = null
CompiledToJsScript(d + "\n" + compiledResult.jsCode, compiledResult.compilationConfiguration)
} ?: compiledResult
ResultWithDiagnostics.Success(actualResult)
}
}
override fun ScriptEvaluationConfiguration.Builder.platformEvaluationConfiguration() {
}
override fun ScriptEvaluationConfiguration.Builder.platformEvaluationConfiguration() {}
override fun isAccepted(arguments: CommonCompilerArguments): Boolean {
return arguments is K2JSCompilerArguments
@@ -43,18 +43,8 @@ class JvmCliScriptEvaluationExtension : AbstractScriptEvaluationExtension() {
return BasicJvmScriptEvaluator()
}
private var environment: KotlinCoreEnvironment? = null
private val scriptCompiler: ScriptJvmCompilerFromEnvironment by lazy {
ScriptJvmCompilerFromEnvironment(environment!!)
}
override suspend fun compilerInvoke(
environment: KotlinCoreEnvironment,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
this.environment = environment
return scriptCompiler.compile(script, scriptCompilationConfiguration)
override fun createScriptCompiler(environment: KotlinCoreEnvironment): ScriptCompilerProxy {
return ScriptJvmCompilerFromEnvironment(environment)
}
override fun isAccepted(arguments: CommonCompilerArguments): Boolean =
@@ -10,7 +10,7 @@ import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.SourceCode
interface ScriptJvmCompilerProxy {
interface ScriptCompilerProxy {
fun compile(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
@@ -0,0 +1,79 @@
/*
* 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 org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.context.ContextForNewModule
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.frontend.js.di.createTopDownAnalyzerForJs
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
abstract class AbstractJsScriptlikeCodeAnalyser(
private val environment: KotlinCoreEnvironment,
private val dependencies: List<ModuleDescriptor>
) {
protected class JsScriptAnalysisResult(
val moduleDescriptor: ModuleDescriptor,
private val scriptDescriptor: ClassDescriptorWithResolutionScopes?,
val bindingContext: BindingContext
) {
val isSuccess: Boolean get() = scriptDescriptor != null
val script: ClassDescriptorWithResolutionScopes get() = scriptDescriptor ?: error("Error occurred")
}
protected fun analysisImpl(psi: KtFile): JsScriptAnalysisResult {
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
val project = environment.project
val builtIns: KotlinBuiltIns = dependencies.single { it.allDependencyModules.isEmpty() }.builtIns
val moduleContext = ContextForNewModule(
ProjectContext(project, "TopDownAnalyzer for JS Script"),
Name.special("<script>"),
builtIns,
platform = null
)
val languageVersionSettings = environment.configuration.languageVersionSettings
val lookupTracker = LookupTracker.DO_NOTHING
val expectActualTracker = ExpectActualTracker.DoNothing
val additionalPackages = emptyList<PackageFragmentProvider>()
val moduleDescriptor = moduleContext.module
moduleDescriptor.setDependencies(dependencies.map { it as ModuleDescriptorImpl } + moduleDescriptor)
val analyzer = createTopDownAnalyzerForJs(
moduleContext, trace,
FileBasedDeclarationProviderFactory(moduleContext.storageManager, listOf(psi)),
languageVersionSettings,
lookupTracker,
expectActualTracker,
additionalPackages
)
val analyzerContext = analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, listOf(psi))
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
val scriptDescriptor = analyzerContext.scripts[psi.script]
assert(scriptDescriptor != null || hasErrors) { "If no errors occurred script descriptor has to be existed" }
return JsScriptAnalysisResult(moduleDescriptor, scriptDescriptor, trace.bindingContext)
}
}
@@ -0,0 +1,61 @@
/*
* 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 org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptCompilerProxy
import org.jetbrains.kotlin.scripting.repl.js.JsCompiledScript
import org.jetbrains.kotlin.scripting.repl.js.JsCoreScriptingCompiler
import org.jetbrains.kotlin.scripting.repl.js.JsScriptDependencyCompiler
import org.jetbrains.kotlin.scripting.repl.js.readLibrariesFromConfiguration
import kotlin.script.experimental.api.*
class JsScriptCompilerWithDependenciesProxy(private val environment: KotlinCoreEnvironment) : ScriptCompilerProxy {
private val nameTables = NameTables(emptyList())
private val symbolTable = SymbolTable()
private val dependencies: List<ModuleDescriptor> = readLibrariesFromConfiguration(environment.configuration)
private val compiler = JsCoreScriptingCompiler(environment, nameTables, symbolTable, dependencies)
private var scriptDependencyCompiler: JsScriptDependencyCompiler? =
JsScriptDependencyCompiler(environment.configuration, nameTables, symbolTable)
override fun compile(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
val parentMessageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]
return withMessageCollector(script = script, parentMessageCollector = parentMessageCollector) { messageCollector ->
environment.configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
try {
val dependenciesCode = scriptDependencyCompiler?.let { scriptDependencyCompiler = null; it.compile(dependencies) } ?: ""
when (val compileResult = compiler.compile(org.jetbrains.kotlin.scripting.repl.js.makeReplCodeLine(0, script.text))) {
is ReplCompileResult.CompiledClasses -> {
val compileJsCode = compileResult.data as String
ResultWithDiagnostics.Success(
JsCompiledScript(dependenciesCode + "\n" + compileJsCode, scriptCompilationConfiguration)
)
}
is ReplCompileResult.Incomplete -> ResultWithDiagnostics.Failure(
ScriptDiagnostic("Incomplete code")
)
is ReplCompileResult.Error -> ResultWithDiagnostics.Failure(
ScriptDiagnostic(
message = compileResult.message,
severity = ScriptDiagnostic.Severity.ERROR
)
)
}
} finally {
if (parentMessageCollector != null)
environment.configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, parentMessageCollector)
}
}
}
}
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptJvmCompilerProxy
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptCompilerProxy
import org.jetbrains.kotlin.scripting.compiler.plugin.dependencies.ScriptsCompilationDependencies
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import kotlin.script.experimental.api.*
@@ -30,7 +30,7 @@ import kotlin.script.experimental.jvm.compilationCache
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jvm
class ScriptJvmCompilerIsolated(val hostConfiguration: ScriptingHostConfiguration) : ScriptJvmCompilerProxy {
class ScriptJvmCompilerIsolated(val hostConfiguration: ScriptingHostConfiguration) : ScriptCompilerProxy {
override fun compile(
script: SourceCode,
@@ -50,7 +50,7 @@ class ScriptJvmCompilerIsolated(val hostConfiguration: ScriptingHostConfiguratio
}
}
class ScriptJvmCompilerFromEnvironment(val environment: KotlinCoreEnvironment) : ScriptJvmCompilerProxy {
class ScriptJvmCompilerFromEnvironment(val environment: KotlinCoreEnvironment) : ScriptCompilerProxy {
override fun compile(
script: SourceCode,
@@ -9,40 +9,36 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
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.repl.*
import org.jetbrains.kotlin.cli.common.repl.LineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.generateJsCode
import org.jetbrains.kotlin.ir.backend.js.generateModuleFragment
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.IrDeserializer
import org.jetbrains.kotlin.ir.util.SymbolTable
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.serialization.js.ModuleKind
import kotlin.script.experimental.api.valueOr
import kotlin.script.experimental.host.StringScriptSource
class DeserializerWithDependencies(val deserializer: IrDeserializer, val dependencies: List<IrModuleFragment>)
class CoreScriptingJsCompiler(
class JsCoreScriptingCompiler(
private val environment: KotlinCoreEnvironment,
private val nameTables: NameTables,
private val symbolTable: SymbolTable,
private val dependencyDescriptors: List<ModuleDescriptor>,
private val createDeserializer: (ModuleDescriptor, SymbolTable, IrBuiltIns) -> DeserializerWithDependencies? = { _, _, _ -> null }
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState = ReplCodeAnalyzer.ResettableAnalyzerState()
) {
private val analyzerEngine: JsReplCodeAnalyzer = JsReplCodeAnalyzer(environment.project, dependencyDescriptors)
private val symbolTable: SymbolTable = SymbolTable()
fun compile(codeLine: ReplCodeLine): ReplCompileResult {
val snippet = codeLine.code
val snippetId = codeLine.no
val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY] as MessageCollector
setIdeaIoUseFallback()
val sourceCode = StringScriptSource(snippet, "line-$snippetId.kts")
@@ -52,28 +48,20 @@ class CoreScriptingJsCompiler(
environment.project
).valueOr { return ReplCompileResult.Error(it.reports.joinToString { r -> r.message }) }
analyzerEngine.analyzeReplLine(snippetKtFile, codeLine).also {
AnalyzerWithCompilerReport.reportDiagnostics(it, messageCollector)
val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY] as MessageCollector
val analyzerEngine = JsReplCodeAnalyzer(environment, dependencyDescriptors, replState)
val analysisResult = analyzerEngine.analyzeReplLine(snippetKtFile, codeLine).also {
AnalyzerWithCompilerReport.reportDiagnostics(it.bindingContext.diagnostics, messageCollector)
if (messageCollector.hasErrors()) return ReplCompileResult.Error("Error while analysis")
}
val module = analysisResult.moduleDescriptor
val bindingContext = analysisResult.bindingContext
val psi2ir = Psi2IrTranslator(environment.configuration.languageVersionSettings)
val psi2irContext = psi2ir.createGeneratorContext(
analyzerEngine.context.module,
analyzerEngine.trace.bindingContext,
symbolTable
)
val deserializerWithDependencies = createDeserializer(psi2irContext.moduleDescriptor, symbolTable, psi2irContext.irBuiltIns)
val irModuleFragment = psi2irContext.generateModuleFragment(listOf(snippetKtFile), deserializerWithDependencies?.deserializer)
val deserializedFragments = deserializerWithDependencies?.dependencies ?: emptyList()
val irFiles = sortDependencies(deserializedFragments).flatMap { it.files } + irModuleFragment.files
irModuleFragment.files.clear()
irModuleFragment.files += irFiles
val psi2irContext = psi2ir.createGeneratorContext(module, bindingContext, symbolTable)
val irModuleFragment = psi2irContext.generateModuleFragment(listOf(snippetKtFile))
val context = JsIrBackendContext(
irModuleFragment.descriptor,
@@ -88,28 +76,13 @@ class CoreScriptingJsCompiler(
ExternalDependenciesGenerator(
irModuleFragment.descriptor,
psi2irContext.symbolTable,
psi2irContext.irBuiltIns,
deserializer = deserializerWithDependencies?.deserializer
psi2irContext.irBuiltIns
).generateUnboundSymbolsAsDependencies()
with(context.implicitDeclarationFile) {
if (!irModuleFragment.files.contains(this)) {
irModuleFragment.files += this
}
}
context.implicitDeclarationFile.declarations.clear()
environment.configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
val code = compileForRepl(
context,
irModuleFragment,
nameTables
)
val code = generateJsCode(context, irModuleFragment, nameTables)
return createCompileResult(
LineId(codeLine),
code
)
return createCompileResult(LineId(codeLine), code)
}
}
@@ -1,35 +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.repl.js
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import java.util.concurrent.locks.ReentrantReadWriteLock
class JsDebuggerCompiler(
environment: KotlinCoreEnvironment,
loadNames: NameTables
) : ReplCompiler {
// TODO: configure dependencies
private val compiler = CoreScriptingJsCompiler(
environment,
loadNames,
readLibrariesFromConfiguration(environment.configuration)
)
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> {
return JsState(lock)
}
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
return ReplCheckResult.Ok()
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
return compiler.compile(codeLine)
}
}
@@ -5,82 +5,33 @@
package org.jetbrains.kotlin.scripting.repl.js
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.context.ContextForNewModule
import org.jetbrains.kotlin.context.MutableModuleContext
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.frontend.js.di.createTopDownAnalyzerForJs
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.AbstractJsScriptlikeCodeAnalyser
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzer
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
class JsReplCodeAnalyzer(private val project: Project, private val dependencies: List<ModuleDescriptor>) {
private val replState = ReplCodeAnalyzer.ResettableAnalyzerState()
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
private val builtIns: KotlinBuiltIns = dependencies.single { it.allDependencyModules.isEmpty() }.builtIns
lateinit var context: MutableModuleContext
private fun createTopDownAnalyzerJS(files: Collection<KtFile>): LazyTopDownAnalyzer {
context = ContextForNewModule(
ProjectContext(project, "TopDownAnalyzer for JS"),
Name.special("<script>"),
builtIns,
platform = null
)
val languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT
val lookupTracker = LookupTracker.DO_NOTHING
val expectActualTracker = ExpectActualTracker.DoNothing
val additionalPackages = mutableListOf<PackageFragmentProvider>()
context.module.setDependencies(dependencies.map { it as ModuleDescriptorImpl } + context.module)
return createTopDownAnalyzerForJs(
context, trace,
FileBasedDeclarationProviderFactory(context.storageManager, files),
languageVersionSettings,
lookupTracker,
expectActualTracker,
additionalPackages
)
}
fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): Diagnostics {
trace.clearDiagnostics()
class JsReplCodeAnalyzer(
environment: KotlinCoreEnvironment,
dependencies: List<ModuleDescriptor>,
private val replState: ReplCodeAnalyzer.ResettableAnalyzerState
) : AbstractJsScriptlikeCodeAnalyser(environment, dependencies) {
fun analyzeReplLine(linePsi: KtFile, codeLine: ReplCodeLine): AnalysisResult {
linePsi.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
replState.submitLine(linePsi, codeLine)
val analyzer = createTopDownAnalyzerJS(listOf(linePsi))
val context = analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, listOf(linePsi))
val result = analysisImpl(linePsi)
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
if (hasErrors) {
replState.lineFailure(linePsi, codeLine)
return if (result.isSuccess) {
replState.lineSuccess(linePsi, codeLine, result.script)
AnalysisResult.success(result.bindingContext, result.moduleDescriptor)
} else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
replState.lineFailure(linePsi, codeLine)
AnalysisResult.compilationError(result.bindingContext)
}
return diagnostics
}
}
@@ -7,21 +7,22 @@ package org.jetbrains.kotlin.scripting.repl.js
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
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 java.util.concurrent.locks.ReentrantReadWriteLock
class JsReplCompiler(
environment: KotlinCoreEnvironment
) : ReplCompiler {
private val nameTables: NameTables = NameTables(emptyList())
private val dependencies: List<ModuleDescriptor> = readLibrariesFromConfiguration(environment.configuration)
val scriptDependencyBinary = ScriptDependencyCompiler(environment, nameTables).compile(dependencies).first
private val compiler = CoreScriptingJsCompiler(environment, nameTables, dependencies)
// Used to compile REPL code lines
class JsReplCompiler(private val environment: KotlinCoreEnvironment) : ReplCompiler {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> {
return JsState(lock)
return JsReplCompilationState(
lock,
NameTables(emptyList()),
readLibrariesFromConfiguration(environment.configuration),
ReplCodeAnalyzer.ResettableAnalyzerState(),
SymbolTable()
)
}
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
@@ -29,6 +30,13 @@ class JsReplCompiler(
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
return compiler.compile(codeLine)
val compilationState = state.asState(JsReplCompilationState::class.java)
return JsCoreScriptingCompiler(
environment,
compilationState.nameTables,
compilationState.symbolTable,
compilationState.dependencies,
compilationState.replState
).compile(codeLine)
}
}
@@ -18,13 +18,16 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.ir.backend.js.loadKlib
import org.jetbrains.kotlin.ir.backend.js.getModuleDescriptorByLibrary
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.util.SymbolTable
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.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.resolve.ScriptLightVirtualFile
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.*
import java.nio.charset.Charset
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.FileBasedScriptSource
import kotlin.script.experimental.jvm.JsDependency
@@ -180,3 +183,12 @@ class DependencyLoader {
}
}
}
class JsReplCompilationState(
lock: ReentrantReadWriteLock,
nameTables: NameTables,
dependencies: List<ModuleDescriptor>,
val replState: ReplCodeAnalyzer.ResettableAnalyzerState,
val symbolTable: SymbolTable
) : JsCompilationState(lock, nameTables, dependencies)
@@ -1,43 +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.repl.js
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import kotlin.script.experimental.api.*
class JsScriptCompiler(
environment: KotlinCoreEnvironment
) : ScriptCompiler {
private val nameTables = NameTables(emptyList())
private val dependencies: List<ModuleDescriptor> = readLibrariesFromConfiguration(environment.configuration)
private val compiler = CoreScriptingJsCompiler(environment, nameTables, dependencies)
val scriptDependencyBinary = ScriptDependencyCompiler(environment, nameTables).compile(dependencies).first
override suspend fun invoke(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
val compileResult = compiler.compile(makeReplCodeLine(0, script.text))
return when (compileResult) {
is CompiledClasses -> ResultWithDiagnostics.Success(
CompiledToJsScript(compileResult.data as String, scriptCompilationConfiguration)
)
is Incomplete -> ResultWithDiagnostics.Failure(
ScriptDiagnostic("Incomplete code")
)
is Error -> ResultWithDiagnostics.Failure(
ScriptDiagnostic(
message = compileResult.message,
severity = ScriptDiagnostic.Severity.ERROR
)
)
}
}
}
@@ -0,0 +1,86 @@
/*
* 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.repl.js
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.generateJsCode
import org.jetbrains.kotlin.ir.backend.js.emptyLoggingContext
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsMangler
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.declarations.impl.IrModuleFragmentImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.storage.LockBasedStorageManager
// Transforms klib into js code in script-friendly way
class JsScriptDependencyCompiler(
private val configuration: CompilerConfiguration,
private val nameTables: NameTables,
private val symbolTable: SymbolTable
) {
fun compile(dependencies: List<ModuleDescriptor>): String {
val builtIns: KotlinBuiltIns = dependencies.single { it.allDependencyModules.isEmpty() }.builtIns
val languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT
val moduleName = Name.special("<script-dependencies>")
val storageManager = LockBasedStorageManager.NO_LOCKS
val moduleDescriptor = ModuleDescriptorImpl(moduleName, storageManager, builtIns, null).also {
it.setDependencies(dependencies.map { d -> d as ModuleDescriptorImpl } + it)
it.initialize(PackageFragmentProvider.Empty)
}
val typeTranslator = TypeTranslator(symbolTable, languageVersionSettings, moduleDescriptor.builtIns).also {
it.constantValueGenerator = ConstantValueGenerator(moduleDescriptor, symbolTable)
}
val irBuiltIns = IrBuiltIns(builtIns, typeTranslator, symbolTable)
val jsLinker = JsIrLinker(moduleDescriptor, JsMangler, emptyLoggingContext, irBuiltIns, symbolTable)
val moduleFragment = IrModuleFragmentImpl(moduleDescriptor, irBuiltIns)
val irDependencies = dependencies.map { jsLinker.deserializeFullModule(it) }
ExternalDependenciesGenerator(
moduleDescriptor = moduleDescriptor,
symbolTable = symbolTable,
irBuiltIns = irBuiltIns,
deserializer = jsLinker
).generateUnboundSymbolsAsDependencies()
moduleFragment.patchDeclarationParents()
val backendContext = JsIrBackendContext(
moduleDescriptor,
irBuiltIns,
symbolTable,
moduleFragment,
emptySet(),
configuration,
true
)
ExternalDependenciesGenerator(
moduleDescriptor = moduleDescriptor,
symbolTable = symbolTable,
irBuiltIns = irBuiltIns,
deserializer = jsLinker
).generateUnboundSymbolsAsDependencies()
moduleFragment.patchDeclarationParents()
moduleFragment.files += irDependencies.flatMap { it.files }
configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
return generateJsCode(backendContext, moduleFragment, nameTables)
}
}
@@ -1,41 +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.repl.js
import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.backend.js.emptyLoggingContext
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsMangler
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.util.SymbolTable
class ScriptDependencyCompiler(
val environment: KotlinCoreEnvironment,
private val nameTables: NameTables = NameTables(emptyList())
) {
fun compile(dependencies: List<ModuleDescriptor>): Pair<String, NameTables> {
val compiler = CoreScriptingJsCompiler(
environment,
nameTables,
dependencies,
::createDeserializer
)
val compileResult = compiler.compile(makeReplCodeLine(0, ""))
return (compileResult as ReplCompileResult.CompiledClasses).data as String to nameTables
}
private fun createDeserializer(m: ModuleDescriptor, symbolTable: SymbolTable, irBuiltIns: IrBuiltIns): DeserializerWithDependencies {
val deserializer = JsIrLinker(m, JsMangler, emptyLoggingContext, irBuiltIns, symbolTable)
val deserializedModuleFragments = m.allDependencyModules.map {
deserializer.deserializeFullModule(it)
}
return DeserializerWithDependencies(deserializer, deserializedModuleFragments)
}
}