Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace compiler internal API with compiler main function call besides completion #709

Merged
merged 6 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 2 additions & 24 deletions common/src/main/kotlin/component/KotlinEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import com.intellij.openapi.util.Disposer
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.createPhaseConfig
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.js.K2JsIrCompiler
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
Expand All @@ -18,7 +14,6 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.ir.backend.js.jsPhases
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor
import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil
Expand All @@ -39,15 +34,14 @@ class KotlinEnvironment(
* See [org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments] and
* [org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments] for list of possible flags
*/
private val additionalCompilerArguments: List<String> = listOf(
val additionalCompilerArguments: List<String> = listOf(
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlin.time.ExperimentalTime",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlin.experimental.ExperimentalTypeInference",
"-Xcontext-receivers",
"-XXLanguage:+RangeUntilOperator"
)
}

Expand Down Expand Up @@ -81,22 +75,6 @@ class KotlinEnvironment(
put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false)
}

private val messageCollector = object : MessageCollector {
override fun clear() {}
override fun hasErrors(): Boolean {
return false
}

override fun report(
severity: CompilerMessageSeverity,
message: String,
location: CompilerMessageSourceLocation?
) {
}
}

val jsIrPhaseConfig = createPhaseConfig(jsPhases, K2JsIrCompiler().createArguments(), messageCollector)

private val environment = KotlinCoreEnvironment.createForProduction(
parentDisposable = Disposer.newDisposable(),
configuration = configuration.copy(),
Expand Down Expand Up @@ -128,4 +106,4 @@ class KotlinEnvironment(
}
}
}
}
}
132 changes: 132 additions & 0 deletions src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.compiler.server.compiler.components

import com.compiler.server.compiler.components.IndexationProvider.Companion.UNRESOLVED_REFERENCE_PREFIX
import com.compiler.server.model.CompilerDiagnostics
import com.compiler.server.model.ErrorDescriptor
import com.compiler.server.model.ProjectSeveriry
import com.compiler.server.model.TextInterval
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.CLITool
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.psi.KtFile
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import kotlin.io.path.*

private fun minusOne(value: Int) = if (value > 0) value - 1 else value

sealed class CompilationResult<out T> {
abstract val compilerDiagnostics: CompilerDiagnostics

fun <R> flatMap(action: (T) -> CompilationResult<R>): CompilationResult<R> = when (this) {
is Compiled -> {
val innerResult = action(result)
val newDiagnostics = (compilerDiagnostics.map.keys + innerResult.compilerDiagnostics.map.keys).associateWith {
val l1 = compilerDiagnostics.map[it]
val l2 = innerResult.compilerDiagnostics.map[it]
if (l1 != null && l2 != null) l1 + l2 else (l1 ?: l2)!!
}.let(::CompilerDiagnostics)
when (innerResult) {
is Compiled -> innerResult.copy(compilerDiagnostics = newDiagnostics)
is NotCompiled -> innerResult.copy(compilerDiagnostics = newDiagnostics)
}
}
is NotCompiled -> this
}
fun <R> map(action: (T) -> R): CompilationResult<R> = when (this) {
is Compiled -> Compiled(compilerDiagnostics, action(result))
is NotCompiled -> this
}
}

data class Compiled<T>(override val compilerDiagnostics: CompilerDiagnostics, val result: T) : CompilationResult<T>()

data class NotCompiled(override val compilerDiagnostics: CompilerDiagnostics) : CompilationResult<Nothing>()

fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List<Path>, arguments: List<String>): CompilationResult<Unit> = tryCompilation(inputDirectory, inputFiles, arguments) {}

