typetools / typetools/checker-framework

@NotOnlyInitialized does not support 1:N circular references

Open
#4,543 1 comment 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.