Fix code generation for null indexed argument value

This commit fixes code generation when an indexed constructor argument
value is null as the method is overloaded and need the value to be
cast to `Object`.

Closes gh-31508
This commit is contained in:
Stéphane Nicoll 2023-10-28 10:52:15 +02:00
parent d3fba6d49b
commit 1762bf4a60
2 changed files with 32 additions and 2 deletions

View File

@ -174,7 +174,9 @@ class BeanDefinitionPropertiesCodeGenerator {
Map<Integer, ValueHolder> indexedValues = constructorValues.getIndexedArgumentValues();
if (!indexedValues.isEmpty()) {
indexedValues.forEach((index, valueHolder) -> {
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
Object value = valueHolder.getValue();
CodeBlock valueCode = castIfNecessary(value == null, Object.class,
generateValue(valueHolder.getName(), value));
code.addStatement(
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
BEAN_DEFINITION_VARIABLE, index, valueCode);
@ -346,6 +348,20 @@ class BeanDefinitionPropertiesCodeGenerator {
}
}
/**
* Cast the specified {@code valueCode} to the specified {@code castType} if
* the {@code castNecessary} is {@code true}. Otherwise return the valueCode
* as is.
* @param castNecessary whether a cast is necessary
* @param castType the type to cast to
* @param valueCode the code for the value
* @return the existing value or a form of {@code (CastType) valueCode} if a
* cast is necessary
*/
private CodeBlock castIfNecessary(boolean castNecessary, Class<?> castType, CodeBlock valueCode) {
return (castNecessary ? CodeBlock.of("($T) $L", castType, valueCode) : valueCode);
}
static class PropertyNamesStack {
private static final ThreadLocal<ArrayDeque<String>> threadLocal = ThreadLocal.withInitial(ArrayDeque::new);

View File

@ -235,6 +235,18 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
}
@Test
void constructorArgumentValuesWhenIndexedNullValue() {
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
compile((actual, compiled) -> {
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
Map<Integer, ValueHolder> values = argumentValues.getIndexedArgumentValues();
assertThat(values.get(0)).satisfies(assertValueHolder(null, null, null));
assertThat(values).hasSize(1);
assertThat(argumentValues.getGenericArgumentValues()).isEmpty();
});
}
@Test
void constructorArgumentValuesWhenGenericValuesWithName() {
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(String.class);
@ -255,7 +267,9 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
}
private Consumer<ValueHolder> assertValueHolder(Object value, @Nullable Class<?> type, @Nullable String name) {
private Consumer<ValueHolder> assertValueHolder(
@Nullable Object value, @Nullable Class<?> type, @Nullable String name) {
return valueHolder -> {
assertThat(valueHolder.getValue()).isEqualTo(value);
assertThat(valueHolder.getType()).isEqualTo((type != null ? type.getName() : null));