fun <T> CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List<Path>, arguments: List<String>, onSuccess: () -> T): CompilationResult<T> {
fun Path.outputFilePathString() = inputDirectory.relativize(this).pathString

val diagnosticsMap = mutableMapOf<String, MutableList<ErrorDescriptor>>().apply {
inputFiles.forEach { put(it.outputFilePathString(), mutableListOf()) }
}
val defaultFileName = inputFiles.singleOrNull()?.outputFilePathString() ?: ""
val exitCode = CLITool.doMainNoExit(this, arguments.toTypedArray(), object : MessageRenderer {
override fun renderPreamble(): String = ""

override fun render(
severity: CompilerMessageSeverity,
message: String,
location: CompilerMessageSourceLocation?
): String {
val textInterval = location?.let {
TextInterval(
start = TextInterval.TextPosition(minusOne(location.line), minusOne(location.column)),
end = TextInterval.TextPosition(minusOne(location.lineEnd), minusOne(location.columnEnd))
)
}
val messageSeverity: ProjectSeveriry = when (severity) {
EXCEPTION, ERROR -> ProjectSeveriry.ERROR
STRONG_WARNING, WARNING -> ProjectSeveriry.WARNING
INFO, LOGGING, OUTPUT -> return ""
}
val errorFilePath = location?.path?.let(::Path)?.outputFilePathString() ?: defaultFileName

val className = if (!message.startsWith(UNRESOLVED_REFERENCE_PREFIX) && severity == ERROR) "red_wavy_line" else messageSeverity.name
val errorDescriptor = ErrorDescriptor(textInterval, message, messageSeverity, className)

diagnosticsMap.getOrPut(errorFilePath) { mutableListOf() }.add(errorDescriptor)
return ""
}

override fun renderUsage(usage: String): String =
render(STRONG_WARNING, usage, null)

override fun renderConclusion(): String = ""

override fun getName(): String = "Redirector"
})
val diagnostics = CompilerDiagnostics(diagnosticsMap)
return when {
diagnostics.any { it.severity == ProjectSeveriry.ERROR } -> NotCompiled(diagnostics)
exitCode.code != 0 -> ErrorDescriptor(
severity = ProjectSeveriry.ERROR,
message = "Compiler finished with non-null exit code ${exitCode.code}: ${exitCode.name}",
interval = null
).let { NotCompiled(CompilerDiagnostics(mapOf(defaultFileName to listOf(it)))) }

else -> Compiled(result = onSuccess(), compilerDiagnostics = diagnostics)
}
}

@OptIn(ExperimentalPathApi::class)
fun <T> usingTempDirectory(action: (path: Path) -> T): T {
val path = getTempDirectory()
path.createDirectories()
return try {
action(path)
} finally {
path.deleteRecursively()
}
}

private fun getTempDirectory(): Path {
val dir = System.getProperty("java.io.tmpdir")
val sessionId = UUID.randomUUID().toString().replace("-", "")
return Paths.get(dir, sessionId)
}

fun List<KtFile>.writeToIoFiles(inputDir: Path): List<Path> {
val ioFiles = map { inputDir / it.name }
for ((ioFile, ktFile) in ioFiles zip this) {
ioFile.writeText(ktFile.text)
}
return ioFiles
}

