diff --git a/src/main/java/spoon/reflect/factory/CodeFactory.java b/src/main/java/spoon/reflect/factory/CodeFactory.java
index ac33e083fc2..62fe24f068c 100644
--- a/src/main/java/spoon/reflect/factory/CodeFactory.java
+++ b/src/main/java/spoon/reflect/factory/CodeFactory.java
@@ -27,6 +27,7 @@
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtNewArray;
import spoon.reflect.code.CtNewClass;
+import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.code.CtTextBlock;
@@ -647,6 +648,19 @@ public CtAnnotation createAnnotation(CtTypeReference the type of the expression
+ * @return a return.
+ */
+ public CtReturn createCtReturn(CtExpression expression) {
+ final CtReturn result = factory.Core().createReturn();
+ result.setReturnedExpression(expression);
+ return result;
+ }
+
/**
* Gets a list of references from a list of elements.
*
diff --git a/src/main/java/spoon/reflect/factory/Factory.java b/src/main/java/spoon/reflect/factory/Factory.java
index 7825d64c6b9..c428ad9fe9c 100644
--- a/src/main/java/spoon/reflect/factory/Factory.java
+++ b/src/main/java/spoon/reflect/factory/Factory.java
@@ -321,6 +321,14 @@ public interface Factory {
*/
CtLocalVariableReference createLocalVariableReference(CtLocalVariable localVariable);
+ /**
+ * @param expression the expression to return
+ * @param the type of the expression
+ * @return a return statement
+ * @see CodeFactory#createCtReturn(CtExpression)
+ */
+ CtReturn createCtReturn(CtExpression expression);
+
/**
* @see CodeFactory#createLocalVariableReference(CtTypeReference,String)
*/
diff --git a/src/main/java/spoon/reflect/factory/FactoryImpl.java b/src/main/java/spoon/reflect/factory/FactoryImpl.java
index 615c157cb1f..d8b5cbb1141 100644
--- a/src/main/java/spoon/reflect/factory/FactoryImpl.java
+++ b/src/main/java/spoon/reflect/factory/FactoryImpl.java
@@ -509,6 +509,11 @@ public CtLocalVariable createLocalVariable(CtTypeReference type, Strin
return Code().createLocalVariable(type, name, defaultExpression);
}
+ @Override
+ public CtReturn createCtReturn(CtExpression expression) {
+ return Code().createCtReturn(expression);
+ }
+
@SuppressWarnings(value = "unchecked")
@Override
public CtNewArray createLiteralArray(T[] value) {
diff --git a/src/main/java/spoon/support/compiler/jdt/ParentExiter.java b/src/main/java/spoon/support/compiler/jdt/ParentExiter.java
index 35021a06db5..00e069c3563 100644
--- a/src/main/java/spoon/support/compiler/jdt/ParentExiter.java
+++ b/src/main/java/spoon/support/compiler/jdt/ParentExiter.java
@@ -251,8 +251,13 @@ public void scanCtType(CtType type) {
return;
} else if (child instanceof CtEnumValue && type instanceof CtEnum) {
((CtEnum) type).addEnumValue((CtEnumValue) child);
- } else if (child instanceof CtField) {
- type.addField((CtField>) child);
+ } else if (child instanceof CtField> field) {
+ // We add the field in addRecordComponent. Afterward, however, JDT visits the Field itself -> Duplication.
+ // To combat this, we delete the existing field and trust JDTs version.
+ if (type instanceof CtRecord record) {
+ record.removeField(record.getField(field.getSimpleName()));
+ }
+ type.addField(field);
return;
} else if (child instanceof CtConstructor) {
return;
diff --git a/src/main/java/spoon/support/reflect/declaration/CtRecordComponentImpl.java b/src/main/java/spoon/support/reflect/declaration/CtRecordComponentImpl.java
index 9fb7553abbd..7a478c2d073 100644
--- a/src/main/java/spoon/support/reflect/declaration/CtRecordComponentImpl.java
+++ b/src/main/java/spoon/support/reflect/declaration/CtRecordComponentImpl.java
@@ -10,17 +10,24 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
+
+import org.jspecify.annotations.Nullable;
import spoon.JLSViolation;
import spoon.reflect.annotations.MetamodelPropertyField;
+import spoon.reflect.code.CtFieldAccess;
+import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtNamedElement;
+import spoon.reflect.declaration.CtRecord;
import spoon.reflect.declaration.CtRecordComponent;
import spoon.reflect.declaration.CtShadowable;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.path.CtRole;
+import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeReference;
+import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.CtVisitor;
import spoon.support.reflect.CtExtendedModifier;
@@ -36,24 +43,47 @@ public class CtRecordComponentImpl extends CtNamedElementImpl implements CtRecor
public CtMethod> toMethod() {
CtMethod> method = this.getFactory().createMethod();
method.setSimpleName(getSimpleName());
- method.setType((CtTypeReference) getType());
+ method.setType(getClonedType());
method.setExtendedModifiers(Collections.singleton(new CtExtendedModifier(ModifierKind.PUBLIC, true)));
- method.setImplicit(true);
- method.setBody(getFactory().createCodeSnippetStatement("return " + getSimpleName()));
- return method;
+
+ CtFieldAccess> ctVariableAccess = (CtFieldAccess>) getFactory().Code()
+ .createVariableRead(getRecordFieldReference(), false);
+
+ method.setBody(getFactory().Code().createCtReturn(ctVariableAccess));
+
+ return makeTreeImplicit(method);
+ }
+
+ private CtFieldReference> getRecordFieldReference() {
+ CtRecord parent = isParentInitialized() ? (CtRecord) getParent() : null;
+
+ // Reference the field we think should exist. It might be added to the record later on, so do not directly
+ // query for it.
+ CtFieldReference> reference = getFactory().createFieldReference()
+ .setFinal(true)
+ .setStatic(false)
+ .setType(getClonedType())
+ .setSimpleName(getSimpleName());
+
+ // We have a parent record, make the field refer to it. Ideally we could do this all the time, but if we
+ // do not yet have a parent that doesn't work.
+ if (parent != null) {
+ reference.setDeclaringType(parent.getReference());
+ }
+
+ return reference;
}
@Override
public CtField> toField() {
CtField> field = this.getFactory().createField();
field.setSimpleName(getSimpleName());
- field.setType((CtTypeReference) getType());
+ field.setType(getClonedType());
Set modifiers = new HashSet<>();
modifiers.add(new CtExtendedModifier(ModifierKind.PRIVATE, true));
modifiers.add(new CtExtendedModifier(ModifierKind.FINAL, true));
field.setExtendedModifiers(modifiers);
- field.setImplicit(true);
- return field;
+ return makeTreeImplicit(field);
}
@Override
@@ -61,6 +91,10 @@ public boolean isImplicit() {
return true;
}
+ private @Nullable CtTypeReference> getClonedType() {
+ return getType() != null ? getType().clone() : null;
+ }
+
@Override
public CtTypeReference