[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:
committed by
Space Team
parent
91b5a71f1a
commit
cae4a9930b
+23
-62
@@ -5,16 +5,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.tree.generator
|
package org.jetbrains.kotlin.fir.tree.generator
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractBuilderConfigurator
|
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirBuilderConfigurator
|
||||||
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
|
|
||||||
|
|
||||||
object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTreeBuilder) {
|
object BuilderConfigurator : AbstractFirBuilderConfigurator<FirTreeBuilder>(FirTreeBuilder.elements) {
|
||||||
fun configureBuilders() = with(firTreeBuilder) {
|
override fun configureBuilders() = with(FirTreeBuilder) {
|
||||||
val declarationBuilder by builder {
|
val declarationBuilder by builder {
|
||||||
fields from declaration without "symbol"
|
fields from declaration without "symbol"
|
||||||
}
|
}
|
||||||
@@ -99,7 +93,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
defaultNull("delegatedConstructor")
|
defaultNull("delegatedConstructor")
|
||||||
defaultNull("body")
|
defaultNull("body")
|
||||||
default("contractDescription", "FirEmptyContractDescription")
|
default("contractDescription", "FirEmptyContractDescription")
|
||||||
useTypes(emptyContractDescriptionType)
|
additionalImports(emptyContractDescriptionType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +102,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
defaultNull("delegatedConstructor")
|
defaultNull("delegatedConstructor")
|
||||||
defaultNull("body")
|
defaultNull("body")
|
||||||
default("contractDescription", "FirEmptyContractDescription")
|
default("contractDescription", "FirEmptyContractDescription")
|
||||||
useTypes(emptyContractDescriptionType)
|
additionalImports(emptyContractDescriptionType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(constructor, "FirConstructorImpl") {
|
builder(constructor, "FirConstructorImpl") {
|
||||||
@@ -147,7 +141,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
default("argumentMapping", "FirEmptyAnnotationArgumentMapping")
|
default("argumentMapping", "FirEmptyAnnotationArgumentMapping")
|
||||||
default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource")
|
default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource")
|
||||||
default("annotationResolvePhase", "FirAnnotationResolvePhase.Unresolved")
|
default("annotationResolvePhase", "FirAnnotationResolvePhase.Unresolved")
|
||||||
useTypes(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
|
additionalImports(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
|
||||||
withCopy()
|
withCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +150,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
default("argumentList", "FirEmptyArgumentList")
|
default("argumentList", "FirEmptyArgumentList")
|
||||||
default("argumentMapping", "FirEmptyAnnotationArgumentMapping")
|
default("argumentMapping", "FirEmptyAnnotationArgumentMapping")
|
||||||
default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource")
|
default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource")
|
||||||
useTypes(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
|
additionalImports(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(arrayLiteral) {
|
builder(arrayLiteral) {
|
||||||
@@ -165,7 +159,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
|
|
||||||
builder(augmentedArraySetCall) {
|
builder(augmentedArraySetCall) {
|
||||||
default("calleeReference", "FirStubReference")
|
default("calleeReference", "FirStubReference")
|
||||||
useTypes(stubReferenceType)
|
additionalImports(stubReferenceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(propertyAccessExpression) {
|
builder(propertyAccessExpression) {
|
||||||
@@ -186,7 +180,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
default("argumentList") {
|
default("argumentList") {
|
||||||
value = "FirEmptyArgumentList"
|
value = "FirEmptyArgumentList"
|
||||||
}
|
}
|
||||||
useTypes(emptyArgumentListType)
|
additionalImports(emptyArgumentListType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(whileLoop) {
|
builder(whileLoop) {
|
||||||
@@ -213,7 +207,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
value = "FirEmptyArgumentList"
|
value = "FirEmptyArgumentList"
|
||||||
}
|
}
|
||||||
defaultNull("dispatchReceiver")
|
defaultNull("dispatchReceiver")
|
||||||
useTypes(emptyArgumentListType)
|
additionalImports(emptyArgumentListType)
|
||||||
}
|
}
|
||||||
|
|
||||||
val configurationForFunctionCallBuilder: LeafBuilderConfigurationContext.() -> Unit = {
|
val configurationForFunctionCallBuilder: LeafBuilderConfigurationContext.() -> Unit = {
|
||||||
@@ -223,7 +217,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
default("argumentList") {
|
default("argumentList") {
|
||||||
value = "FirEmptyArgumentList"
|
value = "FirEmptyArgumentList"
|
||||||
}
|
}
|
||||||
useTypes(emptyArgumentListType)
|
additionalImports(emptyArgumentListType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(functionCall) {
|
builder(functionCall) {
|
||||||
@@ -232,8 +226,8 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
value = "FirFunctionCallOrigin.Regular"
|
value = "FirFunctionCallOrigin.Regular"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder(integerLiteralOperatorCall, init = configurationForFunctionCallBuilder)
|
builder(integerLiteralOperatorCall, config = configurationForFunctionCallBuilder)
|
||||||
builder(implicitInvokeCall, init = configurationForFunctionCallBuilder)
|
builder(implicitInvokeCall, config = configurationForFunctionCallBuilder)
|
||||||
|
|
||||||
builder(getClassCall) {
|
builder(getClassCall) {
|
||||||
parents += callBuilder
|
parents += callBuilder
|
||||||
@@ -269,7 +263,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
default("argumentList") {
|
default("argumentList") {
|
||||||
value = "FirEmptyArgumentList"
|
value = "FirEmptyArgumentList"
|
||||||
}
|
}
|
||||||
useTypes(emptyArgumentListType)
|
additionalImports(emptyArgumentListType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(stringConcatenationCall) {
|
builder(stringConcatenationCall) {
|
||||||
@@ -298,21 +292,21 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
default("status", "FirResolvedDeclarationStatusImpl.DEFAULT_STATUS_FOR_STATUSLESS_DECLARATIONS")
|
default("status", "FirResolvedDeclarationStatusImpl.DEFAULT_STATUS_FOR_STATUSLESS_DECLARATIONS")
|
||||||
default("typeRef", "FirImplicitTypeRefImplWithoutSource")
|
default("typeRef", "FirImplicitTypeRefImplWithoutSource")
|
||||||
withCopy()
|
withCopy()
|
||||||
useTypes(emptyContractDescriptionType, resolvedDeclarationStatusImport, firImplicitTypeWithoutSourceType)
|
additionalImports(emptyContractDescriptionType, resolvedDeclarationStatusImport, firImplicitTypeWithoutSourceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(propertyAccessor) {
|
builder(propertyAccessor) {
|
||||||
parents += functionBuilder
|
parents += functionBuilder
|
||||||
defaultNull("body")
|
defaultNull("body")
|
||||||
default("contractDescription", "FirEmptyContractDescription")
|
default("contractDescription", "FirEmptyContractDescription")
|
||||||
useTypes(emptyContractDescriptionType)
|
additionalImports(emptyContractDescriptionType)
|
||||||
withCopy()
|
withCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(whenExpression) {
|
builder(whenExpression) {
|
||||||
defaultNull("exhaustivenessStatus")
|
defaultNull("exhaustivenessStatus")
|
||||||
default("calleeReference", "FirStubReference")
|
default("calleeReference", "FirStubReference")
|
||||||
useTypes(stubReferenceType)
|
additionalImports(stubReferenceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(resolvedTypeRef) {
|
builder(resolvedTypeRef) {
|
||||||
@@ -352,7 +346,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
parents += typeParametersOwnerBuilder
|
parents += typeParametersOwnerBuilder
|
||||||
defaultNull("body")
|
defaultNull("body")
|
||||||
default("contractDescription", "FirEmptyContractDescription")
|
default("contractDescription", "FirEmptyContractDescription")
|
||||||
useTypes(emptyContractDescriptionType)
|
additionalImports(emptyContractDescriptionType)
|
||||||
openBuilder()
|
openBuilder()
|
||||||
withCopy()
|
withCopy()
|
||||||
}
|
}
|
||||||
@@ -362,17 +356,17 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
|
|
||||||
builder(tryExpression) {
|
builder(tryExpression) {
|
||||||
default("calleeReference", "FirStubReference")
|
default("calleeReference", "FirStubReference")
|
||||||
useTypes(stubReferenceType)
|
additionalImports(stubReferenceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(checkNotNullCall) {
|
builder(checkNotNullCall) {
|
||||||
default("calleeReference", "FirStubReference")
|
default("calleeReference", "FirStubReference")
|
||||||
useTypes(stubReferenceType)
|
additionalImports(stubReferenceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(elvisExpression) {
|
builder(elvisExpression) {
|
||||||
default("calleeReference", "FirStubReference")
|
default("calleeReference", "FirStubReference")
|
||||||
useTypes(stubReferenceType)
|
additionalImports(stubReferenceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder(anonymousInitializer) {
|
builder(anonymousInitializer) {
|
||||||
@@ -442,7 +436,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
fieldPredicate = { it.invisibleField },
|
fieldPredicate = { it.invisibleField },
|
||||||
builderPredicate = { it.wantsCopy },
|
builderPredicate = { it.wantsCopy },
|
||||||
) {
|
) {
|
||||||
useTypes(resolvePhaseExtensionImport)
|
additionalImports(resolvePhaseExtensionImport)
|
||||||
}
|
}
|
||||||
|
|
||||||
configureFieldInAllLeafBuilders(
|
configureFieldInAllLeafBuilders(
|
||||||
@@ -462,40 +456,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
|
|||||||
field = "deprecationsProvider"
|
field = "deprecationsProvider"
|
||||||
) {
|
) {
|
||||||
default(it, "UnresolvedDeprecationProvider")
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ fun main(args: Array<String>) {
|
|||||||
addPureAbstractElement(FirTreeBuilder.elements, pureAbstractElementType)
|
addPureAbstractElement(FirTreeBuilder.elements, pureAbstractElementType)
|
||||||
BuilderConfigurator.configureBuilders()
|
BuilderConfigurator.configureBuilders()
|
||||||
val previouslyGeneratedFiles = collectPreviouslyGeneratedFiles(generationPath)
|
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) }
|
generatedFiles.forEach { GeneratorsFileUtil.writeFileIfContentChanged(it.file, it.newText, logNotChanged = false) }
|
||||||
removeExtraFilesFromPreviousGeneration(previouslyGeneratedFiles, generatedFiles.map { it.file })
|
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 resolveStateAccessAnnotation = type("fir.declarations", "ResolveStateAccess", kind = TypeKind.Class)
|
||||||
val unresolvedExpressionTypeAccessAnnotation = type("fir.expressions", "UnresolvedExpressionTypeAccess", kind = TypeKind.Class)
|
val unresolvedExpressionTypeAccessAnnotation = type("fir.expressions", "UnresolvedExpressionTypeAccess", kind = TypeKind.Class)
|
||||||
val firBuilderDslAnnotation = type("fir.builder", "FirBuilderDsl", kind = TypeKind.Class)
|
val firBuilderDslAnnotation = type("fir.builder", "FirBuilderDsl", kind = TypeKind.Class)
|
||||||
val experimentalContractsAnnotation = type("kotlin.contracts", "ExperimentalContracts", exactPackage = true, kind = TypeKind.Class)
|
|
||||||
|
|||||||
-180
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+32
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
-2
@@ -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.Element
|
||||||
import org.jetbrains.kotlin.fir.tree.generator.model.ElementRef
|
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.fir.tree.generator.printer.BASE_PACKAGE
|
||||||
import org.jetbrains.kotlin.generators.tree.ClassRef
|
import org.jetbrains.kotlin.generators.tree.ClassRef
|
||||||
import org.jetbrains.kotlin.generators.tree.PositionTypeParameterRef
|
import org.jetbrains.kotlin.generators.tree.PositionTypeParameterRef
|
||||||
@@ -26,7 +25,6 @@ abstract class AbstractFirTreeBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val elements = mutableListOf(baseFirElement)
|
val elements = mutableListOf(baseFirElement)
|
||||||
val intermediateBuilders = mutableListOf<IntermediateBuilder>()
|
|
||||||
|
|
||||||
protected fun element(kind: Element.Kind, vararg dependencies: Element): ElementDelegateProvider {
|
protected fun element(kind: Element.Kind, vararg dependencies: Element): ElementDelegateProvider {
|
||||||
return ElementDelegateProvider(kind, dependencies, isSealed = false, predefinedName = null)
|
return ElementDelegateProvider(kind, dependencies, isSealed = false, predefinedName = null)
|
||||||
|
|||||||
+3
-77
@@ -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.
|
* 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
|
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"
|
typealias LeafBuilder = org.jetbrains.kotlin.generators.tree.LeafBuilder<FieldWithDefault, Element, Implementation>
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-3
@@ -14,8 +14,6 @@ sealed class Field : AbstractField<Field>() {
|
|||||||
var parentHasSeparateTransform: Boolean = true
|
var parentHasSeparateTransform: Boolean = true
|
||||||
open var needTransformInOtherChildren: Boolean = false
|
open var needTransformInOtherChildren: Boolean = false
|
||||||
|
|
||||||
open var customInitializationCall: String? = null
|
|
||||||
|
|
||||||
open val isMutableOrEmptyList: Boolean
|
open val isMutableOrEmptyList: Boolean
|
||||||
get() = false
|
get() = false
|
||||||
|
|
||||||
@@ -105,7 +103,7 @@ class FieldWithDefault(override val origin: Field) : Field(), AbstractFieldWithD
|
|||||||
set(_) {}
|
set(_) {}
|
||||||
|
|
||||||
override var defaultValueInImplementation: String? = origin.defaultValueInImplementation
|
override var defaultValueInImplementation: String? = origin.defaultValueInImplementation
|
||||||
var defaultValueInBuilder: String? = null
|
override var defaultValueInBuilder: String? = null
|
||||||
override var isMutable: Boolean = origin.isMutable
|
override var isMutable: Boolean = origin.isMutable
|
||||||
override val isMutableOrEmptyList: Boolean
|
override val isMutableOrEmptyList: Boolean
|
||||||
get() = origin.isMutableOrEmptyList
|
get() = origin.isMutableOrEmptyList
|
||||||
|
|||||||
-17
@@ -12,21 +12,4 @@ class Implementation(element: Element, name: String?) : AbstractImplementation<I
|
|||||||
override val allFields = element.allFields.toMutableList().mapTo(mutableListOf()) {
|
override val allFields = element.allFields.toMutableList().mapTo(mutableListOf()) {
|
||||||
FieldWithDefault(it)
|
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
|
|
||||||
|
|||||||
+33
-297
@@ -5,16 +5,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
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.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.ImportCollector
|
||||||
import org.jetbrains.kotlin.generators.tree.StandardTypes
|
|
||||||
import org.jetbrains.kotlin.generators.tree.printer.GeneratedFile
|
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.printer.printGeneratedType
|
||||||
import org.jetbrains.kotlin.generators.tree.render
|
|
||||||
import org.jetbrains.kotlin.utils.SmartPrinter
|
import org.jetbrains.kotlin.utils.SmartPrinter
|
||||||
import org.jetbrains.kotlin.utils.withIndent
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun Builder.generateCode(generationPath: File): GeneratedFile =
|
fun Builder.generateCode(generationPath: File): GeneratedFile =
|
||||||
@@ -25,303 +26,38 @@ fun Builder.generateCode(generationPath: File): GeneratedFile =
|
|||||||
typeName,
|
typeName,
|
||||||
fileSuppressions = listOf("DuplicatedCode", "unused"),
|
fileSuppressions = listOf("DuplicatedCode", "unused"),
|
||||||
) {
|
) {
|
||||||
addAllImports(usedTypes)
|
BuilderPrinter(this).printBuilder(this@generateCode)
|
||||||
printBuilder(this@generateCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context(ImportCollector)
|
class BuilderPrinter(printer: SmartPrinter) : AbstractBuilderPrinter<Element, Implementation, FieldWithDefault, Field>(printer) {
|
||||||
private fun SmartPrinter.printBuilder(builder: Builder) {
|
|
||||||
if (builder is LeafBuilder && builder.allFields.isEmpty()) {
|
override val implementationDetailAnnotation: ClassRef<*>
|
||||||
printDslBuildFunction(builder, false)
|
get() = firImplementationDetailType
|
||||||
return
|
|
||||||
|
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()}")
|
context(ImportCollector)
|
||||||
when (builder) {
|
override fun copyField(
|
||||||
is IntermediateBuilder -> print("interface ")
|
field: FieldWithDefault,
|
||||||
is LeafBuilder -> {
|
originalParameterName: String,
|
||||||
if (builder.isOpen) {
|
copyBuilderVariableName: String
|
||||||
print("open ")
|
) {
|
||||||
}
|
if (field.typeRef == declarationAttributesType) {
|
||||||
print("class ")
|
printer.println(copyBuilderVariableName, ".", field.name, " = ", originalParameterName, ".", field.name, ".copy()")
|
||||||
}
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println()
|
super.copyField(field, originalParameterName, copyBuilderVariableName)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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("}")
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-2
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
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.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||||
import org.jetbrains.kotlin.generators.tree.printer.GeneratedFile
|
import org.jetbrains.kotlin.generators.tree.printer.GeneratedFile
|
||||||
import org.jetbrains.kotlin.generators.util.GeneratorsFileUtil.GENERATED_MESSAGE
|
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"
|
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>()
|
val generatedFiles = mutableListOf<GeneratedFile>()
|
||||||
builder.elements.mapTo(generatedFiles) { it.generateCode(generationPath) }
|
builder.elements.mapTo(generatedFiles) { it.generateCode(generationPath) }
|
||||||
builder.elements.flatMap { it.allImplementations }.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.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 += printVisitor(builder.elements, generationPath, false)
|
||||||
generatedFiles += printVisitorVoid(builder.elements, generationPath)
|
generatedFiles += printVisitorVoid(builder.elements, generationPath)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(":core:compiler.common"))
|
api(project(":core:compiler.common"))
|
||||||
|
|
||||||
|
implementation(project(":compiler:util"))
|
||||||
implementation(project(":generators"))
|
implementation(project(":generators"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+352
@@ -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()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -32,6 +32,11 @@ abstract class AbstractField<Field : AbstractField<Field>> {
|
|||||||
open val withGetter: Boolean get() = false
|
open val withGetter: Boolean get() = false
|
||||||
open val customSetter: String? get() = null
|
open val customSetter: String? get() = null
|
||||||
|
|
||||||
|
open var customInitializationCall: String? = null
|
||||||
|
|
||||||
|
val invisibleField: Boolean
|
||||||
|
get() = customInitializationCall != null
|
||||||
|
|
||||||
var deprecation: Deprecated? = null
|
var deprecation: Deprecated? = null
|
||||||
|
|
||||||
var visibility: Visibility = Visibility.PUBLIC
|
var visibility: Visibility = Visibility.PUBLIC
|
||||||
|
|||||||
+1
@@ -11,4 +11,5 @@ interface AbstractFieldWithDefaultValue<OriginField : AbstractField<OriginField>
|
|||||||
var customSetter: String?
|
var customSetter: String?
|
||||||
var notNull: Boolean
|
var notNull: Boolean
|
||||||
var defaultValueInImplementation: String?
|
var defaultValueInImplementation: String?
|
||||||
|
var defaultValueInBuilder: String?
|
||||||
}
|
}
|
||||||
+16
@@ -71,4 +71,20 @@ abstract class AbstractImplementation<Implementation, Element, Field>(
|
|||||||
val fieldsWithDefault by lazy { allFields.filter(::withDefault) }
|
val fieldsWithDefault by lazy { allFields.filter(::withDefault) }
|
||||||
|
|
||||||
var requiresOptIn = false
|
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()
|
||||||
|
}
|
||||||
+356
@@ -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
|
if (position < 0) order.size else position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ImplementationKind.hasLeafBuilder: Boolean
|
||||||
|
get() = this == ImplementationKind.FinalClass || this == ImplementationKind.OpenClass
|
||||||
|
|||||||
Reference in New Issue
Block a user