# Annotations Annotations allow API authors to express constraints that tools can enforce. There are many examples of these, along with existing lint checks: * `@VisibleForTesting`: this API is considered private, and has been exposed only for unit testing purposes * `@CheckResult`: anyone calling this method is expected to do something with the return value * `@CallSuper`: anyone overriding this method must also invoke `super` * `@UiThread`: anyone calling this method must be calling from the UI thread * `@Size`: the size of the annotated array or collection must be of a particular size * `@IntRange`: the annotated integer must have a value in the given range ...and so on. Lint has built-in checks to enforce these, along with infrastructure to make them easy to write, and to share analysis such that improvements to one helps them all. This means that you can easily write your own annotations-based checks as well. !!! Tip Note that the annotation support helps you write checks where you check *usages* of annotated elements, not usages of the annotations themselves. If you simply want to look at actual annotations, override `getApplicableUastTypes` to return `listOf(UAnnotation::class.java)`, and override `createUastHandler` to return an `object : UElementHandler` which simply overrides `visitAnnotation`. ## Basics To create a basic annotation checker, there are two required steps: 1. Register the fully qualified name (or names) of the annotation classes you want to analyze, and 2. Implement the `visitAnnotationUsage` callback for handling each occurrence. Here's a basic example: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin override fun applicableAnnotations(): List { return listOf("my.pkg.MyAnnotation") } override fun visitAnnotationUsage( context: JavaContext, element: UElement, annotationInfo: AnnotationInfo, usageInfo: AnnotationUsageInfo ) { val name = annotationInfo.qualifiedName.substringAfterLast('.') val message = "`${usageInfo.type.name}` usage associated with " + "`@$name` on ${annotationInfo.origin}" val location = context.getLocation(element) context.report(TEST_ISSUE, element, location, message) } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All this simple detector does is flag any usage associated with the given annotation, including some information about the usage. If we for example have the following annotated API: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin annotation class MyAnnotation abstract class Book { operator fun contains(@MyAnnotation word: String): Boolean = TODO() fun length(): Int = TODO() @MyAnnotation fun close() = TODO() } operator fun Book.get(@MyAnnotation index: Int): Int = TODO() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...and we then run the above detector on the following test case: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin fun test(book: Book) { val found = "lint" in book val firstWord = book[0] book.close() } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ we get the following output: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text src/book.kt:14: Error: METHOD_CALL_PARAMETER usage associated with @MyAnnotation on PARAMETER val found = "lint" in book ---- src/book.kt:15: Error: METHOD_CALL_PARAMETER usage associated with @MyAnnotation on PARAMETER val firstWord = book[0] - src/book.kt:16: Error: METHOD_CALL usage associated with @MyAnnotation on METHOD book.close() ------- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In the first case, the infix operator “in” will call `contains` under the hood, and here we've annotated the parameter, so lint visits the argument corresponding to that parameter (the literal string “lint”). The second case shows a similar situation where the array syntax will end up calling our extension method, `get()`. And the third case shows the most common scenario: a straightforward method call to an annotated method. In many cases, the above detector implementation is nearly all you have to do to enforce an annotation constraint. For example, in the `@CheckResult` detector, we want to make sure that anyone calling a method annotated with `@CheckResult` will not ignore the method return value. All the lint check has to do is register an interest in *`androidx.annotation.CheckResult`*, and lint will invoke `visitAnnotationUsage` for each method call to the annotated method. Then we just check the method call to make sure that its return value isn't ignored, e.g. that it's stored into a variable or passed into another method call. !!! Tip In `applicableAnnotations`, you typically return the fully qualified names of the annotation classes your detector is targeting. However, in some cases, it's useful to match all annotations of a given name; for example, there are many, many variations of the `@Nullable` annotations, and you don't really want to be in the business of keeping track of and listing all of them here. Lint will also let you specify just the basename of an annotation here, such as `"Nullable"`, and if so, annotations like `androidx.annotation.Nullable` and `org.jetbrains.annotations.Nullable` will both match. ## Annotation Usage Types and isApplicableAnnotationUsage ### Method Override In the detector above, we're including the “usage type” in the error message. The usage type tells you something about how the annotation is associated with the usage element -- and in the above, the first two cases have a usage type of “parameter” because the visited element corresponds to a parameter annotation, and the third one a method annotation. There are many other usage types. For example, if we add the following to the API: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin open class Paperback : Book() { override fun close() { } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ then the detector will emit the following incident since the new method overrides another method that was annotated: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text src/book.kt:14: Error: METHOD_OVERRIDE usage associated with @MyAnnotation on METHOD override fun close() { } ----- 1 errors, 0 warnings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Overriding an annotated element is how the `@CallSuper` detector is implemented, which makes sure that any method which overrides a method annotated with `@CallSuper` is invoking `super` on the overridden method somewhere in the method body. ### Method Return Here's another example, where we have annotated the return value with @MyAnnotation: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin open class Paperback : Book() { fun getDefaultCaption(): String = TODO() @MyAnnotation fun getCaption(imageId: Int): String { if (imageId == 5) { return "Blah blah blah" } else { return getDefaultCaption() } } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here, lint will flag the various exit points from the method associated with the annotation: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text src/book.kt:18: Error: METHOD_RETURN usage associated with @MyAnnotation on METHOD return "Blah blah blah" -------------- src/book.kt:20: Error: METHOD_RETURN usage associated with @MyAnnotation on METHOD return getDefaultCaption() ------------------- 2 errors, 0 warnings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note also that this would have worked if the annotation had been inherited from a super method instead of being explicitly set here. One usage of this mechanism in Lint is the enforcement of return values in methods. For example, if a method has been marked with `@DrawableRes`, Lint will make sure that the returned value of that method will not be of an incompatible resource type (such as `@StringRes`). ### Handling Usage Types As you can see, your callback will be invoked for a wide variety of usage types, and sometimes, they don't apply to the scenario that your detector is interested in. Consider the `@CheckResult` detector again, which makes sure that any calls to a given method will look at the return value. From the “method override” section above, you can see that lint would *also* notify your detector for any method that is *overriding* (rather than calling) a method annotated with `@CheckResult`. We don't want to report those. There are two ways to handle this. The first one is to check whether the usage element is a `UMethod`, which it will be in the overriding case, and return early in that case. The recommended approach, which `CheckResultDetector` uses, is to override the `isApplicableAnnotationUsage` method: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean { return type != AnnotationUsageType.METHOD_OVERRIDE && super.isApplicableAnnotationUsage(type) } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! Tip Notice how we are also calling `super` here and combining the result instead of just using a hardcoded list of expected usage types. This is because, as discussed below, lint already filters out some usage types by default in the super implementation. ### Usage Types Filtered By Default The default implementation of `Detector.isApplicableAnnotationUsage` looks like this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin open fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean { return type != AnnotationUsageType.BINARY && type != AnnotationUsageType.EQUALITY } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These usage types apply to cases where annotated elements are compared for equality or using other binary operators. Initially introducing this support led to a lot of noise and false positives; most of the existing lint checks do not want this, so they're opt-in. An example of a lint check which *does* enforce this is the `@HalfFloat` lint check. In Android, a [HalfFloat](https://developer.android.com/reference/androidx/annotation/ HalfFloat) is a representation of a floating point value (with less precision than a `float`) which is stored in a `short`, normally an integer primitive value. If you annotate a `short` with `@HalfFloat`, including in APIs, lint can help catch cases where you are making mistakes -- such as accidentally widening the value to an int, and so on. Here are some example error messages from lint's unit tests for the half float check: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text src/test/pkg/HalfFloatTest.java:23: Error: Expected a half float here, not a resource id [HalfFloat] method1(getDimension1()); // ERROR --------------- src/test/pkg/HalfFloatTest.java:43: Error: Half-float type in expression widened to int [HalfFloat] int result3 = float1 + 1; // error: widening ------ src/test/pkg/HalfFloatTest.java:50: Error: Half-float type in expression widened to int [HalfFloat] Math.round(float1); // Error: should use Half.round ------ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ### Scopes Many annotations apply not just to methods or fields but to classes and even packages, with the idea that the annotation applies to everything within the package. For example, if we have this annotated API: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers annotation class ThreadSafe annotation class NotThreadSafe @ThreadSafe abstract class Stack { abstract fun size(): Int abstract fun push(item: T) abstract fun pop(): String @NotThreadSafe fun save() { } @NotThreadSafe abstract class FileStack : Stack() { abstract override fun pop(): String } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ And the following test case: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin linenumbers fun test(stack: Stack, fileStack: Stack) { stack.push("Hello") stack.pop() fileStack.push("Hello") fileStack.pop() } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here, `stack.push` call on line 2 resolves to the API method on line 7. That method is not annotated, but it's inside a class that is annotated with `@ThreadSafe`. Similarly for the `pop()` call on line 3. The `fileStack.push` call on line 4 also resolves to the same method as the call on line 2 (even though the concrete type is a `FileStack` instead of a `Stack`), so like on line 2, this call is taken to be thread safe. However, the `fileStack.pop` call on line 6 resolves to the API method on line 14. That method is not annotated, but it's inside a class annotated with `@NotThreadSafe`, which in turn is inside an outer class annotated with `@ThreadSafe`. The intent here is clearly that that method should be considered not thread safe. To help with scenarios like this, lint will provide *all* the annotations (well, all annotations that any lint checks have registered interest in via `getApplicableAnnotations`; it will not include annotations like `java.lang.SuppressWarnings` and so on unless a lint check asks for it). This is provided in the `AnnotationUsageInfo` passed to the `visitAnnotationUsage` parameters. The `annotations` list will include all relevant annotations, **in scope order**. That means that for the above `pop` call on line 5, it will point to first the annotations on the `pop` method (and here there are none), then the `@NotThreadSafe` annotation on the surrounding class, and then the `@ThreadSafe` annotation on the outer class, and then annotations on the file itself and the package. The `index` points to the annotation we're analyzing. If for example our detector had registered an interest in `@ThreadSafe`, it would be called for the second `pop` call as well, since it calls a method inside a `@ThreadSafe` annotation (on the outer class), but the `index` would be 1. The lint check can check all the annotations earlier than the one at the index to see if they “counteract” the annotation, which of course the `@NotThreadSafe` annotation does. Lint uses this mechanism for example for the `@CheckResult` annotation, since some APIs are annotated with `@CheckResult` for whole packages (as an API convention), and then there are explicit exceptions carved out using `@CanIgnoreReturnValue`. There is a method on the `AnnotationUsageInfo`, `anyCloser`, which makes this check easy: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin if (usageInfo.anyCloser { it.qualifiedName == "com.google.errorprone.annotations.CanIgnoreReturnValue" }) { // There's a closer @CanIgnoreReturnValue which cancels the // outer @CheckReturnValue annotation we're analyzing here return } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! Tip You only have to worry about this when there are different annotations that interact with each other. If the same annotation is found in multiple nested contexts, lint *will* include all the annotations in the `AnnotationUsageInfo`, but it will not invoke your callback for any outer occurrences; only the closest one. This is usually what detectors expect: the innermost one “overrides” the outer ones, so lint omits these to help avoid false positives where a lint check author forgot to handle and test this scenario. A good example of this situation is with the `@RequiresApi` annotation; a class may be annotated as requiring a particular API level, but a specific inner class or method within the class can have a more specific `@RequiresApi` annotation, and we only want the detector to be invoked for the innermost one. If for some reason your detector *does need* to handle all of the repeated outer occurrences, note that they're all there in the `annotations` list for the `AnnotationUsageInfo` so you can look for them and handle them when you are invoked for the innermost one. ### Inherited Annotations As we saw in the method overrides section, lint will include annotations in the hierarchy: annotations specified not just on a specific method but super implementations and so on. This is normally what you want -- for example, if a method is annotated with `@CheckResult` (such as `String.trim()`, where it's important to understand that you're not changing the string in place, there's a new string returned so it's probably a mistake to not use it), you probably want any overriding implementations to have the same semantics. However, there are exceptions to this. For example, `@VisibleForTesting`. Perhaps a super class made a method public only for testing purposes, but you have a concrete subclass where you are deliberately supporting the operation, not just from tests. If annotations were always inherited, you would have to create some sort of annotation to “revert” the semantics, e.g. `@VisibleNotJustForTesting`, which would require a lot of noisy annotations. Lint lets you specify the inheritance behavior of individual annotations. For example, the lint check which enforces the `@VisibleForTesting` and `@RestrictTo` annotations handles it like this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin override fun inheritAnnotation(annotation: String): Boolean { // Require restriction annotations to be annotated everywhere return false } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (Note that the API passes in the fully qualified name of the annotation in question so you can control this behavior individually for each annotation when your detector applies to multiple annotations.)