diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/BuilderConfigurator.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/BuilderConfigurator.kt index f5653cc4841..5710879fcc6 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/BuilderConfigurator.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/BuilderConfigurator.kt @@ -5,16 +5,10 @@ package org.jetbrains.kotlin.fir.tree.generator -import org.jetbrains.kotlin.fir.tree.generator.context.AbstractBuilderConfigurator -import org.jetbrains.kotlin.fir.tree.generator.model.Element -import org.jetbrains.kotlin.fir.tree.generator.model.Field -import org.jetbrains.kotlin.fir.tree.generator.model.Implementation -import org.jetbrains.kotlin.fir.tree.generator.model.LeafBuilder -import org.jetbrains.kotlin.fir.tree.generator.printer.invisibleField -import org.jetbrains.kotlin.generators.tree.traverseParents +import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirBuilderConfigurator -object BuilderConfigurator : AbstractBuilderConfigurator(FirTreeBuilder) { - fun configureBuilders() = with(firTreeBuilder) { +object BuilderConfigurator : AbstractFirBuilderConfigurator(FirTreeBuilder.elements) { + override fun configureBuilders() = with(FirTreeBuilder) { val declarationBuilder by builder { fields from declaration without "symbol" } @@ -99,7 +93,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree defaultNull("delegatedConstructor") defaultNull("body") default("contractDescription", "FirEmptyContractDescription") - useTypes(emptyContractDescriptionType) + additionalImports(emptyContractDescriptionType) } } @@ -108,7 +102,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree defaultNull("delegatedConstructor") defaultNull("body") default("contractDescription", "FirEmptyContractDescription") - useTypes(emptyContractDescriptionType) + additionalImports(emptyContractDescriptionType) } builder(constructor, "FirConstructorImpl") { @@ -147,7 +141,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree default("argumentMapping", "FirEmptyAnnotationArgumentMapping") default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource") default("annotationResolvePhase", "FirAnnotationResolvePhase.Unresolved") - useTypes(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType) + additionalImports(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType) withCopy() } @@ -156,7 +150,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree default("argumentList", "FirEmptyArgumentList") default("argumentMapping", "FirEmptyAnnotationArgumentMapping") default("annotationTypeRef", "FirImplicitTypeRefImplWithoutSource") - useTypes(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType) + additionalImports(emptyArgumentListType, emptyAnnotationArgumentMappingType, firImplicitTypeWithoutSourceType) } builder(arrayLiteral) { @@ -165,7 +159,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree builder(augmentedArraySetCall) { default("calleeReference", "FirStubReference") - useTypes(stubReferenceType) + additionalImports(stubReferenceType) } builder(propertyAccessExpression) { @@ -186,7 +180,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree default("argumentList") { value = "FirEmptyArgumentList" } - useTypes(emptyArgumentListType) + additionalImports(emptyArgumentListType) } builder(whileLoop) { @@ -213,7 +207,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree value = "FirEmptyArgumentList" } defaultNull("dispatchReceiver") - useTypes(emptyArgumentListType) + additionalImports(emptyArgumentListType) } val configurationForFunctionCallBuilder: LeafBuilderConfigurationContext.() -> Unit = { @@ -223,7 +217,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree default("argumentList") { value = "FirEmptyArgumentList" } - useTypes(emptyArgumentListType) + additionalImports(emptyArgumentListType) } builder(functionCall) { @@ -232,8 +226,8 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree value = "FirFunctionCallOrigin.Regular" } } - builder(integerLiteralOperatorCall, init = configurationForFunctionCallBuilder) - builder(implicitInvokeCall, init = configurationForFunctionCallBuilder) + builder(integerLiteralOperatorCall, config = configurationForFunctionCallBuilder) + builder(implicitInvokeCall, config = configurationForFunctionCallBuilder) builder(getClassCall) { parents += callBuilder @@ -269,7 +263,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree default("argumentList") { value = "FirEmptyArgumentList" } - useTypes(emptyArgumentListType) + additionalImports(emptyArgumentListType) } builder(stringConcatenationCall) { @@ -298,21 +292,21 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree default("status", "FirResolvedDeclarationStatusImpl.DEFAULT_STATUS_FOR_STATUSLESS_DECLARATIONS") default("typeRef", "FirImplicitTypeRefImplWithoutSource") withCopy() - useTypes(emptyContractDescriptionType, resolvedDeclarationStatusImport, firImplicitTypeWithoutSourceType) + additionalImports(emptyContractDescriptionType, resolvedDeclarationStatusImport, firImplicitTypeWithoutSourceType) } builder(propertyAccessor) { parents += functionBuilder defaultNull("body") default("contractDescription", "FirEmptyContractDescription") - useTypes(emptyContractDescriptionType) + additionalImports(emptyContractDescriptionType) withCopy() } builder(whenExpression) { defaultNull("exhaustivenessStatus") default("calleeReference", "FirStubReference") - useTypes(stubReferenceType) + additionalImports(stubReferenceType) } builder(resolvedTypeRef) { @@ -352,7 +346,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree parents += typeParametersOwnerBuilder defaultNull("body") default("contractDescription", "FirEmptyContractDescription") - useTypes(emptyContractDescriptionType) + additionalImports(emptyContractDescriptionType) openBuilder() withCopy() } @@ -362,17 +356,17 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree builder(tryExpression) { default("calleeReference", "FirStubReference") - useTypes(stubReferenceType) + additionalImports(stubReferenceType) } builder(checkNotNullCall) { default("calleeReference", "FirStubReference") - useTypes(stubReferenceType) + additionalImports(stubReferenceType) } builder(elvisExpression) { default("calleeReference", "FirStubReference") - useTypes(stubReferenceType) + additionalImports(stubReferenceType) } builder(anonymousInitializer) { @@ -442,7 +436,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree fieldPredicate = { it.invisibleField }, builderPredicate = { it.wantsCopy }, ) { - useTypes(resolvePhaseExtensionImport) + additionalImports(resolvePhaseExtensionImport) } configureFieldInAllLeafBuilders( @@ -462,40 +456,7 @@ object BuilderConfigurator : AbstractBuilderConfigurator(FirTree field = "deprecationsProvider" ) { default(it, "UnresolvedDeprecationProvider") - useTypes(unresolvedDeprecationsProviderType) + additionalImports(unresolvedDeprecationsProviderType) } } - - private inline fun findImplementationsWithElementInParents( - element: Element, - implementationPredicate: (Implementation) -> Boolean = { true } - ): Collection { - 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) - } - } - } diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Main.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Main.kt index ff0d6507a35..693fde26746 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Main.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Main.kt @@ -30,7 +30,7 @@ fun main(args: Array) { addPureAbstractElement(FirTreeBuilder.elements, pureAbstractElementType) BuilderConfigurator.configureBuilders() val previouslyGeneratedFiles = collectPreviouslyGeneratedFiles(generationPath) - val generatedFiles = generateElements(FirTreeBuilder, generationPath) + val generatedFiles = generateElements(FirTreeBuilder, BuilderConfigurator, generationPath) generatedFiles.forEach { GeneratorsFileUtil.writeFileIfContentChanged(it.file, it.newText, logNotChanged = false) } removeExtraFilesFromPreviousGeneration(previouslyGeneratedFiles, generatedFiles.map { it.file }) } diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt index 0a7c3738246..b0be0d6c651 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt @@ -126,4 +126,3 @@ val firTransformerType = generatedType("visitors", "FirTransformer") val resolveStateAccessAnnotation = type("fir.declarations", "ResolveStateAccess", kind = TypeKind.Class) val unresolvedExpressionTypeAccessAnnotation = type("fir.expressions", "UnresolvedExpressionTypeAccess", kind = TypeKind.Class) val firBuilderDslAnnotation = type("fir.builder", "FirBuilderDsl", kind = TypeKind.Class) -val experimentalContractsAnnotation = type("kotlin.contracts", "ExperimentalContracts", exactPackage = true, kind = TypeKind.Class) diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractBuilderConfigurator.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractBuilderConfigurator.kt deleted file mode 100644 index e5e6c7f1738..00000000000 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractBuilderConfigurator.kt +++ /dev/null @@ -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(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 - 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) { - builder.fields.removeAll { it.name in names } - } - } - - val fields = Fields() - val parents: MutableList 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 { - 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 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 - } -} diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirBuilderConfigurator.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirBuilderConfigurator.kt new file mode 100644 index 00000000000..3de8537cf05 --- /dev/null +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirBuilderConfigurator.kt @@ -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( + elements: List +) : AbstractBuilderConfigurator(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") + } +} diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirTreeBuilder.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirTreeBuilder.kt index c03afa53a32..9f8d888b055 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirTreeBuilder.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/context/AbstractFirTreeBuilder.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.fir.tree.generator.context import org.jetbrains.kotlin.fir.tree.generator.model.Element import org.jetbrains.kotlin.fir.tree.generator.model.ElementRef -import org.jetbrains.kotlin.fir.tree.generator.model.IntermediateBuilder import org.jetbrains.kotlin.fir.tree.generator.printer.BASE_PACKAGE import org.jetbrains.kotlin.generators.tree.ClassRef import org.jetbrains.kotlin.generators.tree.PositionTypeParameterRef @@ -26,7 +25,6 @@ abstract class AbstractFirTreeBuilder { } val elements = mutableListOf(baseFirElement) - val intermediateBuilders = mutableListOf() protected fun element(kind: Element.Kind, vararg dependencies: Element): ElementDelegateProvider { return ElementDelegateProvider(kind, dependencies, isSealed = false, predefinedName = null) diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Builder.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Builder.kt index 1e019d3b704..52e3b10696f 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Builder.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Builder.kt @@ -1,84 +1,10 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.fir.tree.generator.model -import org.jetbrains.kotlin.generators.tree.* +typealias Builder = org.jetbrains.kotlin.generators.tree.Builder -private const val DEFAULT_BUILDER_PACKAGE = "org.jetbrains.kotlin.fir.tree.builder" - -sealed class Builder : FieldContainer, TypeRef, Importable { - val parents: MutableList = mutableListOf() - val usedTypes: MutableList = mutableListOf() - abstract override val allFields: List - abstract val uselessFields: List - - 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 by lazy { - mutableMapOf().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 by lazy { implementation.fieldsWithoutDefault } - - override val uselessFields: List 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 = mutableListOf() - var materializedElement: Element? = null - - override val allFields: List by lazy { - mutableSetOf().apply { - parents.forEach { this += it.allFields } - this += fields - }.toList() - } - - override val uselessFields: List = emptyList() - override var packageName: String = DEFAULT_BUILDER_PACKAGE - - context(ImportCollector) - override fun renderTo(appendable: Appendable) { - addImport(this) - appendable.append(typeName) - } -} +typealias LeafBuilder = org.jetbrains.kotlin.generators.tree.LeafBuilder diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Field.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Field.kt index 9df1d9255bc..c7b061e8a2a 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Field.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Field.kt @@ -14,8 +14,6 @@ sealed class Field : AbstractField() { var parentHasSeparateTransform: Boolean = true open var needTransformInOtherChildren: Boolean = false - open var customInitializationCall: String? = null - open val isMutableOrEmptyList: Boolean get() = false @@ -105,7 +103,7 @@ class FieldWithDefault(override val origin: Field) : Field(), AbstractFieldWithD set(_) {} override var defaultValueInImplementation: String? = origin.defaultValueInImplementation - var defaultValueInBuilder: String? = null + override var defaultValueInBuilder: String? = null override var isMutable: Boolean = origin.isMutable override val isMutableOrEmptyList: Boolean get() = origin.isMutableOrEmptyList diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Implementation.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Implementation.kt index 1908695130c..9684d50201c 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Implementation.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/model/Implementation.kt @@ -12,21 +12,4 @@ class Implementation(element: Element, name: String?) : AbstractImplementation(printer) { + + override val implementationDetailAnnotation: ClassRef<*> + get() = firImplementationDetailType + + override val builderDslAnnotation: ClassRef<*> + get() = firBuilderDslAnnotation + + override fun actualTypeOfField(field: Field) = field.getMutableType(forBuilder = true) + + context(ImportCollector) + override fun SmartPrinter.printFieldReferenceInImplementationConstructorCall(field: FieldWithDefault) { + print(field.name) + if (field.isMutableOrEmptyList) { + addImport(toMutableOrEmptyImport) + print(".toMutableOrEmpty()") + } } - println("@${firBuilderDslAnnotation.render()}") - when (builder) { - is IntermediateBuilder -> print("interface ") - is LeafBuilder -> { - if (builder.isOpen) { - print("open ") - } - print("class ") - } - } - print(builder.render()) - if (builder.parents.isNotEmpty()) { - print(builder.parents.joinToString(separator = ", ", prefix = " : ") { it.render() }) - } - var hasRequiredFields = false - printBlock { - var needNewLine = false - for (field in builder.allFields) { - val (newLine, requiredFields) = printFieldInBuilder(field, builder, fieldIsUseless = false) - needNewLine = newLine - hasRequiredFields = hasRequiredFields || requiredFields - } - val hasBackingFields = builder.allFields.any { it.nullable } - if (needNewLine) { - println() - } - val buildType = when (builder) { - is LeafBuilder -> builder.implementation.element.render() - is IntermediateBuilder -> builder.materializedElement!!.withStarArgs().render() - } - if (builder is LeafBuilder && builder.implementation.isPublic) { - println("@OptIn(${firImplementationDetailType.render()}::class)") - } - if (builder.parents.isNotEmpty()) { - print("override ") - } - print("fun build(): $buildType") - if (builder is LeafBuilder) { - printBlock { - println("return ${builder.implementation.render()}(") - withIndent { - for (field in builder.allFields) { - if (field.invisibleField) continue - val name = field.name - print(name) - if (field.isMutableOrEmptyList) { - addImport(toMutableOrEmptyImport) - print(".toMutableOrEmpty()") - } - println(",") - } - } - println(")") - } - if (hasBackingFields) { - println() - } + context(ImportCollector) + override fun copyField( + field: FieldWithDefault, + originalParameterName: String, + copyBuilderVariableName: String + ) { + if (field.typeRef == declarationAttributesType) { + printer.println(copyBuilderVariableName, ".", field.name, " = ", originalParameterName, ".", field.name, ".copy()") } else { - println() - } - - if (builder is LeafBuilder) { -// for (field in builder.allFields) { -// printBackingFieldIfNeeded(field) -// } - - val hasUselessFields = builder.uselessFields.isNotEmpty() - if (hasUselessFields) { - println() - builder.uselessFields.forEachIndexed { index, field -> - if (index > 0) { - println() - } - printFieldInBuilder(field, builder, fieldIsUseless = true) - } - } - } - } - if (builder is LeafBuilder) { - println() - printDslBuildFunction(builder, hasRequiredFields) - - if (builder.wantsCopy) { - println() - printDslBuildCopyFunction(builder, hasRequiredFields) + super.copyField(field, originalParameterName, copyBuilderVariableName) } } } - -internal val Field.invisibleField: Boolean get() = customInitializationCall != null - -private fun FieldWithDefault.needBackingField(fieldIsUseless: Boolean) = - (!nullable || notNull) && origin !is FieldList && if (fieldIsUseless) { - defaultValueInImplementation == null - } else { - defaultValueInBuilder == null - } - -private fun FieldWithDefault.needNotNullDelegate(fieldIsUseless: Boolean) = - needBackingField(fieldIsUseless) && (typeRef == StandardTypes.boolean || typeRef == StandardTypes.int) - -context(ImportCollector) -private fun SmartPrinter.printFieldInBuilder( - field: FieldWithDefault, - builder: Builder, - fieldIsUseless: Boolean, -): Pair { - 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("}") -} diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/printer/main.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/printer/main.kt index cdb3add9e20..6a088fdbf88 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/printer/main.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/printer/main.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.fir.tree.generator.printer +import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirBuilderConfigurator import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder import org.jetbrains.kotlin.generators.tree.printer.GeneratedFile import org.jetbrains.kotlin.generators.util.GeneratorsFileUtil.GENERATED_MESSAGE @@ -16,12 +17,16 @@ const val BASE_PACKAGE = "org.jetbrains.kotlin.fir" internal const val TREE_GENERATOR_README = "compiler/fir/tree/tree-generator/Readme.md" -fun generateElements(builder: AbstractFirTreeBuilder, generationPath: File): List { +fun generateElements( + builder: AbstractFirTreeBuilder, + builderConfigurator: AbstractFirBuilderConfigurator<*>, + generationPath: File, +): List { val generatedFiles = mutableListOf() builder.elements.mapTo(generatedFiles) { it.generateCode(generationPath) } builder.elements.flatMap { it.allImplementations }.mapTo(generatedFiles) { it.generateCode(generationPath) } builder.elements.flatMap { it.allImplementations }.mapNotNull { it.builder }.mapTo(generatedFiles) { it.generateCode(generationPath) } - builder.intermediateBuilders.mapTo(generatedFiles) { it.generateCode(generationPath) } + builderConfigurator.intermediateBuilders.mapTo(generatedFiles) { it.generateCode(generationPath) } generatedFiles += printVisitor(builder.elements, generationPath, false) generatedFiles += printVisitorVoid(builder.elements, generationPath) diff --git a/generators/tree-generator-common/build.gradle.kts b/generators/tree-generator-common/build.gradle.kts index 6f3b480f5ff..e1cc9d6f26d 100644 --- a/generators/tree-generator-common/build.gradle.kts +++ b/generators/tree-generator-common/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { api(project(":core:compiler.common")) + implementation(project(":compiler:util")) implementation(project(":generators")) } diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractBuilderPrinter.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractBuilderPrinter.kt new file mode 100644 index 00000000000..aa46874eff4 --- /dev/null +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractBuilderPrinter.kt @@ -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(val printer: SmartPrinter) + where Element : AbstractElement, + Implementation : AbstractImplementation, + BuilderField : AbstractField<*>, + BuilderField : AbstractFieldWithDefaultValue<*>, + ElementField : AbstractField { + + 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) { + addAllImports(builder.usedTypes) + printer.run { + if (builder is LeafBuilder<*, *, *> && builder.allFields.isEmpty()) { + @Suppress("UNCHECKED_CAST") + printDslBuildFunction(builder as LeafBuilder, 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, hasRequiredFields) + + if (builder.wantsCopy) { + println() + printDslBuildCopyFunction(builder, hasRequiredFields) + } + } + } + } + + private fun lambdaParameterForBuilderFunction(builder: Builder, hasRequiredFields: Boolean) = + FunctionParameter( + name = "init", + type = Lambda(receiver = builder, returnType = StandardTypes.unit), + defaultValue = "{}".takeIf { !hasRequiredFields }, + ) + + context(ImportCollector) + private fun SmartPrinter.printDslBuildFunction( + builder: LeafBuilder, + 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, + fieldIsUseless: Boolean, + ): Pair { + 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, + 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, + 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, 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, + 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()") + } + } +} diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractField.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractField.kt index b1c91faea97..b7d6a856755 100644 --- a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractField.kt +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractField.kt @@ -32,6 +32,11 @@ abstract class AbstractField> { 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 diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractFieldWithDefaultValue.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractFieldWithDefaultValue.kt index 1cf3f2db7fc..a9155ea1c54 100644 --- a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractFieldWithDefaultValue.kt +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractFieldWithDefaultValue.kt @@ -11,4 +11,5 @@ interface AbstractFieldWithDefaultValue var customSetter: String? var notNull: Boolean var defaultValueInImplementation: String? + var defaultValueInBuilder: String? } \ No newline at end of file diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractImplementation.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractImplementation.kt index fb3c143c686..cc7e1cdae16 100644 --- a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractImplementation.kt +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/AbstractImplementation.kt @@ -71,4 +71,20 @@ abstract class AbstractImplementation( 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? = null } \ No newline at end of file diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/Builder.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/Builder.kt new file mode 100644 index 00000000000..9e5b0a39294 --- /dev/null +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/Builder.kt @@ -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 : FieldContainer, TypeRefWithNullability, Importable + where BuilderField : AbstractField<*>, + Element : AbstractElement { + + val parents: MutableList> = mutableListOf() + + val usedTypes: MutableList = mutableListOf() + + abstract val uselessFields: List + + 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 by lazy { + mutableMapOf().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( + val implementation: Implementation, +) : Builder() + where BuilderField : AbstractField<*>, + Element : AbstractElement, + Implementation : AbstractImplementation { + override val typeName: String + get() = if (implementation.name != null) { + "${implementation.name}Builder" + } else { + "${implementation.element.typeName}Builder" + } + + override val allFields: List by lazy { implementation.fieldsWithoutDefault } + + override val uselessFields: List 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, + override var packageName: String, +) : Builder() + where BuilderField : AbstractField<*>, + Element : AbstractElement { + val fields: MutableList = mutableListOf() + var materializedElement: Element? = null + + override val allFields: List by lazy { + mutableSetOf().apply { + parents.forEach { this += it.allFields } + this += fields + }.toList() + } + + override val uselessFields: List = emptyList() +} \ No newline at end of file diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/config/AbstractBuilderConfigurator.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/config/AbstractBuilderConfigurator.kt new file mode 100644 index 00000000000..2469637d6e9 --- /dev/null +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/config/AbstractBuilderConfigurator.kt @@ -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( + val elements: List, +) where Element : AbstractElement, + Implementation : AbstractImplementation, + BuilderField : AbstractField<*>, + BuilderField : AbstractFieldWithDefaultValue<*>, + ElementField : AbstractField { + + /** + * 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>() + + /** + * 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 { + 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) -> 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 + + 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 + ) : 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) { + 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> + get() = builder.parents + } + + protected inner class IntermediateBuilderDelegateProvider( + private val block: IntermediateBuilderConfigurationContext.() -> Unit + ) { + lateinit var builder: IntermediateBuilder + + operator fun provideDelegate( + thisRef: Nothing?, + prop: KProperty<*> + ): ReadOnlyProperty> { + val name = namePrefix + prop.name.replaceFirstChar(Char::uppercaseChar) + builder = IntermediateBuilder(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 + ) : BuilderConfigurationContext() { + + /** + * The list of parents of this leaf builder. Can be used for adding builder superclasses to this builder class. + */ + val parents: MutableList> + 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 + } + } +} diff --git a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/utils.kt b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/utils.kt index b4b8f1f1588..6613c0747cf 100644 --- a/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/utils.kt +++ b/generators/tree-generator-common/src/org/jetbrains/kotlin/generators/tree/utils.kt @@ -74,3 +74,6 @@ internal fun > List.reorderFieldsIfNecessary(ord if (position < 0) order.size else position } } + +val ImplementationKind.hasLeafBuilder: Boolean + get() = this == ImplementationKind.FinalClass || this == ImplementationKind.OpenClass