eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
156 stars 123 forks source link

Reconsider contracts for derived type bindings #2210

Open stephan-herrmann opened 5 months ago

stephan-herrmann commented 5 months ago

Follow-up from #2173:

Before JSR 308, the contract of type bindings was simply: the 'same' type is always represented by the same instance of TypeBinding, allowing for == to be used for checking type equality.

With type annotations in the picture, and specifically with the compiler's interest to fully evaluate those for null analysis, identity of type bindings has become a more complex topic.

The new methods TypeBinding.equalsEquals() and TypeBinding.notEquals() consider those types as equal that have the same id. The intention is that types that differ only in type annotations should share the same id.

Internally, methods like TypeSystem.getUnannotatedType() are based on the invariant that each array TypeSystem.types[id] has the "unannotated" type at position 0.

Caveat: while all types with the same id appear in the same TypeSystem.types[id], that array may also contain other derived types with different id, e.g., array, parameterized, raw types of the generic type.

That invariant may be broken, when a type variable has an annotation (semantically a declaration annotation on the type parameter, but technically a type annotation). In this case the type at position 0 is "the original type", which may get annotations set, possibly after the type has been registered in the AnnotatableTypeSystem. For that situation TypeSystem.forceRegisterAsDerived() implements an artificial swap, inserting a clone without annotations into position 0 and moving "the original type" to position 1. This caused problems when the original type was already used in a PTBKey - the problem locally fixed in #2173 .

Additionally, we have the concept of TypeBinding.prototype(). Normally this.prototype() == this, but when an annotated variant is created by ATS, then a clone is created, which remembers the original as its "prototype".

Hence we have an interesting (non-trivial) relationship between TypeSystem.getUnannotatedType() and TypeBinding.prototype(), which deserves a fresh look.

In this issue I plan to investigate the relationships between "unannotated type", "originally declared type" and "prototype". The initial question will be: Is it suitable to use the "unannotated type" as the primordial concept or should a type variable with an annotation (technically type annotation, semantically declaration annotation, see above) be considered the primordial thing, and the unannotated variant a derived thing in this case?

If these things can be clearly separated, then all magic in TypeSystem.forceRegisterAsDerived() plus the fix in #2173 could become obsolete, hopefully providing a more consistent overall story.

stephan-herrmann commented 5 months ago

@srikanth-sankaran feel free to join me at your leisure