Skip to content

Commit

Permalink
Add support for casting expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
andriy-dmytruk committed Sep 22, 2024
1 parent 6a55708 commit 03f9511
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,4 +79,26 @@ public void returnConstantIntArray() throws IOException {

assertEquals("new int[] {1, 2}", result);
}

@Test
public void returnCastedValue() throws IOException {
ExpressionDef castedExpression = new Cast(
TypeDef.primitive("float"),
ExpressionDef.constant(ClassElement.of(Double.TYPE), TypeDef.primitive("double"), 10.5)
);
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) }
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -461,6 +464,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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<GenerateMyBean3, Object> {
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 03f9511

Please sign in to comment.