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
@@ -7,9 +7,10 @@ package org.jetbrains.kotlin.fir.lightTree
import com.intellij.lang.LighterASTNode
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 org.jetbrains.kotlin.KtIoFileSourceFile
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.KtSourceFileLinesMapping
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.diagnostics.DiagnosticContext
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.parsing.KotlinLightParser
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.readSourceFileWithMapping
import java.io.File
import java.nio.file.Path
@@ -46,7 +48,7 @@ class LightTree2Fir(
return builder.lightTree
}
fun buildLightTree(code: String): FlyweightCapableTreeStructure<LighterASTNode> {
fun buildLightTree(code: CharSequence): FlyweightCapableTreeStructure<LighterASTNode> {
val builder = PsiBuilderFactoryImpl().createBuilder(parserDefinition, makeLexer(), code)
KotlinLightParser.parse(builder)
return builder.lightTree
@@ -58,25 +60,25 @@ class LightTree2Fir(
}
fun buildFirFile(file: File): FirFile {
val code = FileUtil.loadFile(file, CharsetToolkit.UTF8, true)
return buildFirFile(code, file.name, file.path)
val sourceFile = KtIoFileSourceFile(file)
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(
session, scopeProvider, lightTree, diagnosticsReporter = diagnosticsReporter,
diagnosticContext = makeDiagnosticContext(path)
).convertFile(lightTree.root, fileName, path)
}
diagnosticContext = makeDiagnosticContext(sourceFile.path)
).convertFile(lightTree.root, sourceFile, linesMapping)
fun buildFirFile(code: String, fileName: String, path: String?): FirFile {
val lightTree = buildLightTree(code)
return DeclarationsConverter(
session, scopeProvider, lightTree, diagnosticsReporter = diagnosticsReporter,
diagnosticContext = makeDiagnosticContext(path)
).convertFile(lightTree.root, fileName, path)
}
fun buildFirFile(code: CharSequence, sourceFile: KtSourceFile, linesMapping: KtSourceFileLinesMapping): FirFile =
buildFirFile(buildLightTree(code), sourceFile, linesMapping)
private fun makeDiagnosticContext(path: String?) =
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.parsePreamble]
*/
fun convertFile(file: LighterASTNode, fileName: String = "", filePath: String?): FirFile {
fun convertFile(file: LighterASTNode, sourceFile: KtSourceFile, linesMapping: KtSourceFileLinesMapping): FirFile {
if (file.tokenType != KT_FILE) {
//TODO throw error
throw Exception()
@@ -123,8 +123,9 @@ class DeclarationsConverter(
source = file.toFirSourceElement()
origin = FirDeclarationOrigin.Source
moduleData = baseModuleData
name = fileName
path = filePath
name = sourceFile.name
this.sourceFile = sourceFile
this.sourceFileLinesMapping = linesMapping
this.packageDirective = packageDirective ?: buildPackageDirective { packageFqName = context.packageFqName }
annotations += fileAnnotationList
imports += importList
@@ -10,11 +10,15 @@ import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.impl.DebugUtil
import com.intellij.testFramework.TestDataPath
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.builder.AbstractRawFirBuilderTestCase
import org.jetbrains.kotlin.fir.builder.StubFirScopeProvider
import org.jetbrains.kotlin.fir.session.FirSessionFactory
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.readSourceFileWithMapping
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
import org.junit.runner.RunWith
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) {
val lightTree = LightTree2Fir.buildLightTree(text)
DebugUtil.lightTreeToString(lightTree, false)
} else {
val firFile = converter.buildFirFile(text, fileName, filePath)
val firFile = converter.buildFirFile(text, sourceFile, linesMapping)
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")
println("BASE PATH: $path")
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 {
generateFirFromLightTree(onlyLightTree, lightTreeConverter, text, it.name, it.path)
generateFirFromLightTree(onlyLightTree, lightTreeConverter, code, sourceFile, linesMapping)
}
counter++
@@ -10,6 +10,8 @@ import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.testFramework.TestDataPath
import com.intellij.util.PathUtil
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.fir.FirRenderer
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.session.FirSessionFactory
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.readSourceFileWithMapping
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners
import org.jetbrains.kotlin.toSourceLinesMapping
import org.junit.runner.RunWith
import java.io.File
@@ -61,15 +65,17 @@ class TreesCompareTest : AbstractRawFirBuilderTestCase() {
diagnosticsReporter = null
)
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
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 treeFromPsi = StringBuilder().also { FirRenderer(it).visitFile(firFileFromPsi) }.toString()
//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()
return@compareBase treeFromLightTree == treeFromPsi
@@ -100,7 +106,12 @@ class TreesCompareTest : AbstractRawFirBuilderTestCase() {
.replace("<Unsupported LValue.*?>".toRegex(), "<Unsupported LValue>")
//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()
.replace("<Unsupported LValue.*?>".toRegex(), "<Unsupported LValue>")
@@ -911,7 +911,8 @@ open class RawFirBuilder(
moduleData = baseModuleData
origin = FirDeclarationOrigin.Source
name = file.name
path = file.virtualFile?.path
sourceFile = KtPsiSourceFile(file)
sourceFileLinesMapping = KtPsiSourceFileLinesMapping(file)
packageDirective = buildPackageDirective {
packageFqName = context.packageFqName
source = file.packageDirective?.toKtPsiSourceElement()