[FIR generator] Extract the Builder class to a common module

We want to use it to generate builder classes for other
trees, just like we already do with elements (see `AbstractElement` and
`AbsractImplementation`).
This commit is contained in:
Sergej Jaskiewicz
2023-11-18 13:38:45 +01:00
committed by Space Team
parent 91b5a71f1a
commit cae4a9930b
19 changed files with 924 additions and 642 deletions
@@ -5,16 +5,10 @@
package org.jetbrains.kotlin.fir.tree.generator
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractBuilderConfigurator
import org.jetbrains.kotlin.fir.tree.generator.model.Element
import org.jetbrains.kotlin.fir.tree.generator.model.Field
import org.jetbrains.kotlin.fir.tree.generator.model.Implementation
import org.jetbrains.kotlin.fir.tree.generator.model.LeafBuilder
import org.jetbrains.kotlin.fir.tree.generator.printer.invisibleField
import org.jetbrains.kotlin.generators.tree.traverseParents
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirBuilderConfigurator
object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTreeBuilder) {
fun configureBuilders() = with(firTreeBuilder) {
object BuilderConfigurator : AbstractFirBuilderConfigurator<FirTreeBuilder>(FirTreeBuilder.elements) {
override fun configureBuilders() = with(FirTreeBuilder) {
val declarationBuilder by builder {
fields from declaration without "symbol"
}
@@ -99,7 +93,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
defaultNull("delegatedConstructor")
defaultNull("body")
default("contractDescription", "FirEmptyContractDescription")
useTypes(emptyContractDescriptionType)
additionalImports(emptyContractDescriptionType)
}
}
@@ -108,7 +102,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
defaultNull("delegatedConstructor")
defaultNull("body")
default("contractDescription", "FirEmptyContractDescription")
useTypes(emptyContractDescriptionType)
additionalImports(emptyContractDescriptionType)
}
builder(constructor, "FirConstructorImpl") {
@@ -147,7 +141,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
default("argumentMapping", "FirEmptyAnnotationArgumentMapping")
default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource")
default("annotationResolvePhase", "FirAnnotationResolvePhase.Unresolved")
useTypes(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
additionalImports(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
withCopy()
}
@@ -156,7 +150,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
default("argumentList", "FirEmptyArgumentList")
default("argumentMapping", "FirEmptyAnnotationArgumentMapping")
default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource")
useTypes(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
additionalImports(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
}
builder(arrayLiteral) {
@@ -165,7 +159,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
builder(augmentedArraySetCall) {
default("calleeReference", "FirStubReference")
useTypes(stubReferenceType)
additionalImports(stubReferenceType)
}
builder(propertyAccessExpression) {
@@ -186,7 +180,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
default("argumentList") {
value = "FirEmptyArgumentList"
}
useTypes(emptyArgumentListType)
additionalImports(emptyArgumentListType)
}
builder(whileLoop) {
@@ -213,7 +207,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
value = "FirEmptyArgumentList"
}
defaultNull("dispatchReceiver")
useTypes(emptyArgumentListType)
additionalImports(emptyArgumentListType)
}
val configurationForFunctionCallBuilder: LeafBuilderConfigurationContext.() -> Unit = {
@@ -223,7 +217,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
default("argumentList") {
value = "FirEmptyArgumentList"
}
useTypes(emptyArgumentListType)
additionalImports(emptyArgumentListType)
}
builder(functionCall) {
@@ -232,8 +226,8 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
value = "FirFunctionCallOrigin.Regular"
}
}
builder(integerLiteralOperatorCall, init = configurationForFunctionCallBuilder)
builder(implicitInvokeCall, init = configurationForFunctionCallBuilder)
builder(integerLiteralOperatorCall, config = configurationForFunctionCallBuilder)
builder(implicitInvokeCall, config = configurationForFunctionCallBuilder)
builder(getClassCall) {
parents += callBuilder
@@ -269,7 +263,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
default("argumentList") {
value = "FirEmptyArgumentList"
}
useTypes(emptyArgumentListType)
additionalImports(emptyArgumentListType)
}
builder(stringConcatenationCall) {
@@ -298,21 +292,21 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
default("status", "FirResolvedDeclarationStatusImpl.DEFAULT_STATUS_FOR_STATUSLESS_DECLARATIONS")
default("typeRef", "FirImplicitTypeRefImplWithoutSource")
withCopy()
useTypes(emptyContractDescriptionType, resolvedDeclarationStatusImport, firImplicitTypeWithoutSourceType)
additionalImports(emptyContractDescriptionType, resolvedDeclarationStatusImport, firImplicitTypeWithoutSourceType)
}
builder(propertyAccessor) {
parents += functionBuilder
defaultNull("body")
default("contractDescription", "FirEmptyContractDescription")
useTypes(emptyContractDescriptionType)
additionalImports(emptyContractDescriptionType)
withCopy()
}
builder(whenExpression) {
defaultNull("exhaustivenessStatus")
default("calleeReference", "FirStubReference")
useTypes(stubReferenceType)
additionalImports(stubReferenceType)
}
builder(resolvedTypeRef) {
@@ -352,7 +346,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
parents += typeParametersOwnerBuilder
defaultNull("body")
default("contractDescription", "FirEmptyContractDescription")
useTypes(emptyContractDescriptionType)
additionalImports(emptyContractDescriptionType)
openBuilder()
withCopy()
}
@@ -362,17 +356,17 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
builder(tryExpression) {
default("calleeReference", "FirStubReference")
useTypes(stubReferenceType)
additionalImports(stubReferenceType)
}
builder(checkNotNullCall) {
default("calleeReference", "FirStubReference")
useTypes(stubReferenceType)
additionalImports(stubReferenceType)
}
builder(elvisExpression) {
default("calleeReference", "FirStubReference")
useTypes(stubReferenceType)
additionalImports(stubReferenceType)
}
builder(anonymousInitializer) {
@@ -442,7 +436,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
fieldPredicate = { it.invisibleField },
builderPredicate = { it.wantsCopy },
) {
useTypes(resolvePhaseExtensionImport)
additionalImports(resolvePhaseExtensionImport)
}
configureFieldInAllLeafBuilders(
@@ -462,40 +456,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
field = "deprecationsProvider"
) {
default(it, "UnresolvedDeprecationProvider")
useTypes(unresolvedDeprecationsProviderType)
additionalImports(unresolvedDeprecationsProviderType)
}
}
private inline fun findImplementationsWithElementInParents(
element: Element,
implementationPredicate: (Implementation) -> Boolean = { true }
): Collection<Implementation> {
return FirTreeBuilder.elements.flatMap { it.allImplementations }.mapNotNullTo(mutableSetOf()) {
if (!implementationPredicate(it)) return@mapNotNullTo null
var hasAnnotations = false
if (it.element == element) return@mapNotNullTo null
it.element.traverseParents {
if (it == element) {
hasAnnotations = true
}
}
it.takeIf { hasAnnotations }
}
}
private fun configureFieldInAllLeafBuilders(
field: String,
builderPredicate: ((LeafBuilder) -> Boolean)? = null,
fieldPredicate: ((Field) -> Boolean)? = null,
init: LeafBuilderConfigurationContext.(field: String) -> Unit
) {
val builders = FirTreeBuilder.elements.flatMap { it.allImplementations }.mapNotNull { it.builder }
for (builder in builders) {
if (builderPredicate != null && !builderPredicate(builder)) continue
if (!builder.allFields.any { it.name == field }) continue
if (fieldPredicate != null && !fieldPredicate(builder[field])) continue
LeafBuilderConfigurationContext(builder).init(field)
}
}
}
@@ -30,7 +30,7 @@ fun main(args: Array<String>) {
addPureAbstractElement(FirTreeBuilder.elements, pureAbstractElementType)
BuilderConfigurator.configureBuilders()
val previouslyGeneratedFiles = collectPreviouslyGeneratedFiles(generationPath)
val generatedFiles = generateElements(FirTreeBuilder, generationPath)
val generatedFiles = generateElements(FirTreeBuilder, BuilderConfigurator, generationPath)
generatedFiles.forEach { GeneratorsFileUtil.writeFileIfContentChanged(it.file, it.newText, logNotChanged = false) }
removeExtraFilesFromPreviousGeneration(previouslyGeneratedFiles, generatedFiles.map { it.file })
}
@@ -126,4 +126,3 @@ val firTransformerType = generatedType("visitors", "FirTransformer")
val resolveStateAccessAnnotation = type("fir.declarations", "ResolveStateAccess", kind = TypeKind.Class)
val unresolvedExpressionTypeAccessAnnotation = type("fir.expressions", "UnresolvedExpressionTypeAccess", kind = TypeKind.Class)
val firBuilderDslAnnotation = type("fir.builder", "FirBuilderDsl", kind = TypeKind.Class)
val experimentalContractsAnnotation = type("kotlin.contracts", "ExperimentalContracts", exactPackage = true, kind = TypeKind.Class)
@@ -1,180 +0,0 @@
/*
* Copyright 2010-2020 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.fir.tree.generator.context
import org.jetbrains.kotlin.fir.tree.generator.model.*
import org.jetbrains.kotlin.utils.DummyDelegate
import org.jetbrains.kotlin.generators.tree.Importable
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
abstract class AbstractBuilderConfigurator<T : AbstractFirTreeBuilder>(val firTreeBuilder: T) {
abstract class BuilderConfigurationContext {
abstract val builder: Builder
private fun getField(name: String): FieldWithDefault {
return builder[name]
}
fun useTypes(vararg types: Importable) {
types.forEach { builder.usedTypes += it }
}
fun defaultNoReceivers(notNullExplicitReceiver: Boolean = false) {
if (!notNullExplicitReceiver) {
defaultNull("explicitReceiver")
}
defaultNull("dispatchReceiver", "extensionReceiver")
}
fun default(field: String, value: String) {
default(field) {
this.value = value
}
}
fun defaultTrue(field: String) {
default(field) {
value = "true"
}
}
fun defaultFalse(vararg fields: String) {
for (field in fields) {
default(field) {
value = "false"
}
}
}
fun defaultNull(vararg fields: String) {
for (field in fields) {
default(field) {
value = "null"
}
require(getField(field).nullable) {
"$field is not nullable field"
}
}
}
fun default(field: String, init: DefaultValueContext.() -> Unit) {
DefaultValueContext(getField(field)).apply(init).applyConfiguration()
}
inner class DefaultValueContext(private val field: FieldWithDefault) {
var value: String? = null
var notNull: Boolean? = null
fun applyConfiguration() {
if (value != null) field.defaultValueInBuilder = value
if (notNull != null) field.notNull = notNull!!
}
}
}
class IntermediateBuilderConfigurationContext(override val builder: IntermediateBuilder) : BuilderConfigurationContext() {
inner class Fields {
// fields from <element>
infix fun from(element: Element): ExceptConfigurator {
builder.fields += element.allFields.map {
FieldWithDefault(it.copy())
}
builder.packageName = "${element.packageName}.builder"
builder.materializedElement = element
return ExceptConfigurator()
}
}
inner class ExceptConfigurator {
infix fun without(name: String) {
without(listOf(name))
}
infix fun without(names: List<String>) {
builder.fields.removeAll { it.name in names }
}
}
val fields = Fields()
val parents: MutableList<IntermediateBuilder> get() = builder.parents
}
inner class IntermediateBuilderDelegateProvider(
private val name: String?,
private val block: IntermediateBuilderConfigurationContext.() -> Unit
) {
lateinit var builder: IntermediateBuilder
operator fun provideDelegate(
thisRef: Nothing?,
prop: KProperty<*>
): ReadOnlyProperty<Nothing?, IntermediateBuilder> {
val name = name ?: "Fir${prop.name.replaceFirstChar(Char::uppercaseChar)}"
builder = IntermediateBuilder(name).apply {
firTreeBuilder.intermediateBuilders += this
IntermediateBuilderConfigurationContext(this).block()
}
return DummyDelegate(builder)
}
}
inner class LeafBuilderConfigurationContext(override val builder: LeafBuilder) : BuilderConfigurationContext() {
val parents: MutableList<IntermediateBuilder> get() = builder.parents
fun openBuilder() {
builder.isOpen = true
}
fun withCopy() {
builder.wantsCopy = true
}
}
fun builder(name: String? = null, block: IntermediateBuilderConfigurationContext.() -> Unit): IntermediateBuilderDelegateProvider {
return IntermediateBuilderDelegateProvider(name, block)
}
fun builder(element: Element, type: String? = null, init: LeafBuilderConfigurationContext.() -> Unit) {
val implementation = element.extractImplementation(type)
val builder = implementation.builder
requireNotNull(builder)
LeafBuilderConfigurationContext(builder).apply(init)
}
private fun Element.extractImplementation(type: String?): Implementation {
return if (type == null) {
allImplementations.filter { it.kind?.hasLeafBuilder == true }.singleOrNull() ?: this@AbstractBuilderConfigurator.run {
val message = buildString {
appendLine("${this@extractImplementation} has multiple implementations:")
for (implementation in allImplementations) {
appendLine(" - ${implementation.typeName}")
}
appendLine("Please specify implementation is needed")
}
throw IllegalArgumentException(message)
}
} else {
allImplementations.firstOrNull { it.typeName == type } ?: this@AbstractBuilderConfigurator.run {
val message = buildString {
appendLine("${this@extractImplementation} has not implementation $type. Existing implementations:")
for (implementation in allImplementations) {
appendLine(" - ${implementation.typeName}")
}
appendLine("Please specify implementation is needed")
}
throw IllegalArgumentException(message)
}
}
}
fun noBuilder(element: Element, type: String? = null) {
val implementation = element.extractImplementation(type)
implementation.builder = null
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2020 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.fir.tree.generator.context
import org.jetbrains.kotlin.fir.tree.generator.model.Element
import org.jetbrains.kotlin.fir.tree.generator.model.Field
import org.jetbrains.kotlin.fir.tree.generator.model.FieldWithDefault
import org.jetbrains.kotlin.fir.tree.generator.model.Implementation
import org.jetbrains.kotlin.generators.tree.config.AbstractBuilderConfigurator
abstract class AbstractFirBuilderConfigurator<T : AbstractFirTreeBuilder>(
elements: List<Element>
) : AbstractBuilderConfigurator<Element, Implementation, FieldWithDefault, Field>(elements) {
final override val namePrefix: String
get() = "Fir"
final override val defaultBuilderPackage: String
get() = "org.jetbrains.kotlin.fir.tree.builder"
final override fun builderFieldFromElementField(elementField: Field) = FieldWithDefault(elementField.copy())
protected fun BuilderConfigurationContext.defaultNoReceivers(notNullExplicitReceiver: Boolean = false) {
if (!notNullExplicitReceiver) {
defaultNull("explicitReceiver")
}
defaultNull("dispatchReceiver", "extensionReceiver")
}
}
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.fir.tree.generator.context
import org.jetbrains.kotlin.fir.tree.generator.model.Element
import org.jetbrains.kotlin.fir.tree.generator.model.ElementRef
import org.jetbrains.kotlin.fir.tree.generator.model.IntermediateBuilder
import org.jetbrains.kotlin.fir.tree.generator.printer.BASE_PACKAGE
import org.jetbrains.kotlin.generators.tree.ClassRef
import org.jetbrains.kotlin.generators.tree.PositionTypeParameterRef
@@ -26,7 +25,6 @@ abstract class AbstractFirTreeBuilder {
}
val elements = mutableListOf(baseFirElement)
val intermediateBuilders = mutableListOf<IntermediateBuilder>()
protected fun element(kind: Element.Kind, vararg dependencies: Element): ElementDelegateProvider {
return ElementDelegateProvider(kind, dependencies, isSealed = false, predefinedName = null)
@@ -1,84 +1,10 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.fir.tree.generator.model
import org.jetbrains.kotlin.generators.tree.*
typealias Builder = org.jetbrains.kotlin.generators.tree.Builder<FieldWithDefault, Element>
private const val DEFAULT_BUILDER_PACKAGE = "org.jetbrains.kotlin.fir.tree.builder"
sealed class Builder : FieldContainer<FieldWithDefault>, TypeRef, Importable {
val parents: MutableList<IntermediateBuilder> = mutableListOf()
val usedTypes: MutableList<Importable> = mutableListOf()
abstract override val allFields: List<FieldWithDefault>
abstract val uselessFields: List<FieldWithDefault>
abstract override val packageName: String
override fun get(fieldName: String): FieldWithDefault {
return allFields.firstOrNull { it.name == fieldName }
?: throw IllegalArgumentException("Builder $typeName doesn't contains field $fieldName")
}
private val fieldsFromParentIndex: Map<String, Boolean> by lazy {
mutableMapOf<String, Boolean>().apply {
for (field in allFields + uselessFields) {
this[field.name] = parents.any { field.name in it.allFields.map { it.name } }
}
}
}
fun isFromParent(field: Field): Boolean = fieldsFromParentIndex.getValue(field.name)
override fun substitute(map: TypeParameterSubstitutionMap) = this
}
class LeafBuilder(val implementation: Implementation) : Builder() {
override val typeName: String
get() = if (implementation.name != null) {
"${implementation.name}Builder"
} else {
"${implementation.element.typeName}Builder"
}
context(ImportCollector)
override fun renderTo(appendable: Appendable) {
addImport(this)
appendable.append(typeName)
}
override val allFields: List<FieldWithDefault> by lazy { implementation.fieldsWithoutDefault }
override val uselessFields: List<FieldWithDefault> by lazy {
val fieldsFromParents = parents.flatMap { it.allFields }.distinct()
val fieldsFromImplementation = implementation.allFields
(fieldsFromImplementation - allFields).filter { it in fieldsFromParents }
}
override val packageName: String = implementation.packageName.replace(".impl", ".builder")
var isOpen: Boolean = false
var wantsCopy: Boolean = false
}
class IntermediateBuilder(override val typeName: String) : Builder() {
val fields: MutableList<FieldWithDefault> = mutableListOf()
var materializedElement: Element? = null
override val allFields: List<FieldWithDefault> by lazy {
mutableSetOf<FieldWithDefault>().apply {
parents.forEach { this += it.allFields }
this += fields
}.toList()
}
override val uselessFields: List<FieldWithDefault> = emptyList()
override var packageName: String = DEFAULT_BUILDER_PACKAGE
context(ImportCollector)
override fun renderTo(appendable: Appendable) {
addImport(this)
appendable.append(typeName)
}
}
typealias LeafBuilder = org.jetbrains.kotlin.generators.tree.LeafBuilder<FieldWithDefault, Element, Implementation>
@@ -14,8 +14,6 @@ sealed class Field : AbstractField<Field>() {
var parentHasSeparateTransform: Boolean = true
open var needTransformInOtherChildren: Boolean = false
open var customInitializationCall: String? = null
open val isMutableOrEmptyList: Boolean
get() = false
@@ -105,7 +103,7 @@ class FieldWithDefault(override val origin: Field) : Field(), AbstractFieldWithD
set(_) {}
override var defaultValueInImplementation: String? = origin.defaultValueInImplementation
var defaultValueInBuilder: String? = null
override var defaultValueInBuilder: String? = null
override var isMutable: Boolean = origin.isMutable
override val isMutableOrEmptyList: Boolean
get() = origin.isMutableOrEmptyList
@@ -12,21 +12,4 @@ class Implementation(element: Element, name: String?) : AbstractImplementation<I
override val allFields = element.allFields.toMutableList().mapTo(mutableListOf()) {
FieldWithDefault(it)
}
override var kind: ImplementationKind? = null
set(value) {
field = value
if (kind != ImplementationKind.FinalClass) {
isPublic = true
}
if (value?.hasLeafBuilder == true) {
builder = builder ?: LeafBuilder(this)
} else {
builder = null
}
}
var builder: LeafBuilder? = null
}
val ImplementationKind.hasLeafBuilder: Boolean
get() = this == ImplementationKind.FinalClass || this == ImplementationKind.OpenClass
@@ -5,16 +5,17 @@
package org.jetbrains.kotlin.fir.tree.generator.printer
import org.jetbrains.kotlin.fir.tree.generator.*
import org.jetbrains.kotlin.fir.tree.generator.declarationAttributesType
import org.jetbrains.kotlin.fir.tree.generator.firBuilderDslAnnotation
import org.jetbrains.kotlin.fir.tree.generator.firImplementationDetailType
import org.jetbrains.kotlin.fir.tree.generator.model.*
import org.jetbrains.kotlin.fir.tree.generator.toMutableOrEmptyImport
import org.jetbrains.kotlin.generators.tree.AbstractBuilderPrinter
import org.jetbrains.kotlin.generators.tree.ClassRef
import org.jetbrains.kotlin.generators.tree.ImportCollector
import org.jetbrains.kotlin.generators.tree.StandardTypes
import org.jetbrains.kotlin.generators.tree.printer.GeneratedFile
import org.jetbrains.kotlin.generators.tree.printer.printBlock
import org.jetbrains.kotlin.generators.tree.printer.printGeneratedType
import org.jetbrains.kotlin.generators.tree.render
import org.jetbrains.kotlin.utils.SmartPrinter
import org.jetbrains.kotlin.utils.withIndent
import java.io.File
fun Builder.generateCode(generationPath: File): GeneratedFile =
@@ -25,303 +26,38 @@ fun Builder.generateCode(generationPath: File): GeneratedFile =
typeName,
fileSuppressions = listOf("DuplicatedCode", "unused"),
) {
addAllImports(usedTypes)
printBuilder(this@generateCode)
BuilderPrinter(this).printBuilder(this@generateCode)
}
context(ImportCollector)
private fun SmartPrinter.printBuilder(builder: Builder) {
if (builder is LeafBuilder && builder.allFields.isEmpty()) {
printDslBuildFunction(builder, false)
return
class BuilderPrinter(printer: SmartPrinter) : AbstractBuilderPrinter<Element, Implementation, FieldWithDefault, Field>(printer) {
override val implementationDetailAnnotation: ClassRef<*>
get() = firImplementationDetailType
override val builderDslAnnotation: ClassRef<*>
get() = firBuilderDslAnnotation
override fun actualTypeOfField(field: Field) = field.getMutableType(forBuilder = true)
context(ImportCollector)
override fun SmartPrinter.printFieldReferenceInImplementationConstructorCall(field: FieldWithDefault) {
print(field.name)
if (field.isMutableOrEmptyList) {
addImport(toMutableOrEmptyImport)
print(".toMutableOrEmpty()")
}
}
println("@${firBuilderDslAnnotation.render()}")
when (builder) {
is IntermediateBuilder -> print("interface ")
is LeafBuilder -> {
if (builder.isOpen) {
print("open ")
}
print("class ")
}
}
print(builder.render())
if (builder.parents.isNotEmpty()) {
print(builder.parents.joinToString(separator = ", ", prefix = " : ") { it.render() })
}
var hasRequiredFields = false
printBlock {
var needNewLine = false
for (field in builder.allFields) {
val (newLine, requiredFields) = printFieldInBuilder(field, builder, fieldIsUseless = false)
needNewLine = newLine
hasRequiredFields = hasRequiredFields || requiredFields
}
val hasBackingFields = builder.allFields.any { it.nullable }
if (needNewLine) {
println()
}
val buildType = when (builder) {
is LeafBuilder -> builder.implementation.element.render()
is IntermediateBuilder -> builder.materializedElement!!.withStarArgs().render()
}
if (builder is LeafBuilder && builder.implementation.isPublic) {
println("@OptIn(${firImplementationDetailType.render()}::class)")
}
if (builder.parents.isNotEmpty()) {
print("override ")
}
print("fun build(): $buildType")
if (builder is LeafBuilder) {
printBlock {
println("return ${builder.implementation.render()}(")
withIndent {
for (field in builder.allFields) {
if (field.invisibleField) continue
val name = field.name
print(name)
if (field.isMutableOrEmptyList) {
addImport(toMutableOrEmptyImport)
print(".toMutableOrEmpty()")
}
println(",")
}
}
println(")")
}
if (hasBackingFields) {
println()
}
context(ImportCollector)
override fun copyField(
field: FieldWithDefault,
originalParameterName: String,
copyBuilderVariableName: String
) {
if (field.typeRef == declarationAttributesType) {
printer.println(copyBuilderVariableName, ".", field.name, " = ", originalParameterName, ".", field.name, ".copy()")
} else {
println()
}
if (builder is LeafBuilder) {
// for (field in builder.allFields) {
// printBackingFieldIfNeeded(field)
// }
val hasUselessFields = builder.uselessFields.isNotEmpty()
if (hasUselessFields) {
println()
builder.uselessFields.forEachIndexed { index, field ->
if (index > 0) {
println()
}
printFieldInBuilder(field, builder, fieldIsUseless = true)
}
}
}
}
if (builder is LeafBuilder) {
println()
printDslBuildFunction(builder, hasRequiredFields)
if (builder.wantsCopy) {
println()
printDslBuildCopyFunction(builder, hasRequiredFields)
super.copyField(field, originalParameterName, copyBuilderVariableName)
}
}
}
internal val Field.invisibleField: Boolean get() = customInitializationCall != null
private fun FieldWithDefault.needBackingField(fieldIsUseless: Boolean) =
(!nullable || notNull) && origin !is FieldList && if (fieldIsUseless) {
defaultValueInImplementation == null
} else {
defaultValueInBuilder == null
}
private fun FieldWithDefault.needNotNullDelegate(fieldIsUseless: Boolean) =
needBackingField(fieldIsUseless) && (typeRef == StandardTypes.boolean || typeRef == StandardTypes.int)
context(ImportCollector)
private fun SmartPrinter.printFieldInBuilder(
field: FieldWithDefault,
builder: Builder,
fieldIsUseless: Boolean,
): Pair<Boolean, Boolean> {
if (field.withGetter && !fieldIsUseless || field.invisibleField) return false to false
if (field.origin is FieldList) {
printFieldListInBuilder(field.origin, builder, fieldIsUseless)
return true to false
}
val defaultValue = if (fieldIsUseless)
field.defaultValueInImplementation.also { requireNotNull(it) }
else
field.defaultValueInBuilder
printDeprecationOnUselessFieldIfNeeded(field, builder, fieldIsUseless)
printModifiers(builder, field, fieldIsUseless)
print("var ${field.name}: ${field.typeRef.render()}")
var hasRequiredFields = false
val needNewLine = when {
fieldIsUseless -> {
println()
withIndent {
println("get() = throw IllegalStateException()")
println("set(_) {")
withIndent {
println("throw IllegalStateException()")
}
println("}")
}
true
}
builder is IntermediateBuilder -> {
println()
false
}
field.needNotNullDelegate(fieldIsUseless) -> {
println(" by kotlin.properties.Delegates.notNull<${field.typeRef.render()}>()")
hasRequiredFields = true
true
}
field.needBackingField(fieldIsUseless) -> {
// println()
// withIndent {
// println("get() = _$name ?: throw IllegalArgumentException(\"$name should be initialized\")")
// println("set(value) {")
// withIndent {
// println("_$name = value")
// }
// println("}")
// println()
// }
// false
println()
hasRequiredFields = true
true
}
else -> {
println(" = $defaultValue")
true
}
}
return needNewLine to hasRequiredFields
}
private fun SmartPrinter.printDeprecationOnUselessFieldIfNeeded(field: Field, builder: Builder, fieldIsUseless: Boolean) {
if (fieldIsUseless) {
println("@Deprecated(\"Modification of '${field.name}' has no impact for ${builder.typeName}\", level = DeprecationLevel.HIDDEN)")
}
}
context(ImportCollector)
private fun SmartPrinter.printFieldListInBuilder(
field: FieldList,
builder: Builder,
fieldIsUseless: Boolean,
) {
printDeprecationOnUselessFieldIfNeeded(field, builder, fieldIsUseless)
printModifiers(builder, field, fieldIsUseless)
print("val ${field.name}: ${field.getMutableType(forBuilder = true).render()}")
if (builder is LeafBuilder) {
print(" = mutableListOf()")
}
println()
}
private fun SmartPrinter.printModifiers(builder: Builder, field: Field, fieldIsUseless: Boolean) {
if (builder is IntermediateBuilder) {
print("abstract ")
}
if (builder.isFromParent(field)) {
print("override ")
} else if (builder is LeafBuilder && builder.isOpen) {
print("open ")
}
if (builder is LeafBuilder && field is FieldWithDefault && field.needBackingField(fieldIsUseless) && !fieldIsUseless && !field.needNotNullDelegate(fieldIsUseless)) {
print("lateinit ")
}
}
context(ImportCollector)
private fun SmartPrinter.printDslBuildFunction(
builder: LeafBuilder,
hasRequiredFields: Boolean
) {
val isEmpty = builder.allFields.isEmpty()
if (!isEmpty) {
println("@OptIn(${experimentalContractsAnnotation.render()}::class)")
print("inline ")
} else if (builder.implementation.isPublic) {
println("@OptIn(${firImplementationDetailType.render()}::class)")
}
print("fun ")
builder.implementation.element.params.takeIf { it.isNotEmpty() }?.let {
print(it.joinToString(separator = ", ", prefix = "<", postfix = "> "))
}
val name = builder.implementation.name?.replaceFirst("Fir", "") ?: builder.implementation.element.name
print("build${name}(")
if (!isEmpty) {
print("init: ${builder.render()}.() -> Unit")
if (!hasRequiredFields) {
print(" = {}")
}
}
println("): ${builder.implementation.element.render()} {")
withIndent {
if (!isEmpty) {
addStarImport("kotlin.contracts")
println("contract {")
withIndent {
println("callsInPlace(init, InvocationKind.EXACTLY_ONCE)")
}
println("}")
}
print("return ")
if (isEmpty) {
println("${builder.implementation.render()}()")
} else {
println("${builder.render()}().apply(init).build()")
}
}
println("}")
}
context(ImportCollector)
private fun SmartPrinter.printDslBuildCopyFunction(
builder: LeafBuilder,
hasRequiredFields: Boolean,
) {
val optIns =
builder.allFields.filter { !it.invisibleField }.mapNotNullTo(mutableSetOf(experimentalContractsAnnotation)) { it.optInAnnotation }
println("@OptIn(${optIns.joinToString { "${it.render()}::class" }})")
print("inline ")
print("fun ")
builder.implementation.element.params.takeIf { it.isNotEmpty() }?.let {
print(it.joinToString(separator = ", ", prefix = "<", postfix = "> "))
}
val builderType = builder.render()
val name = builder.implementation.name?.replaceFirst("Fir", "") ?: builder.implementation.element.name
print("build${name}Copy(")
print("original: ${builder.implementation.element.render()}, init: $builderType.() -> Unit")
if (!hasRequiredFields) {
print(" = {}")
}
println("): ${builder.implementation.element.render()} {")
withIndent {
println("contract {")
withIndent {
println("callsInPlace(init, InvocationKind.EXACTLY_ONCE)")
}
println("}")
println("val copyBuilder = $builderType()")
for (field in builder.allFields) {
when {
field.invisibleField -> {}
field.origin is FieldList -> println("copyBuilder.${field.name}.addAll(original.${field.name})")
field.typeRef == declarationAttributesType -> println("copyBuilder.${field.name} = original.${field.name}.copy()")
field.notNull -> println("original.${field.name}?.let { copyBuilder.${field.name} = it }")
else -> println("copyBuilder.${field.name} = original.${field.name}")
}
}
println("return copyBuilder.apply(init).build()")
}
println("}")
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.fir.tree.generator.printer
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirBuilderConfigurator
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
import org.jetbrains.kotlin.generators.tree.printer.GeneratedFile
import org.jetbrains.kotlin.generators.util.GeneratorsFileUtil.GENERATED_MESSAGE
@@ -16,12 +17,16 @@ const val BASE_PACKAGE = "org.jetbrains.kotlin.fir"
internal const val TREE_GENERATOR_README = "compiler/fir/tree/tree-generator/Readme.md"
fun generateElements(builder: AbstractFirTreeBuilder, generationPath: File): List<GeneratedFile> {
fun generateElements(
builder: AbstractFirTreeBuilder,
builderConfigurator: AbstractFirBuilderConfigurator<*>,
generationPath: File,
): List<GeneratedFile> {
val generatedFiles = mutableListOf<GeneratedFile>()
builder.elements.mapTo(generatedFiles) { it.generateCode(generationPath) }
builder.elements.flatMap { it.allImplementations }.mapTo(generatedFiles) { it.generateCode(generationPath) }
builder.elements.flatMap { it.allImplementations }.mapNotNull { it.builder }.mapTo(generatedFiles) { it.generateCode(generationPath) }
builder.intermediateBuilders.mapTo(generatedFiles) { it.generateCode(generationPath) }
builderConfigurator.intermediateBuilders.mapTo(generatedFiles) { it.generateCode(generationPath) }
generatedFiles += printVisitor(builder.elements, generationPath, false)
generatedFiles += printVisitorVoid(builder.elements, generationPath)