[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
@@ -6,6 +6,7 @@ plugins {
dependencies {
api(project(":core:compiler.common"))
implementation(project(":compiler:util"))
implementation(project(":generators"))
}
@@ -0,0 +1,352 @@
/*
* 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.generators.tree
import org.jetbrains.kotlin.generators.tree.printer.FunctionParameter
import org.jetbrains.kotlin.generators.tree.printer.printBlock
import org.jetbrains.kotlin.generators.tree.printer.printFunctionWithBlockBody
import org.jetbrains.kotlin.utils.SmartPrinter
import org.jetbrains.kotlin.utils.withIndent
abstract class AbstractBuilderPrinter<Element, Implementation, BuilderField, ElementField>(val printer: SmartPrinter)
where Element : AbstractElement<Element, ElementField, Implementation>,
Implementation : AbstractImplementation<Implementation, Element, BuilderField>,
BuilderField : AbstractField<*>,
BuilderField : AbstractFieldWithDefaultValue<*>,
ElementField : AbstractField<ElementField> {
companion object {
private val experimentalContractsAnnotation =
type("kotlin.contracts", "ExperimentalContracts", TypeKind.Class)
}
protected abstract val implementationDetailAnnotation: ClassRef<*>
protected abstract val builderDslAnnotation: ClassRef<*>
context(ImportCollector)
protected open fun SmartPrinter.printFieldReferenceInImplementationConstructorCall(field: BuilderField) {
print(field.name)
}
protected open fun actualTypeOfField(field: ElementField): TypeRefWithNullability =
if (field is ListField) StandardTypes.mutableList.withArgs(field.baseType) else field.typeRef
context(ImportCollector)
protected open fun copyField(field: BuilderField, originalParameterName: String, copyBuilderVariableName: String) {
printer.run {
when {
field.origin is ListField -> println(
copyBuilderVariableName,
".",
field.name,
".addAll(",
originalParameterName,
".",
field.name,
")",
)
field.notNull -> println(
originalParameterName,
".",
field.name,
"?.let { ",
copyBuilderVariableName,
".",
field.name,
" = it }",
)
else -> println(copyBuilderVariableName, ".", field.name, " = ", originalParameterName, ".", field.name)
}
}
}
context(ImportCollector)
fun printBuilder(builder: Builder<BuilderField, Element>) {
addAllImports(builder.usedTypes)
printer.run {
if (builder is LeafBuilder<*, *, *> && builder.allFields.isEmpty()) {
@Suppress("UNCHECKED_CAST")
printDslBuildFunction(builder as LeafBuilder<BuilderField, Element, Implementation>, hasRequiredFields = false)
return
}
println("@", builderDslAnnotation.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(", implementationDetailAnnotation.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
printFieldReferenceInImplementationConstructorCall(field)
println(",")
}
}
println(")")
}
if (hasBackingFields) {
println()
}
} else {
println()
}
if (builder is LeafBuilder<*, *, *>) {
if (builder.uselessFields.isNotEmpty()) {
println()
builder.uselessFields.forEachIndexed { index, field ->
if (index > 0) {
println()
}
printFieldInBuilder(field, builder, fieldIsUseless = true)
}
}
}
}
if (builder is LeafBuilder<*, *, *>) {
println()
@Suppress("UNCHECKED_CAST")
printDslBuildFunction(builder as LeafBuilder<BuilderField, Element, Implementation>, hasRequiredFields)
if (builder.wantsCopy) {
println()
printDslBuildCopyFunction(builder, hasRequiredFields)
}
}
}
}
private fun lambdaParameterForBuilderFunction(builder: Builder<BuilderField, Element>, hasRequiredFields: Boolean) =
FunctionParameter(
name = "init",
type = Lambda(receiver = builder, returnType = StandardTypes.unit),
defaultValue = "{}".takeIf { !hasRequiredFields },
)
context(ImportCollector)
private fun SmartPrinter.printDslBuildFunction(
builder: LeafBuilder<BuilderField, Element, Implementation>,
hasRequiredFields: Boolean,
) {
val isEmpty = builder.allFields.isEmpty()
if (!isEmpty) {
println("@OptIn(", experimentalContractsAnnotation.render(), "::class)")
} else if (builder.implementation.isPublic) {
println("@OptIn(", implementationDetailAnnotation.render(), "::class)")
}
val name = builder.implementation.name?.replaceFirst("Fir", "") ?: builder.implementation.element.name
val initParameter = if (isEmpty) null else lambdaParameterForBuilderFunction(builder, hasRequiredFields)
printFunctionWithBlockBody(
name = "build$name",
parameters = listOfNotNull(initParameter),
returnType = builder.implementation.element,
typeParameters = builder.implementation.element.params,
isInline = !isEmpty,
) {
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()")
}
}
}
private fun BuilderField.needBackingField(fieldIsUseless: Boolean) =
(!nullable || notNull) && origin !is ListField && if (fieldIsUseless) {
defaultValueInImplementation == null
} else {
defaultValueInBuilder == null
}
private fun BuilderField.needNotNullDelegate(fieldIsUseless: Boolean) =
needBackingField(fieldIsUseless) && (typeRef == StandardTypes.boolean || typeRef == StandardTypes.int)
context(ImportCollector)
private fun SmartPrinter.printFieldInBuilder(
field: BuilderField,
builder: Builder<BuilderField, Element>,
fieldIsUseless: Boolean,
): Pair<Boolean, Boolean> {
if (field.withGetter && !fieldIsUseless || field.invisibleField) return false to false
if (field.origin is ListField) {
@Suppress("UNCHECKED_CAST")
printFieldListInBuilder(field.origin as ElementField, 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 = false) -> {
println(" by kotlin.properties.Delegates.notNull<${field.typeRef.render()}>()")
hasRequiredFields = true
true
}
field.needBackingField(fieldIsUseless = false) -> {
println()
hasRequiredFields = true
true
}
else -> {
println(" = $defaultValue")
true
}
}
return needNewLine to hasRequiredFields
}
private fun SmartPrinter.printDeprecationOnUselessFieldIfNeeded(
field: AbstractField<*>,
builder: Builder<BuilderField, Element>,
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: ElementField,
builder: Builder<BuilderField, Element>,
fieldIsUseless: Boolean,
) {
printDeprecationOnUselessFieldIfNeeded(field, builder, fieldIsUseless)
printModifiers(builder, field, fieldIsUseless)
print("val ", field.name, ": ", actualTypeOfField(field).render())
if (builder is LeafBuilder<*, *, *>) {
print(" = mutableListOf()")
}
println()
}
private fun SmartPrinter.printModifiers(builder: Builder<BuilderField, Element>, field: AbstractField<*>, fieldIsUseless: Boolean) {
if (builder is IntermediateBuilder) {
print("abstract ")
}
if (builder.isFromParent(field)) {
print("override ")
} else if (builder is LeafBuilder<*, *, *> && builder.isOpen) {
print("open ")
}
@Suppress("UNCHECKED_CAST")
if (builder is LeafBuilder<*, *, *> &&
field is AbstractFieldWithDefaultValue<*> &&
(field as BuilderField).needBackingField(fieldIsUseless) &&
!fieldIsUseless &&
!field.needNotNullDelegate(fieldIsUseless = false)
) {
print("lateinit ")
}
}
context(ImportCollector)
private fun SmartPrinter.printDslBuildCopyFunction(
builder: LeafBuilder<BuilderField, Element, Implementation>,
hasRequiredFields: Boolean,
) {
val optIns = builder.allFields
.filter { !it.invisibleField }
.mapNotNullTo(mutableSetOf(experimentalContractsAnnotation)) { it.optInAnnotation }
println("@OptIn(", optIns.joinToString { "${it.render()}::class" }, ")")
// FIXME: Avoid FIR-specific code here
val name = builder.implementation.name?.replaceFirst("Fir", "") ?: builder.implementation.element.name
val originalParameter = FunctionParameter(name = "original", type = builder.implementation.element)
val initParameter = lambdaParameterForBuilderFunction(builder, hasRequiredFields)
printFunctionWithBlockBody(
name = "build${name}Copy",
parameters = listOf(originalParameter, initParameter),
returnType = builder.implementation.element,
typeParameters = builder.implementation.element.params,
isInline = true,
) {
print("contract")
printBlock {
println("callsInPlace(init, InvocationKind.EXACTLY_ONCE)")
}
val copyBuilderVariableName = "copyBuilder"
println("val ", copyBuilderVariableName, " = ", builder.render(), "()")
for (field in builder.allFields) {
if (field.invisibleField) continue
copyField(field, originalParameter.name, copyBuilderVariableName)
}
println("return ", copyBuilderVariableName, ".apply(", initParameter.name, ").build()")
}
}
}
@@ -32,6 +32,11 @@ abstract class AbstractField<Field : AbstractField<Field>> {
open val withGetter: Boolean get() = false
open val customSetter: String? get() = null
open var customInitializationCall: String? = null
val invisibleField: Boolean
get() = customInitializationCall != null
var deprecation: Deprecated? = null
var visibility: Visibility = Visibility.PUBLIC
@@ -11,4 +11,5 @@ interface AbstractFieldWithDefaultValue<OriginField : AbstractField<OriginField>
var customSetter: String?
var notNull: Boolean
var defaultValueInImplementation: String?
var defaultValueInBuilder: String?
}
@@ -71,4 +71,20 @@ abstract class AbstractImplementation<Implementation, Element, Field>(
val fieldsWithDefault by lazy { allFields.filter(::withDefault) }
var requiresOptIn = false
override var kind: ImplementationKind? = null
set(value) {
field = value
if (kind != ImplementationKind.FinalClass) {
isPublic = true
}
@Suppress("UNCHECKED_CAST")
builder = if (value?.hasLeafBuilder == true) {
builder ?: LeafBuilder(this as Implementation)
} else {
null
}
}
var builder: LeafBuilder<Field, Element, Implementation>? = null
}
@@ -0,0 +1,90 @@
/*
* 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.generators.tree
sealed class Builder<BuilderField, Element> : FieldContainer<BuilderField>, TypeRefWithNullability, Importable
where BuilderField : AbstractField<*>,
Element : AbstractElement<Element, *, *> {
val parents: MutableList<IntermediateBuilder<BuilderField, Element>> = mutableListOf()
val usedTypes: MutableList<Importable> = mutableListOf()
abstract val uselessFields: List<BuilderField>
override fun get(fieldName: String): BuilderField {
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: AbstractField<*>): Boolean = fieldsFromParentIndex.getValue(field.name)
override fun substitute(map: TypeParameterSubstitutionMap) = this
context(ImportCollector)
override fun renderTo(appendable: Appendable) {
addImport(this)
appendable.append(typeName)
}
override val nullable: Boolean
get() = false
override fun copy(nullable: Boolean) = this
}
class LeafBuilder<BuilderField, Element, Implementation>(
val implementation: Implementation,
) : Builder<BuilderField, Element>()
where BuilderField : AbstractField<*>,
Element : AbstractElement<Element, *, Implementation>,
Implementation : AbstractImplementation<Implementation, Element, BuilderField> {
override val typeName: String
get() = if (implementation.name != null) {
"${implementation.name}Builder"
} else {
"${implementation.element.typeName}Builder"
}
override val allFields: List<BuilderField> by lazy { implementation.fieldsWithoutDefault }
override val uselessFields: List<BuilderField> 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<BuilderField, Element>(
override val typeName: String,
override var packageName: String,
) : Builder<BuilderField, Element>()
where BuilderField : AbstractField<*>,
Element : AbstractElement<Element, *, *> {
val fields: MutableList<BuilderField> = mutableListOf()
var materializedElement: Element? = null
override val allFields: List<BuilderField> by lazy {
mutableSetOf<BuilderField>().apply {
parents.forEach { this += it.allFields }
this += fields
}.toList()
}
override val uselessFields: List<BuilderField> = emptyList()
}
@@ -0,0 +1,356 @@
/*
* 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.generators.tree.config
import org.jetbrains.kotlin.generators.tree.*
import org.jetbrains.kotlin.generators.tree.config.AbstractImplementationConfigurator.ImplementationContext.DefaultValueContext
import org.jetbrains.kotlin.utils.DummyDelegate
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* Provides a DSL to configure builder classes for tree nodes, for example, add intermediate builders, or add default values
* for properties in the generated builders.
*/
abstract class AbstractBuilderConfigurator<Element, Implementation, BuilderField, ElementField>(
val elements: List<Element>,
) where Element : AbstractElement<Element, ElementField, Implementation>,
Implementation : AbstractImplementation<Implementation, Element, BuilderField>,
BuilderField : AbstractField<*>,
BuilderField : AbstractFieldWithDefaultValue<*>,
ElementField : AbstractField<ElementField> {
/**
* The prefix that will be used to generate names of builder classes.
*
* Should be the same as [AbstractElement.namePrefix].
*/
protected abstract val namePrefix: String
/**
* The package in which [IntermediateBuilder]s should be generated.
*/
protected abstract val defaultBuilderPackage: String
/**
* A customization point to fine-tune existing builder classes or add new ones.
*
* Override this method and use the following DSL methods to configure builder generation:
* - [builder]
* - [noBuilder]
* - [configureFieldInAllLeafBuilders]
*/
abstract fun configureBuilders()
/**
* Must return a copy of [elementField] that will be used in builder configuration.
*/
protected abstract fun builderFieldFromElementField(elementField: ElementField): BuilderField
val intermediateBuilders = mutableListOf<IntermediateBuilder<BuilderField, Element>>()
/**
* Provides a way to configure an intermediate builder class.
*
* @param config The configuration block. See [IntermediateBuilderConfigurationContext]'s documentation for description of its DSL
* methods.
*/
protected fun builder(config: IntermediateBuilderConfigurationContext.() -> Unit) = IntermediateBuilderDelegateProvider(config)
/**
* Provides a way to configure a leaf builder class, i.e. the builder class responsible for finally constructing an instance of
* the corresponding implementation class.
*
* @param element The element for which to configure builder generation.
* @param config The configuration block. See [LeafBuilderConfigurationContext]'s documentation for description of its DSL
* methods.
*/
protected fun builder(element: Element, type: String? = null, config: LeafBuilderConfigurationContext.() -> Unit) {
val implementation = element.extractImplementation(type)
val builder = implementation.builder
requireNotNull(builder)
LeafBuilderConfigurationContext(builder).apply(config)
}
/**
* Disables generating any builder classes for [element].
*/
protected fun noBuilder(element: Element, type: String? = null) {
val implementation = element.extractImplementation(type)
implementation.builder = null
}
private fun Element.extractImplementation(type: String?): Implementation {
return if (type == null) {
allImplementations.singleOrNull { it.kind?.hasLeafBuilder == true } ?: 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)
}
}
}
/**
* Out of all implementations, returns those for which [implementationPredicate] returns `true`
* _and_ [element] is one of its non-immediate parents.
*/
protected inline fun findImplementationsWithElementInParents(
element: Element,
implementationPredicate: (Implementation) -> Boolean = { true }
): Collection<Implementation> {
return elements
.flatMap { it.allImplementations }
.mapNotNullTo(mutableSetOf()) { implementation ->
if (!implementationPredicate(implementation)) return@mapNotNullTo null
if (implementation.element == element) return@mapNotNullTo null
val hasElementInParents = implementation.element.traverseParentsUntil { it == element }
implementation.takeIf { hasElementInParents }
}
}
/**
* Allows to batch-apply [config] to certain fields in _all_ the builders that satisfy the given
* [builderPredicate].
*
* @param field The name of the field to configure across all builder classes.
* @param builderPredicate Only builders satisfying this predicate will participate in this configuration.
* @param fieldPredicate Only fields satisfying this predicate will be configured.
* @param config The configuration block. Accepts the field name as an argument.
* See [LeafBuilderConfigurationContext]'s documentation for description of its DSL methods.
*/
protected fun configureFieldInAllLeafBuilders(
field: String,
builderPredicate: ((LeafBuilder<BuilderField, Element, Implementation>) -> Boolean)? = null,
fieldPredicate: ((BuilderField) -> Boolean)? = null,
config: LeafBuilderConfigurationContext.(field: String) -> Unit
) {
val builders = 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).config(field)
}
}
/**
* A DSL for configuring one or more intermediate or leaf builder classes.
*/
protected abstract inner class BuilderConfigurationContext {
protected abstract val builder: Builder<BuilderField, Element>
private fun getField(name: String): BuilderField {
return builder[name]
}
/**
* Types/functions that you want to additionally import in the file with the builder class.
*
* This is useful if, for example, default values of fields reference classes or functions from other packages.
*
* Note that classes referenced in field types will be imported automatically.
*/
fun additionalImports(vararg types: Importable) {
types.forEach { builder.usedTypes += it }
}
/**
* Specifies the default value of [field] in this builder class. The default value can be arbitrary code.
*
* Use [additionalImports] if the default value uses types/functions that are not otherwise imported.
*/
fun default(field: String, value: String) {
default(field) {
this.value = value
}
}
/**
* Specifies that the default value of each field in [fields] in this builder class should be `true`.
*/
fun defaultTrue(vararg fields: String) {
for (field in fields) {
default(field) {
value = "true"
}
}
}
/**
* Specifies that the default value of each field in [fields] in this builder class should be `false`.
*/
fun defaultFalse(vararg fields: String) {
for (field in fields) {
default(field) {
value = "false"
}
}
}
/**
* Specifies that the default value of each field of [fields] in this builder class should be `null`.
*
* Note: the field must be configured as nullable.
*/
fun defaultNull(vararg fields: String) {
for (field in fields) {
default(field) {
value = "null"
}
require(getField(field).nullable) {
"$field is not nullable field"
}
}
}
/**
* Allows to configure the default value of [field] in this builder class.
*
* See the [DefaultValueContext] documentation for description of its DSL methods.
*/
fun default(field: String, init: DefaultValueContext.() -> Unit) {
DefaultValueContext(getField(field)).apply(init).applyConfiguration()
}
/**
* A DSL for configuring a field's default value.
*/
inner class DefaultValueContext(private val field: BuilderField) {
/**
* The default value of this field in the builder class. Can be arbitrary code.
*
* Use [additionalImports] if the default value uses types/functions that are not otherwise imported.
*/
var value: String? = null
fun applyConfiguration() {
if (value != null) field.defaultValueInBuilder = value
}
}
}
/**
* A DSL for configuring one or more intermediate builder classes.
*
* Use the following syntax for configuring the set of generated fields in this builder class:
* ```kotlin
* fields from myElement // To use all fields from myElement in the builder class
* fields from myElement without "myField" // To use all fields except myField from myElement in the builder class.
* fields from myElement without listOf("foo", "bar") // To use all fields except foo and bar from myElement in the builder class.
* ```
*/
protected inner class IntermediateBuilderConfigurationContext(
override val builder: IntermediateBuilder<BuilderField, Element>
) : BuilderConfigurationContext() {
inner class Fields {
/**
* Copy all fields from [element] to this builder class.
*/
infix fun from(element: Element): ExceptConfigurator {
builder.fields += element.allFields.map(this@AbstractBuilderConfigurator::builderFieldFromElementField)
builder.packageName = "${element.packageName}.builder"
builder.materializedElement = element
return ExceptConfigurator()
}
}
inner class ExceptConfigurator {
/**
* Exclude the field with [name] from this builder class.
*/
infix fun without(name: String) {
without(listOf(name))
}
/**
* Exclude the fields with [names] from this builder class.
*/
infix fun without(names: List<String>) {
builder.fields.removeAll { it.name in names }
}
}
/**
* A configurator for copying fields from some element to this intermediate builder.
*
* See [IntermediateBuilderConfigurationContext] for the usage example.
*/
val fields = Fields()
/**
* The list of parents of this intermediate builder. Can be used for adding builder superclasses to this builder class.
*/
val parents: MutableList<IntermediateBuilder<BuilderField, Element>>
get() = builder.parents
}
protected inner class IntermediateBuilderDelegateProvider(
private val block: IntermediateBuilderConfigurationContext.() -> Unit
) {
lateinit var builder: IntermediateBuilder<BuilderField, Element>
operator fun provideDelegate(
thisRef: Nothing?,
prop: KProperty<*>
): ReadOnlyProperty<Nothing?, IntermediateBuilder<BuilderField, Element>> {
val name = namePrefix + prop.name.replaceFirstChar(Char::uppercaseChar)
builder = IntermediateBuilder<BuilderField, Element>(name, defaultBuilderPackage).apply {
intermediateBuilders += this
IntermediateBuilderConfigurationContext(this).block()
}
return DummyDelegate(builder)
}
}
/**
* A DSL for configuring one or more leaf builder classes.
*/
protected inner class LeafBuilderConfigurationContext(
override val builder: LeafBuilder<BuilderField, Element, Implementation>
) : BuilderConfigurationContext() {
/**
* The list of parents of this leaf builder. Can be used for adding builder superclasses to this builder class.
*/
val parents: MutableList<IntermediateBuilder<BuilderField, Element>>
get() = builder.parents
/**
* Makes this builder an open class.
*/
fun openBuilder() {
builder.isOpen = true
}
/**
* In addition to the regular `build*()` function, generate `build*Copy()` function that accepts
* an instance of the corresponding tree element and copies values from that instance to the builder, allowing to change them
* in the process.
*/
fun withCopy() {
builder.wantsCopy = true
}
}
}
@@ -74,3 +74,6 @@ internal fun <Field : AbstractField<*>> List<Field>.reorderFieldsIfNecessary(ord
if (position < 0) order.size else position
}
}
val ImplementationKind.hasLeafBuilder: Boolean
get() = this == ImplementationKind.FinalClass || this == ImplementationKind.OpenClass