FIR LT: Introduce source file abstraction, carry it from parsing to IR
along with source lines mapping, allows to "emulate" usage of the PSI files which allows to extract source file and line mapping info on every stage from source element. It makes sense to use this mapping for the error reporting too.
This commit is contained in:
+91
-2
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.fir
|
||||
|
||||
import org.jetbrains.kotlin.SequentialFilePositionFinder
|
||||
import org.jetbrains.kotlin.cli.common.messages.*
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
|
||||
@@ -13,7 +12,9 @@ import org.jetbrains.kotlin.diagnostics.KtPsiDiagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
|
||||
object FirDiagnosticsCompilerResultsReporter {
|
||||
fun reportToMessageCollector(
|
||||
@@ -55,6 +56,7 @@ object FirDiagnosticsCompilerResultsReporter {
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// TODO: bring KtSourceFile and KtSourceFileLinesMapping here and rewrite reporting via it to avoid code duplication
|
||||
// NOTE: SequentialPositionFinder relies on the ascending order of the input offsets, so the code relies
|
||||
// on the the appropriate sorting above
|
||||
// Also the end offset is ignored, as it is irrelevant for the CLI reporting
|
||||
@@ -132,4 +134,91 @@ object FirDiagnosticsCompilerResultsReporter {
|
||||
|
||||
fun BaseDiagnosticsCollector.reportToMessageCollector(messageCollector: MessageCollector, renderDiagnosticName: Boolean) {
|
||||
FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(this, messageCollector, renderDiagnosticName)
|
||||
}
|
||||
}
|
||||
|
||||
private class KtSourceFilePos(val line: Int, val column: Int, val lineContent: String?) {
|
||||
|
||||
// NOTE: This method is used for presenting positions to the user
|
||||
override fun toString(): String = if (line < 0) "(offset: $column line unknown)" else "($line,$column)"
|
||||
|
||||
companion object {
|
||||
val NONE = KtSourceFilePos(-1, -1, null)
|
||||
}
|
||||
}
|
||||
|
||||
private class SequentialFilePositionFinder(file: File) : Closeable {
|
||||
|
||||
private var reader: InputStreamReader = file.reader(/* TODO: select proper charset */)
|
||||
|
||||
private var currentLineContent: String? = null
|
||||
private val buffer = CharArray(255)
|
||||
private var bufLength = -1
|
||||
private var bufPos = 0
|
||||
private var endOfStream = false
|
||||
private var skipNextLf = false
|
||||
|
||||
private var charsRead = 0
|
||||
private var currentLine = 0
|
||||
|
||||
// assuming that if called multiple times, calls should be sorted by ascending offset
|
||||
fun findNextPosition(offset: Int, withLineContents: Boolean = true): KtSourceFilePos {
|
||||
assert(offset >= charsRead - (currentLineContent?.length ?: 0))
|
||||
|
||||
fun posInCurrentLine(): KtSourceFilePos? {
|
||||
val col = offset - (charsRead - currentLineContent!!.length - 1)/* beginning of line offset */ + 1 /* col is 1-based */
|
||||
return if (col <= currentLineContent!!.length)
|
||||
KtSourceFilePos(currentLine, col, if (withLineContents) currentLineContent else null)
|
||||
else null
|
||||
}
|
||||
|
||||
if (offset < charsRead) {
|
||||
return posInCurrentLine()!!
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (currentLineContent == null) {
|
||||
currentLineContent = readNextLine()
|
||||
}
|
||||
|
||||
posInCurrentLine()?.let { return@findNextPosition it }
|
||||
|
||||
if (endOfStream) return KtSourceFilePos(-1, offset, if (withLineContents) currentLineContent else null)
|
||||
|
||||
currentLineContent = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readNextLine() = buildString {
|
||||
while (true) {
|
||||
if (bufPos >= bufLength) {
|
||||
bufLength = reader.read(buffer)
|
||||
bufPos = 0
|
||||
if (bufLength < 0) {
|
||||
endOfStream = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
val c = buffer[bufPos++]
|
||||
charsRead++
|
||||
when {
|
||||
c == '\n' && skipNextLf -> {
|
||||
skipNextLf = false
|
||||
}
|
||||
c == '\n' || c == '\r' -> {
|
||||
currentLine++
|
||||
skipNextLf = c == '\r'
|
||||
break
|
||||
}
|
||||
else -> {
|
||||
append(c)
|
||||
skipNextLf = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
reader.close()
|
||||
}
|
||||
}
|
||||
|
||||
+28
-8
@@ -7,12 +7,17 @@ package org.jetbrains.kotlin.cli.jvm.compiler
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.ProjectScope
|
||||
import org.jetbrains.kotlin.KtIoFileSourceFile
|
||||
import org.jetbrains.kotlin.KtPsiSourceFile
|
||||
import org.jetbrains.kotlin.KtSourceFile
|
||||
import org.jetbrains.kotlin.KtVirtualFileSourceFile
|
||||
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
||||
import org.jetbrains.kotlin.fir.FirModuleData
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
@@ -65,16 +70,33 @@ open class VfsBasedProjectEnvironment(
|
||||
psiFinderExtensionPoint.registerExtension(FirJavaElementFinder(firSession, project), project)
|
||||
}
|
||||
|
||||
private fun List<VirtualFile>.toSearchScope(allowOutOfProjectRoots: Boolean) =
|
||||
takeIf { it.isNotEmpty() }
|
||||
?.let {
|
||||
if (allowOutOfProjectRoots) GlobalSearchScope.filesWithLibrariesScope(project, it)
|
||||
else GlobalSearchScope.filesWithoutLibrariesScope(project, it)
|
||||
}
|
||||
?: GlobalSearchScope.EMPTY_SCOPE
|
||||
|
||||
override fun getSearchScopeByIoFiles(files: Iterable<File>, allowOutOfProjectRoots: Boolean): AbstractProjectFileSearchScope =
|
||||
PsiBasedProjectFileSearchScope(
|
||||
files
|
||||
.mapNotNull { localFileSystem.findFileByPath(it.absolutePath) }
|
||||
.toList()
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.let {
|
||||
if (allowOutOfProjectRoots) GlobalSearchScope.filesWithLibrariesScope(project, it)
|
||||
else GlobalSearchScope.filesWithoutLibrariesScope(project, it)
|
||||
} ?: GlobalSearchScope.EMPTY_SCOPE
|
||||
.toSearchScope(allowOutOfProjectRoots)
|
||||
)
|
||||
|
||||
override fun getSearchScopeBySourceFiles(files: Iterable<KtSourceFile>, allowOutOfProjectRoots: Boolean): AbstractProjectFileSearchScope =
|
||||
PsiBasedProjectFileSearchScope(
|
||||
files
|
||||
.mapNotNull {
|
||||
when (it) {
|
||||
is KtPsiSourceFile -> it.psiFile.virtualFile
|
||||
is KtVirtualFileSourceFile -> it.virtualFile
|
||||
is KtIoFileSourceFile -> localFileSystem.findFileByPath(it.file.absolutePath)
|
||||
else -> null // TODO: find out whether other use cases should be supported
|
||||
}
|
||||
}
|
||||
.toSearchScope(allowOutOfProjectRoots)
|
||||
)
|
||||
|
||||
override fun getSearchScopeByDirectories(directories: Iterable<File>): AbstractProjectFileSearchScope =
|
||||
@@ -108,8 +130,6 @@ open class VfsBasedProjectEnvironment(
|
||||
fileSearchScope: AbstractProjectFileSearchScope
|
||||
) = FirJavaFacade(firSession, baseModuleData, project.createJavaClassFinder(fileSearchScope.asPsiSearchScope()))
|
||||
|
||||
override fun getFileText(filePath: String): String? =
|
||||
localFileSystem.findFileByPath(filePath)?.inputStream?.reader(Charsets.UTF_8)?.readText()
|
||||
}
|
||||
|
||||
private fun AbstractProjectFileSearchScope.asPsiSearchScope() =
|
||||
|
||||
@@ -17,6 +17,8 @@ import com.intellij.openapi.vfs.VirtualFileSystem
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.io.URLUtil
|
||||
import org.jetbrains.kotlin.KtSourceFile
|
||||
import org.jetbrains.kotlin.KtVirtualFileSourceFile
|
||||
import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl
|
||||
@@ -105,13 +107,12 @@ fun compileModulesUsingFrontendIrAndLightTree(
|
||||
val moduleConfiguration = compilerConfiguration.copy().applyModuleProperties(module, buildFile).apply {
|
||||
addAll(JVMConfigurationKeys.FRIEND_PATHS, module.getFriendPaths())
|
||||
}
|
||||
val platformSources = linkedSetOf<File>()
|
||||
val commonSources = linkedSetOf<File>()
|
||||
val platformSources = linkedSetOf<KtSourceFile>()
|
||||
val commonSources = linkedSetOf<KtSourceFile>()
|
||||
|
||||
// !!
|
||||
compilerConfiguration.kotlinSourceRoots.forAllFiles(compilerConfiguration, projectEnvironment.project) { virtualFile, isCommon ->
|
||||
val file = File(virtualFile.canonicalPath ?: virtualFile.path)
|
||||
if (!file.isFile) error("TODO: better error: file not found $virtualFile")
|
||||
val file = KtVirtualFileSourceFile(virtualFile)
|
||||
if (isCommon) commonSources.add(file)
|
||||
else platformSources.add(file)
|
||||
}
|
||||
@@ -261,14 +262,14 @@ fun compileModuleToAnalyzedFir(
|
||||
diagnosticsReporter: DiagnosticReporter,
|
||||
performanceManager: CommonCompilerPerformanceManager?
|
||||
): ModuleCompilerAnalyzedOutput {
|
||||
var sourcesScope = environment.projectEnvironment.getSearchScopeByIoFiles(input.platformSources) //!!
|
||||
var sourcesScope = environment.projectEnvironment.getSearchScopeBySourceFiles(input.platformSources)
|
||||
val sessionProvider = FirProjectSessionProvider()
|
||||
val extendedAnalysisMode = input.configuration.getBoolean(CommonConfigurationKeys.USE_FIR_EXTENDED_CHECKERS)
|
||||
|
||||
val commonSession = runIf(
|
||||
input.commonSources.isNotEmpty() && input.configuration.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)
|
||||
) {
|
||||
val commonSourcesScope = environment.projectEnvironment.getSearchScopeByIoFiles(input.commonSources) //!!
|
||||
val commonSourcesScope = environment.projectEnvironment.getSearchScopeBySourceFiles(input.commonSources)
|
||||
sourcesScope -= commonSourcesScope
|
||||
createSession(
|
||||
"${input.targetId.name}-common",
|
||||
@@ -310,12 +311,11 @@ fun compileModuleToAnalyzedFir(
|
||||
// raw fir
|
||||
val commonRawFir = commonSession?.buildFirViaLightTree(
|
||||
input.commonSources,
|
||||
environment.projectEnvironment,
|
||||
diagnosticsReporter,
|
||||
countFilesAndLines
|
||||
)
|
||||
val rawFir =
|
||||
session.buildFirViaLightTree(input.platformSources, environment.projectEnvironment, diagnosticsReporter, countFilesAndLines)
|
||||
session.buildFirViaLightTree(input.platformSources, diagnosticsReporter, countFilesAndLines)
|
||||
|
||||
// resolution
|
||||
commonSession?.apply {
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.jvm.compiler.pipeline
|
||||
|
||||
import org.jetbrains.kotlin.KtSourceFile
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
@@ -27,9 +28,9 @@ import java.io.File
|
||||
data class ModuleCompilerInput(
|
||||
val targetId: TargetId,
|
||||
val commonPlatform: TargetPlatform,
|
||||
val commonSources: Collection<File>,
|
||||
val commonSources: Collection<KtSourceFile>,
|
||||
val platform: TargetPlatform,
|
||||
val platformSources: Collection<File>,
|
||||
val platformSources: Collection<KtSourceFile>,
|
||||
val configuration: CompilerConfiguration,
|
||||
val friendFirModules: Collection<FirModuleData> = emptyList()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user