diff --git a/sourcegen-generator-java/src/main/java/io/micronaut/sourcegen/JavaPoetSourceGenerator.java b/sourcegen-generator-java/src/main/java/io/micronaut/sourcegen/JavaPoetSourceGenerator.java index a8c3d153..32a7df93 100644 --- a/sourcegen-generator-java/src/main/java/io/micronaut/sourcegen/JavaPoetSourceGenerator.java +++ b/sourcegen-generator-java/src/main/java/io/micronaut/sourcegen/JavaPoetSourceGenerator.java @@ -163,7 +163,6 @@ private void writeEnum(Writer writer, EnumDef enumDef) throws IOException { private void writeClass(Writer writer, ClassDef classDef) throws IOException { TypeSpec.Builder classBuilder = TypeSpec.classBuilder(classDef.getSimpleName()); - TypeDef thisType = classDef.asTypeDef(); classBuilder.addModifiers(classDef.getModifiersArray()); classDef.getTypeVariables().stream().map(t -> asTypeVariable(t, classDef)).forEach(classBuilder::addTypeVariable); classDef.getSuperinterfaces().stream().map(typeDef -> asType(typeDef, classDef)).forEach(classBuilder::addSuperinterface); @@ -537,6 +536,12 @@ private CodeBlock renderExpression(@Nullable ObjectDef objectDef, MethodDef meth if (expressionDef instanceof ExpressionDef.Convert convertExpressionDef) { return renderExpression(objectDef, methodDef, convertExpressionDef.expressionDef()); } + if (expressionDef instanceof ExpressionDef.Cast castExpressionDef) { + return CodeBlock.concat( + CodeBlock.of("($T) ", asType(castExpressionDef.type(), objectDef)), + renderExpression(objectDef, methodDef, castExpressionDef.expressionDef()) + ); + } if (expressionDef instanceof ExpressionDef.Constant constant) { return renderConstantExpression(constant); } diff --git a/sourcegen-generator-java/src/test/java/io/micronaut/sourcegen/javapoet/write/ExpressionWriteTest.java b/sourcegen-generator-java/src/test/java/io/micronaut/sourcegen/javapoet/write/ExpressionWriteTest.java index aec7e272..36c4c338 100644 --- a/sourcegen-generator-java/src/test/java/io/micronaut/sourcegen/javapoet/write/ExpressionWriteTest.java +++ b/sourcegen-generator-java/src/test/java/io/micronaut/sourcegen/javapoet/write/ExpressionWriteTest.java @@ -3,6 +3,7 @@ import io.micronaut.inject.ast.ClassElement; import io.micronaut.sourcegen.model.ClassTypeDef; import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.ExpressionDef.Cast; import io.micronaut.sourcegen.model.TypeDef; import io.micronaut.sourcegen.model.VariableDef; import org.junit.Test; @@ -78,4 +79,25 @@ public void returnConstantIntArray() throws IOException { assertEquals("new int[] {1, 2}", result); } + + @Test + public void returnCastedValue() throws IOException { + ExpressionDef castedExpression = ExpressionDef + .constant(ClassElement.of(Double.TYPE), TypeDef.primitive("double"), 10.5) + .cast(TypeDef.primitive("float")); + String result = writeMethodWithExpression(castedExpression); + + assertEquals("(float) 10.5d", result); + } + + @Test + public void returnCastedValue2() throws IOException { + ExpressionDef castedExpression = new Cast( + TypeDef.of(Object.class), + ExpressionDef.constant(ClassElement.of(String.class), TypeDef.of(String.class), "hello") + ); + String result = writeMethodWithExpression(castedExpression); + + assertEquals("(Object) \"hello\"", result); + } } diff --git a/sourcegen-generator-kotlin/src/main/kotlin/io/micronaut/sourcegen/KotlinPoetSourceGenerator.kt b/sourcegen-generator-kotlin/src/main/kotlin/io/micronaut/sourcegen/KotlinPoetSourceGenerator.kt index d2aaf7dc..931c1d72 100644 --- a/sourcegen-generator-kotlin/src/main/kotlin/io/micronaut/sourcegen/KotlinPoetSourceGenerator.kt +++ b/sourcegen-generator-kotlin/src/main/kotlin/io/micronaut/sourcegen/KotlinPoetSourceGenerator.kt @@ -101,9 +101,9 @@ class KotlinPoetSourceGenerator : SourceGenerator { private fun writeInterface(writer: Writer, interfaceDef: InterfaceDef) { val interfaceBuilder = TypeSpec.interfaceBuilder(interfaceDef.simpleName) interfaceBuilder.addModifiers(asKModifiers(interfaceDef.modifiers)) - interfaceDef.typeVariables.stream().map { tv: TypeDef.TypeVariable -> this.asTypeVariable(tv, interfaceDef) } + interfaceDef.typeVariables.stream().map { tv: TypeDef.TypeVariable -> asTypeVariable(tv, interfaceDef) } .forEach { typeVariable: TypeVariableName -> interfaceBuilder.addTypeVariable(typeVariable) } - interfaceDef.superinterfaces.stream().map { typeDef: TypeDef -> this.asType(typeDef, interfaceDef) } + interfaceDef.superinterfaces.stream().map { typeDef: TypeDef -> asType(typeDef, interfaceDef) } .forEach { it: TypeName -> interfaceBuilder.addSuperinterface( it @@ -168,9 +168,9 @@ class KotlinPoetSourceGenerator : SourceGenerator { private fun writeClass(writer: Writer, classDef: ClassDef) { val classBuilder = TypeSpec.classBuilder(classDef.simpleName) classBuilder.addModifiers(asKModifiers(classDef.modifiers)) - classDef.typeVariables.stream().map { tv: TypeDef.TypeVariable -> this.asTypeVariable(tv, classDef) } + classDef.typeVariables.stream().map { tv: TypeDef.TypeVariable -> asTypeVariable(tv, classDef) } .forEach { typeVariable: TypeVariableName -> classBuilder.addTypeVariable(typeVariable) } - classDef.superinterfaces.stream().map { typeDef: TypeDef -> this.asType(typeDef, classDef) } + classDef.superinterfaces.stream().map { typeDef: TypeDef -> asType(typeDef, classDef) } .forEach { it: TypeName -> classBuilder.addSuperinterface( it @@ -268,9 +268,9 @@ class KotlinPoetSourceGenerator : SourceGenerator { val classBuilder = TypeSpec.classBuilder(recordDef.simpleName) classBuilder.addModifiers(KModifier.DATA) classBuilder.addModifiers(asKModifiers(recordDef.modifiers)) - recordDef.typeVariables.stream().map { tv: TypeDef.TypeVariable -> this.asTypeVariable(tv, recordDef) } + recordDef.typeVariables.stream().map { tv: TypeDef.TypeVariable -> asTypeVariable(tv, recordDef) } .forEach { typeVariable: TypeVariableName -> classBuilder.addTypeVariable(typeVariable) } - recordDef.superinterfaces.stream().map { typeDef: TypeDef -> this.asType(typeDef, recordDef) } + recordDef.superinterfaces.stream().map { typeDef: TypeDef -> asType(typeDef, recordDef) } .forEach { it: TypeName -> classBuilder.addSuperinterface( it, @@ -338,7 +338,7 @@ class KotlinPoetSourceGenerator : SourceGenerator { private fun writeEnumDef(writer: Writer, enumDef: EnumDef) { val enumBuilder = TypeSpec.enumBuilder(enumDef.simpleName) enumBuilder.addModifiers(asKModifiers(enumDef.modifiers)) - enumDef.superinterfaces.stream().map { typeDef: TypeDef -> this.asType(typeDef, enumDef) } + enumDef.superinterfaces.stream().map { typeDef: TypeDef -> asType(typeDef, enumDef) } .forEach { it: TypeName -> enumBuilder.addSuperinterface(it) } enumDef.javadoc.forEach(Consumer { format: String -> enumBuilder.addKdoc(format) }) enumDef.annotations.stream().map { annotationDef: AnnotationDef -> asAnnotationSpec(annotationDef) } @@ -479,77 +479,6 @@ class KotlinPoetSourceGenerator : SourceGenerator { return funBuilder.build() } - @OptIn(KotlinPoetJavaPoetPreview::class) - private fun asType(typeDef: TypeDef, objectDef: ObjectDef?): TypeName { - val result: TypeName = if (typeDef == TypeDef.THIS) { - if (objectDef == null) { - throw java.lang.IllegalStateException("This type is used outside of the instance scope!") - } - asType(objectDef.asTypeDef(), null) - } else if (typeDef is TypeDef.Array) { - asArray(typeDef, objectDef) - } else if (typeDef is ClassTypeDef.Parameterized) { - asClassName(typeDef.rawType).parameterizedBy( - typeDef.typeArguments.map { v: TypeDef -> this.asType(v, objectDef) } - ) - } else if (typeDef is TypeDef.Primitive) { - when (typeDef.name) { - "void" -> UNIT - "byte" -> com.squareup.javapoet.TypeName.BYTE.toKTypeName() - "short" -> com.squareup.javapoet.TypeName.SHORT.toKTypeName() - "char" -> com.squareup.javapoet.TypeName.CHAR.toKTypeName() - "int" -> com.squareup.javapoet.TypeName.INT.toKTypeName() - "long" -> com.squareup.javapoet.TypeName.LONG.toKTypeName() - "float" -> com.squareup.javapoet.TypeName.FLOAT.toKTypeName() - "double" -> com.squareup.javapoet.TypeName.DOUBLE.toKTypeName() - "boolean" -> com.squareup.javapoet.TypeName.BOOLEAN.toKTypeName() - else -> throw IllegalStateException("Unrecognized primitive name: " + typeDef.name) - } - } else if (typeDef is ClassTypeDef) { - asClassName(typeDef) - } else if (typeDef is TypeDef.Wildcard) { - if (typeDef.lowerBounds.isNotEmpty()) { - WildcardTypeName.consumerOf( - asType( - typeDef.lowerBounds[0], - objectDef - ) - ) - } else { - WildcardTypeName.producerOf( - asType( - typeDef.upperBounds[0], - objectDef - ) - ) - } - } else if (typeDef is TypeDef.TypeVariable) { - return asTypeVariable(typeDef, objectDef) - } else { - throw IllegalStateException("Unrecognized type definition $typeDef") - } - if (typeDef.isNullable) { - return asNullable(result) - } - return result - } - - private fun asArray(classType: TypeDef.Array, objectDef: ObjectDef?): TypeName { - var newDef = ClassTypeDef.Parameterized( - ClassTypeDef.of("kotlin.Array"), listOf(classType.componentType)) - for (i in 2.. classType.dimensions) { - newDef = ClassTypeDef.Parameterized(ClassTypeDef.of("kotlin.Array"), listOf(newDef)) - } - return this.asType(newDef, objectDef) - } - - private fun asTypeVariable(tv: TypeDef.TypeVariable, objectDef: ObjectDef?): TypeVariableName { - return TypeVariableName( - tv.name, - tv.bounds.stream().map { v: TypeDef -> this.asType(v, objectDef) }.toList() - ) - } - @JvmRecord private data class ExpResult(val rendered: CodeBlock, val type: TypeDef) companion object { @@ -596,6 +525,77 @@ class KotlinPoetSourceGenerator : SourceGenerator { }.toList() } + @OptIn(KotlinPoetJavaPoetPreview::class) + private fun asType(typeDef: TypeDef, objectDef: ObjectDef?): TypeName { + val result: TypeName = if (typeDef == TypeDef.THIS) { + if (objectDef == null) { + throw java.lang.IllegalStateException("This type is used outside of the instance scope!") + } + asType(objectDef.asTypeDef(), null) + } else if (typeDef is TypeDef.Array) { + asArray(typeDef, objectDef) + } else if (typeDef is ClassTypeDef.Parameterized) { + asClassName(typeDef.rawType).parameterizedBy( + typeDef.typeArguments.map { v: TypeDef -> this.asType(v, objectDef) } + ) + } else if (typeDef is TypeDef.Primitive) { + when (typeDef.name) { + "void" -> UNIT + "byte" -> com.squareup.javapoet.TypeName.BYTE.toKTypeName() + "short" -> com.squareup.javapoet.TypeName.SHORT.toKTypeName() + "char" -> com.squareup.javapoet.TypeName.CHAR.toKTypeName() + "int" -> com.squareup.javapoet.TypeName.INT.toKTypeName() + "long" -> com.squareup.javapoet.TypeName.LONG.toKTypeName() + "float" -> com.squareup.javapoet.TypeName.FLOAT.toKTypeName() + "double" -> com.squareup.javapoet.TypeName.DOUBLE.toKTypeName() + "boolean" -> com.squareup.javapoet.TypeName.BOOLEAN.toKTypeName() + else -> throw IllegalStateException("Unrecognized primitive name: " + typeDef.name) + } + } else if (typeDef is ClassTypeDef) { + asClassName(typeDef) + } else if (typeDef is TypeDef.Wildcard) { + if (typeDef.lowerBounds.isNotEmpty()) { + WildcardTypeName.consumerOf( + asType( + typeDef.lowerBounds[0], + objectDef + ) + ) + } else { + WildcardTypeName.producerOf( + asType( + typeDef.upperBounds[0], + objectDef + ) + ) + } + } else if (typeDef is TypeDef.TypeVariable) { + return asTypeVariable(typeDef, objectDef) + } else { + throw IllegalStateException("Unrecognized type definition $typeDef") + } + if (typeDef.isNullable) { + return asNullable(result) + } + return result + } + + private fun asTypeVariable(tv: TypeDef.TypeVariable, objectDef: ObjectDef?): TypeVariableName { + return TypeVariableName( + tv.name, + tv.bounds.stream().map { v: TypeDef -> asType(v, objectDef) }.toList() + ) + } + + private fun asArray(classType: TypeDef.Array, objectDef: ObjectDef?): TypeName { + var newDef = ClassTypeDef.Parameterized( + ClassTypeDef.of("kotlin.Array"), listOf(classType.componentType)) + for (i in 2.. classType.dimensions) { + newDef = ClassTypeDef.Parameterized(ClassTypeDef.of("kotlin.Array"), listOf(newDef)) + } + return asType(newDef, objectDef) + } + private fun renderStatement( objectDef: ObjectDef?, methodDef: MethodDef, @@ -679,6 +679,15 @@ class KotlinPoetSourceGenerator : SourceGenerator { resultType ) } + if (expressionDef is ExpressionDef.Cast) { + val codeBuilder = CodeBlock.builder() + codeBuilder.add(renderExpression(objectDef, methodDef, expressionDef.expressionDef).rendered) + codeBuilder.add(" as %T", KotlinPoetSourceGenerator.asType(expressionDef.type, objectDef)) + return ExpResult( + codeBuilder.build(), + expressionDef.type + ) + } if (expressionDef is VariableDef) { return renderVariable(objectDef, methodDef, expressionDef) } diff --git a/sourcegen-model/src/main/java/io/micronaut/sourcegen/model/ExpressionDef.java b/sourcegen-model/src/main/java/io/micronaut/sourcegen/model/ExpressionDef.java index 77da050b..0c06e839 100644 --- a/sourcegen-model/src/main/java/io/micronaut/sourcegen/model/ExpressionDef.java +++ b/sourcegen-model/src/main/java/io/micronaut/sourcegen/model/ExpressionDef.java @@ -36,7 +36,10 @@ */ @Experimental public sealed interface ExpressionDef - permits ExpressionDef.CallInstanceMethod, ExpressionDef.CallStaticMethod, ExpressionDef.Condition, ExpressionDef.Constant, ExpressionDef.Convert, ExpressionDef.IfElse, ExpressionDef.NewArrayInitialized, ExpressionDef.NewArrayOfSize, ExpressionDef.NewInstance, ExpressionDef.Switch, ExpressionDef.SwitchYieldCase, VariableDef { + permits ExpressionDef.CallInstanceMethod, ExpressionDef.CallStaticMethod, + ExpressionDef.Condition, ExpressionDef.Constant, ExpressionDef.Convert, ExpressionDef.Cast, + ExpressionDef.IfElse, ExpressionDef.NewArrayInitialized, ExpressionDef.NewArrayOfSize, + ExpressionDef.NewInstance, ExpressionDef.Switch, ExpressionDef.SwitchYieldCase, VariableDef { /** * The condition of this variable. @@ -102,6 +105,17 @@ default StatementDef returning() { return new StatementDef.Return(this); } + /** + * Cast expression to a different type. + * + * @param type The type to cast to + * @return The cast expression + */ + @NonNull + default ExpressionDef.Cast cast(TypeDef type) { + return new Cast(type, this); + } + /** * The conditional statement based on this expression. * @@ -461,6 +475,19 @@ record Convert(TypeDef type, ExpressionDef expressionDef) implements ExpressionDef { } + /** + * The cast expression. No checks are performed on the types and casting expression is + * always generated. + * + * @param type The type to cast to + * @param expressionDef The expression to cast + * @author Andriy Dmytruk + * @since 1.3 + */ + @Experimental + record Cast(TypeDef type, ExpressionDef expressionDef) implements ExpressionDef { + } + /** * The constant expression. * diff --git a/test-suite-custom-generators/src/main/java/io/micronaut/sourcegen/custom/visitor/GenerateMyBean3Visitor.java b/test-suite-custom-generators/src/main/java/io/micronaut/sourcegen/custom/visitor/GenerateMyBean3Visitor.java index d04ffa60..a899ba62 100644 --- a/test-suite-custom-generators/src/main/java/io/micronaut/sourcegen/custom/visitor/GenerateMyBean3Visitor.java +++ b/test-suite-custom-generators/src/main/java/io/micronaut/sourcegen/custom/visitor/GenerateMyBean3Visitor.java @@ -22,24 +22,20 @@ import io.micronaut.inject.processing.ProcessingException; import io.micronaut.inject.visitor.TypeElementVisitor; import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.sourcegen.custom.example.GenerateMyBean1; import io.micronaut.sourcegen.custom.example.GenerateMyBean3; import io.micronaut.sourcegen.generator.SourceGenerator; import io.micronaut.sourcegen.generator.SourceGenerators; -import io.micronaut.sourcegen.model.AnnotationDef; import io.micronaut.sourcegen.model.ClassDef; import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; import io.micronaut.sourcegen.model.FieldDef; import io.micronaut.sourcegen.model.MethodDef; import io.micronaut.sourcegen.model.ParameterDef; -import io.micronaut.sourcegen.model.PropertyDef; import io.micronaut.sourcegen.model.StatementDef; import io.micronaut.sourcegen.model.TypeDef; import io.micronaut.sourcegen.model.VariableDef; import javax.lang.model.element.Modifier; -import java.io.IOException; -import java.util.List; @Internal public final class GenerateMyBean3Visitor implements TypeElementVisitor { @@ -72,6 +68,20 @@ public void visitClass(ClassElement element, VisitorContext context) { )) .build() ) + .addMethod(MethodDef.builder("castPrimitive") + .addParameter(ParameterDef.of("value", TypeDef.primitive(Double.TYPE))) + .returns(TypeDef.primitive(Float.TYPE)) + .addStatement(new StatementDef.Return( + new ExpressionDef.Cast( + TypeDef.primitive(Float.TYPE), + new VariableDef.MethodParameter( + "value", + TypeDef.primitive(Double.TYPE) + ) + ) + )) + .build() + ) .build(); SourceGenerator sourceGenerator = SourceGenerators.findByLanguage(context.getLanguage()).orElse(null);