From 61b410e2760d31d07eaa229ffcded33a4eeec8fc Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Fri, 12 Apr 2019 21:33:27 +0300 Subject: [PATCH] Implement fir to html dump --- compiler/fir/dump/build.gradle.kts | 33 + .../jetbrains/kotlin/fir/dump/colors.dark.css | 81 ++ .../kotlin/fir/dump/colors.white.css | 70 + .../org/jetbrains/kotlin/fir/dump/logic.js | 79 ++ .../org/jetbrains/kotlin/fir/dump/style.css | 61 + .../jetbrains/kotlin/fir/dump/HtmlFirDump.kt | 1252 +++++++++++++++++ settings.gradle | 1 + 7 files changed, 1577 insertions(+) create mode 100644 compiler/fir/dump/build.gradle.kts create mode 100644 compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.dark.css create mode 100644 compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.white.css create mode 100644 compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/logic.js create mode 100644 compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/style.css create mode 100644 compiler/fir/dump/src/org/jetbrains/kotlin/fir/dump/HtmlFirDump.kt diff --git a/compiler/fir/dump/build.gradle.kts b/compiler/fir/dump/build.gradle.kts new file mode 100644 index 00000000000..39d96734fee --- /dev/null +++ b/compiler/fir/dump/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * 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. + */ + +plugins { + kotlin("jvm") + id("jps-compatible") +} + +repositories { + maven { setUrl("https://dl.bintray.com/kotlin/kotlinx.html/") } +} + +dependencies { + compile(project(":core:descriptors")) + compile(project(":core:deserialization")) + compile(project(":compiler:fir:cones")) + compile(project(":compiler:fir:tree")) + compile(project(":compiler:fir:resolve")) + compile(project(":compiler:fir:java")) + compile(project(":compiler:cli")) + + compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + compile("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.12") +} + +sourceSets { + "main" { projectDefault() } +} + + +testsJar() \ No newline at end of file diff --git a/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.dark.css b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.dark.css new file mode 100644 index 00000000000..4dd1fd7bc98 --- /dev/null +++ b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.dark.css @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2019 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. + */ + +body.dark { + background-color: #2b2b2b; + color: #bababa; +} + +.dark .toggle-theme { + background-color: #6a7878; +} + +.dark .return-label { + color: #6a7878; + background-color: #333333; +} + +.dark .label { + color: dodgerblue; + background-color: #333333; +} + +.dark .expression-type { + color: #6a7878; + background-color: #333333; +} + +.dark .keyword { + color: #8ba558; +} + +.dark a.ref.selected { + background-color: rgba(77, 255, 27, 0.44); +} + +.dark .unresolved { + text-decoration-color: blue; +} + +.dark .error { + text-decoration-color: red; +} + +.dark .error.unsupported { + text-decoration-color: cyan; +} + +.dark a.ref.external { + color: #bababa; +} + +.dark a.container-ref, +.dark a.ref, +.dark a.ref:visited, +.dark a.package-fqn, +.dark a.package-fqn:visited { + color: rgb(223, 190, 149); +} + +.dark a.container-ref:visited { + color: rgb(223, 185, 67); +} + +.dark :target { + background-color: #9f5000; +} + +.dark .string-literal { + color: #98b884; +} + +.dark .declaration .simple-name { + color: #dfb943 +} + +.dark .line { + background-color: #313335; + border-right: thin solid darkgray; +} \ No newline at end of file diff --git a/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.white.css b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.white.css new file mode 100644 index 00000000000..54f01bd82e1 --- /dev/null +++ b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/colors.white.css @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2019 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. + */ + +.dark .toggle-theme { + background-color: #dfdfdf; +} + +.white .return-label { + background-color: lightgray; +} + +.white .label { + color: dodgerblue; + background-color: lightgray; +} + +.white .expression-type { + background-color: lightgray; +} + +.white .keyword { + color: #8ba558; +} + +.white a.ref.selected { + background-color: rgba(77, 255, 27, 0.44); +} + +.white .unresolved { + text-decoration-color: blue; +} + +.white .error { + text-decoration-color: red; +} + +.white .error.unsupported { + text-decoration-color: cyan; +} + +.white a.ref.external { + color: black; +} + +.white a.container-ref, +.white a.ref, +.white a.ref:visited, +.white a.package-fqn, +.white a.package-fqn:visited { + color: rgb(0, 0, 238); +} + +.white :target { + background-color: yellow; +} + +.white .string-literal { + color: #98b884; +} + +.white .declaration .simple-name { + font-weight: bold; +} + +.white .line { + background-color: lightgray; + border-right: thin solid darkgray; +} \ No newline at end of file diff --git a/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/logic.js b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/logic.js new file mode 100644 index 00000000000..f161180ade9 --- /dev/null +++ b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/logic.js @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2019 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. + */ + +function updateSelectedRefs() { + $("a.ref.selected").removeClass("selected"); + $("a.ref").each(function () { + if ($(this).attr("jshref") == window.location.href) { + $(this).addClass("selected"); + } + }); +} + +var key = "fir-dump-theme"; + + +var theme = localStorage.getItem(key); +if (!theme) { + theme = "white" +} + +function toggleTheme() { + var body = $("body"); + body.removeClass(theme); + if (theme === "white") { + theme = "dark" + } + else { + theme = "white" + } + body.addClass(theme); + + localStorage.setItem(key, theme); +} + +$(document).ready(function () { + $(".fold-container").click(function (event) { + $(this).toggleClass("unfold"); + event.stopPropagation(); + }); + var refs = $('a.ref:not(.external)'); + refs.each(function () { + var href = $(this).prop('href'); + $(this).attr('href', 'javascript:void(0);'); + $(this).attr('jshref', href); + }); + refs.bind('click', function (e) { + var href = $(this).attr('jshref'); + if (!e.metaKey && e.ctrlKey) { + e.metaKey = e.ctrlKey; + } + if (e.metaKey) { + location.href = href; + return false; + } + }); + updateSelectedRefs(); + $(window).on('hashchange', function (e) { + updateSelectedRefs() + }); + + + var body = $("body"); + body.prepend(""); + body.addClass(theme); + + +}); + +window.onload = function () { + var $line = $('.line'); + var maxWidth = Math.max.apply(Math, $line.map(function () { + return $(this).width(); + }).get()); + $line.each(function () { + $(this).width(maxWidth) + }) +}; \ No newline at end of file diff --git a/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/style.css b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/style.css new file mode 100644 index 00000000000..b60ebff552f --- /dev/null +++ b/compiler/fir/dump/resources/org/jetbrains/kotlin/fir/dump/style.css @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2019 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. + */ + +pre { + counter-reset: line; +} + +.line:after { + content: counter(line); + -webkit-user-select: none; + padding-right: 5px; + margin-right: 0; + margin-left: auto; +} + +.line { + counter-increment: line; + display: inline-flex; +} + +.unresolved { + text-decoration-style: wavy; + text-decoration: underline; +} + +.error { + text-decoration-style: wavy; + text-decoration: underline; +} + +a.container-ref, +a.package-fqn, +a.ref { + text-decoration: none; +} + +.expression-type, .label, .return-label { + border-radius: 5px; + padding-left: 5px; + padding-right: 5px; +} + +.fold-region { + display: none; +} + +.fold-container.unfold > .fold-region { + display: inline; +} + +.toggle-theme { + border-style: solid; + border-width: thin; + border-radius: 10px; +} + +.line { + margin-right: 5px; +} \ No newline at end of file diff --git a/compiler/fir/dump/src/org/jetbrains/kotlin/fir/dump/HtmlFirDump.kt b/compiler/fir/dump/src/org/jetbrains/kotlin/fir/dump/HtmlFirDump.kt new file mode 100644 index 00000000000..e58a475bc22 --- /dev/null +++ b/compiler/fir/dump/src/org/jetbrains/kotlin/fir/dump/HtmlFirDump.kt @@ -0,0 +1,1252 @@ +/* + * Copyright 2010-2019 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.dump + +import kotlinx.html.* +import kotlinx.html.stream.appendHTML +import org.apache.commons.lang.StringEscapeUtils +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition +import org.jetbrains.kotlin.fir.expressions.FirWhenSubjectExpression +import org.jetbrains.kotlin.fir.references.FirErrorNamedReference +import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference +import org.jetbrains.kotlin.fir.symbols.ConeSymbol +import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.impl.FirTypePlaceholderProjection +import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid +import org.jetbrains.kotlin.ir.expressions.IrConstKind +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.Variance +import java.io.File +import java.io.Writer + + +internal interface FirLinkResolver { + fun nearPackage(fqName: FqName): String? + + fun symbolSignature(symbol: ConeSymbol): String + fun nearSymbolLocation(symbol: ConeSymbol): String? + + fun classLocation(classId: ClassId): String? + + fun HEAD.supplementary() +} + + +private const val PACKAGE_INDEX = "package-index.html" +private const val MODULE_INDEX = "module-index.html" + + +private fun HEAD.commonHead() { + meta { charset = "utf-8" } + script(src = "https://code.jquery.com/jquery-3.4.0.slim.min.js") { + integrity = "sha256-ZaXnYkHGqIhqTbJ6MB4l9Frs/r7U4jlx7ir8PJYBqbI=" + @Suppress("SpellCheckingInspection") + attributes["crossorigin"] = "anonymous" + } +} + + +private class PackageInfo(val fqName: FqName, val moduleInfo: ModuleInfo) { + val contents = LinkedHashSet() + val packageRoot = fqName.pathSegments().fold(moduleInfo.moduleRoot) { dir, segment -> dir.resolve(segment.asString()) }.also { + it.mkdirs() + } + +} + +private class ModuleInfo(val name: String, outputRoot: File) { + val packages = mutableMapOf() + val moduleRoot = outputRoot.resolve(name).also { + it.mkdirs() + } +} + +private class SupplementaryGenerator(val outputRoot: File) { + fun generateIndex(moduleInfo: ModuleInfo, writer: Writer) { + writer.appendHTML().html { + head { + title { +moduleInfo.name } + commonHead() + supplementary(this, moduleInfo.moduleRoot) + } + body { + h2 { +moduleInfo.name } + ul { + for (packageInfo in moduleInfo.packages.values) { + li { + a( + href = linkToIndex(packageInfo, moduleInfo.moduleRoot), + classes = "container-ref" + ) { + +packageInfo.fqName.asString() + } + } + } + } + } + } + } + + fun linkToIndex(moduleInfo: ModuleInfo, from: File): String { + return moduleInfo.moduleRoot.resolve(MODULE_INDEX).relativeTo(from).path + } + + fun linkToIndex(packageInfo: PackageInfo, from: File): String { + return packageInfo.packageRoot.resolve(PACKAGE_INDEX).relativeTo(from).path + } + + fun generateIndex(packageInfo: PackageInfo, writer: Writer) { + writer.appendHTML().html { + head { + title { +packageInfo.fqName.asString() } + commonHead() + supplementary(this, packageInfo.packageRoot) + } + body { + h4 { + +"In module: " + a( + href = linkToIndex(packageInfo.moduleInfo, packageInfo.packageRoot), + classes = "container-ref" + ) { + +packageInfo.moduleInfo.name + } + } + h2 { +packageInfo.fqName.asString() } + + ul { + for (file in packageInfo.contents) { + li { + a(href = "./$file.fir.html", classes = "container-ref") { +file } + } + } + } + } + } + } + + + fun supplementary(head: HEAD, originDir: File) = with(head) { + for (file in jsFiles) { + script(src = outputRoot.resolve(file).relativeTo(originDir).path) {} + } + for (styleSheet in cssFiles) { + styleLink(outputRoot.resolve(styleSheet).relativeTo(originDir).path) + } + } +} + + +private val jsFiles = listOf("logic.js") +private val cssFiles = listOf("style.css", "colors.white.css", "colors.dark.css") + + +class MultiModuleHtmlFirDump(private val outputRoot: File) { + + private val modules = mutableMapOf() + + private lateinit var currentModule: ModuleInfo + private var inModule = false + private var finished = false + private var index = Index() + + fun module(module: String, block: () -> Unit) { + require(!finished) + inModule = true + currentModule = modules.getOrPut(module) { + ModuleInfo(module, outputRoot) + } + + block() + inModule = false + index = Index() + } + + private val supplementaryGenerator = SupplementaryGenerator(outputRoot) + + fun finish() { + require(!finished) + finished = true + for (module in modules.values) { + module.moduleRoot.resolve(MODULE_INDEX) + .writer().use { + supplementaryGenerator.generateIndex(module, it) + } + for (packageInfo in module.packages.values) { + packageInfo.packageRoot.resolve(PACKAGE_INDEX) + .writer().use { + supplementaryGenerator.generateIndex(packageInfo, it) + } + } + } + val supplementaryFiles = jsFiles + cssFiles + + for (file in supplementaryFiles) { + val stream = this::class.java.getResourceAsStream(file) + outputRoot.resolve(file).outputStream().use { outputStream -> + stream.copyTo(outputStream) + } + } + } + + private fun locationForFile(firFile: FirFile, packageInfo: PackageInfo = packageForFile(firFile)): File { + var name = firFile.name + var index = 0 + while (!packageInfo.contents.add(name)) { + name = firFile.name + ".${index++}" + } + return packageInfo.packageRoot.resolve("$name.fir.html") + } + + private fun packageForFile(firFile: FirFile): PackageInfo { + val packageFqName = firFile.packageFqName + return currentModule.packages.getOrPut(packageFqName) { + PackageInfo(packageFqName, currentModule) + } + } + + fun indexFile(file: FirFile) { + val location = locationForFile(file) + index.files[file] = location + file.accept(index.visitor(location)) + } + + fun generateFile(file: FirFile) { + require(inModule) + + val dumpOutput = index.files[file] ?: error("No location for ${file.name}") + val dumper = HtmlFirDump(LinkResolver(dumpOutput)) + val builder = StringBuilder() + dumper.generate(file, builder) + + dumpOutput.writeText(builder.toString()) + } + + + private inner class LinkResolver(origin: File) : FirLinkResolver { + + override fun HEAD.supplementary() { + supplementaryGenerator.supplementary(this, originDir) + } + + override fun symbolSignature(symbol: ConeSymbol): String { + val id = index.symbolIds[symbol] ?: error("Not found $symbol") + return "id$id" + } + + private val originDir = origin.parentFile + + override fun nearPackage(fqName: FqName): String? { + val packageInfo = currentModule.packages[fqName] ?: return null + return packageInfo.packageRoot.resolve(PACKAGE_INDEX).relativeTo(originDir).path + } + + override fun classLocation(classId: ClassId): String? { + val location = index.classes[classId] ?: return null + return location.relativeTo(originDir).path + "#$classId" + } + + override fun nearSymbolLocation(symbol: ConeSymbol): String? { + val location = index.symbols[symbol] ?: return null + return location.relativeTo(originDir).path + "#${symbolSignature(symbol)}" + } + } + + private inner class Index { + val files = mutableMapOf() + val classes = mutableMapOf() + val symbols = mutableMapOf, File>() + val symbolIds = mutableMapOf, Int>() + private var symbolCounter = 0 + + fun visitor(location: File): FirVisitorVoid { + return object : FirVisitorVoid() { + override fun visitElement(element: FirElement) { + element.acceptChildren(this) + } + + override fun visitRegularClass(regularClass: FirRegularClass) { + classes[regularClass.classId] = location + super.visitRegularClass(regularClass) + } + + fun indexDeclaration(callableDeclaration: FirCallableDeclaration) { + symbols[callableDeclaration.symbol] = location + symbolIds[callableDeclaration.symbol] = symbolCounter++ + } + + override fun visitVariable(variable: FirVariable) { + indexDeclaration(variable) + super.visitVariable(variable) + } + + override fun visitValueParameter(valueParameter: FirValueParameter) { + indexDeclaration(valueParameter) + super.visitValueParameter(valueParameter) + } + + override fun visitNamedFunction(namedFunction: FirNamedFunction) { + indexDeclaration(namedFunction) + super.visitNamedFunction(namedFunction) + } + + override fun visitProperty(property: FirProperty) { + indexDeclaration(property) + super.visitProperty(property) + } + + override fun visitConstructor(constructor: FirConstructor) { + indexDeclaration(constructor) + super.visitConstructor(constructor) + } + } + } + + } +} + +class HtmlFirDump internal constructor(private var linkResolver: FirLinkResolver) { + + + fun generate(element: FirFile, builder: StringBuilder) { + builder.appendHTML().html { + generate(element) + } + } + + private fun FlowContent.keyword(text: String) { + span(classes = "keyword") { +text } + } + + private fun FlowContent.line(block: FlowContent.() -> Unit) { + nl() + block() + br + } + + private fun FlowContent.nl(block: FlowContent.() -> Unit = {}) { + span(classes = "line") { block() } + } + + private fun FlowContent.inl() { + nl() + ident() + } + + private fun FlowContent.simpleName(name: Name) { + span(classes = "simple-name") { + +name.asString() + } + } + + private fun FlowContent.unresolved(block: FlowContent.() -> Unit) { + span(classes = "unresolved") { + block() + } + } + + private fun FlowContent.resolved(block: FlowContent.() -> Unit) { + span(classes = "resolved") { + block() + } + } + + private fun FlowContent.error(block: SPAN.() -> Unit) { + span(classes = "error") { + block() + } + } + + private fun FlowContent.packageName(fqName: FqName) { + line { + keyword("package") + ws + val link = linkResolver.nearPackage(fqName)!! + a(href = link, classes = "package-fqn") { +fqName.asString() } + } + } + + private val FlowContent.ws get() = +" " + + private fun FlowContent.fqn(name: FqName) { + +name.asString() + } + + private fun FlowContent.inlineUnsupported(element: Any) { + span(classes = "fold-container") { + span(classes = "error unsupported") { + +"Unsupported: ${element::class}" + } + span("fold-region") { + val content = when (element) { + is FirElement -> element.render() + else -> element.toString() + } + +"\n" + +content + +"\n" + } + } + } + + private fun FlowContent.unsupported(element: FirElement) { + line { + inlineUnsupported(element) + } + } + + private fun FlowContent.modality(modality: Modality?) { + if (modality == null) return + keyword(modality.name.toLowerCase()) + } + + private fun FlowContent.visibility(visibility: Visibility) { + if (visibility == Visibilities.UNKNOWN) + return unresolved { keyword("public?") } + return keyword(visibility.toString()) + } + + private fun FlowContent.declarationStatus(status: FirDeclarationStatus) { + visibility(status.visibility) + ws + modality(status.modality) + ws + if (status.isCompanion) { + keyword("companion ") + } + if (status.isInline) { + keyword("inline ") + } + if (status.isInner) { + keyword("inner ") + } + if (status.isExpect) { + keyword("expect ") + } + if (status.isActual) { + keyword("actual ") + } + if (status.isOverride) { + keyword("override ") + } + if (status.isOperator) { + keyword("operator ") + } + if (status.isInfix) { + keyword("infix ") + } + if (status.isInline) { + keyword("inline ") + } + if (status.isTailRec) { + keyword("tailrec ") + } + if (status.isExternal) { + keyword("external ") + } + if (status.isConst) { + keyword("const ") + } + if (status.isLateInit) { + keyword("lateinit ") + } + if (status.isInner) { + keyword("inner ") + } + if (status.isCompanion) { + keyword("companion ") + } + if (status.isData) { + keyword("data ") + } + if (status.isSuspend) { + keyword("suspend ") + } + if (status.isStatic) { + keyword("static ") + } + } + + + private fun FlowContent.anchoredName(name: Name, signature: String) { + span(classes = "declaration") { + id = signature + simpleName(name) + } + } + + private var currentIdent = 0 + + private fun FlowContent.ident(level: Int = currentIdent) { + +" ".repeat(level * 4) + } + + @Suppress("SpellCheckingInspection") + private fun FlowContent.iline(block: FlowContent.() -> Unit) { + inl() + block() + br + } + + private fun FlowContent.withIdentLevel(block: FlowContent.() -> Unit) { + currentIdent++ + block() + currentIdent-- + } + + private fun FlowContent.generateList(list: List, separator: String = ", ", generate: FlowContent.(E) -> Unit) { + if (list.isEmpty()) return + generate(list.first()) + for (element in list.drop(1)) { + +separator + generate(element) + } + } + + + private fun FlowContent.generate(klass: FirRegularClass) { + inl() + + declarationStatus(klass.status) + + + when (klass.classKind) { + ClassKind.CLASS -> keyword("class") + ClassKind.INTERFACE -> keyword("interface") + ClassKind.ENUM_CLASS -> keyword("enum class") + ClassKind.ENUM_ENTRY -> Unit // ? + ClassKind.ANNOTATION_CLASS -> keyword("annotation class") + ClassKind.OBJECT -> keyword("object") + } + ws + anchoredName(klass.name, klass.classId.asString()) + if (klass.superTypeRefs.isNotEmpty()) { + +": " + generateList(klass.superTypeRefs) { + generate(it) + } + } + if (klass.declarations.isNotEmpty()) { + +" {" + br + + withIdentLevel { + for (declaration in klass.declarations) { + generate(declaration) + line {} + } + } + + + inl() + +"}" + } + br + + } + + private fun FlowContent.generate(flexibleType: ConeFlexibleType) { + generate(flexibleType.lowerBound) + +".." + generate(flexibleType.upperBound) + } + + private fun FlowContent.generate(type: ConeClassType) { + resolved { + val link = linkResolver.classLocation(type.lookupTag.classId) + declarationRef(link, setOf("class-fqn")) { + title = type.lookupTag.classId.asString() + fqn(type.lookupTag.classId.relativeClassName) + } + } + } + + private fun FlowContent.generate(variableAssignment: FirVariableAssignment) { + generateReceiver(variableAssignment) + generate(variableAssignment.lValue) + +" " + +variableAssignment.operation.operator + +" " + generate(variableAssignment.rValue) + } + + private fun FlowContent.generate(projection: ConeKotlinTypeProjection) { + when (projection) { + is ConeStarProjection -> +"*" + is ConeTypedProjection -> { + when (projection.kind) { + ProjectionKind.IN -> keyword("in ") + ProjectionKind.OUT -> keyword("out ") + else -> { + } + } + + generate(projection.type) + } + } + } + + private fun FlowContent.stringLiteral(value: Any?) { + val text = StringEscapeUtils.escapeJava(value.toString()) + span("string-literal") { + when (value) { + is String -> +"\"$text\"" + is Char -> +"'$text'" + else -> error("Unknown string literal: \"$value\" ${value?.let { it::class }}") + } + } + } + + private fun FlowContent.generate(expression: FirConstExpression<*>) { + val value = expression.value + if (value == null && expression.kind != IrConstKind.Null) { + return error { + +"null value" + } + } + + when (expression.kind) { + IrConstKind.Null -> keyword("null") + IrConstKind.Boolean -> keyword(value.toString()) + IrConstKind.String, IrConstKind.Char -> + stringLiteral(value) + IrConstKind.Byte -> { + +value.toString() + keyword("B") + } + IrConstKind.Short -> { + +value.toString() + keyword("S") + } + IrConstKind.Int -> { + +value.toString() + keyword("I") + } + IrConstKind.Long -> { + +value.toString() + keyword("L") + } + IrConstKind.Float -> { + +value.toString() + keyword("F") + } + IrConstKind.Double -> { + +value.toString() + keyword("D") + } + } + + } + + private fun FlowContent.generate(type: ConeKotlinType) { + when (type) { + is ConeClassErrorType -> error { +type.reason } + is ConeClassType -> generate(type) + is ConeAbbreviatedType -> inlineUnsupported(type) + is ConeTypeParameterType -> resolved { +type.lookupTag.name.asString() } + is ConeTypeVariableType -> resolved { +type.lookupTag.name.asString() } + is ConeFlexibleType -> resolved { generate(type) } + is ConeCapturedType -> inlineUnsupported(type) + is ConeDefinitelyNotNullType -> inlineUnsupported(type) + } + if (type.typeArguments.isNotEmpty()) { + +"<" + generateList(type.typeArguments.toList()) { + generate(it) + } + +">" + } + if (type.isMarkedNullable) { + +"?" + } + } + + private fun FlowContent.generate(typeProjection: FirTypeProjection) { + when (typeProjection) { + is FirTypeProjectionWithVariance -> { + generate(typeProjection.variance) + generate(typeProjection.typeRef) + } + is FirStarProjection -> +"*" + is FirTypePlaceholderProjection -> +"_" + } + } + + private fun FlowContent.generate(variance: Variance) { + when (variance) { + Variance.INVARIANT -> Unit + Variance.IN_VARIANCE -> keyword("in ") + Variance.OUT_VARIANCE -> keyword("out ") + } + } + + private fun FlowContent.generateTypeProjections(typeProjections: List) { + if (typeProjections.isEmpty()) return + +"<" + generateList(typeProjections) { + generate(it) + } + +">" + } + + private fun FlowContent.generate(typeRef: FirTypeRef) { + when (typeRef) { + is FirErrorTypeRef -> error { +typeRef.reason } + is FirResolvedTypeRef -> generate(typeRef.type) + is FirImplicitTypeRef -> unresolved { keyword("") } + is FirUserTypeRef -> unresolved { + generateList(typeRef.qualifier, separator = ".") { + simpleName(it.name) + generateTypeProjections(it.typeArguments) + } + if (typeRef.isMarkedNullable) { + +"?" + } + } + else -> inlineUnsupported(typeRef) + } + } + + private fun FlowContent.generate(memberDeclaration: FirMemberDeclaration) { + when (memberDeclaration) { + is FirRegularClass -> generate(memberDeclaration) + is FirNamedFunction -> generate(memberDeclaration) + is FirProperty -> generate(memberDeclaration) + is FirConstructor -> generate(memberDeclaration) + else -> unsupported(memberDeclaration) + } + } + + private fun FlowContent.generateTypeParameters(typeParameterContainer: FirTypeParameterContainer) { + if (typeParameterContainer.typeParameters.isEmpty()) return + +"<" + generateList(typeParameterContainer.typeParameters) { + generate(it.variance) + if (it.isReified) { + keyword("reified ") + } + simpleName(it.name) + if (it.bounds.isNotEmpty()) { + +": " + generateList(it.bounds) { bound -> + generate(bound) + } + } + } + +"> " + } + + private fun FlowContent.generateReceiver(declaration: FirCallableDeclaration) { + generateReceiver(declaration.receiverTypeRef) + } + + private fun FlowContent.generateReceiver(receiverTypeRef: FirTypeRef?) { + receiverTypeRef ?: return + generate(receiverTypeRef) + +"." + } + + private fun FlowContent.generate(property: FirProperty) { + //anchor + iline { + declarationStatus(property.status) + if (property.isVal) { + keyword("val ") + } else { + keyword("var ") + } + generateTypeParameters(property) + generateReceiver(property) + symbolRef(property.symbol) { + simpleName(property.name) + } + +": " + generate(property.returnTypeRef) + + + val initializer = property.initializer + if (initializer != null) { + +" = " + generate(initializer) + } + } + + } + + + private fun FlowContent.exprType(type: FirTypeRef, block: SPAN.() -> Unit) { + span(classes = "typed-expression fold-container") { + block() + span(classes = "expression-type fold-region") { + +": " + generate(type) + } + } + } + + private fun FlowContent.generate(statement: FirStatement) { + when (statement) { + is FirAnonymousFunction -> generate(statement, isStatement = true) + is FirWhileLoop -> generate(statement) + is FirWhenExpression -> generate(statement, isStatement = true) + is FirTryExpression -> generate(statement, isStatement = true) + is FirExpression -> iline { generate(statement) } + is FirVariable -> iline { generate(statement) } + is FirVariableAssignment -> iline { generate(statement) } + else -> unsupported(statement) + } + } + + private fun FlowContent.generate(variable: FirVariable) { + if (variable.isVal) { + keyword("val ") + } else { + keyword("var ") + } + + symbolAnchor(variable.symbol) { simpleName(variable.name) } + +": " + generate(variable.returnTypeRef) + val initializer = variable.initializer + if (initializer != null) { + +" = " + generate(initializer) + } + } + + private fun FlowContent.declarationRef( + href: String?, + classes: Set = emptySet(), + body: A.() -> Unit + ) { + a(href = href, classes = "ref") { + this.classes += classes + if (href == null) { + this.classes += "external" + } + body() + } + } + + private fun FlowContent.symbolRef(symbol: ConeSymbol?, body: FlowContent.() -> Unit) { + val link = if (symbol == null) null else linkResolver.nearSymbolLocation(symbol) + declarationRef(link, setOf("symbol")) { + body() + } + } + + private fun FlowContent.generate(reference: FirReference) { + when (reference) { + is FirSuperReference -> keyword("super") + is FirThisReference -> { + keyword("this") + val label = reference.labelName ?: "" + span("label") { + +"@" + +label + } + } + is FirErrorNamedReference -> { + error { + title = reference.errorReason + simpleName(reference.name) + } + } + is FirSimpleNamedReference -> { + unresolved { + simpleName(reference.name) + } + } + is FirResolvedCallableReference -> { + resolved { + symbolRef(reference.coneSymbol) { + simpleName(reference.name) + } + } + } + } + } + + private fun FlowContent.generateReceiver(access: FirQualifiedAccess) { + val explicitReceiver = access.explicitReceiver + if (explicitReceiver != null) { + generate(explicitReceiver) + if (access.safe) { + +"?." + } else { + +"." + } + } + } + + private fun FlowContent.generate(functionCall: FirFunctionCall) { + generateReceiver(functionCall) + + generate(functionCall.calleeReference) + generateTypeProjections(functionCall.typeArguments) + +"(" + generateList(functionCall.arguments) { + generate(it) + } + +")" + } + + private fun FlowContent.generateBinary(expression: FirOperatorCall) { + val (first, second) = expression.arguments + generate(first) + ws + unresolved { +expression.operation.operator } + ws + generate(second) + } + + private fun FlowContent.generateUnary(expression: FirOperatorCall) { + val (first) = expression.arguments + unresolved { +expression.operation.operator } + ws + generate(first) + } + + private fun FlowContent.generate(expression: FirOperatorCall) { + when (expression.arguments.size) { + 1 -> generateUnary(expression) + 2 -> generateBinary(expression) + else -> inlineUnsupported(expression) + } + } + + private fun FlowContent.generate(typeOperatorCall: FirTypeOperatorCall) { + val (expression) = typeOperatorCall.arguments + generate(expression) + ws + keyword(typeOperatorCall.operation.operator) + ws + generate(typeOperatorCall.conversionTypeRef) + } + + @Suppress("UNUSED_PARAMETER") + private fun FlowContent.generate(elseIfTrueCondition: FirElseIfTrueCondition) { + keyword("else") + } + + @Suppress("UNUSED_PARAMETER") + private fun FlowContent.generate(whenSubjectExpression: FirWhenSubjectExpression) { + +"" + } + + private fun FlowContent.generateMultiLineExpression(isStatement: Boolean, body: FlowContent.() -> Unit) { + if (!isStatement) { + br + currentIdent++ + } + body() + if (!isStatement) { + currentIdent-- + inl() + } + } + + private fun FlowContent.generate(tryExpression: FirTryExpression, isStatement: Boolean) { + generateMultiLineExpression(!isStatement) { + iline { + keyword("try ") + generateBlockIfAny(tryExpression.tryBlock) + } + + for (catch in tryExpression.catches) { + keyword(" catch ") + +"(" + generate(catch.parameter) + +") " + generateBlockIfAny(catch.block) + } + val finallyBlock = tryExpression.finallyBlock + if (finallyBlock != null) { + keyword(" finally ") + generateBlockIfAny(finallyBlock) + } + } + } + + private fun FlowContent.generate(whileLoop: FirWhileLoop) { + iline { + keyword("while ") + +"(" + generate(whileLoop.condition) + +") " + generateBlockIfAny(whileLoop.block) + } + } + + private fun FlowContent.generate(whenExpression: FirWhenExpression, isStatement: Boolean) { + generateMultiLineExpression(isStatement) { + iline { + keyword("when") + +" (" + whenExpression.subjectVariable?.let { generate(it) } + ?: whenExpression.subject?.let { generate(it) } + +") {" + } + withIdentLevel { + for (branch in whenExpression.branches) { + inl() + generate(branch.condition) + +" -> {" + if (branch.result.statements.isNotEmpty()) { + br + withIdentLevel { + generateBlockContent(branch.result) + } + inl() + } + +"}" + br + } + } + iline { + +"}" + } + } + } + + private fun FlowContent.generate(throwExpression: FirThrowExpression) { + keyword("throw ") + generate(throwExpression.exception) + } + + private fun FlowContent.generate(expression: FirExpression) { + exprType(expression.typeRef) { + when (expression) { + is FirAnonymousFunction -> generate(expression, isStatement = false) + is FirThrowExpression -> generate(expression) + is FirWhenSubjectExpression -> generate(expression) + is FirElseIfTrueCondition -> generate(expression) + is FirWhenExpression -> generate(expression, isStatement = false) + is FirTryExpression -> generate(expression, isStatement = false) + is FirConstExpression<*> -> generate(expression) + is FirReturnExpression -> { + span("return-label") { + symbolRef((expression.target.labeledElement as? FirSymbolOwner<*>)?.symbol) { + +"^" + +(expression.target.labelName ?: "") + } + } + generate(expression.result) + } + is FirFunctionCall -> { + generate(expression) + } + is FirQualifiedAccessExpression -> generate(expression) + is FirNamedArgumentExpression -> { + simpleName(expression.name) + +" = " + generate(expression.expression) + } + is FirTypeOperatorCall -> generate(expression) + is FirOperatorCall -> generate(expression) + else -> inlineUnsupported(expression) + } + } + } + + private fun FlowContent.generate(qualifiedAccessExpression: FirQualifiedAccessExpression) { + generateReceiver(qualifiedAccessExpression) + generate(qualifiedAccessExpression.calleeReference) + } + + private fun FlowContent.generateBlockContent(block: FirBlock) { + for (statement in block.statements) { + generate(statement) + } + } + + private fun FlowContent.symbolAnchor(symbol: ConeSymbol, body: FlowContent.() -> Unit) { + span(classes = "declaration") { + id = linkResolver.symbolSignature(symbol) + body() + } + } + + private fun FlowContent.generate(valueParameter: FirValueParameter) { + symbolAnchor(valueParameter.symbol) { simpleName(valueParameter.name) } + +": " + generate(valueParameter.returnTypeRef) + val defaultValue = valueParameter.defaultValue + if (defaultValue != null) { + +" = " + generate(defaultValue) + } + } + + private fun FlowContent.generateBlockIfAny(block: FirBlock?) { + if (block == null) return + +" {" + generateMultiLineExpression(isStatement = false) { + generateBlockContent(block) + } + +"}" + } + + private fun FlowContent.generate(function: FirNamedFunction) { + iline { + declarationStatus(function.status) + keyword("fun ") + generateTypeParameters(function) + generateReceiver(function) + symbolRef(function.symbol) { + simpleName(function.name) + } + +"(" + generateList(function.valueParameters) { + generate(it) + } + +"): " + generate(function.returnTypeRef) + generateBlockIfAny(function.body) + } + + } + + private fun FlowContent.generate(function: FirConstructor) { + iline { + declarationStatus(function.status) + symbolAnchor(function.symbol) { + keyword("constructor") + } + generateTypeParameters(function) + +"(" + generateList(function.valueParameters) { + generate(it) + } + +"): " + generate(function.returnTypeRef) + generateBlockIfAny(function.body) + } + } + + private fun FlowContent.generate(anonymousFunction: FirAnonymousFunction, isStatement: Boolean) { + generateMultiLineExpression(isStatement) { + iline { + keyword("fun ") + generateReceiver(anonymousFunction.receiverTypeRef) + + +"(" + generateList(anonymousFunction.valueParameters) { + generate(it) + } + +"): " + generate(anonymousFunction.returnTypeRef) + generateBlockIfAny(anonymousFunction.body) + } + } + } + + private fun FlowContent.generate(declaration: FirDeclaration) { + when (declaration) { + is FirMemberDeclaration -> generate(declaration) + else -> unsupported(declaration) + } + } + + private fun FlowContent.generate(import: FirImport) { + fun simpleRender() { + val fqName = import.importedFqName + if (fqName != null) { + fqn(fqName) + if (import.isAllUnder) + +"." + } + if (import.isAllUnder) { + +"*" + } + } + + line { + keyword("import") + ws + when (import) { + is FirResolvedImport -> { + val classId = import.resolvedClassId + if (classId == null) { + val importedFqName = import.importedFqName + if (importedFqName != null) { + fqn(importedFqName) + } else { + error { +"no fqn" } + } + } else { + +classId.asString() + } + if (import.isAllUnder) { + +".*" + } + } + else -> { + unresolved { + simpleRender() + } + } + } + val alias = import.aliasName + if (alias != null) { + ws + keyword("as") + ws + simpleName(alias) + } + } + } + + private fun HTML.generate(file: FirFile) { + head { + title { +file.name } + commonHead() + with(linkResolver) { + supplementary() + } + } + body { + h4 { + +"Source: " + val vFile = file.psi?.containingFile?.virtualFile + if (vFile != null) { + a(href = vFile.url, classes = "container-ref") { + +vFile.path + } + } else { + +"No source" + } + } + h2 { + +file.name + } + pre { + packageName(file.packageFqName) + line {} + for (import in file.imports) { + generate(import) + } + line {} + for (declaration in file.declarations) { + generate(declaration) + line {} + } + } + } + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 8414c16dd69..200edaa64a2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include ":kotlin-build-common", ":compiler:fir:resolve", ":compiler:fir:java", ":compiler:fir:modularized-tests", + ":compiler:fir:dump", ":compiler:frontend", ":compiler:frontend.common", ":compiler:frontend.java",