typetools / typetools/checker-framework
@NotOnlyInitialized does not support 1:N circular references
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 1.1k
- Forks
- 440
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 134
Description
public class Test {
final ArrayList<Child> children = new ArrayList<>();
Test() {
// assignment.type.incompatible
children.add(new Child(this));
}
static final class Child {
final @NotOnlyInitialized Object parent;
protected Child(@UnknownInitialization Object parent) {
this.parent = parent;
}
}
}
Results in:
error: [argument.type.incompatible] incompatible argument for parameter e of add.
children.add(new Child(this));
^
found : @UnderInitialization(com.google.android.gms.thunderbird.worker.Test.Child.class) @NonNull Child
required: @Initialized @NonNull Child
I don't quite understand the root cause of this, so it's difficult to say what the proper solution is... The obvious solution would be to apply @NotOnlyInitialized to the type parameter of childern, ie: ArrayList<@NotOnlyInitialized Child> children. However, NotOnlyInitialized is not currently a type-use annotation, and I'm not even sure if would be sound to apply the annotation like this or not.
More deeply however, I do not understand why the result of new Child(...) is UnderInitialization - why does the following fail?
public class Test {
final Child child;
Test() {
// assignment.type.incompatible
child = new Child(this);
}
static final class Child {
final @NotOnlyInitialized Object parent;
protected Child(@UnknownInitialization Object parent) {
this.parent = parent;
}
}
}
It seems to me that after new Child(this) is invoked, the result of that expression should be Initialized. The Child class is final, there are no possible subclasses, and every NonNull field within Child has obviously been initialized. This meets the definition of Initialized as given in the documentation. Is this because Initialized also implies that all fields within the type must also be Initialized? If so, this doesn't appear to be documented anywhere I can find. The only related sentence in the manual reads, "Eventually, when all constructors complete, the type is @Initialized." However there are never any details given on how it's determined when constructors are complete, and at what point after that a type is considered initialized. In the example above, all constructors have clearly completed it seems to me, and yet the type is considered UnderInitialization instead of Initialized.
This problem is vaguely referenced in the manual section on circular references, where the parent field is annotated with NotOnlyInitialized in order to avoid the problem, but it is never explained why this is necessary, and it comes across as magic code.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the manual's circular-initialization section and reproduce the two Test examples from the issue. Trace how @NotOnlyInitialized, @UnknownInitialization, and constructor completion are handled for the ArrayList and direct-field cases. Done means establishing whether the behavior is incorrect or insufficiently documented, with a clear resolution for the reported diagnostics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- compilers, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100