diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step2intermediate/MoreTypes.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step2intermediate/MoreTypes.kt index 0430db8e..df44ed47 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step2intermediate/MoreTypes.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step2intermediate/MoreTypes.kt @@ -1,48 +1,46 @@ package com.virtuslab.pulumikotlin.codegen.step2intermediate -import com.squareup.kotlinpoet.ClassName +import com.pulumi.core.Output +import com.pulumi.kotlin.ConvertibleToJava +import com.pulumi.kotlin.PulumiTagMarker +import com.pulumi.kotlin.options.CustomResourceOptions +import com.pulumi.kotlin.options.CustomResourceOptionsBuilder import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.MemberName.Companion.member -import com.squareup.kotlinpoet.ParameterizedTypeName -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.asTypeName object MoreTypes { object Kotlin { object Pulumi { - fun toJava(): MemberName { - return MemberName("com.pulumi.kotlin", "toJava") - } + fun toJavaExtensionMethod() = MemberName("com.pulumi.kotlin", "toJava") - fun applySuspend(): MemberName { - return MemberName("com.pulumi.kotlin", "applySuspend") - } + fun toKotlinExtensionMethod() = MemberName("com.pulumi.kotlin", "toKotlin") - fun ConvertibleToJava(type: TypeName): ParameterizedTypeName = - ClassName("com.pulumi.kotlin", "ConvertibleToJava").parameterizedBy(type) - } + fun applySuspendExtensionMethod() = MemberName("com.pulumi.kotlin", "applySuspend") + + fun applyValueExtensionMethod() = MemberName("com.pulumi.kotlin", "applyValue") + + fun pulumiDslMarkerAnnotation() = PulumiTagMarker::class.asClassName() + + fun convertibleToJavaClass() = ConvertibleToJava::class.asClassName() + + fun customResourceOptionsClass() = CustomResourceOptions::class.asClassName() - fun Pair(leftType: TypeName, rightType: TypeName): ParameterizedTypeName { - return ClassName("kotlin", "Pair").parameterizedBy(leftType, rightType) + fun customResourceOptionsBuilderClass() = CustomResourceOptionsBuilder::class.asClassName() } + + fun coroutinesFutureAwaitExtensionMethod() = MemberName("kotlinx.coroutines.future", "await") + + fun pairClass() = Pair::class.asTypeName() } object Java { object Pulumi { + fun outputOfMethod() = outputClass().member("of") - object Output { - val of: MemberName - get() = Output().member("of") - } - - fun Output(): ClassName { - return ClassName("com.pulumi.core", "Output") - } - - fun Output(type: TypeName): ParameterizedTypeName { - return ClassName("com.pulumi.core", "Output").parameterizedBy(type) - } + fun outputClass() = Output::class.asClassName() } } } diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/FieldTypes.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/FieldTypes.kt index f0577bc9..4bafcb21 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/FieldTypes.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/FieldTypes.kt @@ -2,11 +2,11 @@ package com.virtuslab.pulumikotlin.codegen.step3codegen import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.TypeName -import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Java.Pulumi.outputClass import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetExtensions.parameterizedBy sealed class FieldType { - abstract val type: T abstract fun toTypeName(): TypeName @@ -20,7 +20,7 @@ data class NormalField(override val type: T, val mappingCode data class OutputWrappedField(override val type: T) : FieldType() { override fun toTypeName(): ParameterizedTypeName { - return MoreTypes.Java.Pulumi.Output(type.toTypeName()) + return outputClass().parameterizedBy(type) } } @@ -28,6 +28,12 @@ data class Field( val name: String, val fieldType: FieldType, val required: Boolean, - val overloads: List> = emptyList(), + val overloads: List> = emptyList(), val kDoc: KDoc, -) +) { + fun toTypeName(): TypeName = + fieldType.toTypeName().copy(nullable = !required) + + fun toNullableTypeName(): TypeName = + fieldType.toTypeName().copy(nullable = true) +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetExtensions.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetExtensions.kt new file mode 100644 index 00000000..fdbfed57 --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetExtensions.kt @@ -0,0 +1,40 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.virtuslab.pulumikotlin.codegen.expressions.Code +import com.virtuslab.pulumikotlin.codegen.step2intermediate.Type + +object KotlinPoetExtensions { + fun TypeSpec.Builder.addFunctions(vararg funSpecs: FunSpec) = addFunctions(funSpecs.toList()) + + fun TypeSpec.Builder.addProperties(vararg propertySpecs: PropertySpec) = addProperties(propertySpecs.toList()) + + fun FileSpec.Builder.addImport(memberName: MemberName) = addImport(memberName.packageName, memberName.simpleName) + + fun FileSpec.Builder.addImports(vararg memberNames: MemberName) = + apply { + memberNames.forEach { + addImport(it) + } + } + + fun FileSpec.Builder.addTypes(vararg typeSpecs: TypeSpec) = addTypes(typeSpecs.toList()) + + fun FileSpec.Builder.addTypes(types: Iterable) = + apply { + types.forEach { + addType(it) + } + } + + fun ClassName.parameterizedBy(vararg types: Type) = parameterizedBy(types.map { it.toTypeName() }) + + fun CodeBlock.Builder.add(code: Code) = add(code.toCodeBlock().toKotlinPoetCodeBlock()) +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetPatterns.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetPatterns.kt index dd3c4313..9aa4f812 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetPatterns.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/KotlinPoetPatterns.kt @@ -1,6 +1,9 @@ package com.virtuslab.pulumikotlin.codegen.step3codegen import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.KModifier.SUSPEND import com.squareup.kotlinpoet.LIST import com.squareup.kotlinpoet.LambdaTypeName import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy @@ -10,26 +13,60 @@ import com.virtuslab.pulumikotlin.codegen.expressions.Assignment import com.virtuslab.pulumikotlin.codegen.expressions.CustomExpression import com.virtuslab.pulumikotlin.codegen.expressions.Expression import com.virtuslab.pulumikotlin.codegen.expressions.add +import com.virtuslab.pulumikotlin.codegen.expressions.callLet +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ComplexType import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedComplexType typealias MappingCode = (Expression) -> Expression object KotlinPoetPatterns { + /** + * Example output: + * + * ``` + * List Unit> + * ``` + */ fun listOfLambdas(innerType: TypeName): TypeName = LIST.parameterizedBy(builderLambda(innerType)) + /** + * @see listOfLambdas + */ + fun listOfLambdas(innerType: ComplexType): TypeName = + listOfLambdas(innerType.toBuilderTypeName()) + + /** + * @see listOfLambdas + */ fun listOfLambdas(innerType: ReferencedComplexType): TypeName = listOfLambdas(innerType.toBuilderTypeName()) - fun builderLambda(innerType: ReferencedComplexType): TypeName = + /** + * Example output: + * + * ``` + * suspend InnerType.() -> Unit + * ``` + */ + fun builderLambda(innerType: ComplexType): TypeName = builderLambda(innerType.toBuilderTypeName()) + /** + * @see builderLambda + */ fun builderLambda(innerType: TypeName): TypeName = LambdaTypeName .get(receiver = innerType, returnType = UNIT) .copy(suspending = true) + /** + * @see builderLambda + */ + fun builderLambda(innerType: ReferencedComplexType): TypeName = + builderLambda(innerType.toBuilderTypeName()) + data class BuilderSettingCodeBlock(val mappingCode: MappingCode? = null, val code: String, val args: List) { fun withMappingCode(mappingCode: MappingCode): BuilderSettingCodeBlock { return copy(mappingCode = mappingCode) @@ -54,4 +91,65 @@ object KotlinPoetPatterns { fun create(code: String, vararg args: Any?) = BuilderSettingCodeBlock(null, code, args.toList()) } } + + /** + * Example output: + * + * ``` + * suspend fun name(vararg argument: ParameterType): Unit { + * val toBeMapped = something.toList().somethingElse() + * val mapped = mappingCode(toBeMapped) + * this.field = mapped + * } + * ``` + */ + fun builderPattern( + name: String, + parameterType: TypeName, + kDoc: KDoc, + codeBlock: BuilderSettingCodeBlock, + parameterModifiers: List = emptyList(), + ): FunSpec { + return FunSpec + .builder(name) + .addModifiers(SUSPEND) + .addParameter( + "argument", + parameterType, + parameterModifiers, + ) + .addCode(codeBlock.toCodeBlock(name)) + .addDocsToBuilderMethod(kDoc, "argument") + .build() + } + + /** + * Example output: + * + * ``` + * val toBeMapped = something.toList().somethingElse() + * val mapped = mappingCode(toBeMapped) + * this.field = mapped + * ``` + */ + fun mappingCodeBlock( + field: NormalField<*>, + required: Boolean, + name: String, + code: String, + vararg args: Any?, + ): CodeBlock { + val expression = CustomExpression("toBeMapped").callLet(!required) { arg -> field.mappingCode(arg) } + return CodeBlock.builder() + .addStatement("val toBeMapped = $code", *args) + .add(Assignment("mapped", expression)) + .addStatement("") + .addStatement("this.%N = mapped", name) + .build() + } + + fun FunSpec.Builder.addDocsToBuilderMethod(kDoc: KDoc, paramName: String) = apply { + addDocs("@param $paramName ${kDoc.description.orEmpty()}") + addDeprecationWarningIfAvailable(kDoc) + } } diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/functions/FunctionGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/functions/FunctionGenerator.kt index 4cbdaf5d..e3920469 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/functions/FunctionGenerator.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/functions/FunctionGenerator.kt @@ -23,9 +23,11 @@ import com.virtuslab.pulumikotlin.codegen.step2intermediate.Direction.Output import com.virtuslab.pulumikotlin.codegen.step2intermediate.FunctionType import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType.Java import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType.Kotlin +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.coroutinesFutureAwaitExtensionMethod import com.virtuslab.pulumikotlin.codegen.step2intermediate.NamingFlags import com.virtuslab.pulumikotlin.codegen.step2intermediate.Subject.Function import com.virtuslab.pulumikotlin.codegen.step2intermediate.Subject.Resource +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetExtensions.addImport import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderLambda import com.virtuslab.pulumikotlin.codegen.step3codegen.addDeprecationWarningIfAvailable import com.virtuslab.pulumikotlin.codegen.step3codegen.addDocs @@ -53,7 +55,7 @@ object FunctionGenerator { name.toFunctionGroupObjectName(namingFlags), ) .addType(objectSpecBuilder.build()) - .addImport("kotlinx.coroutines.future", "await") + .addImport(coroutinesFutureAwaitExtensionMethod()) .build() listOf(fileSpec) diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/resources/ResourceGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/resources/ResourceGenerator.kt index 5a7dbf64..05a23087 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/resources/ResourceGenerator.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/resources/ResourceGenerator.kt @@ -1,9 +1,6 @@ package com.virtuslab.pulumikotlin.codegen.step3codegen.resources import com.pulumi.kotlin.KotlinResource -import com.pulumi.kotlin.PulumiTagMarker -import com.pulumi.kotlin.options.CustomResourceOptions -import com.pulumi.kotlin.options.CustomResourceOptionsBuilder import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -15,13 +12,14 @@ import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.STRING import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.UNIT -import com.squareup.kotlinpoet.asClassName import com.virtuslab.pulumikotlin.codegen.expressions.addCode import com.virtuslab.pulumikotlin.codegen.step2intermediate.Depth.Root import com.virtuslab.pulumikotlin.codegen.step2intermediate.Direction.Output import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType.Java import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType.Kotlin -import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.customResourceOptionsBuilderClass +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.customResourceOptionsClass +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.pulumiDslMarkerAnnotation import com.virtuslab.pulumikotlin.codegen.step2intermediate.NamingFlags import com.virtuslab.pulumikotlin.codegen.step2intermediate.ResourceType import com.virtuslab.pulumikotlin.codegen.step2intermediate.Subject.Resource @@ -51,10 +49,6 @@ object ResourceGenerator { } private fun buildArgsClass(fileSpecBuilder: FileSpec.Builder, resourceType: ResourceType) { - val dslTag = PulumiTagMarker::class.asClassName() - - val customResourceOptionsClassName = CustomResourceOptions::class.asClassName() - val javaFlags = NamingFlags(Root, Resource, Output, Java) val kotlinFlags = NamingFlags(Root, Resource, Output, Kotlin) @@ -63,12 +57,8 @@ object ResourceGenerator { val javaResourceClassName = ClassName(names.toResourcePackage(javaFlags), names.toResourceName(javaFlags)) val fields = resourceType.outputFields.map { field -> - PropertySpec.builder( - field.name, - MoreTypes.Java.Pulumi.Output( - field.fieldType.type.toTypeName().copy(nullable = !field.required), - ), - ) + PropertySpec + .builder(field.name, field.toTypeName()) .getter( FunSpec.getterBuilder() .addCode(toKotlinFunctionResource(field.name, field.fieldType.type, !field.required)) @@ -127,18 +117,17 @@ object ResourceGenerator { .addDocs("@param block The arguments to use to populate this resource's properties.") .build() - val customResourceOptionsBuilderClassName = CustomResourceOptionsBuilder::class.asClassName() val optsFunction = FunSpec .builder("opts") .addModifiers(KModifier.SUSPEND) .addParameter( "block", LambdaTypeName.get( - customResourceOptionsBuilderClassName, + customResourceOptionsBuilderClass(), returnType = UNIT, ).copy(suspending = true), ) - .addStatement("val builder = %T()", customResourceOptionsBuilderClassName) + .addStatement("val builder = %T()", customResourceOptionsBuilderClass()) .addStatement("block(builder)") .addStatement("this.opts = builder.build()") .addDocs("@param block A bag of options that control this resource's behavior.") @@ -151,7 +140,7 @@ object ResourceGenerator { .addModifiers(INTERNAL) .build(), ) - .addAnnotation(dslTag) + .addAnnotation(pulumiDslMarkerAnnotation()) .addProperties( listOf( PropertySpec.builder("name", STRING.copy(nullable = true)) @@ -162,9 +151,9 @@ object ResourceGenerator { .mutable(true) .initializer("null") .build(), - PropertySpec.builder("opts", customResourceOptionsClassName) + PropertySpec.builder("opts", customResourceOptionsClass()) .mutable(true) - .initializer("%T()", customResourceOptionsClassName) + .initializer("%T()", customResourceOptionsClass()) .build(), ), ) diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/EnumTypeGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/EnumTypeGenerator.kt index 4f562589..933491b5 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/EnumTypeGenerator.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/EnumTypeGenerator.kt @@ -3,12 +3,14 @@ package com.virtuslab.pulumikotlin.codegen.step3codegen.types import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.TypeSpec import com.virtuslab.pulumikotlin.codegen.step2intermediate.EnumType import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType -import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.ConvertibleToJava +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.convertibleToJavaClass import com.virtuslab.pulumikotlin.codegen.step2intermediate.RootType import com.virtuslab.pulumikotlin.codegen.step2intermediate.TypeMetadata +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetExtensions.parameterizedBy import com.virtuslab.pulumikotlin.codegen.step3codegen.types.ToJava.toJavaEnumFunction import com.virtuslab.pulumikotlin.codegen.step3codegen.types.ToKotlin.toKotlinEnumFunction @@ -41,7 +43,7 @@ object EnumTypeGenerator { private fun prepareConvertibleToJavaInterface(typeMetadata: TypeMetadata): ParameterizedTypeName { val javaNames = typeMetadata.names(LanguageType.Java) val javaClass = ClassName(javaNames.packageName, javaNames.className) - return ConvertibleToJava(javaClass) + return convertibleToJavaClass().parameterizedBy(javaClass) } private fun buildEnumFileSpec(enumType: EnumType, enumTypeSpec: TypeSpec): FileSpec { diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/TypeGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/TypeGenerator.kt index 55b9f704..a85e5248 100644 --- a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/TypeGenerator.kt +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/TypeGenerator.kt @@ -2,61 +2,50 @@ package com.virtuslab.pulumikotlin.codegen.step3codegen.types import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.KModifier.INTERNAL -import com.squareup.kotlinpoet.KModifier.SUSPEND -import com.squareup.kotlinpoet.KModifier.VARARG import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec -import com.virtuslab.pulumikotlin.codegen.expressions.Assignment import com.virtuslab.pulumikotlin.codegen.expressions.ConstructObjectExpression -import com.virtuslab.pulumikotlin.codegen.expressions.CustomExpression import com.virtuslab.pulumikotlin.codegen.expressions.CustomExpressionBuilder import com.virtuslab.pulumikotlin.codegen.expressions.Return -import com.virtuslab.pulumikotlin.codegen.expressions.add import com.virtuslab.pulumikotlin.codegen.expressions.addCode -import com.virtuslab.pulumikotlin.codegen.expressions.callLet import com.virtuslab.pulumikotlin.codegen.expressions.invoke import com.virtuslab.pulumikotlin.codegen.step2intermediate.ComplexType import com.virtuslab.pulumikotlin.codegen.step2intermediate.Direction.Output -import com.virtuslab.pulumikotlin.codegen.step2intermediate.EitherType import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType -import com.virtuslab.pulumikotlin.codegen.step2intermediate.ListType -import com.virtuslab.pulumikotlin.codegen.step2intermediate.MapType -import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes -import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.ConvertibleToJava -import com.virtuslab.pulumikotlin.codegen.step2intermediate.PrimitiveType -import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedComplexType +import com.virtuslab.pulumikotlin.codegen.step2intermediate.LanguageType.Kotlin +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Java.Pulumi.outputOfMethod +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.applySuspendExtensionMethod +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.applyValueExtensionMethod +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.convertibleToJavaClass +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.pulumiDslMarkerAnnotation +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.toJavaExtensionMethod +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.Pulumi.toKotlinExtensionMethod +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedType import com.virtuslab.pulumikotlin.codegen.step2intermediate.RootType import com.virtuslab.pulumikotlin.codegen.step2intermediate.Subject.Function import com.virtuslab.pulumikotlin.codegen.step2intermediate.TypeMetadata import com.virtuslab.pulumikotlin.codegen.step3codegen.Field -import com.virtuslab.pulumikotlin.codegen.step3codegen.FieldType -import com.virtuslab.pulumikotlin.codegen.step3codegen.KDoc -import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns -import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderLambda -import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.listOfLambdas +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetExtensions.addImports +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetExtensions.addTypes import com.virtuslab.pulumikotlin.codegen.step3codegen.NormalField import com.virtuslab.pulumikotlin.codegen.step3codegen.OutputWrappedField import com.virtuslab.pulumikotlin.codegen.step3codegen.addDeprecationWarningIfAvailable import com.virtuslab.pulumikotlin.codegen.step3codegen.addDocs import com.virtuslab.pulumikotlin.codegen.step3codegen.types.ToJava.toJavaFunction import com.virtuslab.pulumikotlin.codegen.step3codegen.types.ToKotlin.toKotlinFunction +import com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters.AllSetterGenerators +import com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters.Setter import com.virtuslab.pulumikotlin.codegen.utils.letIf import java.util.Random import kotlin.streams.asSequence object TypeGenerator { - data class GenerationOptions( - val shouldGenerateBuilders: Boolean = true, - val implementToJava: Boolean = true, - val implementToKotlin: Boolean = true, - ) fun generateTypes( types: List, @@ -67,393 +56,235 @@ object TypeGenerator { val isFunction = usageKind.subject == Function val isOutput = usageKind.direction == Output val fields = if (isFunction || isOutput) { - type.fields.map { (name, typeAndOptionality) -> - Field( - name, - NormalField(typeAndOptionality.type) { expr -> expr }, - typeAndOptionality.required, - overloads = emptyList(), - typeAndOptionality.kDoc, - ) - } + prepareFieldsForTypesUsedForFunctionsOrAsOutput(type) } else { - type.fields.map { (name, typeAndOptionality) -> - Field( - name, - OutputWrappedField(typeAndOptionality.type), - typeAndOptionality.required, - listOf( - NormalField(typeAndOptionality.type) { argument -> - MoreTypes.Java.Pulumi.Output.of(argument) - }, - ), - typeAndOptionality.kDoc, - ) - } + prepareFields(type) } - generateType(type.metadata, fields, generationOptions) + generateFile(Context(type.metadata, fields, generationOptions)) } return generatedTypes.plus(EnumTypeGenerator.generateEnums(types)) } - private fun generateType( - typeMetadata: TypeMetadata, - fields: List>, - options: GenerationOptions = GenerationOptions(), - ): FileSpec { - val names = typeMetadata.names(LanguageType.Kotlin) - - val fileSpec = FileSpec.builder( - names.packageName, - names.className, - ) - - val argsClassName = ClassName(names.packageName, names.className) - - val dslTag = ClassName("com.pulumi.kotlin", "PulumiTagMarker") - - val classBuilder = TypeSpec.classBuilder(argsClassName) - .letIf(options.implementToJava) { - val convertibleToJava = ConvertibleToJava(typeMetadata.names(LanguageType.Java).kotlinPoetClassName) - it.addSuperinterface(convertibleToJava) - } - .addModifiers(KModifier.DATA) - - val constructor = FunSpec.constructorBuilder() - .addDeprecationWarningIfAvailable(typeMetadata.kDoc) - - fields.forEach { field -> - val isRequired = field.required - val typeName = field.fieldType.toTypeName().copy(nullable = !isRequired) - - classBuilder.addProperty( - PropertySpec.builder(field.name, typeName) - .initializer(field.name) - .addDeprecationWarningIfAvailable(field.kDoc) - .build(), + private fun prepareFields(type: ComplexType) = + type.fields.map { (name, typeAndOptionality) -> + Field( + name, + OutputWrappedField(typeAndOptionality.type), + typeAndOptionality.required, + listOf( + NormalField(typeAndOptionality.type) { argument -> + outputOfMethod().invoke(argument) + }, + ), + typeAndOptionality.kDoc, ) + } - constructor.addParameter( - ParameterSpec.builder(field.name, typeName) - .letIf(!isRequired) { - it.defaultValue("%L", null) - } - .build(), + private fun prepareFieldsForTypesUsedForFunctionsOrAsOutput(type: ComplexType) = + type.fields.map { (name, typeAndOptionality) -> + Field( + name, + NormalField(typeAndOptionality.type) { it }, + typeAndOptionality.required, + overloads = emptyList(), + typeAndOptionality.kDoc, ) } - classBuilder.primaryConstructor(constructor.build()) + private fun generateFile(context: Context): FileSpec { + val names = kotlinNames(context) - classBuilder.letIf(options.implementToJava) { - it.addFunction(toJavaFunction(typeMetadata, fields)) - } - classBuilder.letIf(options.implementToKotlin) { - it.addType( - TypeSpec.companionObjectBuilder() - .addFunction(toKotlinFunction(typeMetadata, fields)) - .build(), + return FileSpec + .builder(names.packageName, names.className) + .addImports( + applySuspendExtensionMethod(), + applyValueExtensionMethod(), + toJavaExtensionMethod(), + toKotlinExtensionMethod(), ) - } + .addTypes( + generateArgsClass(context), + generateArgsBuilderClass(context), + ) + .build() + } + + private fun generateArgsClass(context: Context): TypeSpec { + val (typeMetadata, fields, options) = context + + val (constructor, properties) = generatePropertiesPerFieldWithConstructor(context) val classDocs = typeMetadata.kDoc.description.orEmpty() val propertyDocs = fields.joinToString("\n") { "@property ${it.name} ${it.kDoc.description.orEmpty()}" } - classBuilder.addDocs("$classDocs\n$propertyDocs") - - val argsClass = classBuilder.build() - - val argsBuilderClassName = ClassName(names.packageName, names.builderClassName) - - val arguments = fields.associate { - val requiredPart = if (it.required) "!!" else "" - it.name to CustomExpressionBuilder.start("%N$requiredPart", it.name).build() - } + return TypeSpec.classBuilder(argsClassName(context)) + .addModifiers(KModifier.DATA) + .primaryConstructor(constructor) + .addProperties(properties) + .addDocs("$classDocs\n$propertyDocs") + .letIf(options.implementToJava) { + val innerType = typeMetadata.names(LanguageType.Java).kotlinPoetClassName + val convertibleToJava = convertibleToJavaClass().parameterizedBy(innerType) + it + .addSuperinterface(convertibleToJava) + .addFunction(toJavaFunction(typeMetadata, fields)) + } + .letIf(options.implementToKotlin) { + it.addType( + TypeSpec.companionObjectBuilder() + .addFunction(toKotlinFunction(typeMetadata, fields)) + .build(), + ) + } + .build() + } - val argsBuilderClass = TypeSpec - .classBuilder(argsBuilderClassName) + private fun generateArgsBuilderClass(context: Context): TypeSpec { + return TypeSpec + .classBuilder(argsBuilderClassName(context)) .primaryConstructor( FunSpec .constructorBuilder() .addModifiers(INTERNAL) .build(), ) - .addAnnotation(dslTag) - .addProperties( - fields.map { - PropertySpec - .builder(it.name, it.fieldType.toTypeName().copy(nullable = true)) - .initializer("null") - .mutable(true) - .addModifiers(KModifier.PRIVATE) - .build() - }, - ) - .addFunctions( - fields.flatMap { field -> - generateFunctionsForInput(field) - }, - ) - .addFunction( - FunSpec.builder("build") - .addModifiers(INTERNAL) - .returns(argsClassName) - .addCode(Return(ConstructObjectExpression(argsClassName, arguments))) - .build(), - ) - .addDeprecationWarningIfAvailable(typeMetadata.kDoc) - .addDocs("Builder for [${argsClassName.simpleName}].") + .addAnnotation(pulumiDslMarkerAnnotation()) + .addProperties(generatePropertiesPerField(context)) + .addFunctions(generateMethodsPerField(context) + generateBuildMethod(context)) + .addDeprecationWarningIfAvailable(context.typeMetadata.kDoc) + .addDocs("Builder for [${argsClassName(context).simpleName}].") .build() - - fileSpec - .addImport("com.pulumi.kotlin", "applySuspend") - .addImport("com.pulumi.kotlin", "applyValue") - .addImport("com.pulumi.kotlin", "toJava") - .addImport("com.pulumi.kotlin", "toKotlin") - .let { - if (options.shouldGenerateBuilders) { - it.addType(argsBuilderClass) - } else { - it - } - } - .addType(argsClass) - - return fileSpec.build() } - private fun mappingCodeBlock( - field: NormalField<*>, - required: Boolean, - name: String, - code: String, - vararg args: Any?, - ): CodeBlock { - val expression = CustomExpression("toBeMapped").callLet(!required) { arg -> field.mappingCode(arg) } - return CodeBlock.builder() - .addStatement("val toBeMapped = $code", *args) - .add(Assignment("mapped", expression)) - .addStatement("") - .addStatement("this.%N = mapped", name) - .build() + private fun generatePropertiesPerField(context: Context): List { + return context.fields.map { + PropertySpec + .builder(it.name, it.toNullableTypeName()) + .initializer("null") + .mutable(true) + .addModifiers(KModifier.PRIVATE) + .build() + } } - private fun builderPattern( - name: String, - parameterType: TypeName, - kDoc: KDoc, - codeBlock: KotlinPoetPatterns.BuilderSettingCodeBlock, - parameterModifiers: List = emptyList(), - ): FunSpec { - return FunSpec - .builder(name) - .addModifiers(SUSPEND) - .addParameter( - "argument", - parameterType, - parameterModifiers, - ) - .addCode(codeBlock.toCodeBlock(name)) - .addDocsToBuilderMethod(kDoc, "argument") + private fun generatePropertiesPerFieldWithConstructor(context: Context): Pair> { + val properties = context.fields.map { field -> + PropertySpec + .builder(field.name, field.toTypeName()) + .initializer(field.name) + .addDeprecationWarningIfAvailable(field.kDoc) + .build() + } + + val constructorParameters = context.fields.map { field -> + ParameterSpec.builder(field.name, field.toTypeName()) + .letIf(!field.required) { + it.defaultValue("%L", null) + } + .build() + } + val constructor = FunSpec + .constructorBuilder() + .addDeprecationWarningIfAvailable(context.typeMetadata.kDoc) + .addParameters(constructorParameters) .build() - } - private fun FunSpec.Builder.preventJvmPlatformNameClash(): FunSpec.Builder { - return addAnnotation( - AnnotationSpec.builder(JvmName::class) - .addMember("%S", randomStringWith16Characters()) - .build(), - ) + return Pair(constructor, properties) } - private fun randomStringWith16Characters() = - Random().ints('a'.code, 'z'.code).asSequence().map { it.toChar() }.take(16).joinToString("") - - private fun specialMethodsForComplexType( - name: String, - field: NormalField, - kDoc: KDoc, - ): List { - val builderTypeName = field.type.toBuilderTypeName() - return listOf( - builderPattern( - name, - builderLambda(builderTypeName), - kDoc, - KotlinPoetPatterns.BuilderSettingCodeBlock - .create("%T().applySuspend{ argument() }.build()", builderTypeName) - .withMappingCode(field.mappingCode), - ), - ) - } - - private fun specialMethodsForList( - name: String, - field: NormalField, - kDoc: KDoc, - ): List { - val innerType = field.type.innerType - val builderPattern = when (innerType) { - is ReferencedComplexType -> { - val commonCodeBlock = KotlinPoetPatterns.BuilderSettingCodeBlock - .create( - "argument.toList().map { %T().applySuspend{ it() }.build() }", - innerType.toBuilderTypeName(), - ) - .withMappingCode(field.mappingCode) - - listOf( - builderPattern(name, listOfLambdas(innerType), kDoc, commonCodeBlock), - builderPattern( - name, - builderLambda(innerType), - kDoc, - commonCodeBlock, - parameterModifiers = listOf(VARARG), - ), - ) - } - - else -> emptyList() + /** + * Example output: + * + * ``` + * fun build(): BucketArgs { + * BucketArgs( + * name1 = name1!! + * name2 = name2 + * name3 = name3 + * ) + * } + * ``` + */ + private fun generateBuildMethod(context: Context): FunSpec { + val arguments = context.fields.associate { + val requiredPart = if (it.required) "!!" else "" + it.name to CustomExpressionBuilder.start("%N$requiredPart", it.name).build() } - val justValuesPassedAsVarargArguments = listOf( - FunSpec - .builder(name) - .addModifiers(SUSPEND) - .addParameter("values", innerType.toTypeName(), VARARG) - .addCode(mappingCodeBlock(field, false, name, "values.toList()")) - .addDocsToBuilderMethod(kDoc, "values") - .build(), - ) - - return builderPattern + justValuesPassedAsVarargArguments + return FunSpec.builder("build") + .addModifiers(INTERNAL) + .returns(argsClassName(context)) + .addCode(Return(ConstructObjectExpression(argsClassName(context), fields = arguments))) + .build() } - private fun specialMethodsForMap( - name: String, - field: NormalField, - kDoc: KDoc, - ): List { - val leftInnerType = field.type.firstType - val rightInnerType = field.type.secondType - - val builderPattern = when (rightInnerType) { - is ReferencedComplexType -> { - val commonCodeBlock = KotlinPoetPatterns.BuilderSettingCodeBlock - .create( - "argument.toList().map { (left, right) -> left to %T().applySuspend{ right() }.build() }", - rightInnerType.toBuilderTypeName(), - ) - .withMappingCode(field.mappingCode) + private fun generateMethodsPerField(context: Context): List { + val fields = context.fields - listOf( - builderPattern( - name, - MoreTypes.Kotlin.Pair(leftInnerType.toTypeName(), builderLambda(rightInnerType)), - kDoc, - commonCodeBlock, - parameterModifiers = listOf(VARARG), - ), - ) + val normalFunctions = fields.map { field -> Setter.from(field) } + val overloadFunctions = fields.flatMap { field -> + field.overloads.map { overload -> + Setter.from(field, overload) } - - else -> emptyList() } + val allRecipes = normalFunctions + overloadFunctions - val justValuesPassedAsVarargArguments = listOf( - FunSpec - .builder(name) - .addParameter( - "values", - MoreTypes.Kotlin.Pair(leftInnerType.toTypeName(), rightInnerType.toTypeName()), - VARARG, - ) - .addCode(mappingCodeBlock(field, false, name, "values.toMap()")) - .addDocsToBuilderMethod(kDoc, "values") - .build(), - ) - - return builderPattern + justValuesPassedAsVarargArguments + return allRecipes + .flatMap { generateMethodsForField(it) } + .map { withoutJvmPlatformNameClashRisk(it) } } - private fun generateFunctionsForInput(field: Field<*>): List { - val functionsForDefaultField = generateFunctionsForInput2( - field.name, - field.required, - field.fieldType, - field.kDoc, - ) - - val functionsForOverloads = field.overloads.flatMap { - generateFunctionsForInput2(field.name, field.required, it, field.kDoc) - } - - val allFunctions = functionsForDefaultField + functionsForOverloads + private fun generateMethodsForField(field: Setter): Iterable { + return AllSetterGenerators.generate(field) + } - return allFunctions.map { - it - .toBuilder() - .preventJvmPlatformNameClash() - .build() - } + private fun argsBuilderClassName(context: Context): ClassName { + val names = kotlinNames(context) + return ClassName(names.packageName, names.builderClassName) } - private fun generateFunctionsForInput2( - name: String, - required: Boolean, - fieldType: FieldType<*>, - kDoc: KDoc, - ): List { - val functions = when (fieldType) { - is NormalField -> { - val basicFunction = FunSpec - .builder(name) - .addModifiers(SUSPEND) - .addParameter("value", fieldType.toTypeName().copy(nullable = !required)) - .addCode(mappingCodeBlock(fieldType, required, name, "value")) - .addDocsToBuilderMethod(kDoc, "value") - .build() - - @Suppress("UNCHECKED_CAST") - val otherFunctions = when (fieldType.type) { - is ReferencedComplexType -> specialMethodsForComplexType( - name, - fieldType as NormalField, - kDoc, - ) - - is ListType -> specialMethodsForList(name, fieldType as NormalField, kDoc) - is MapType -> specialMethodsForMap(name, fieldType as NormalField, kDoc) - is PrimitiveType -> listOf() - is EitherType -> listOf() - else -> listOf() - } + private fun argsClassName(context: Context): ClassName { + val names = kotlinNames(context) + return ClassName(names.packageName, names.className) + } - otherFunctions + basicFunction - } + private fun kotlinNames(context: Context) = context.typeMetadata.names(Kotlin) - is OutputWrappedField -> { - listOf( - FunSpec - .builder(name) - .addModifiers(SUSPEND) - .addParameter("value", fieldType.toTypeName().copy(nullable = !required)) - .addCode("this.%N = value", name) - .addDocsToBuilderMethod(kDoc, "value") - .build(), - ) - } - } + private fun withoutJvmPlatformNameClashRisk(funSpec: FunSpec): FunSpec { + val randomJvmNameAnnotation = AnnotationSpec + .builder(JvmName::class) + .addMember("%S", randomStringWith16Characters()) + .build() - return functions + return funSpec + .toBuilder() + .addAnnotation(randomJvmNameAnnotation) + .build() } - private fun FunSpec.Builder.addDocsToBuilderMethod(kDoc: KDoc, paramName: String) = apply { - addDocs("@param $paramName ${kDoc.description.orEmpty()}") - addDeprecationWarningIfAvailable(kDoc) - } + // The number of characters does not matter here, 16 is an arbitrary number. + @Suppress("MagicNumber") + private fun randomStringWith16Characters() = + Random() + .ints('a'.code, 'z'.code) + .asSequence() + .map { it.toChar() } + .take(16) + .joinToString("") + + data class GenerationOptions( + val shouldGenerateBuilders: Boolean = true, + val implementToJava: Boolean = true, + val implementToKotlin: Boolean = true, + ) + + private data class Context( + val typeMetadata: TypeMetadata, + val fields: List>, + val options: GenerationOptions, + ) } diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/BasicSetterGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/BasicSetterGenerator.kt new file mode 100644 index 00000000..10c29a99 --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/BasicSetterGenerator.kt @@ -0,0 +1,28 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters + +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier.SUSPEND +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.addDocsToBuilderMethod +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.mappingCodeBlock +import com.virtuslab.pulumikotlin.codegen.step3codegen.NormalField + +object BasicSetterGenerator : SetterGenerator { + override fun generate(setter: Setter): Iterable { + val normalField = setter.fieldType as? NormalField ?: return emptyList() + + val name = setter.name + val required = setter.fieldRequired + val kDoc = setter.kDoc + + return listOf( + FunSpec + .builder(name) + .addModifiers(SUSPEND) + .addParameter("value", normalField.toTypeName().copy(nullable = !required)) + .addCode(mappingCodeBlock(normalField, required, name, "value")) + .addDocsToBuilderMethod(kDoc, "value") + .build(), + ) + } +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/ComplexTypeSetterGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/ComplexTypeSetterGenerator.kt new file mode 100644 index 00000000..0692ea75 --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/ComplexTypeSetterGenerator.kt @@ -0,0 +1,28 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters + +import com.squareup.kotlinpoet.FunSpec +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedComplexType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.BuilderSettingCodeBlock +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderLambda +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderPattern +import com.virtuslab.pulumikotlin.codegen.step3codegen.NormalField + +object ComplexTypeSetterGenerator : SetterGenerator { + override fun generate(setter: Setter): Iterable { + val typedField = setter.fieldType as? NormalField<*> ?: return emptyList() + val type = typedField.type as? ReferencedComplexType ?: return emptyList() + + val builderTypeName = type.toBuilderTypeName() + + return listOf( + builderPattern( + setter.name, + builderLambda(builderTypeName), + setter.kDoc, + BuilderSettingCodeBlock + .create("%T().applySuspend{ argument() }.build()", builderTypeName) + .withMappingCode(typedField.mappingCode), + ), + ) + } +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/ListTypeSetterGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/ListTypeSetterGenerator.kt new file mode 100644 index 00000000..5035f126 --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/ListTypeSetterGenerator.kt @@ -0,0 +1,62 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters + +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier.SUSPEND +import com.squareup.kotlinpoet.KModifier.VARARG +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ListType +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedComplexType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.BuilderSettingCodeBlock +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.addDocsToBuilderMethod +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderLambda +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderPattern +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.listOfLambdas +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.mappingCodeBlock +import com.virtuslab.pulumikotlin.codegen.step3codegen.NormalField + +object ListTypeSetterGenerator : SetterGenerator { + override fun generate(setter: Setter): Iterable { + val normalField = setter.fieldType as? NormalField<*> ?: return emptyList() + val type = normalField.type as? ListType ?: return emptyList() + + val innerType = type.innerType + + val name = setter.name + val kDoc = setter.kDoc + + val builderPattern = when (innerType) { + is ReferencedComplexType -> { + val commonCodeBlock = BuilderSettingCodeBlock + .create( + "argument.toList().map { %T().applySuspend{ it() }.build() }", + innerType.toBuilderTypeName(), + ) + .withMappingCode(normalField.mappingCode) + + listOf( + builderPattern(name, listOfLambdas(innerType), kDoc, commonCodeBlock), + builderPattern( + name, + builderLambda(innerType), + kDoc, + commonCodeBlock, + parameterModifiers = listOf(VARARG), + ), + ) + } + + else -> emptyList() + } + + val justValuesPassedAsVarargArguments = listOf( + FunSpec + .builder(name) + .addModifiers(SUSPEND) + .addParameter("values", innerType.toTypeName(), VARARG) + .addCode(mappingCodeBlock(normalField, required = false, name, "values.toList()")) + .addDocsToBuilderMethod(kDoc, "values") + .build(), + ) + + return builderPattern + justValuesPassedAsVarargArguments + } +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/MapTypeSetterGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/MapTypeSetterGenerator.kt new file mode 100644 index 00000000..ece0a077 --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/MapTypeSetterGenerator.kt @@ -0,0 +1,66 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters + +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier.VARARG +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MapType +import com.virtuslab.pulumikotlin.codegen.step2intermediate.MoreTypes.Kotlin.pairClass +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedComplexType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetExtensions.parameterizedBy +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.BuilderSettingCodeBlock +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.addDocsToBuilderMethod +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderLambda +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.builderPattern +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.mappingCodeBlock +import com.virtuslab.pulumikotlin.codegen.step3codegen.NormalField + +object MapTypeSetterGenerator : SetterGenerator { + override fun generate(setter: Setter): Iterable { + val normalField = setter.fieldType as? NormalField<*> ?: return emptyList() + val type = normalField.type as? MapType ?: return emptyList() + + val leftInnerType = type.firstType + val rightInnerType = type.secondType + + val name = setter.name + val kDoc = setter.kDoc + + val builderPattern = when (rightInnerType) { + is ReferencedComplexType -> { + val commonCodeBlock = BuilderSettingCodeBlock + .create( + "argument.toList().map { (left, right) -> left to %T().applySuspend{ right() }.build() }", + rightInnerType.toBuilderTypeName(), + ) + .withMappingCode(normalField.mappingCode) + + listOf( + builderPattern( + name, + pairClass().parameterizedBy(leftInnerType.toTypeName(), builderLambda(rightInnerType)), + kDoc, + commonCodeBlock, + parameterModifiers = listOf(VARARG), + ), + ) + } + + else -> emptyList() + } + + val justValuesPassedAsVarargArguments = listOf( + FunSpec + .builder(name) + .addParameter( + "values", + pairClass().parameterizedBy(leftInnerType, rightInnerType), + VARARG, + ) + .addCode(mappingCodeBlock(normalField, required = false, name, "values.toMap()")) + .addDocsToBuilderMethod(kDoc, "values") + .build(), + ) + + return builderPattern + justValuesPassedAsVarargArguments + } +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/OutputWrappedSetterGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/OutputWrappedSetterGenerator.kt new file mode 100644 index 00000000..fa6f69ce --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/OutputWrappedSetterGenerator.kt @@ -0,0 +1,23 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters + +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier.SUSPEND +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KotlinPoetPatterns.addDocsToBuilderMethod +import com.virtuslab.pulumikotlin.codegen.step3codegen.OutputWrappedField + +object OutputWrappedSetterGenerator : SetterGenerator { + override fun generate(setter: Setter): Iterable { + val outputWrappedField = setter.fieldType as? OutputWrappedField ?: return emptyList() + + return listOf( + FunSpec + .builder(setter.name) + .addModifiers(SUSPEND) + .addParameter("value", outputWrappedField.toTypeName()) + .addCode("this.%N = value", setter.name) + .addDocsToBuilderMethod(setter.kDoc, "value") + .build(), + ) + } +} diff --git a/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/SetterGenerator.kt b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/SetterGenerator.kt new file mode 100644 index 00000000..d2b06904 --- /dev/null +++ b/src/main/kotlin/com/virtuslab/pulumikotlin/codegen/step3codegen/types/setters/SetterGenerator.kt @@ -0,0 +1,45 @@ +package com.virtuslab.pulumikotlin.codegen.step3codegen.types.setters + +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.TypeName +import com.virtuslab.pulumikotlin.codegen.step2intermediate.ReferencedType +import com.virtuslab.pulumikotlin.codegen.step3codegen.Field +import com.virtuslab.pulumikotlin.codegen.step3codegen.FieldType +import com.virtuslab.pulumikotlin.codegen.step3codegen.KDoc + +interface SetterGenerator { + fun generate(setter: Setter): Iterable +} + +data class Setter( + val name: String, + val fieldType: FieldType, + val fieldRequired: Boolean, + val kDoc: KDoc, +) { + + fun toTypeName(): TypeName = + fieldType.toTypeName().copy(nullable = !fieldRequired) + + companion object { + fun from(originalField: Field, overload: FieldType) = + Setter(originalField.name, overload, originalField.required, originalField.kDoc) + + fun from(field: Field) = + Setter(field.name, field.fieldType, field.required, field.kDoc) + } +} + +object AllSetterGenerators : SetterGenerator { + private val generators = listOf( + BasicSetterGenerator, + OutputWrappedSetterGenerator, + ComplexTypeSetterGenerator, + ListTypeSetterGenerator, + MapTypeSetterGenerator, + ) + + override fun generate(setter: Setter): Iterable { + return generators.flatMap { generator -> generator.generate(setter) } + } +}