diff --git a/spec-docs/flexible-java-types.md b/spec-docs/flexible-java-types.md index 232206c920d..d7381dd81dc 100644 --- a/spec-docs/flexible-java-types.md +++ b/spec-docs/flexible-java-types.md @@ -139,4 +139,260 @@ The compiler issues warnings specific to `@Nullable`/`@NotNull` in the following - a `@Nullable` value is assigned to a not-null location (including passing parameters and receivers to functions/properties); - a nullable value is assigned to a `@NotNull` location; - a `@NotNull` value is dereferenced with a safe call (`?.`), used in `!!` or on the left-hand side of an elvis operator `?:`; - - a `@NotNull` value is compared with `null` through `==`, `!=`, `===` or `!==` \ No newline at end of file + - a `@NotNull` value is compared with `null` through `==`, `!=`, `===` or `!==` + +## More precise type information from annotations + +Goals: + - Catch more errors related to nullability in case of Java interop + - Keep all class hierarchies consistent at all times (no hierarchy errors related to incorrect annotations) + - (!) If the code compiled with annotations, it should always compile without annotations (because external annotations may disappear) + + This process never results in errors. On any mismatch, a bare platform signature is used (and a warning issued). + +### Annotations recognized by the compiler + +- `org.jetbrains.annotations.Nullable` - value may be null/accepts nulls +- `org.jetbrains.annotations.NotNull` - value can not be null/passing null leads to an exception +- `org.jetbrains.annotations.ReadOnly` - only non-mutating methods can be used on this collection/iterable/iterator +- `org.jetbrains.annotations.Mutable` - mutating methods can be used on this collection/iterable/iterator +- `kotlin.jvm.KotlinSignature(str)` - `str` is a string representation of a more precise signature + +See [appendix](#appendix) for more details + +### Enhancing signatures with annotated declarations + +NOTE: the intention is that if the enhanced signature is not compatible with the overridden signatures from superclasses, +it is discarded, and a warning is issued. We also would like to discard only the mismatching parts of the signature, and thus keep as +much information as possible. + +Example: + +``` java +class Super { + void foo(@NotNull String p) {...} +} + +class Sub extends Super { + @Override + void foo(@Nullable String p) {...} // Warning: Signature does not match the one in the superclass, discarded +} +``` + +#### @KotlinSignature + +``` java +package kotlin.jvm; + +@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD}) +public @interface KotlinSignature { + String value(); +} +``` + +Usage: + +``` java +class C { + @KotlinSignature("fun foo(): MutableList") + List foo() { ... } +} + +``` + +Name resolution: `@KotlinSignature` can use short names from types, because full names are already specified in respective positions in +the Java signature. + +- If there's a `@KotlinSignature` annotation, other annotations are ignored +- If erasure of the signature specified by `@KotlinSignature` differs from the actual erased signature, + a warning is reported and the `@KotlinSignature` annotation is ignored +- Otherwise, the signature from `@KotlinSignature` is used. + +#### @Nullable, @NotNull, @ReadOnly, @Mutable + +What can be annotated: + - field: annotation applies to its type + - method: annotation applies to its return type + - parameter: annotation applies to its type + - type (in Java 8): annotation applies to this type + +Consider a type `(L..U?)`. Nullability annotations enhance it in the following way: + - `@Nullable`: `(L?..U?)` + - `@NotNull`: `(L..U)` + +Note that if upper and lower bound of a flexible type are the same, it is replaced by the bounds (e.g. `(T?..T?) => T?`) + +Consider a collection type `(MC..C?)` (upper bound may be nullable or not). Mutability annotations enhance it in the following way: + - `@ReadOnly`: `(C..C?)` + - `@Mutable`: `(MC..MC?)` + +Nullability annotations are applied after mutability annotations. + +Examples: + +| Java | Kotlin| +|------|-------| +| `Foo` | `Foo!` | +| `@Nullable Foo` | `Foo?` | +| `@NotNull Foo` | `Foo` | +| `List` | `(Mutable)List!` | +| `@ReadOnly List` | `List!` | +| `@Mutable List` | `MutableList!` | +| `@NotNull @Mutable List` | `MutableList` | +| `@Nullable @ReadOnly List` | `List?` | + +*NOTE*: array types are never flattened: `@NotNull Object[]` becomes `(Array..Array)`. + +### Propagating type information from superclasses + +A signature is represented as a list of its parts: + - upper bounds of type parameters + - value parameter types + - return type + +Enhancement rules (the result of their application is called a *propagated signature*) for each part: + - collect annotations from all supertypes and the override in the subclass + - for parts other than return type (which may be covariantly overridden) if there are conflicts (`@Nullable` together with `@NotNull` or + `@ReadOnly` together with `@Mutable`), discard the respective annotations and issue appropriate warnings + - for return types (only the 0-index, see below): + - fist, take annotations from supertypes, and among them: if there's `@NotNull`, discard `@Nullable`, if there's `@Mutable` discard `@ReadOnly` + - then if in the subtype there's `@Nullable` and in the supertype there's `@NotNull`, discard the nullability annotations (analogously, + for mutability annotations) + - apply the annotations and check compatibility with all parts from supertypes, if there's any incompatibility, issue a warning and take + a platform type + +> NOTE: Only flexible types are enhanced, because we want to avoid cases like this +``` java + void foo(@Nullable int x) {...} +``` +this code is incorrect, but Java does not reject it, so if we see this as a Kotlin declaration +``` kotlin + fun foo(x: Int?) +``` +we can't even call it properly (this, in theory, can be worked around by storing pure Java signatures alongside Kotlin ones). + +Detecting annotations on parts from supertypes: + - consider all types have the form of `(L..U)`, where an inflexible type `T` is written `(T..T)` + - if `L` is nullable, say that `@Nullable` annotation is present + - if `U` is not-null, say that `@NotNull` is present + - if `L` is a read-only collection/iterable/iterator type, say that `@ReadOnly` is present + - if `U` is a mutable collection/iterable/iterator type, say that `@Mutable` is present + +Examples: + +``` java +interface A { + @NotNull + String foo(@NotNull String p); +} + +interface B { + @Nullable + String foo(@Nullable String p); +} + +interface C extends A, B { + // this is an override in Java, but would not be an override in Kotlin because of a conflict in parameter types: String vs String? + // Thus, the resulting descriptor is + // fun foo(p: String!): String + // return type is covariantly enhanced to not-null, + // a warning issued about the parameter + + @Override + String foo(String p); +} +``` + +Other cases: + +``` +R foo(@NotNull P p) // super A +R foo(P p) // super B +R foo(P p) // subclass C + +// Result: +fun foo(p: P): R! // parameter type propagated from A +``` + +``` +R foo(@NotNull P p) // super A +R foo(P p) // super B +R foo(@Nullable P p) // subclass C + +// Result: +fun foo(p: P!): R! // conflict on parameter between A and C +``` + +``` +R foo(P p) // super A +R foo(P p) // super B +R foo(@NotNull P p) // subclass C + +// Result: +fun foo(p: P): R! // parameter type specified in C, no conflict with superclasses +``` + +``` +@NotNull +R foo(P p) // super A +R foo(P p) // super B +@Nullable +R foo(P p) // subclass C + +// Result: +fun foo(p: P!): R! // conflict on return type: subtype wants a nullable, but not-null already promised +``` + +``` +R foo(@NotNull @ReadOnly List p) // super A +R foo(@Nullable @ReadOnly List p) // subclass B + +// Result: +fun foo(p: List!): R! // conflict on nullability, no conflict on mutability +``` + +``` +fun foo(MutableList p): R // super A, written in Kotlin or has @KotlinSignature +@Nullable +R foo(List p) // subclass B + +// Result: +fun foo(MutableList p): R! // parameter propagated from superclass (@Mutable, @NotNull), conflict on return type +``` + +*NOTE*: nullability warnings should still be reported in the Kotlin code in case of discarding the enhancing information due to conflicts. + +**Propagation into generic arguments**. Since annotations have to be propagated to type arguments as well as the head type constructor, +the following procedure is used. First, every sub-tree of the type is assigned an index which is its zero-based position in the textual +representation of the type (`0` is root). Example: for `A>`, indices go as follows: `0 - A<...>, 1 - B, 2 - C, 3 - D, 4 - E`, +which corresponds to the left-to-right breadth-first walk of the tree representation of the type. For flexible types, both bounds are indexed +in the same way: `(A..C)` gives `0 - (A..C), 1 - B and D`. Now, in the aforementioned procedure, annotations are collected and +considered *at each index*, and only index 0 of the return type is considered as possibly covariant (this is done for simplicity). + +Example: + - `Mutable(List)!` + - `Mutable(List)!` + - 0: `Mutable(List)!`, `Mutable(List)!` + - 1: `A!`, `A!`, `A?`, `A?` + +NOTE: if the set of descriptors overridden by the resulting enhanced signature differs from the set overridden by the platform signature, +the enhanced signature must be discarded and a warning issued. + +Checklist: + - any platform signature should override any enhanced/propagated signature created for the same member or one of its overridden. + +### Appendix + +We can also support the following annotations out-of-the-box: +* [`android.support.annotation`](https://developer.android.com/reference/android/support/annotation/package-summary.html) + * [`android.support.annotation.Nullable`](https://developer.android.com/reference/android/support/annotation/Nullable.html) + * [`android.support.annotation.NonNull`](https://developer.android.com/reference/android/support/annotation/NonNull.html) +* From [FindBugs](http://findbugs.sourceforge.net/manual/annotations.html) and [`javax.annotation`](https://code.google.com/p/jsr-305/source/browse/trunk/ri/src/main/java/javax/annotation/) + * `*.annotations.CheckForNull` + * `*.NonNull` + * `*.Nullable` +* [`javax.validation.constraints`](http://docs.oracle.com/javaee/6/api/javax/validation/constraints/package-summary.html) + * `NotNull` and `NotNull.List` +* [Project Lombok](http://projectlombok.org/features/NonNull.html) +* [`org.eclipse.jdt.annotation`](https://wiki.eclipse.org/JDT_Core/Null_Analysis) +* [`org.checkerframework.checker.nullness.qual`](http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#nullness-checker)