[Bug]: Inconsistent parent for `CtTypeReferenceImpl` in `CtRecordComponentImpl` which points to a `CtMethodImpl`
- Dominant language
- Java
- Stars
- 2k
- Forks
- 392
- Avg merge
- 11h 24m
- Merged PRs (30d)
- 36
Description
### Describe the bug
When visiting all elements (keeping track of the parents) in the model, one encounters an element (I guess it is the Type of the Record Field) where its parent doesn't match the one that was kept track of.
It scans the elements like this:
```
1. CtUnnamedModule (?)
2. CtRootPackage (?)
3. CtRecordImpl (D:/projects/playground/Main:1)
4. CtRecordComponentImpl (?)
5. CtTypeReferenceImpl (D:/projects/playground/Main:1)
```
and for some reason the `CtTypeReferenceImpl#getParent` does not point to `CtRecordComponentImpl`, but to a `CtMethodImpl`.

For some reason the `CtMethodImpl` has the same reference to the type as the `CtRecordComponentImpl`, but `CtFieldImpl` has a distinct one:

This is likely the result of a missing clone somewhere in the code?
My guess is that it is this line in the `CtRecordComponentImpl`:

### Source code you are trying to analyze/transform
```Java
public record Main(String string) {}
```
### Source code for your Spoon processing
```Java
package org.example;
import spoon.Launcher;
import spoon.compiler.Environment;
import spoon.processing.Processor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.DefaultImportComparator;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.ForceImportProcessor;
import spoon.reflect.visitor.ImportCleaner;
import spoon.reflect.visitor.ImportConflictDetector;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.support.compiler.VirtualFile;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
public final class Main {
private Main() {
}
private static final class ParentChecker extends CtScanner {
private final List invalidElements;
private final Deque stack;
private ParentChecker() {
this.invalidElements = new ArrayList<>();
this.stack = new ArrayDeque<>();
}
public static List checkConsistency(CtElement ctElement) {
ParentChecker parentChecker = new ParentChecker();
parentChecker.scan(ctElement);
return parentChecker.invalidElements;
}
@Override
public void enter(CtElement element) {
if (!this.stack.isEmpty() && (!element.isParentInitialized() || element.getParent() != this.stack.peek())) {
this.invalidElements.add(new InvalidElement(element, this.stack));
}
this.stack.push(element);
}
@Override
protected void exit(CtElement e) {
this.stack.pop();
}
public record InvalidElement(CtElement element, Deque stack) {
public InvalidElement {
stack = new ArrayDeque<>(stack);
}
public String reason() {
String name = this.element instanceof CtNamedElement ctNamedElement ? "-" + ctNamedElement.getSimpleName() : "";
return (this.element.isParentInitialized() ? "inconsistent (%s)".formatted(this.element.getParent().getClass().getSimpleName()) : "null")
+ " parent for " + this.element.getClass() + name
+ " - " + this.element.getPosition()
+ " - " + this.stack.peek();
}
public String dumpStack() {
List output = new ArrayList<>();
for (CtElement ctElement : this.stack) {
output.add(" " + ctElement.getClass().getSimpleName()
+ " " + (ctElement.getPosition().isValidPosition() ? String.valueOf(ctElement.getPosition()) : "(?)")
);
}
return String.join(System.lineSeparator(), output);
}
@Override
public String toString() {
return "%s%n%s".formatted(this.reason(), this.dumpStack());
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof InvalidElement that)) {
return false;
}
return this.element == that.element();
}
@Override
public int hashCode() {
return System.identityHashCode(this.element);
}
}
}
private static CtModel buildModel() {
Launcher launcher = new Launcher();
Environment environment = launcher.getEnvironment();
environment.setPrettyPrinterCreator(() -> new DefaultJavaPrettyPrinter(environment) {
{
// copy-pasted from StandardEnvironment#createPrettyPrinterAutoImport
List> preprocessors = List.of(
// try to import as many types as possible
new ForceImportProcessor(),
// remove unused imports first. Do not add new imports at a time when conflicts are not resolved
new ImportCleaner().setCanAddImports(false),
// solve conflicts, the current imports are relevant too
new ImportConflictDetector(),
// compute final imports
new ImportCleaner().setImportComparator(new DefaultImportComparator())
);
this.setIgnoreImplicit(false);
this.setPreprocessors(preprocessors);
this.setMinimizeRoundBrackets(true);
}
});
environment.setComplianceLevel(21);
launcher.addInputResource(new VirtualFile(
"public record Main(String string) {}",
"Main"
));
return launcher.buildModel();
}
public static void main(String[] args) {
CtModel ctModel = buildModel();
for (var elem : ParentChecker.checkConsistency(ctModel.getUnnamedModule())) {
System.out.println(elem);
}
}
public static T reduce(CtQuery ctQuery, Class itemClass, T first, BiFunction function) {
AtomicReference result = new AtomicReference<>(first);
ctQuery.forEach((R out) -> {
if (out != null && itemClass.isAssignableFrom(out.getClass())) {
result.getAndUpdate(value -> function.apply(value, out));
}
});
return result.get();
}
}
```
### Actual output
```Java
inconsistent (CtMethodImpl) parent for class spoon.support.reflect.reference.CtTypeReferenceImpl - (D:/projects/playground/Main:1) - String string
CtRecordComponentImpl (?)
CtRecordImpl (D:/projects/playground/Main:1)
CtRootPackage (?)
CtUnnamedModule (?)
Process finished with exit code 0
```
### Expected output
```Java
Process finished with exit code 0
```
### Spoon Version
11.0.0
### JVM Version
openjdk version "21.0.2" 2024-01-16 LTS OpenJDK Runtime Environment Zulu21.32+17-CA (build 21.0.2+13-LTS) OpenJDK 64-Bit Server VM Zulu21.32+17-CA (build 21.0.2+13-LTS, mixed mode, sharing)
### What operating system are you using?
Windows 11
Contributor guide
Research direction
Start by running the provided Java 21 reproducer for `public record Main(String string) {}` and inspect `CtRecordComponentImpl`, `CtTypeReferenceImpl`, and the parent traversal in `ParentChecker`. Trace why the component type is also associated with a `CtMethodImpl`; done means the traversal reports no inconsistent parent and the behavior is covered by a regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100