Spec for enhancing Java declarations with annotations

This commit is contained in:
Andrey Breslav
2015-02-26 18:44:12 +03:00
committed by Denis Zharkov
parent 4d6ab824c8
commit 64866183f7
+256
View File
@@ -140,3 +140,259 @@ The compiler issues warnings specific to `@Nullable`/`@NotNull` in the following
- 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 `!==`
## 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<String?>")
List<String> 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<T>..C<T>?)` (upper bound may be nullable or not). Mutability annotations enhance it in the following way:
- `@ReadOnly`: `(C<T>..C<T>?)`
- `@Mutable`: `(MC<T>..MC<T>?)`
Nullability annotations are applied after mutability annotations.
Examples:
| Java | Kotlin|
|------|-------|
| `Foo` | `Foo!` |
| `@Nullable Foo` | `Foo?` |
| `@NotNull Foo` | `Foo` |
| `List<T>` | `(Mutable)List<T!>!` |
| `@ReadOnly List<T>` | `List<T!>!` |
| `@Mutable List<T>` | `MutableList<T!>!` |
| `@NotNull @Mutable List<T>` | `MutableList<T!>` |
| `@Nullable @ReadOnly List<T>` | `List<T!>?` |
*NOTE*: array types are never flattened: `@NotNull Object[]` becomes `(Array<Any!>..Array<out Any!>)`.
### 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<T> p) // super A
R foo(@Nullable @ReadOnly List<T> p) // subclass B
// Result:
fun foo(p: List<T>!): R! // conflict on nullability, no conflict on mutability
```
```
fun foo(MutableList<T> p): R // super A, written in Kotlin or has @KotlinSignature
@Nullable
R foo(List<T> p) // subclass B
// Result:
fun foo(MutableList<T> 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<B, C<D, E>>`, indices go as follows: `0 - A<...>, 1 - B, 2 - C<D, E>, 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<B>..C<D>)` gives `0 - (A<B>..C<D>), 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)<A!>!`
- `Mutable(List)<A?>!`
- 0: `Mutable(List)<A>!`, `Mutable(List)<A?>!`
- 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)