Create visitor generator for FIR (KT-24062 in progress)
This commit is contained in:
committed by
Mikhail Glukhikh
parent
4755a494f1
commit
1a80477c1c
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package tasks
|
||||
|
||||
import groovy.util.Node
|
||||
import groovy.util.XmlParser
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
||||
|
||||
open class WriteCopyrightToFile : DefaultTask() {
|
||||
|
||||
@InputFile
|
||||
var path = project.file("${project.rootDir}/.idea/copyright/apache.xml")
|
||||
|
||||
@OutputFile
|
||||
var outputFile: File? = null
|
||||
|
||||
@Input
|
||||
var commented: Boolean = true
|
||||
|
||||
@TaskAction
|
||||
fun write() {
|
||||
if (commented) {
|
||||
outputFile!!.writeText(project.readCopyrightCommented())
|
||||
} else {
|
||||
outputFile!!.writeText(project.readCopyright())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Project.readCopyright(): String {
|
||||
val file = rootDir.resolve(".idea/copyright/apache.xml")
|
||||
|
||||
assert(file.exists()) {
|
||||
"File $file with copyright not found"
|
||||
}
|
||||
|
||||
|
||||
val xmlParser = XmlParser()
|
||||
val node = xmlParser.parse(file)
|
||||
assert(node.attribute("name") == "CopyrightManager") {
|
||||
"Copyright format changed occasionally?"
|
||||
}
|
||||
|
||||
val copyrightBlock = node.children().filterIsInstance<Node>().single()
|
||||
val noticeNode = copyrightBlock.children().filterIsInstance<Node>().single { it.attribute("name") == "notice" }
|
||||
return noticeNode.attribute("value").toString().replace("$today.year", GregorianCalendar()[Calendar.YEAR].toString())
|
||||
}
|
||||
|
||||
fun Project.readCopyrightCommented(): String {
|
||||
return "/*\n" + readCopyright().prependIndent(" * ") + "\n */"
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,39 @@ plugins {
|
||||
|
||||
jvmTarget = "1.6"
|
||||
|
||||
val generatorClasspath by configurations.creating
|
||||
|
||||
dependencies {
|
||||
compile(project(":core:descriptors"))
|
||||
generatorClasspath(project("visitors-generator"))
|
||||
// Necessary only to store bound PsiElement inside FirElement
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core", "annotations") }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"main" {
|
||||
projectDefault()
|
||||
java.srcDir("visitors")
|
||||
}
|
||||
"test" {}
|
||||
}
|
||||
}
|
||||
|
||||
val generateVisitors by tasks.creating(JavaExec::class) {
|
||||
val generationRoot = "$projectDir/src/org/jetbrains/kotlin/fir/"
|
||||
val output = "$projectDir/visitors"
|
||||
|
||||
val allSourceFiles = fileTree(generationRoot) {
|
||||
include("**/*.kt")
|
||||
}
|
||||
|
||||
inputs.files(allSourceFiles)
|
||||
outputs.files(output)
|
||||
|
||||
classpath = generatorClasspath
|
||||
args(generationRoot, output)
|
||||
main = "org.jetbrains.kotlin.fir.visitors.generator.VisitorsGeneratorKt"
|
||||
}
|
||||
|
||||
val compileKotlin by tasks
|
||||
|
||||
compileKotlin.dependsOn(generateVisitors)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
annotation class VisitedSupertype
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
import tasks.WriteCopyrightToFile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
jvmTarget = "1.6"
|
||||
|
||||
dependencies {
|
||||
compile(project(":compiler:psi"))
|
||||
|
||||
compile(intellijCoreDep()) { includeJars("intellij-core") }
|
||||
compile(intellijDep()) {
|
||||
includeJars("trove4j", "picocontainer-1.2", "annotations", rootProject = rootProject)
|
||||
isTransitive = false
|
||||
}
|
||||
compile(intellijDep()) { includeJars("guava", rootProject = rootProject) }
|
||||
}
|
||||
|
||||
val writeCopyright by task<WriteCopyrightToFile> {
|
||||
outputFile = file("$buildDir/copyright/notice.txt")
|
||||
commented = true
|
||||
}
|
||||
|
||||
val processResources by tasks
|
||||
processResources.dependsOn(writeCopyright)
|
||||
|
||||
sourceSets {
|
||||
"main" {
|
||||
projectDefault()
|
||||
resources.srcDir("$buildDir/copyright")
|
||||
}
|
||||
"test" {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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.visitors.generator
|
||||
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
||||
|
||||
class DataCollector {
|
||||
|
||||
private val references = mutableMapOf<String, List<String>>()
|
||||
private val packagePerClass = mutableMapOf<String, FqName>()
|
||||
|
||||
fun readFile(file: KtFile) {
|
||||
file.acceptChildren(object : KtVisitorVoid() {
|
||||
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
super.visitKtElement(element)
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitClass(klass: KtClass) {
|
||||
val className = klass.name
|
||||
if (klass.isInterface() && className != null) {
|
||||
packagePerClass[className] = file.packageFqName
|
||||
|
||||
val manual = klass.superTypeListEntries.find {
|
||||
it.typeReference?.findDescendantOfType<KtAnnotationEntry> { annotationEntry ->
|
||||
annotationEntry.shortName?.asString() == VISITED_SUPERTYPE_ANNOTATION_NAME
|
||||
} != null
|
||||
}
|
||||
if (manual != null) {
|
||||
references[className] = listOfNotNull(manual.typeAsUserType?.referencedName)
|
||||
} else {
|
||||
references[className] =
|
||||
klass.superTypeListEntries.mapNotNull {
|
||||
it.typeAsUserType?.referencedName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visitClass(klass)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
private fun Map<String, List<String>>.computeBackReferences(): Map<String, List<String>> {
|
||||
val result = mutableMapOf<String, List<String>>()
|
||||
|
||||
this.forEach { (k, v) ->
|
||||
v.forEach {
|
||||
result.merge(it, listOf(k)) { a, b -> a + b }
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
fun computeResult(): ReferencesData {
|
||||
val back = references.computeBackReferences()
|
||||
|
||||
val keysToKeep =
|
||||
generateSequence(listOf(FIR_ELEMENT_CLASS_NAME)) { firName ->
|
||||
firName.flatMap { back[it].orEmpty() }.takeUnless { it.isEmpty() }
|
||||
}.flatten().toSet()
|
||||
|
||||
val cleanBack = back.filterKeys { it in keysToKeep }
|
||||
return ReferencesData(
|
||||
cleanBack.computeBackReferences(),
|
||||
cleanBack,
|
||||
packagePerClass.filterKeys { it in keysToKeep }.values.distinct()
|
||||
)
|
||||
}
|
||||
|
||||
data class ReferencesData(val direct: Map<String, List<String>>, val back: Map<String, List<String>>, val usedPackages: List<FqName>)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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.visitors.generator
|
||||
|
||||
import com.intellij.core.CoreApplicationEnvironment
|
||||
import com.intellij.core.CoreProjectEnvironment
|
||||
import com.intellij.lang.MetaLanguage
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
import org.jetbrains.kotlin.script.StandardScriptDefinition
|
||||
|
||||
internal data class PsiSetup(
|
||||
val applicationEnvironment: CoreApplicationEnvironment,
|
||||
val projectEnvironment: CoreProjectEnvironment,
|
||||
val project: Project,
|
||||
val disposable: Disposable
|
||||
)
|
||||
|
||||
internal fun setup(): PsiSetup {
|
||||
val disposable = Disposer.newDisposable()
|
||||
|
||||
val applicationEnvironment = CoreApplicationEnvironment(disposable, false)
|
||||
|
||||
val projectEnvironment = CoreProjectEnvironment(disposable, applicationEnvironment)
|
||||
|
||||
CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), MetaLanguage.EP_NAME, MetaLanguage::class.java)
|
||||
|
||||
|
||||
applicationEnvironment.registerApplicationService(ScriptDefinitionProvider::class.java, NoopScriptDefinitionProvider())
|
||||
applicationEnvironment.registerFileType(KotlinFileType.INSTANCE, "kt")
|
||||
applicationEnvironment.registerParserDefinition(KotlinParserDefinition())
|
||||
|
||||
|
||||
val project = projectEnvironment.project
|
||||
return PsiSetup(applicationEnvironment, projectEnvironment, project, disposable)
|
||||
}
|
||||
|
||||
internal inline fun <T> withPsiSetup(l: PsiSetup.() -> T): T {
|
||||
val setup = setup()
|
||||
val t = setup.l()
|
||||
Disposer.dispose(setup.disposable)
|
||||
return t
|
||||
}
|
||||
|
||||
private class NoopScriptDefinitionProvider : ScriptDefinitionProvider {
|
||||
override fun getDefaultScriptDefinition(): KotlinScriptDefinition {
|
||||
return StandardScriptDefinition
|
||||
}
|
||||
|
||||
override fun getKnownFilenameExtensions(): Sequence<String> {
|
||||
return emptySequence()
|
||||
}
|
||||
|
||||
override fun isScript(fileName: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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.visitors.generator
|
||||
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.SingleRootFileViewProvider
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
|
||||
|
||||
const val FIR_ELEMENT_CLASS_NAME = "FirElement"
|
||||
const val VISITOR_PACKAGE = "org.jetbrains.kotlin.fir.visitors"
|
||||
const val SIMPLE_VISITOR_NAME = "FirVisitor"
|
||||
const val UNIT_VISITOR_NAME = "FirVisitorVoid"
|
||||
const val VISITED_SUPERTYPE_ANNOTATION_NAME = "VisitedSupertype"
|
||||
|
||||
const val WARNING_GENERATED_FILE =
|
||||
"""/** This file generated by :compiler:fir:tree:generateVisitors. DO NOT MODIFY MANUALLY! */"""
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
val rootPath = File(args[0])
|
||||
val output = File(args[1])
|
||||
|
||||
val packageDirectory = output.resolve(VISITOR_PACKAGE.replace('.', '/'))
|
||||
packageDirectory.mkdirs()
|
||||
|
||||
withPsiSetup {
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
val vfm = VirtualFileManager.getInstance()
|
||||
|
||||
|
||||
val dataCollector = DataCollector()
|
||||
|
||||
rootPath.walkTopDown()
|
||||
.filter {
|
||||
it.extension == "kt"
|
||||
}.map {
|
||||
(vfm.getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem).findFileByIoFile(it)
|
||||
}.flatMap {
|
||||
SingleRootFileViewProvider(psiManager, it!!).allFiles.asSequence()
|
||||
}
|
||||
.filterIsInstance<KtFile>()
|
||||
.forEach(dataCollector::readFile)
|
||||
|
||||
|
||||
val data = dataCollector.computeResult()
|
||||
|
||||
|
||||
packageDirectory
|
||||
.resolve("${SIMPLE_VISITOR_NAME}Generated.kt")
|
||||
.writeText(
|
||||
SimpleVisitorGenerator(data).generate()
|
||||
)
|
||||
|
||||
packageDirectory
|
||||
.resolve("${UNIT_VISITOR_NAME}Generated.kt")
|
||||
.writeText(
|
||||
UnitVisitorGenerator(data).generate()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val String.classNameWithoutFir get() = this.removePrefix("Fir")
|
||||
|
||||
|
||||
fun DataCollector.ReferencesData.walkHierarchyTopDown(from: String, l: (p: String, e: String) -> Unit) {
|
||||
val referents = back[from] ?: return
|
||||
for (referent in referents) {
|
||||
l(from, referent)
|
||||
walkHierarchyTopDown(referent, l)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractVisitorGenerator(val referencesData: DataCollector.ReferencesData) {
|
||||
fun Printer.generateFunction(
|
||||
name: String,
|
||||
parameters: Map<String, String>,
|
||||
returnType: String,
|
||||
override: Boolean = false,
|
||||
final: Boolean = false,
|
||||
body: (Printer.() -> Unit)?
|
||||
) {
|
||||
|
||||
if (body == null) {
|
||||
print("abstract ")
|
||||
} else {
|
||||
printIndent()
|
||||
if (!final) {
|
||||
printWithNoIndent("open ")
|
||||
}
|
||||
if (override) {
|
||||
if (final) {
|
||||
printWithNoIndent("final ")
|
||||
}
|
||||
printWithNoIndent("override ")
|
||||
}
|
||||
}
|
||||
printWithNoIndent("fun ", name, "(")
|
||||
parameters
|
||||
.flatMap { (a, b) ->
|
||||
listOf(a, ": ", b, ", ")
|
||||
}.dropLast(1)
|
||||
.forEach {
|
||||
printWithNoIndent(it)
|
||||
}
|
||||
printWithNoIndent(")")
|
||||
if (returnType != "Unit") {
|
||||
printWithNoIndent(": ", returnType)
|
||||
}
|
||||
if (body != null) {
|
||||
printlnWithNoIndent(" {")
|
||||
indented {
|
||||
body()
|
||||
}
|
||||
println("}")
|
||||
} else {
|
||||
printlnWithNoIndent()
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
protected inline fun Printer.indented(l: () -> Unit) {
|
||||
pushIndent()
|
||||
l()
|
||||
popIndent()
|
||||
}
|
||||
|
||||
protected fun Printer.generateCall(name: String, args: List<String>) {
|
||||
printWithNoIndent(name, "(")
|
||||
separatedOneLine(args, ", ")
|
||||
printWithNoIndent(")")
|
||||
}
|
||||
|
||||
protected fun Printer.separatedOneLine(iterable: Iterable<Any>, separator: Any) {
|
||||
var first = true
|
||||
for (element in iterable) {
|
||||
if (!first) {
|
||||
printWithNoIndent(separator)
|
||||
} else {
|
||||
first = false
|
||||
}
|
||||
printWithNoIndent(element)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun Printer.generateDefaultImports() {
|
||||
referencesData.usedPackages.forEach {
|
||||
println("import ", it.asString(), ".*")
|
||||
}
|
||||
println()
|
||||
println()
|
||||
}
|
||||
|
||||
val String.safeName
|
||||
get() = when (this) {
|
||||
"class" -> "klass"
|
||||
else -> this
|
||||
}
|
||||
|
||||
fun generate(): String {
|
||||
val builder = StringBuilder()
|
||||
val printer = Printer(builder, " ")
|
||||
printer.apply {
|
||||
println(javaClass.getResource("/notice.txt").readText())
|
||||
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
generateDefaultImports()
|
||||
println(WARNING_GENERATED_FILE)
|
||||
|
||||
printer.generateContent()
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
abstract fun Printer.generateContent()
|
||||
|
||||
}
|
||||
|
||||
|
||||
class SimpleVisitorGenerator(referencesData: DataCollector.ReferencesData) : AbstractVisitorGenerator(referencesData) {
|
||||
override fun Printer.generateContent() {
|
||||
println("abstract class $SIMPLE_VISITOR_NAME<R, D> {")
|
||||
indented {
|
||||
generateFunction(
|
||||
"visitElement",
|
||||
parameters = mapOf(
|
||||
"element" to FIR_ELEMENT_CLASS_NAME,
|
||||
"data" to "D"
|
||||
),
|
||||
returnType = "R",
|
||||
body = null
|
||||
)
|
||||
referencesData.walkHierarchyTopDown(from = FIR_ELEMENT_CLASS_NAME) { parent, element ->
|
||||
generateVisit(element, parent)
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
|
||||
private fun Printer.generateVisit(className: String, parent: String) {
|
||||
val shortcutName = className.classNameWithoutFir
|
||||
val parameterName = shortcutName.decapitalize().safeName
|
||||
generateFunction(
|
||||
name = "visit$shortcutName",
|
||||
parameters = mapOf(
|
||||
parameterName to className,
|
||||
"data" to "D"
|
||||
),
|
||||
returnType = "R"
|
||||
) {
|
||||
print("return ")
|
||||
generateCall("visit${parent.classNameWithoutFir}", listOf(parameterName, "data"))
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UnitVisitorGenerator(referencesData: DataCollector.ReferencesData) : AbstractVisitorGenerator(referencesData) {
|
||||
override fun Printer.generateContent() {
|
||||
println("abstract class $UNIT_VISITOR_NAME : $SIMPLE_VISITOR_NAME<Unit, Nothing?>() {")
|
||||
indented {
|
||||
generateFunction(
|
||||
"visitElement",
|
||||
parameters = mapOf(
|
||||
"element" to FIR_ELEMENT_CLASS_NAME
|
||||
),
|
||||
returnType = "Unit",
|
||||
body = null
|
||||
)
|
||||
|
||||
referencesData.walkHierarchyTopDown(FIR_ELEMENT_CLASS_NAME) { parent, klass ->
|
||||
generateVisit(klass, parent)
|
||||
}
|
||||
|
||||
referencesData.back.keys.forEach {
|
||||
generateTrampolineVisit(it)
|
||||
}
|
||||
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
|
||||
|
||||
private fun Printer.generateVisit(className: String, parent: String) {
|
||||
val shortcutName = className.classNameWithoutFir
|
||||
val parameterName = shortcutName.decapitalize().safeName
|
||||
generateFunction(
|
||||
name = "visit$shortcutName",
|
||||
parameters = mapOf(
|
||||
parameterName to className
|
||||
),
|
||||
returnType = "Unit"
|
||||
) {
|
||||
printIndent()
|
||||
generateCall("visit${parent.classNameWithoutFir}", listOf(parameterName, "null"))
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Printer.generateTrampolineVisit(className: String) {
|
||||
val shortcutName = className.classNameWithoutFir
|
||||
val parameterName = shortcutName.decapitalize().safeName
|
||||
generateFunction(
|
||||
name = "visit$shortcutName",
|
||||
parameters = mapOf(
|
||||
parameterName to className,
|
||||
"data" to "Nothing?"
|
||||
),
|
||||
returnType = "Unit",
|
||||
override = true,
|
||||
final = true
|
||||
) {
|
||||
printIndent()
|
||||
generateCall("visit$shortcutName", listOf(parameterName))
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ include ":kotlin-build-common",
|
||||
":compiler:serialization",
|
||||
":compiler:psi",
|
||||
":compiler:fir:tree",
|
||||
":compiler:fir:tree:visitors-generator",
|
||||
":compiler:fir:psi2fir",
|
||||
":compiler:frontend",
|
||||
":compiler:frontend.java",
|
||||
|
||||
Reference in New Issue
Block a user