val PATH_SEPARATOR: String = java.io.File.pathSeparator
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.compiler.server.compiler.components
import com.compiler.server.compiler.KotlinFile
import com.compiler.server.compiler.KotlinResolutionFacade
import com.compiler.server.model.Analysis
import com.compiler.server.model.ErrorDescriptor
import com.compiler.server.model.CompilerDiagnostics
import com.compiler.server.model.ProjectType
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
Expand Down Expand Up @@ -72,11 +72,11 @@ class CompletionProvider(
importVariants(file, prefix, errors, line, character, projectType)
} else emptyList()
descriptorInfo.descriptors.toMutableList().apply {
sortWith(Comparator { a, b ->
sortWith { a, b ->
val (a1, a2) = a.presentableName()
val (b1, b2) = b.presentableName()
("$a1$a2").compareTo("$b1$b2", true)
})
}
}.mapNotNull { descriptor -> completionVariantFor(prefix, descriptor, element) } +
keywordsCompletionVariants(KtTokens.KEYWORDS, prefix) +
keywordsCompletionVariants(KtTokens.SOFT_KEYWORDS, prefix) +
Expand Down Expand Up @@ -113,15 +113,16 @@ class CompletionProvider(
private fun importVariants(
file: KotlinFile,
prefix: String,
errors: Map<String, List<ErrorDescriptor>>,
compilerDiagnostics: CompilerDiagnostics,
line: Int,
character: Int,
projectType: ProjectType
): List<Completion> {
val importCompletionVariants = indexationProvider.getClassesByName(prefix, projectType)
?.map { it.toCompletion() } ?: emptyList()
val currentErrors = errors[file.kotlinFile.name]?.filter {
it.interval.start.line == line &&
val currentErrors = compilerDiagnostics.map[file.kotlinFile.name]?.filter {
it.interval != null &&
it.interval.start.line == line &&
it.interval.start.ch <= character &&
it.interval.end.line == line &&
it.interval.end.ch >= character &&
Expand Down Expand Up @@ -257,7 +258,7 @@ class CompletionProvider(

// This code is a fragment of org.jetbrains.kotlin.idea.completion.CompletionSession from Kotlin IDE Plugin
// with a few simplifications which were possible because webdemo has very restricted environment (and well,
// because requirements on compeltion' quality in web-demo are lower)
// because requirements on completion' quality in web-demo are lower)
private inner class VisibilityFilter(
private val inDescriptor: DeclarationDescriptor,
private val bindingContext: BindingContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import com.intellij.psi.PsiFile
import component.KotlinEnvironment
import model.Completion
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.cli.js.klib.TopDownAnalyzerFacadeForWasmJs
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.js.klib.TopDownAnalyzerFacadeForJSIR
import org.jetbrains.kotlin.cli.js.klib.TopDownAnalyzerFacadeForWasmJs
import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
Expand All @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.incremental.components.InlineConstTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices
import org.jetbrains.kotlin.name.Name
Expand Down Expand Up @@ -60,15 +59,15 @@ class ErrorAnalyzer(
projectType: ProjectType
): ErrorsAndAnalysis {
val analysis = when {
projectType.isJvmRelated() ->analysisOf(files, coreEnvironment)
projectType.isJvmRelated() -> analysisOf(files, coreEnvironment)
projectType.isJsRelated() -> analyzeFileForJs(files, coreEnvironment)
projectType == ProjectType.WASM -> analyzeFileForWasm(files, coreEnvironment)
else -> throw IllegalArgumentException("Unknown platform: $projectType")
}
return ErrorsAndAnalysis(
errorsFrom(
analysis.analysisResult.bindingContext.diagnostics.all(),
files.associate { it.name to anylizeErrorsFrom(it, projectType) },
CompilerDiagnostics(files.associate { it.name to analyzeErrorsFrom(it, projectType) }),
projectType
),
analysis
Expand Down Expand Up @@ -233,24 +232,13 @@ class ErrorAnalyzer(

fun errorsFrom(
diagnostics: Collection<Diagnostic>,
errors: Map<String, List<ErrorDescriptor>>,
compilerDiagnostics: CompilerDiagnostics,
projectType: ProjectType
): Map<String, List<ErrorDescriptor>> {
return (errors and errorsFrom(diagnostics, projectType)).map { (fileName, errors) ->
fileName to errors.sortedWith { o1, o2 ->
val line = o1.interval.start.line.compareTo(o2.interval.start.line)
when (line) {
0 -> o1.interval.start.ch.compareTo(o2.interval.start.ch)
else -> line
}
}
}.toMap()
}

fun isOnlyWarnings(errors: Map<String, List<ErrorDescriptor>>) =
errors.none { it.value.any { error -> error.severity == ProjectSeveriry.ERROR } }
): CompilerDiagnostics = (compilerDiagnostics.map and errorsFrom(diagnostics, projectType)).map { (fileName, errors) ->
fileName to errors.sortedWith(Comparator.comparing({ it.interval?.start }, nullsFirst()))
}.toMap().let(::CompilerDiagnostics)

private fun anylizeErrorsFrom(file: PsiFile, projectType: ProjectType): List<ErrorDescriptor> {
private fun analyzeErrorsFrom(file: PsiFile, projectType: ProjectType): List<ErrorDescriptor> {
class Visitor : PsiElementVisitor() {
val errors = mutableListOf<PsiErrorElement>()
override fun visitElement(element: PsiElement) {
Expand Down Expand Up @@ -362,4 +350,4 @@ class ErrorAnalyzer(
}
}

data class ErrorsAndAnalysis(val errors: Map<String, List<ErrorDescriptor>>, val analysis: Analysis)
data class ErrorsAndAnalysis(val compilerDiagnostics: CompilerDiagnostics, val analysis: Analysis)
Loading