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:
Ilya Chernikov
2022-02-19 21:07:21 +01:00
committed by teamcity
parent bd60d4b2a6
commit 03cbfea737
45 changed files with 539 additions and 239 deletions
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.DiagnosticCheckerFilter import org.jetbrains.kotlin.analysis.low.level.api.fir.api.DiagnosticCheckerFilter
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirModuleResolveState import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirModuleResolveState
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDiagnosticsForFile import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDiagnosticsForFile
import org.jetbrains.kotlin.fir.analysis.AbstractFirAnalyzerFacade
import org.jetbrains.kotlin.diagnostics.KtDiagnostic import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.fir.AbstractFirAnalyzerFacade
import org.jetbrains.kotlin.fir.backend.Fir2IrResult import org.jetbrains.kotlin.fir.backend.Fir2IrResult
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.cli.common.fir package org.jetbrains.kotlin.cli.common.fir
import org.jetbrains.kotlin.SequentialFilePositionFinder
import org.jetbrains.kotlin.cli.common.messages.* import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.diagnostics.KtDiagnostic 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.Severity
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import java.io.Closeable
import java.io.File import java.io.File
import java.io.InputStreamReader
object FirDiagnosticsCompilerResultsReporter { object FirDiagnosticsCompilerResultsReporter {
fun reportToMessageCollector( fun reportToMessageCollector(
@@ -55,6 +56,7 @@ object FirDiagnosticsCompilerResultsReporter {
) )
} }
else -> { 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 // NOTE: SequentialPositionFinder relies on the ascending order of the input offsets, so the code relies
// on the the appropriate sorting above // on the the appropriate sorting above
// Also the end offset is ignored, as it is irrelevant for the CLI reporting // 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) { fun BaseDiagnosticsCollector.reportToMessageCollector(messageCollector: MessageCollector, renderDiagnosticName: Boolean) {
FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(this, messageCollector, renderDiagnosticName) 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()
}
}
@@ -7,12 +7,17 @@ package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.psi.PsiElementFinder import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.ProjectScope 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.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
@@ -65,16 +70,33 @@ open class VfsBasedProjectEnvironment(
psiFinderExtensionPoint.registerExtension(FirJavaElementFinder(firSession, project), project) 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 = override fun getSearchScopeByIoFiles(files: Iterable<File>, allowOutOfProjectRoots: Boolean): AbstractProjectFileSearchScope =
PsiBasedProjectFileSearchScope( PsiBasedProjectFileSearchScope(
files files
.mapNotNull { localFileSystem.findFileByPath(it.absolutePath) } .mapNotNull { localFileSystem.findFileByPath(it.absolutePath) }
.toList() .toSearchScope(allowOutOfProjectRoots)
.takeIf { it.isNotEmpty() } )
?.let {
if (allowOutOfProjectRoots) GlobalSearchScope.filesWithLibrariesScope(project, it) override fun getSearchScopeBySourceFiles(files: Iterable<KtSourceFile>, allowOutOfProjectRoots: Boolean): AbstractProjectFileSearchScope =
else GlobalSearchScope.filesWithoutLibrariesScope(project, it) PsiBasedProjectFileSearchScope(
} ?: GlobalSearchScope.EMPTY_SCOPE 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 = override fun getSearchScopeByDirectories(directories: Iterable<File>): AbstractProjectFileSearchScope =
@@ -108,8 +130,6 @@ open class VfsBasedProjectEnvironment(
fileSearchScope: AbstractProjectFileSearchScope fileSearchScope: AbstractProjectFileSearchScope
) = FirJavaFacade(firSession, baseModuleData, project.createJavaClassFinder(fileSearchScope.asPsiSearchScope())) ) = 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() = private fun AbstractProjectFileSearchScope.asPsiSearchScope() =
@@ -17,6 +17,8 @@ import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.io.URLUtil 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.analyzer.common.CommonPlatformAnalyzerServices
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl
@@ -105,13 +107,12 @@ fun compileModulesUsingFrontendIrAndLightTree(
val moduleConfiguration = compilerConfiguration.copy().applyModuleProperties(module, buildFile).apply { val moduleConfiguration = compilerConfiguration.copy().applyModuleProperties(module, buildFile).apply {
addAll(JVMConfigurationKeys.FRIEND_PATHS, module.getFriendPaths()) addAll(JVMConfigurationKeys.FRIEND_PATHS, module.getFriendPaths())
} }
val platformSources = linkedSetOf<File>() val platformSources = linkedSetOf<KtSourceFile>()
val commonSources = linkedSetOf<File>() val commonSources = linkedSetOf<KtSourceFile>()
// !! // !!
compilerConfiguration.kotlinSourceRoots.forAllFiles(compilerConfiguration, projectEnvironment.project) { virtualFile, isCommon -> compilerConfiguration.kotlinSourceRoots.forAllFiles(compilerConfiguration, projectEnvironment.project) { virtualFile, isCommon ->
val file = File(virtualFile.canonicalPath ?: virtualFile.path) val file = KtVirtualFileSourceFile(virtualFile)
if (!file.isFile) error("TODO: better error: file not found $virtualFile")
if (isCommon) commonSources.add(file) if (isCommon) commonSources.add(file)
else platformSources.add(file) else platformSources.add(file)
} }
@@ -261,14 +262,14 @@ fun compileModuleToAnalyzedFir(
diagnosticsReporter: DiagnosticReporter, diagnosticsReporter: DiagnosticReporter,
performanceManager: CommonCompilerPerformanceManager? performanceManager: CommonCompilerPerformanceManager?
): ModuleCompilerAnalyzedOutput { ): ModuleCompilerAnalyzedOutput {
var sourcesScope = environment.projectEnvironment.getSearchScopeByIoFiles(input.platformSources) //!! var sourcesScope = environment.projectEnvironment.getSearchScopeBySourceFiles(input.platformSources)
val sessionProvider = FirProjectSessionProvider() val sessionProvider = FirProjectSessionProvider()
val extendedAnalysisMode = input.configuration.getBoolean(CommonConfigurationKeys.USE_FIR_EXTENDED_CHECKERS) val extendedAnalysisMode = input.configuration.getBoolean(CommonConfigurationKeys.USE_FIR_EXTENDED_CHECKERS)
val commonSession = runIf( val commonSession = runIf(
input.commonSources.isNotEmpty() && input.configuration.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects) 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 sourcesScope -= commonSourcesScope
createSession( createSession(
"${input.targetId.name}-common", "${input.targetId.name}-common",
@@ -310,12 +311,11 @@ fun compileModuleToAnalyzedFir(
// raw fir // raw fir
val commonRawFir = commonSession?.buildFirViaLightTree( val commonRawFir = commonSession?.buildFirViaLightTree(
input.commonSources, input.commonSources,
environment.projectEnvironment,
diagnosticsReporter, diagnosticsReporter,
countFilesAndLines countFilesAndLines
) )
val rawFir = val rawFir =
session.buildFirViaLightTree(input.platformSources, environment.projectEnvironment, diagnosticsReporter, countFilesAndLines) session.buildFirViaLightTree(input.platformSources, diagnosticsReporter, countFilesAndLines)
// resolution // resolution
commonSession?.apply { commonSession?.apply {
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.cli.jvm.compiler.pipeline package org.jetbrains.kotlin.cli.jvm.compiler.pipeline
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -27,9 +28,9 @@ import java.io.File
data class ModuleCompilerInput( data class ModuleCompilerInput(
val targetId: TargetId, val targetId: TargetId,
val commonPlatform: TargetPlatform, val commonPlatform: TargetPlatform,
val commonSources: Collection<File>, val commonSources: Collection<KtSourceFile>,
val platform: TargetPlatform, val platform: TargetPlatform,
val platformSources: Collection<File>, val platformSources: Collection<KtSourceFile>,
val configuration: CompilerConfiguration, val configuration: CompilerConfiguration,
val friendFirModules: Collection<FirModuleData> = emptyList() val friendFirModules: Collection<FirModuleData> = emptyList()
) )
@@ -87,7 +87,7 @@ abstract class CheckerContext : MutableDiagnosticContext() {
get() = session.languageVersionSettings get() = session.languageVersionSettings
override val containingFilePath: String? override val containingFilePath: String?
get() = containingDeclarations.firstOrNull()?.let { (it as? FirFile)?.path } get() = containingDeclarations.firstOrNull()?.let { (it as? FirFile)?.sourceFile?.path }
} }
/** /**
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.fir.pipeline package org.jetbrains.kotlin.fir.pipeline
import com.intellij.openapi.util.text.StringUtilRt import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.builder.PsiHandlingMode import org.jetbrains.kotlin.fir.builder.PsiHandlingMode
@@ -14,15 +14,12 @@ import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir
import org.jetbrains.kotlin.fir.resolve.providers.firProvider import org.jetbrains.kotlin.fir.resolve.providers.firProvider
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirProviderImpl import org.jetbrains.kotlin.fir.resolve.providers.impl.FirProviderImpl
import org.jetbrains.kotlin.fir.session.environment.AbstractProjectEnvironment
import org.jetbrains.kotlin.fir.session.sourcesToPathsMapper import org.jetbrains.kotlin.fir.session.sourcesToPathsMapper
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import java.io.File import org.jetbrains.kotlin.readSourceFileWithMapping
import java.io.FileNotFoundException
fun FirSession.buildFirViaLightTree( fun FirSession.buildFirViaLightTree(
files: Collection<File>, files: Collection<KtSourceFile>,
projectEnvironment: AbstractProjectEnvironment,
diagnosticsReporter: DiagnosticReporter? = null, diagnosticsReporter: DiagnosticReporter? = null,
reportFilesAndLines: ((Int, Int) -> Unit)? = null reportFilesAndLines: ((Int, Int) -> Unit)? = null
): List<FirFile> { ): List<FirFile> {
@@ -32,14 +29,15 @@ fun FirSession.buildFirViaLightTree(
val shouldCountLines = (reportFilesAndLines != null) val shouldCountLines = (reportFilesAndLines != null)
var linesCount = 0 var linesCount = 0
val firFiles = files.map { file -> val firFiles = files.map { file ->
val text = projectEnvironment.getFileText(file.absolutePath) ?: throw FileNotFoundException(file.path) val (code, linesMapping) = file.getContentsAsStream().reader(Charsets.UTF_8).use {
val code = StringUtilRt.convertLineSeparators(text) it.readSourceFileWithMapping()
if (shouldCountLines) {
linesCount += code.count { it == '\n' } // assuming converted line separators
} }
builder.buildFirFile(code, file.name, file.path).also { firFile -> if (shouldCountLines) {
linesCount += linesMapping.lastOffset
}
builder.buildFirFile(code, file, linesMapping).also { firFile ->
firProvider.recordFile(firFile) firProvider.recordFile(firFile)
sourcesToPathsMapper.registerFileSource(firFile.source!!, file.path) sourcesToPathsMapper.registerFileSource(firFile.source!!, file.path ?: file.name)
} }
} }
reportFilesAndLines?.invoke(files.count(), linesCount) reportFilesAndLines?.invoke(files.count(), linesCount)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.fir.session.environment package org.jetbrains.kotlin.fir.session.environment
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.java.FirJavaFacade import org.jetbrains.kotlin.fir.java.FirJavaFacade
@@ -49,6 +50,8 @@ interface AbstractProjectEnvironment {
fun getSearchScopeByIoFiles(files: Iterable<File>, allowOutOfProjectRoots: Boolean = false): AbstractProjectFileSearchScope fun getSearchScopeByIoFiles(files: Iterable<File>, allowOutOfProjectRoots: Boolean = false): AbstractProjectFileSearchScope
fun getSearchScopeBySourceFiles(files: Iterable<KtSourceFile>, allowOutOfProjectRoots: Boolean = false): AbstractProjectFileSearchScope
fun getSearchScopeByDirectories(directories: Iterable<File>): AbstractProjectFileSearchScope fun getSearchScopeByDirectories(directories: Iterable<File>): AbstractProjectFileSearchScope
fun getSearchScopeForProjectLibraries(): AbstractProjectFileSearchScope fun getSearchScopeForProjectLibraries(): AbstractProjectFileSearchScope
@@ -60,6 +63,4 @@ interface AbstractProjectEnvironment {
baseModuleData: FirModuleData, baseModuleData: FirModuleData,
fileSearchScope: AbstractProjectFileSearchScope fileSearchScope: AbstractProjectFileSearchScope
): FirJavaFacade ): FirJavaFacade
fun getFileText(filePath: String): String?
} }
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.fir.backend package org.jetbrains.kotlin.fir.backend
import org.jetbrains.kotlin.KtPsiSourceFileLinesMapping
import org.jetbrains.kotlin.KtSourceFileLinesMappingFromLineStartOffsets
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.ir.BuiltinSymbolsBase import org.jetbrains.kotlin.backend.common.ir.BuiltinSymbolsBase
@@ -167,7 +169,18 @@ class Fir2IrConverter(
private fun registerFileAndClasses(file: FirFile, moduleFragment: IrModuleFragment) { private fun registerFileAndClasses(file: FirFile, moduleFragment: IrModuleFragment) {
val fileEntry = when (file.origin) { val fileEntry = when (file.origin) {
FirDeclarationOrigin.Source -> FirDeclarationOrigin.Source ->
file.psi?.let { PsiIrFileEntry(it as KtFile) } ?: NaiveSourceBasedFileEntryImpl(file.path ?: file.name, intArrayOf(0)) file.psi?.let { PsiIrFileEntry(it as KtFile) }
?: when (val linesMapping = file.sourceFileLinesMapping) {
is KtSourceFileLinesMappingFromLineStartOffsets ->
NaiveSourceBasedFileEntryImpl(
file.sourceFile?.path ?: file.sourceFile?.name ?: file.name,
linesMapping.lineStartOffsets,
linesMapping.lastOffset
)
is KtPsiSourceFileLinesMapping -> PsiIrFileEntry(linesMapping.psiFile)
else ->
NaiveSourceBasedFileEntryImpl(file.sourceFile?.path ?: file.sourceFile?.name ?: file.name)
}
FirDeclarationOrigin.Synthetic -> NaiveSourceBasedFileEntryImpl(file.name) FirDeclarationOrigin.Synthetic -> NaiveSourceBasedFileEntryImpl(file.name)
else -> error("Unsupported file origin: ${file.origin}") else -> error("Unsupported file origin: ${file.origin}")
} }
@@ -7,9 +7,10 @@ package org.jetbrains.kotlin.fir.lightTree
import com.intellij.lang.LighterASTNode import com.intellij.lang.LighterASTNode
import com.intellij.lang.impl.PsiBuilderFactoryImpl import com.intellij.lang.impl.PsiBuilderFactoryImpl
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.util.diff.FlyweightCapableTreeStructure import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.KtIoFileSourceFile
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.diagnostics.DiagnosticContext import org.jetbrains.kotlin.diagnostics.DiagnosticContext
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.fir.scopes.FirScopeProvider
import org.jetbrains.kotlin.lexer.KotlinLexer import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.parsing.KotlinLightParser import org.jetbrains.kotlin.parsing.KotlinLightParser
import org.jetbrains.kotlin.parsing.KotlinParserDefinition import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.readSourceFileWithMapping
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
@@ -46,7 +48,7 @@ class LightTree2Fir(
return builder.lightTree return builder.lightTree
} }
fun buildLightTree(code: String): FlyweightCapableTreeStructure<LighterASTNode> { fun buildLightTree(code: CharSequence): FlyweightCapableTreeStructure<LighterASTNode> {
val builder = PsiBuilderFactoryImpl().createBuilder(parserDefinition, makeLexer(), code) val builder = PsiBuilderFactoryImpl().createBuilder(parserDefinition, makeLexer(), code)
KotlinLightParser.parse(builder) KotlinLightParser.parse(builder)
return builder.lightTree return builder.lightTree
@@ -58,25 +60,25 @@ class LightTree2Fir(
} }
fun buildFirFile(file: File): FirFile { fun buildFirFile(file: File): FirFile {
val code = FileUtil.loadFile(file, CharsetToolkit.UTF8, true) val sourceFile = KtIoFileSourceFile(file)
return buildFirFile(code, file.name, file.path) val (code, linesMapping) = with(file.inputStream().reader(Charsets.UTF_8)) {
this.readSourceFileWithMapping()
}
return buildFirFile(code, sourceFile, linesMapping)
} }
fun buildFirFile(lightTreeFile: LightTreeFile): FirFile = with(lightTreeFile) { fun buildFirFile(
lightTree: FlyweightCapableTreeStructure<LighterASTNode>,
sourceFile: KtSourceFile,
linesMapping: KtSourceFileLinesMapping
): FirFile =
DeclarationsConverter( DeclarationsConverter(
session, scopeProvider, lightTree, diagnosticsReporter = diagnosticsReporter, session, scopeProvider, lightTree, diagnosticsReporter = diagnosticsReporter,
diagnosticContext = makeDiagnosticContext(path) diagnosticContext = makeDiagnosticContext(sourceFile.path)
).convertFile(lightTree.root, fileName, path) ).convertFile(lightTree.root, sourceFile, linesMapping)
}
fun buildFirFile(code: String, fileName: String, path: String?): FirFile { fun buildFirFile(code: CharSequence, sourceFile: KtSourceFile, linesMapping: KtSourceFileLinesMapping): FirFile =
val lightTree = buildLightTree(code) buildFirFile(buildLightTree(code), sourceFile, linesMapping)
return DeclarationsConverter(
session, scopeProvider, lightTree, diagnosticsReporter = diagnosticsReporter,
diagnosticContext = makeDiagnosticContext(path)
).convertFile(lightTree.root, fileName, path)
}
private fun makeDiagnosticContext(path: String?) = private fun makeDiagnosticContext(path: String?) =
if (diagnosticsReporter == null) null else object : DiagnosticContext { if (diagnosticsReporter == null) null else object : DiagnosticContext {
@@ -86,9 +88,3 @@ class LightTree2Fir(
} }
} }
data class LightTreeFile(
val lightTree: FlyweightCapableTreeStructure<LighterASTNode>,
val fileName: String,
val path: String?
)
@@ -88,7 +88,7 @@ class DeclarationsConverter(
* [org.jetbrains.kotlin.parsing.KotlinParsing.parseFile] * [org.jetbrains.kotlin.parsing.KotlinParsing.parseFile]
* [org.jetbrains.kotlin.parsing.KotlinParsing.parsePreamble] * [org.jetbrains.kotlin.parsing.KotlinParsing.parsePreamble]
*/ */
fun convertFile(file: LighterASTNode, fileName: String = "", filePath: String?): FirFile { fun convertFile(file: LighterASTNode, sourceFile: KtSourceFile, linesMapping: KtSourceFileLinesMapping): FirFile {
if (file.tokenType != KT_FILE) { if (file.tokenType != KT_FILE) {
//TODO throw error //TODO throw error
throw Exception() throw Exception()
@@ -123,8 +123,9 @@ class DeclarationsConverter(
source = file.toFirSourceElement() source = file.toFirSourceElement()
origin = FirDeclarationOrigin.Source origin = FirDeclarationOrigin.Source
moduleData = baseModuleData moduleData = baseModuleData
name = fileName name = sourceFile.name
path = filePath this.sourceFile = sourceFile
this.sourceFileLinesMapping = linesMapping
this.packageDirective = packageDirective ?: buildPackageDirective { packageFqName = context.packageFqName } this.packageDirective = packageDirective ?: buildPackageDirective { packageFqName = context.packageFqName }
annotations += fileAnnotationList annotations += fileAnnotationList
imports += importList imports += importList
@@ -10,11 +10,15 @@ import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.impl.DebugUtil import com.intellij.psi.impl.DebugUtil
import com.intellij.testFramework.TestDataPath import com.intellij.testFramework.TestDataPath
import com.intellij.util.PathUtil import com.intellij.util.PathUtil
import org.jetbrains.kotlin.KtIoFileSourceFile
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.fir.FirRenderer import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.builder.AbstractRawFirBuilderTestCase import org.jetbrains.kotlin.fir.builder.AbstractRawFirBuilderTestCase
import org.jetbrains.kotlin.fir.builder.StubFirScopeProvider import org.jetbrains.kotlin.fir.builder.StubFirScopeProvider
import org.jetbrains.kotlin.fir.session.FirSessionFactory import org.jetbrains.kotlin.fir.session.FirSessionFactory
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.readSourceFileWithMapping
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.File import java.io.File
@@ -33,12 +37,15 @@ class TotalKotlinTest : AbstractRawFirBuilderTestCase() {
} }
} }
private fun generateFirFromLightTree(onlyLightTree: Boolean, converter: LightTree2Fir, text: String, fileName: String, filePath: String) { private fun generateFirFromLightTree(
onlyLightTree: Boolean, converter: LightTree2Fir,
text: CharSequence, sourceFile: KtSourceFile, linesMapping: KtSourceFileLinesMapping
) {
if (onlyLightTree) { if (onlyLightTree) {
val lightTree = LightTree2Fir.buildLightTree(text) val lightTree = LightTree2Fir.buildLightTree(text)
DebugUtil.lightTreeToString(lightTree, false) DebugUtil.lightTreeToString(lightTree, false)
} else { } else {
val firFile = converter.buildFirFile(text, fileName, filePath) val firFile = converter.buildFirFile(text, sourceFile, linesMapping)
StringBuilder().also { FirRenderer(it).visitFile(firFile) }.toString() StringBuilder().also { FirRenderer(it).visitFile(firFile) }.toString()
} }
} }
@@ -57,9 +64,12 @@ class TotalKotlinTest : AbstractRawFirBuilderTestCase() {
if (onlyLightTree) println("LightTree generation") else println("Fir from LightTree converter") if (onlyLightTree) println("LightTree generation") else println("Fir from LightTree converter")
println("BASE PATH: $path") println("BASE PATH: $path")
path.walkTopDown { path.walkTopDown {
val text = FileUtil.loadFile(it, CharsetToolkit.UTF8, true).trim() val sourceFile = KtIoFileSourceFile(it)
val (code, linesMapping) = with(it.inputStream().reader(Charsets.UTF_8)) {
this.readSourceFileWithMapping()
}
time += measureNanoTime { time += measureNanoTime {
generateFirFromLightTree(onlyLightTree, lightTreeConverter, text, it.name, it.path) generateFirFromLightTree(onlyLightTree, lightTreeConverter, code, sourceFile, linesMapping)
} }
counter++ counter++
@@ -10,6 +10,8 @@ import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.testFramework.TestDataPath import com.intellij.testFramework.TestDataPath
import com.intellij.util.PathUtil import com.intellij.util.PathUtil
import junit.framework.TestCase import junit.framework.TestCase
import org.jetbrains.kotlin.KtInMemoryTextSourceFile
import org.jetbrains.kotlin.KtIoFileSourceFile
import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest.Companion.DIAGNOSTIC_IN_TESTDATA_PATTERN import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest.Companion.DIAGNOSTIC_IN_TESTDATA_PATTERN
import org.jetbrains.kotlin.fir.FirRenderer import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.builder.AbstractRawFirBuilderTestCase import org.jetbrains.kotlin.fir.builder.AbstractRawFirBuilderTestCase
@@ -19,7 +21,9 @@ import org.jetbrains.kotlin.fir.lightTree.walkTopDown
import org.jetbrains.kotlin.fir.lightTree.walkTopDownWithTestData import org.jetbrains.kotlin.fir.lightTree.walkTopDownWithTestData
import org.jetbrains.kotlin.fir.session.FirSessionFactory import org.jetbrains.kotlin.fir.session.FirSessionFactory
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.readSourceFileWithMapping
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
import org.jetbrains.kotlin.toSourceLinesMapping
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.File import java.io.File
@@ -61,15 +65,17 @@ class TreesCompareTest : AbstractRawFirBuilderTestCase() {
diagnosticsReporter = null diagnosticsReporter = null
) )
compareBase(System.getProperty("user.dir"), withTestData = false) { file -> compareBase(System.getProperty("user.dir"), withTestData = false) { file ->
val text = FileUtil.loadFile(file, CharsetToolkit.UTF8, true).trim() val (text, linesMapping) = with(file.inputStream().reader(Charsets.UTF_8)) {
this.readSourceFileWithMapping()
}
//psi //psi
val ktFile = createPsiFile(FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.path)), text) as KtFile val ktFile = createPsiFile(FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.path)), text.toString().trim()) as KtFile
val firFileFromPsi = ktFile.toFirFile() val firFileFromPsi = ktFile.toFirFile()
val treeFromPsi = StringBuilder().also { FirRenderer(it).visitFile(firFileFromPsi) }.toString() val treeFromPsi = StringBuilder().also { FirRenderer(it).visitFile(firFileFromPsi) }.toString()
//light tree //light tree
val firFileFromLightTree = lightTreeConverter.buildFirFile(text, file.name, file.path) val firFileFromLightTree = lightTreeConverter.buildFirFile(text, KtIoFileSourceFile(file), linesMapping)
val treeFromLightTree = StringBuilder().also { FirRenderer(it).visitFile(firFileFromLightTree) }.toString() val treeFromLightTree = StringBuilder().also { FirRenderer(it).visitFile(firFileFromLightTree) }.toString()
return@compareBase treeFromLightTree == treeFromPsi return@compareBase treeFromLightTree == treeFromPsi
@@ -100,7 +106,12 @@ class TreesCompareTest : AbstractRawFirBuilderTestCase() {
.replace("<Unsupported LValue.*?>".toRegex(), "<Unsupported LValue>") .replace("<Unsupported LValue.*?>".toRegex(), "<Unsupported LValue>")
//light tree //light tree
val firFileFromLightTree = lightTreeConverter.buildFirFile(text, file.name, file.path) val firFileFromLightTree =
lightTreeConverter.buildFirFile(
text,
KtInMemoryTextSourceFile(file.name, file.path, text),
text.toSourceLinesMapping()
)
val treeFromLightTree = StringBuilder().also { FirRenderer(it).visitFile(firFileFromLightTree) }.toString() val treeFromLightTree = StringBuilder().also { FirRenderer(it).visitFile(firFileFromLightTree) }.toString()
.replace("<Unsupported LValue.*?>".toRegex(), "<Unsupported LValue>") .replace("<Unsupported LValue.*?>".toRegex(), "<Unsupported LValue>")
@@ -911,7 +911,8 @@ open class RawFirBuilder(
moduleData = baseModuleData moduleData = baseModuleData
origin = FirDeclarationOrigin.Source origin = FirDeclarationOrigin.Source
name = file.name name = file.name
path = file.virtualFile?.path sourceFile = KtPsiSourceFile(file)
sourceFileLinesMapping = KtPsiSourceFileLinesMapping(file)
packageDirective = buildPackageDirective { packageDirective = buildPackageDirective {
packageFqName = context.packageFqName packageFqName = context.packageFqName
source = file.packageDirective?.toKtPsiSourceElement() source = file.packageDirective?.toKtPsiSourceElement()
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.fir.declarations package org.jetbrains.kotlin.fir.declarations
import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.FirPackageDirective import org.jetbrains.kotlin.fir.FirPackageDirective
@@ -29,7 +31,8 @@ abstract class FirFile : FirDeclaration() {
abstract val imports: List<FirImport> abstract val imports: List<FirImport>
abstract val declarations: List<FirDeclaration> abstract val declarations: List<FirDeclaration>
abstract val name: String abstract val name: String
abstract val path: String? abstract val sourceFile: KtSourceFile?
abstract val sourceFileLinesMapping: KtSourceFileLinesMapping?
abstract override val symbol: FirFileSymbol abstract override val symbol: FirFileSymbol
override fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visitFile(this, data) override fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visitFile(this, data)
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.fir.declarations.builder
import kotlin.contracts.* import kotlin.contracts.*
import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.FirPackageDirective import org.jetbrains.kotlin.fir.FirPackageDirective
import org.jetbrains.kotlin.fir.builder.FirAnnotationContainerBuilder import org.jetbrains.kotlin.fir.builder.FirAnnotationContainerBuilder
@@ -39,7 +41,8 @@ class FirFileBuilder : FirAnnotationContainerBuilder {
val imports: MutableList<FirImport> = mutableListOf() val imports: MutableList<FirImport> = mutableListOf()
val declarations: MutableList<FirDeclaration> = mutableListOf() val declarations: MutableList<FirDeclaration> = mutableListOf()
lateinit var name: String lateinit var name: String
var path: String? = null var sourceFile: KtSourceFile? = null
var sourceFileLinesMapping: KtSourceFileLinesMapping? = null
override fun build(): FirFile { override fun build(): FirFile {
return FirFileImpl( return FirFileImpl(
@@ -53,7 +56,8 @@ class FirFileBuilder : FirAnnotationContainerBuilder {
imports, imports,
declarations, declarations,
name, name,
path, sourceFile,
sourceFileLinesMapping,
) )
} }
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.fir.declarations.impl package org.jetbrains.kotlin.fir.declarations.impl
import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.FirPackageDirective import org.jetbrains.kotlin.fir.FirPackageDirective
import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirDeclaration
@@ -35,7 +37,8 @@ internal class FirFileImpl(
override val imports: MutableList<FirImport>, override val imports: MutableList<FirImport>,
override val declarations: MutableList<FirDeclaration>, override val declarations: MutableList<FirDeclaration>,
override val name: String, override val name: String,
override val path: String?, override val sourceFile: KtSourceFile?,
override val sourceFileLinesMapping: KtSourceFileLinesMapping?,
) : FirFile() { ) : FirFile() {
override val symbol: FirFileSymbol = FirFileSymbol() override val symbol: FirFileSymbol = FirFileSymbol()
@@ -415,7 +415,8 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
+fieldList(import).withTransform() +fieldList(import).withTransform()
+declarations.withTransform() +declarations.withTransform()
+stringField("name") +stringField("name")
+stringField("path", nullable = true) +field("sourceFile", sourceFileType, nullable = true)
+field("sourceFileLinesMapping", sourceFileLinesMappingType, nullable = true)
+symbol("FirFileSymbol") +symbol("FirFileSymbol")
} }
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.fir.tree.generator package org.jetbrains.kotlin.fir.tree.generator
import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
@@ -23,6 +25,8 @@ import org.jetbrains.kotlin.types.SmartcastStability
import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.Variance
val sourceElementType = type(KtSourceElement::class) val sourceElementType = type(KtSourceElement::class)
val sourceFileType = type(KtSourceFile::class)
val sourceFileLinesMappingType = type(KtSourceFileLinesMapping::class)
val jumpTargetType = type("fir", "FirTarget") val jumpTargetType = type("fir", "FirTarget")
val constKindType = type("types", "ConstantValueKind") val constKindType = type("types", "ConstantValueKind")
val operationType = type("fir.expressions", "FirOperation") val operationType = type("fir.expressions", "FirOperation")
@@ -14,9 +14,6 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.IElementType
import com.intellij.util.diff.FlyweightCapableTreeStructure import com.intellij.util.diff.FlyweightCapableTreeStructure
import java.io.Closeable
import java.io.File
import java.io.InputStreamReader
sealed class KtSourceElementKind sealed class KtSourceElementKind
@@ -444,90 +441,3 @@ inline fun LighterASTNode.toKtLightSourceElement(
endOffset: Int = this.endOffset endOffset: Int = this.endOffset
): KtLightSourceElement = KtLightSourceElement(this, startOffset, endOffset, tree, kind) ): KtLightSourceElement = KtLightSourceElement(this, startOffset, endOffset, tree, kind)
class KtSourceFilePosition(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 = KtSourceFilePosition(-1, -1, null)
}
}
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): KtSourceFilePosition {
assert(offset >= charsRead - (currentLineContent?.length ?: 0))
fun posInCurrentLine(): KtSourceFilePosition? {
val col = offset - (charsRead - currentLineContent!!.length - 1)/* beginning of line offset */ + 1 /* col is 1-based */
return if (col <= currentLineContent!!.length)
KtSourceFilePosition(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 KtSourceFilePosition(-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()
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2022 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
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStream
interface KtSourceFile {
val name: String
val path: String?
fun getContentsAsStream(): InputStream
}
class KtPsiSourceFile(val psiFile: PsiFile) : KtSourceFile {
override val name: String
get() = psiFile.name
override val path: String?
get() = psiFile.virtualFile?.path
override fun getContentsAsStream(): InputStream = psiFile.virtualFile.inputStream
}
class KtVirtualFileSourceFile(val virtualFile: VirtualFile) : KtSourceFile {
override val name: String
get() = virtualFile.name
override val path: String
get() = virtualFile.path
override fun getContentsAsStream(): InputStream = virtualFile.inputStream
}
class KtIoFileSourceFile(val file: File) : KtSourceFile {
override val name: String
get() = file.name
override val path: String
get() = FileUtilRt.toSystemIndependentName(file.path)
override fun getContentsAsStream(): InputStream = file.inputStream()
}
class KtInMemoryTextSourceFile(
override val name: String,
override val path: String?,
val text: CharSequence
) : KtSourceFile {
override fun getContentsAsStream(): InputStream = ByteArrayInputStream(text.toString().toByteArray())
}
@@ -0,0 +1,126 @@
/*
* Copyright 2010-2022 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
import com.intellij.openapi.editor.Document
import com.intellij.psi.PsiFile
import java.io.InputStreamReader
interface KtSourceFileLinesMapping {
fun getLineStartOffset(line: Int): Int
fun getLineAndColumnByOffset(offset: Int): Pair<Int, Int>
fun getLineByOffset(offset: Int): Int
val lastOffset: Int
val linesCount: Int
}
class KtPsiSourceFileLinesMapping(val psiFile: PsiFile) : KtSourceFileLinesMapping {
private val document: Document? by lazy { psiFile.viewProvider.document }
override fun getLineStartOffset(line: Int): Int =
document?.getLineStartOffset(line) ?: -1
override fun getLineAndColumnByOffset(offset: Int): Pair<Int, Int> =
document?.let {
val lineNumber = it.getLineNumber(offset)
val lineStartOffset = it.getLineStartOffset(lineNumber)
lineNumber to offset - lineStartOffset
} ?: (-1 to -1)
override fun getLineByOffset(offset: Int): Int =
document?.getLineNumber(offset) ?: -1
override val lastOffset: Int
get() = document?.textLength ?: -1
override val linesCount: Int
get() = document?.lineCount ?: 0
}
open class KtSourceFileLinesMappingFromLineStartOffsets(
val lineStartOffsets: IntArray, override val lastOffset: Int
) : KtSourceFileLinesMapping {
override fun getLineStartOffset(line: Int): Int = lineStartOffsets[line]
override fun getLineAndColumnByOffset(offset: Int): Pair<Int, Int> {
val lineNumber = getLineByOffset(offset)
if (lineNumber < 0) return -1 to -1
val lineStartOffset = lineStartOffsets[lineNumber]
return lineNumber to offset - lineStartOffset
}
override fun getLineByOffset(offset: Int): Int {
val index = lineStartOffsets.binarySearch(offset)
return if (index >= 0) index else -index - 2
}
override val linesCount: Int
get() = lineStartOffsets.size
}
/**
* Reads file contents from reader, converts line separators and calculates source lines to file offsets mapping
*
* Returns KtSourceFileLinesMapping and char sequence (StringBuilder to avoid premature copying) containing converted text
* The separators are converted similarly to the com.intellij.openapi.util.text.StringUtilRt algorithms
*/
fun InputStreamReader.readSourceFileWithMapping(): Pair<CharSequence, KtSourceFileLinesMapping> {
val buffer = CharArray(255)
var bufLength = -1
var bufPos = 0
var skipNextLf = false
var charsRead = 0
val lineOffsets = mutableListOf(0) // TODO: consider using implicit first line offset (needs to be handled properly in IR)
val sb = StringBuilder()
while (true) {
if (bufPos >= bufLength) {
bufLength = read(buffer)
bufPos = 0
if (bufLength < 0) {
break
}
} else {
val c = buffer[bufPos++]
charsRead++
when {
c == '\n' && skipNextLf -> {
lineOffsets[lineOffsets.size - 1] = charsRead
skipNextLf = false
}
c == '\n' || c == '\r' -> {
sb.append('\n')
lineOffsets.add(charsRead)
skipNextLf = c == '\r'
}
else -> {
sb.append(c)
skipNextLf = false
}
}
}
}
return sb to KtSourceFileLinesMappingFromLineStartOffsets(lineOffsets.toIntArray(), charsRead)
}
/**
* Extracts source lines to offsets mapping from text
*
* intended for using mainly in tests, so no care is taken about performance or possible corner cases
*/
fun CharSequence.toSourceLinesMapping(): KtSourceFileLinesMapping {
val lineOffsets = mutableListOf(0)
var offset = 0
for (c in this) {
offset++
if (c == '\n') lineOffsets.add(offset)
}
return KtSourceFileLinesMappingFromLineStartOffsets(lineOffsets.toIntArray(), offset)
}
@@ -8,6 +8,9 @@ package org.jetbrains.kotlin.incremental
import com.intellij.ide.highlighter.JavaFileType import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiJavaModule import com.intellij.psi.PsiJavaModule
import org.jetbrains.kotlin.KtIoFileSourceFile
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtVirtualFileSourceFile
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensionsImpl
import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor
@@ -99,7 +102,7 @@ class IncrementalFirJvmCompilerRunner(
val moduleName = args.moduleName ?: JvmProtoBufUtil.DEFAULT_MODULE_NAME val moduleName = args.moduleName ?: JvmProtoBufUtil.DEFAULT_MODULE_NAME
val targetId = TargetId(moduleName, "java-production") // TODO: get rid of magic constant val targetId = TargetId(moduleName, "java-production") // TODO: get rid of magic constant
val dirtySources = linkedSetOf<File>().apply { addAll(sourcesToCompile) } val dirtySources = linkedSetOf<KtSourceFile>().apply { sourcesToCompile.forEach { add(KtIoFileSourceFile(it)) } }
// TODO: probably shoudl be passed along with sourcesToCompile // TODO: probably shoudl be passed along with sourcesToCompile
// TODO: file path normalization // TODO: file path normalization
@@ -175,12 +178,11 @@ class IncrementalFirJvmCompilerRunner(
createProjectEnvironment(configuration, rootDisposable, EnvironmentConfigFiles.JVM_CONFIG_FILES, messageCollector) createProjectEnvironment(configuration, rootDisposable, EnvironmentConfigFiles.JVM_CONFIG_FILES, messageCollector)
// -sources // -sources
val allPlatformSourceFiles = linkedSetOf<File>() // TODO: get from caller val allPlatformSourceFiles = linkedSetOf<KtSourceFile>() // TODO: get from caller
val allCommonSourceFiles = linkedSetOf<File>() val allCommonSourceFiles = linkedSetOf<KtSourceFile>()
configuration.kotlinSourceRoots.forAllFiles(configuration, projectEnvironment.project) { virtualFile, isCommon -> configuration.kotlinSourceRoots.forAllFiles(configuration, projectEnvironment.project) { virtualFile, isCommon ->
val file = File(virtualFile.canonicalPath ?: virtualFile.path) val file = KtVirtualFileSourceFile(virtualFile)
if (!file.isFile) error("TODO: better error: file not found $virtualFile")
if (isCommon) allCommonSourceFiles.add(file) if (isCommon) allCommonSourceFiles.add(file)
else allPlatformSourceFiles.add(file) else allPlatformSourceFiles.add(file)
} }
@@ -203,8 +205,8 @@ class IncrementalFirJvmCompilerRunner(
val compilerInput = ModuleCompilerInput( val compilerInput = ModuleCompilerInput(
targetId, targetId,
CommonPlatforms.defaultCommonPlatform, dirtySources.filter { it in allCommonSourceFiles }, CommonPlatforms.defaultCommonPlatform, allCommonSourceFiles.filter { dirtySources.any { df -> df.path == it.path } },
JvmPlatforms.unspecifiedJvmPlatform, dirtySources.filter { it in allPlatformSourceFiles }, JvmPlatforms.unspecifiedJvmPlatform, allPlatformSourceFiles.filter { dirtySources.any { df -> df.path == it.path } },
configuration configuration
) )
@@ -227,7 +229,10 @@ class IncrementalFirJvmCompilerRunner(
mainClassFqName = findMainClass(analysisResults.fir) mainClassFqName = findMainClass(analysisResults.fir)
} }
allCompiledSources.addAll(dirtySources) // TODO: switch the whole IC to KtSourceFile instead of FIle
dirtySources.forEach {
allCompiledSources.add(File(it.path!!))
}
if (diagnosticsReporter.hasErrors) { if (diagnosticsReporter.hasErrors) {
diagnosticsReporter.reportToMessageCollector(messageCollector, renderDiagnosticName) diagnosticsReporter.reportToMessageCollector(messageCollector, renderDiagnosticName)
@@ -250,7 +255,9 @@ class IncrementalFirJvmCompilerRunner(
} }
} }
caches.inputsCache.removeOutputForSourceFiles(newDirtySources) caches.inputsCache.removeOutputForSourceFiles(newDirtySources)
dirtySources.addAll(newDirtySources) newDirtySources.forEach {
dirtySources.add(KtIoFileSourceFile(it))
}
projectEnvironment.localFileSystem.refresh(false) projectEnvironment.localFileSystem.refresh(false)
} }
} }
@@ -18,14 +18,9 @@ package org.jetbrains.kotlin.backend.common
import org.jetbrains.kotlin.AbstractKtSourceElement import org.jetbrains.kotlin.AbstractKtSourceElement
import org.jetbrains.kotlin.KtOffsetsOnlySourceElement import org.jetbrains.kotlin.KtOffsetsOnlySourceElement
import org.jetbrains.kotlin.KtRealPsiSourceElement
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.util.render
fun CommonBackendContext.reportWarning(message: String, irFile: IrFile?, irElement: IrElement) { fun CommonBackendContext.reportWarning(message: String, irFile: IrFile?, irElement: IrElement) {
report(irElement, irFile, message, false) report(irElement, irFile, message, false)
@@ -37,12 +32,6 @@ fun <E> MutableList<E>.pop() = this.removeAt(size - 1)
fun <E> MutableList<E>.peek(): E? = if (size == 0) null else this[size - 1] fun <E> MutableList<E>.peek(): E? = if (size == 0) null else this[size - 1]
fun findKtSourceElement(irElement: IrElement, irDeclaration: IrDeclaration): KtSourceElement {
val psiElement = PsiSourceManager.findPsiElement(irElement, irDeclaration)
?: throw AssertionError("No PsiElement found for '${irElement.render()}'")
return KtRealPsiSourceElement(psiElement)
}
fun IrElement.sourceElement(): AbstractKtSourceElement? = fun IrElement.sourceElement(): AbstractKtSourceElement? =
if (startOffset != UNDEFINED_OFFSET) KtOffsetsOnlySourceElement(this.startOffset, this.endOffset) if (startOffset != UNDEFINED_OFFSET) KtOffsetsOnlySourceElement(this.startOffset, this.endOffset)
else null else null
@@ -285,7 +285,7 @@ fun IrFile.getKtFile(): KtFile? =
fun IrFile.getIoFile(): File? = fun IrFile.getIoFile(): File? =
when (val fe = fileEntry) { when (val fe = fileEntry) {
is PsiIrFileEntry -> fe.psiFile.virtualFile?.path?.let(::File) is PsiIrFileEntry -> fe.psiFile.virtualFile?.path?.let(::File)
else -> File(fe.name).takeIf { it.exists() } else -> File(fe.name)
} }
inline fun IrElement.hasChild(crossinline block: (IrElement) -> Boolean): Boolean { inline fun IrElement.hasChild(crossinline block: (IrElement) -> Boolean): Boolean {
@@ -186,7 +186,8 @@ val IrFileEntry.lineStartOffsets: IntArray
class NaiveSourceBasedFileEntryImpl( class NaiveSourceBasedFileEntryImpl(
override val name: String, override val name: String,
private val lineStartOffsets: IntArray = intArrayOf() private val lineStartOffsets: IntArray = intArrayOf(),
override val maxOffset: Int = UNDEFINED_OFFSET
) : IrFileEntry { ) : IrFileEntry {
val lineStartOffsetsAreEmpty: Boolean val lineStartOffsetsAreEmpty: Boolean
get() = lineStartOffsets.isEmpty() get() = lineStartOffsets.isEmpty()
@@ -215,15 +216,19 @@ class NaiveSourceBasedFileEntryImpl(
if (offset == SYNTHETIC_OFFSET) return 0 if (offset == SYNTHETIC_OFFSET) return 0
if (offset < 0) return -1 if (offset < 0) return -1
val lineNumber = getLineNumber(offset) val lineNumber = getLineNumber(offset)
return offset - lineStartOffsets[lineNumber] return if (lineNumber < 0) -1 else offset - lineStartOffsets[lineNumber]
} }
override val maxOffset: Int override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int): SourceRangeInfo =
get() = UNDEFINED_OFFSET SourceRangeInfo(
filePath = name,
override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int): SourceRangeInfo { startOffset = beginOffset,
return SourceRangeInfo(name, beginOffset, -1, -1, endOffset, -1, -1) startLineNumber = getLineNumber(beginOffset),
} startColumnNumber = getColumnNumber(beginOffset),
endOffset = endOffset,
endLineNumber = getLineNumber(endOffset),
endColumnNumber = getColumnNumber(endOffset)
)
} }
private fun IrClass.getPropertyDeclaration(name: String): IrProperty? { private fun IrClass.getPropertyDeclaration(name: String): IrProperty? {
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.sourceFiles
import com.intellij.lang.LighterASTNode
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
data class LightTreeFile(
val lightTree: FlyweightCapableTreeStructure<LighterASTNode>,
val sourceFile: KtSourceFile,
val linesMapping: KtSourceFileLinesMapping
)
@@ -6,11 +6,13 @@
package org.jetbrains.kotlin.test.services package org.jetbrains.kotlin.test.services
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.KtInMemoryTextSourceFile
import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir
import org.jetbrains.kotlin.fir.lightTree.LightTreeFile
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.sourceFiles.LightTreeFile
import org.jetbrains.kotlin.test.model.TestFile import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.toSourceLinesMapping
import java.io.File import java.io.File
abstract class SourceFilePreprocessor(val testServices: TestServices) { abstract class SourceFilePreprocessor(val testServices: TestServices) {
@@ -101,9 +103,10 @@ fun SourceFileProvider.getKtFilesForSourceFiles(testFiles: Collection<TestFile>,
fun SourceFileProvider.getLightTreeKtFileForSourceFile(testFile: TestFile): LightTreeFile { fun SourceFileProvider.getLightTreeKtFileForSourceFile(testFile: TestFile): LightTreeFile {
val shortName = testFile.name.substringAfterLast('/').substringAfterLast('\\') val shortName = testFile.name.substringAfterLast('/').substringAfterLast('\\')
val file = getRealFileForSourceFile(testFile) val sourceFile = KtInMemoryTextSourceFile(shortName, "/$shortName", getContentOfSourceFile(testFile))
val lightTree = LightTree2Fir.buildLightTree(file.readText()) val linesMapping = sourceFile.text.toSourceLinesMapping()
return LightTreeFile(lightTree, shortName, "/$shortName") // emulating behavior of KtTestUtil.createFile so path looks the same in testdata val lightTree = LightTree2Fir.buildLightTree(sourceFile.text)
return LightTreeFile(lightTree, sourceFile, linesMapping)
} }
fun SourceFileProvider.getLightTreeFilesForSourceFiles(testFiles: Collection<TestFile>): Map<TestFile, LightTreeFile> { fun SourceFileProvider.getLightTreeFilesForSourceFiles(testFiles: Collection<TestFile>): Map<TestFile, LightTreeFile> {
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.backend.classic package org.jetbrains.kotlin.test.backend.classic
import org.jetbrains.kotlin.KtPsiSourceFile
import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.DefaultCodegenFactory import org.jetbrains.kotlin.codegen.DefaultCodegenFactory
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
@@ -46,7 +47,7 @@ class ClassicJvmBackendFacade(
javaCompilerFacade.compileJavaFiles(module, configuration, generationState.factory) javaCompilerFacade.compileJavaFiles(module, configuration, generationState.factory)
return BinaryArtifacts.Jvm( return BinaryArtifacts.Jvm(
generationState.factory, generationState.factory,
psiFiles.map { SourceFileInfo(it, JvmFileClassUtil.getFileClassInfoNoResolve(it)) } psiFiles.map { SourceFileInfo(KtPsiSourceFile(it), JvmFileClassUtil.getFileClassInfoNoResolve(it)) }
) )
} }
} }
@@ -14,7 +14,10 @@ import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.dumpTreesFromLineNumber
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi2ir.generators.DeclarationStubGeneratorImpl import org.jetbrains.kotlin.psi2ir.generators.DeclarationStubGeneratorImpl
@@ -46,7 +49,7 @@ class IrTextDumpHandler(testServices: TestServices) : AbstractIrHandler(testServ
} }
fun List<IrFile>.groupWithTestFiles(module: TestModule): List<Pair<TestFile?, IrFile>> = mapNotNull { irFile -> fun List<IrFile>.groupWithTestFiles(module: TestModule): List<Pair<TestFile?, IrFile>> = mapNotNull { irFile ->
val name = irFile.fileEntry.name.removePrefix("/") val name = File(irFile.fileEntry.name).name
val testFile = module.files.firstOrNull { it.name == name } val testFile = module.files.firstOrNull { it.name == name }
testFile to irFile testFile to irFile
} }
@@ -61,7 +61,7 @@ open class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(t
val classLoader = createAndVerifyClassLoader(module, info.classFileFactory, reportProblems) val classLoader = createAndVerifyClassLoader(module, info.classFileFactory, reportProblems)
try { try {
for (fileInfo in fileInfos) { for (fileInfo in fileInfos) {
if (fileContainsBoxMethod(fileInfo.file)) { if (fileContainsBoxMethod(fileInfo.sourceFile)) {
boxMethodFound = true boxMethodFound = true
callBoxMethodAndCheckResultWithCleanup( callBoxMethodAndCheckResultWithCleanup(
fileInfo.info, fileInfo.info,
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.moduleStructure import org.jetbrains.kotlin.test.services.moduleStructure
import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumper import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumper
import org.jetbrains.kotlin.test.utils.withExtension import org.jetbrains.kotlin.test.utils.withExtension
import java.io.File
class SMAPDumpHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) { class SMAPDumpHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
companion object { companion object {
@@ -39,7 +40,7 @@ class SMAPDumpHandler(testServices: TestServices) : JvmBinaryArtifactHandler(tes
val originalFileNames = module.files.map { it.name } val originalFileNames = module.files.map { it.name }
val compiledSmaps = CommonSMAPTestUtil.extractSMAPFromClasses(info.classFileFactory.getClassFiles()).mapNotNull { val compiledSmaps = CommonSMAPTestUtil.extractSMAPFromClasses(info.classFileFactory.getClassFiles()).mapNotNull {
val name = it.sourceFile.removePrefix("/") val name = File(it.sourceFile).name
val index = originalFileNames.indexOf(name) val index = originalFileNames.indexOf(name)
val testFile = module.files[index] val testFile = module.files[index]
if (NO_SMAP_DUMP in testFile.directives) return@mapNotNull null if (NO_SMAP_DUMP in testFile.directives) return@mapNotNull null
@@ -57,7 +58,7 @@ class SMAPDumpHandler(testServices: TestServices) : JvmBinaryArtifactHandler(tes
dumper.builderForModule(module).apply { dumper.builderForModule(module).apply {
for (source in compiledData.values) { for (source in compiledData.values) {
appendLine("// FILE: ${source.sourceFile.removePrefix("/")}") appendLine("// FILE: ${File(source.sourceFile).name}")
appendLine(source.smap ?: "") appendLine(source.smap ?: "")
} }
} }
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.backend.ir package org.jetbrains.kotlin.test.backend.ir
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory
import org.jetbrains.kotlin.codegen.CodegenFactory import org.jetbrains.kotlin.codegen.CodegenFactory
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
@@ -35,7 +36,8 @@ sealed class IrBackendInput : ResultingArtifact.BackendInput<IrBackendInput>() {
data class JvmIrBackendInput( data class JvmIrBackendInput(
val state: GenerationState, val state: GenerationState,
val codegenFactory: JvmIrCodegenFactory, val codegenFactory: JvmIrCodegenFactory,
val backendInput: JvmIrCodegenFactory.JvmIrBackendInput val backendInput: JvmIrCodegenFactory.JvmIrBackendInput,
val sourceFiles: List<KtSourceFile>
) : IrBackendInput() { ) : IrBackendInput() {
override val irModuleFragment: IrModuleFragment override val irModuleFragment: IrModuleFragment
get() = backendInput.irModuleFragment get() = backendInput.irModuleFragment
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.backend.ir package org.jetbrains.kotlin.test.backend.ir
import org.jetbrains.kotlin.KtPsiSourceFile
import org.jetbrains.kotlin.backend.common.BackendException import org.jetbrains.kotlin.backend.common.BackendException
import org.jetbrains.kotlin.backend.jvm.MultifileFacadeFileEntry import org.jetbrains.kotlin.backend.jvm.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.lower.getFileClassInfoFromIrFile import org.jetbrains.kotlin.backend.jvm.lower.getFileClassInfoFromIrFile
@@ -21,7 +22,6 @@ import org.jetbrains.kotlin.test.model.SourceFileInfo
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import java.io.File
class JvmIrBackendFacade( class JvmIrBackendFacade(
testServices: TestServices testServices: TestServices
@@ -48,14 +48,20 @@ class JvmIrBackendFacade(
val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
javaCompilerFacade.compileJavaFiles(module, configuration, state.factory) javaCompilerFacade.compileJavaFiles(module, configuration, state.factory)
fun sourceFileInfos(irFile: IrFile, allowNestedMultifileFacades: Boolean): List<SourceFileInfo<*>> = fun sourceFileInfos(irFile: IrFile, allowNestedMultifileFacades: Boolean): List<SourceFileInfo> =
when (val fileEntry = irFile.fileEntry) { when (val fileEntry = irFile.fileEntry) {
is PsiIrFileEntry -> { is PsiIrFileEntry -> {
listOf(SourceFileInfo(fileEntry.psiFile, JvmFileClassUtil.getFileClassInfoNoResolve(fileEntry.psiFile as KtFile))) listOf(
SourceFileInfo(
KtPsiSourceFile(fileEntry.psiFile),
JvmFileClassUtil.getFileClassInfoNoResolve(fileEntry.psiFile as KtFile)
)
)
} }
is NaiveSourceBasedFileEntryImpl -> { is NaiveSourceBasedFileEntryImpl -> {
val file = File(fileEntry.name) val sourceFile = inputArtifact.sourceFiles.find { it.path == fileEntry.name }
listOf(SourceFileInfo(file, getFileClassInfoFromIrFile(irFile, file.name))) if (sourceFile == null) emptyList() // synthetic files, like CoroutineHelpers.kt, are ignored here
else listOf(SourceFileInfo(sourceFile, getFileClassInfoFromIrFile(irFile, sourceFile.name)))
} }
is MultifileFacadeFileEntry -> { is MultifileFacadeFileEntry -> {
if (!allowNestedMultifileFacades) error("nested multi-file facades are not allowed") if (!allowNestedMultifileFacades) error("nested multi-file facades are not allowed")
@@ -64,7 +64,8 @@ class ClassicFrontend2IrConverter(
return IrBackendInput.JvmIrBackendInput( return IrBackendInput.JvmIrBackendInput(
state, state,
codegenFactory, codegenFactory,
codegenFactory.convertToIr(CodegenFactory.IrConversionInput.fromGenerationStateAndFiles(state, psiFiles.values)) codegenFactory.convertToIr(CodegenFactory.IrConversionInput.fromGenerationStateAndFiles(state, psiFiles.values)),
emptyList()
) )
} }
@@ -50,6 +50,7 @@ class Fir2IrResultsConverter(
// TODO: handle fir from light tree // TODO: handle fir from light tree
val ktFiles = inputArtifact.firFiles.values.mapNotNull { it.psi as KtFile? } val ktFiles = inputArtifact.firFiles.values.mapNotNull { it.psi as KtFile? }
val sourceFiles = inputArtifact.firFiles.values.mapNotNull { it.sourceFile }
// Create and initialize the module and its dependencies // Create and initialize the module and its dependencies
val project = compilerConfigurationProvider.getProject(module) val project = compilerConfigurationProvider.getProject(module)
@@ -82,7 +83,8 @@ class Fir2IrResultsConverter(
extensions, extensions,
FirJvmBackendExtension(inputArtifact.session, components), FirJvmBackendExtension(inputArtifact.session, components),
notifyCodegenStart = {}, notifyCodegenStart = {},
) ),
sourceFiles
) )
} }
} }
@@ -18,7 +18,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.VfsBasedProjectEnvironment
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmModularRoots import org.jetbrains.kotlin.cli.jvm.config.jvmModularRoots
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.fir.analysis.FirAnalyzerFacade import org.jetbrains.kotlin.fir.FirAnalyzerFacade
import org.jetbrains.kotlin.fir.checkers.registerExtendedCommonCheckers import org.jetbrains.kotlin.fir.checkers.registerExtendedCommonCheckers
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlin.fir.moduleData import org.jetbrains.kotlin.fir.moduleData
@@ -5,9 +5,9 @@
package org.jetbrains.kotlin.test.frontend.fir package org.jetbrains.kotlin.test.frontend.fir
import org.jetbrains.kotlin.fir.AbstractFirAnalyzerFacade
import org.jetbrains.kotlin.fir.FirAnalyzerFacade
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.AbstractFirAnalyzerFacade
import org.jetbrains.kotlin.fir.analysis.FirAnalyzerFacade
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.test.model.FrontendKinds import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.model.ResultingArtifact import org.jetbrains.kotlin.test.model.ResultingArtifact
@@ -5,19 +5,20 @@
package org.jetbrains.kotlin.test.model package org.jetbrains.kotlin.test.model
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.codegen.ClassFileFactory import org.jetbrains.kotlin.codegen.ClassFileFactory
import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo
import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.CompilerResult
import org.jetbrains.kotlin.js.facade.TranslationResult import org.jetbrains.kotlin.js.facade.TranslationResult
import java.io.File import java.io.File
class SourceFileInfo<F>( class SourceFileInfo(
val file: F, val sourceFile: KtSourceFile,
val info: JvmFileClassInfo val info: JvmFileClassInfo
) )
object BinaryArtifacts { object BinaryArtifacts {
class Jvm(val classFileFactory: ClassFileFactory, val fileInfos: Collection<SourceFileInfo<*>>) : ResultingArtifact.Binary<Jvm>() { class Jvm(val classFileFactory: ClassFileFactory, val fileInfos: Collection<SourceFileInfo>) : ResultingArtifact.Binary<Jvm>() {
override val kind: BinaryKind<Jvm> override val kind: BinaryKind<Jvm>
get() = ArtifactKinds.Jvm get() = ArtifactKinds.Jvm
} }
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.test.services.sourceProviders package org.jetbrains.kotlin.test.services.sourceProviders
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.KtPsiSourceFile
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.REQUIRES_SEPARATE_PROCESS import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.REQUIRES_SEPARATE_PROCESS
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.JDK_KIND import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.JDK_KIND
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
@@ -39,11 +41,12 @@ open class MainFunctionForBlackBoxTestsSourceProvider(testServices: TestServices
return containsBoxMethod(file.originalContent) return containsBoxMethod(file.originalContent)
} }
fun <T> fileContainsBoxMethod(file: T): Boolean = fun fileContainsBoxMethod(sourceFile: KtSourceFile): Boolean =
when (file) { when (sourceFile) {
is PsiFile -> containsBoxMethod(file.text) is KtPsiSourceFile -> containsBoxMethod(sourceFile.psiFile.text)
is File -> containsBoxMethod(file.readText()) else -> with(sourceFile.getContentsAsStream().reader(Charsets.UTF_8)) {
else -> error("Unknown file type: $file") containsBoxMethod(this.readText())
}
} }
fun containsBoxMethod(fileContent: String): Boolean { fun containsBoxMethod(fileContent: String): Boolean {
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.inline.GENERATE_SMAP
import org.jetbrains.kotlin.test.KotlinBaseTest import org.jetbrains.kotlin.test.KotlinBaseTest
import org.jetbrains.kotlin.test.util.JUnit4Assertions import org.jetbrains.kotlin.test.util.JUnit4Assertions
import org.junit.Assert import org.junit.Assert
import java.io.File
import java.io.StringReader import java.io.StringReader
object SMAPTestUtil { object SMAPTestUtil {
@@ -62,7 +63,7 @@ object SMAPTestUtil {
}.associateBy { it.sourceFile } }.associateBy { it.sourceFile }
for (source in sourceData) { for (source in sourceData) {
val ktFileName = "/" + source.sourceFile val ktFileName = "/" + File(source.sourceFile).name
.replace(".smap-nonseparate-compilation", ".kt") .replace(".smap-nonseparate-compilation", ".kt")
.replace(".smap-separate-compilation", ".kt") .replace(".smap-separate-compilation", ".kt")
.replace(".smap", ".kt") .replace(".smap", ".kt")
@@ -11,6 +11,7 @@ import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementFinder import com.intellij.psi.PsiElementFinder
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.KtInMemoryTextSourceFile
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest
@@ -46,6 +47,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
import org.jetbrains.kotlin.toSourceLinesMapping
import java.io.File import java.io.File
abstract class AbstractFirBaseDiagnosticsTest : BaseDiagnosticsTest() { abstract class AbstractFirBaseDiagnosticsTest : BaseDiagnosticsTest() {
@@ -133,7 +135,12 @@ abstract class AbstractFirBaseDiagnosticsTest : BaseDiagnosticsTest() {
if (useLightTree) { if (useLightTree) {
val lightTreeBuilder = LightTree2Fir(session, firProvider.kotlinScopeProvider) val lightTreeBuilder = LightTree2Fir(session, firProvider.kotlinScopeProvider)
ktFiles.mapTo(firFiles) { ktFiles.mapTo(firFiles) {
val firFile = lightTreeBuilder.buildFirFile(it.text, it.name, it.virtualFilePath) val firFile =
lightTreeBuilder.buildFirFile(
it.text,
KtInMemoryTextSourceFile(it.name, it.virtualFilePath, it.text),
it.text.toSourceLinesMapping()
)
(session.firProvider as FirProviderImpl).recordFile(firFile) (session.firProvider as FirProviderImpl).recordFile(firFile)
firFile firFile
} }
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.KtIoFileSourceFile
import org.jetbrains.kotlin.fir.builder.RawFirBuilder import org.jetbrains.kotlin.fir.builder.RawFirBuilder
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.diagnostics.ConeStubDiagnostic import org.jetbrains.kotlin.fir.diagnostics.ConeStubDiagnostic
@@ -26,6 +27,7 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.readSourceFileWithMapping
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
@@ -139,16 +141,18 @@ class FirResolveBench(val withProgress: Boolean, val listener: BenchListener? =
return files.map { file -> return files.map { file ->
val before = vmStateSnapshot() val before = vmStateSnapshot()
val firFile: FirFile val firFile: FirFile
val code: String
val time = measureNanoTime { val time = measureNanoTime {
code = FileUtil.loadFile(file, CharsetToolkit.UTF8, true).trim() val sourceFile = KtIoFileSourceFile(file)
firFile = builder.buildFirFile(code, file.name, file.path) val (code, linesMapping) = with(file.inputStream().reader(Charsets.UTF_8)) {
this.readSourceFileWithMapping()
}
totalLines += linesMapping.linesCount
firFile = builder.buildFirFile(code, sourceFile, linesMapping)
(builder.session.firProvider as FirProviderImpl).recordFile(firFile) (builder.session.firProvider as FirProviderImpl).recordFile(firFile)
} }
val after = vmStateSnapshot() val after = vmStateSnapshot()
val diff = after - before val diff = after - before
recordTime(builder::class, diff, time) recordTime(builder::class, diff, time)
totalLines += StringUtil.countNewLines(code)
firFile firFile
}.also { }.also {
listener?.after(builder::class) listener?.after(builder::class)
@@ -29,7 +29,7 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.fir.analysis.FirAnalyzerFacade import org.jetbrains.kotlin.fir.FirAnalyzerFacade
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendClassResolver import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendClassResolver
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendExtension import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendExtension
import org.jetbrains.kotlin.fir.createSessionForTests import org.jetbrains.kotlin.fir.createSessionForTests
@@ -1,17 +1,16 @@
/* /*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Copyright 2010-2022 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. * 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.fir.analysis package org.jetbrains.kotlin.fir
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.diagnostics.KtDiagnostic import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector
import org.jetbrains.kotlin.fir.backend.Fir2IrConverter import org.jetbrains.kotlin.fir.backend.Fir2IrConverter
import org.jetbrains.kotlin.fir.backend.Fir2IrResult import org.jetbrains.kotlin.fir.backend.Fir2IrResult
import org.jetbrains.kotlin.fir.backend.jvm.Fir2IrJvmSpecialAnnotationSymbolProvider import org.jetbrains.kotlin.fir.backend.jvm.Fir2IrJvmSpecialAnnotationSymbolProvider
@@ -21,8 +20,6 @@ import org.jetbrains.kotlin.fir.builder.PsiHandlingMode
import org.jetbrains.kotlin.fir.builder.RawFirBuilder import org.jetbrains.kotlin.fir.builder.RawFirBuilder
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir
import org.jetbrains.kotlin.fir.lightTree.LightTreeFile
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.providers.firProvider import org.jetbrains.kotlin.fir.resolve.providers.firProvider
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirProviderImpl import org.jetbrains.kotlin.fir.resolve.providers.impl.FirProviderImpl
@@ -31,6 +28,7 @@ import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmDescriptorMangler
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi2ir.generators.GeneratorExtensions import org.jetbrains.kotlin.psi2ir.generators.GeneratorExtensions
import org.jetbrains.kotlin.sourceFiles.LightTreeFile
abstract class AbstractFirAnalyzerFacade { abstract class AbstractFirAnalyzerFacade {
abstract val scopeSession: ScopeSession abstract val scopeSession: ScopeSession
@@ -63,7 +61,7 @@ class FirAnalyzerFacade(
firFiles = if (useLightTree) { firFiles = if (useLightTree) {
val builder = LightTree2Fir(session, firProvider.kotlinScopeProvider) val builder = LightTree2Fir(session, firProvider.kotlinScopeProvider)
lightTreeFiles.map { lightTreeFiles.map {
builder.buildFirFile(it).also { firFile -> builder.buildFirFile(it.lightTree, it.sourceFile, it.linesMapping).also { firFile ->
firProvider.recordFile(firFile) firProvider.recordFile(firFile)
} }
} }