1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.tools.metalava
18
19 import com.android.tools.metalava.model.AnnotationItem
20 import com.android.tools.metalava.model.Codebase
21 import com.android.tools.metalava.model.FieldItem
22 import com.android.tools.metalava.model.Item
23 import com.android.tools.metalava.model.MethodItem
24 import com.android.tools.metalava.model.ParameterItem
25 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
26 import com.android.tools.metalava.model.TypeItem
27 import com.android.tools.metalava.model.findAnnotation
28 import com.android.tools.metalava.model.hasAnnotation
29
30 /**
31 * Performs null migration analysis, looking at previous API signature files and new signature
32 * files, and replacing new @Nullable and @NonNull annotations with @RecentlyNullable
33 * and @RecentlyNonNull.
34 *
35 * TODO: Enforce compatibility across type use annotations, e.g. changing parameter value from
36 * {@code @NonNull List<@Nullable String>} to {@code @NonNull List<@NonNull String>} is forbidden.
37 */
38 class NullnessMigration : ComparisonVisitor(visitAddedItemsRecursively = true) {
comparenull39 override fun compare(old: Item, new: Item) {
40 if (hasNullnessInformation(new) && !hasNullnessInformation(old)) {
41 new.markRecent()
42 }
43 }
44
45 // Note: We don't override added(new: Item) to mark newly added methods as newly
46 // having nullness annotations: those APIs are themselves new, so there's no reason
47 // to mark the nullness contract as migration (warning- rather than error-severity)
48
comparenull49 override fun compare(old: MethodItem, new: MethodItem) {
50 @Suppress("ConstantConditionIf")
51 if (SUPPORT_TYPE_USE_ANNOTATIONS) {
52 val newType = new.returnType()
53 val oldType = old.returnType()
54 checkType(oldType, newType)
55 }
56 }
57
comparenull58 override fun compare(old: FieldItem, new: FieldItem) {
59 @Suppress("ConstantConditionIf")
60 if (SUPPORT_TYPE_USE_ANNOTATIONS) {
61 val newType = new.type()
62 val oldType = old.type()
63 checkType(oldType, newType)
64 }
65 }
66
comparenull67 override fun compare(old: ParameterItem, new: ParameterItem) {
68 @Suppress("ConstantConditionIf")
69 if (SUPPORT_TYPE_USE_ANNOTATIONS) {
70 val newType = new.type()
71 val oldType = old.type()
72 checkType(oldType, newType)
73 }
74 }
75
76 @Suppress("UNUSED_PARAMETER")
hasNullnessInformationnull77 private fun hasNullnessInformation(type: TypeItem): Boolean {
78 return if (SUPPORT_TYPE_USE_ANNOTATIONS) {
79 // TODO: support type use
80 false
81 } else {
82 false
83 }
84 }
85
86 @Suppress("UNUSED_PARAMETER")
checkTypenull87 private fun checkType(old: TypeItem, new: TypeItem) {
88 if (hasNullnessInformation(new)) {
89 assert(SUPPORT_TYPE_USE_ANNOTATIONS)
90 // TODO: support type use
91 }
92 }
93
94 companion object {
migrateNullsnull95 fun migrateNulls(codebase: Codebase, previous: Codebase) {
96 CodebaseComparator(
97 apiVisitorConfig = @Suppress("DEPRECATION") options.apiVisitorConfig,
98 )
99 .compare(NullnessMigration(), previous, codebase)
100 }
101
hasNullnessInformationnull102 fun hasNullnessInformation(item: Item): Boolean {
103 return isNullable(item) || isNonNull(item)
104 }
105
findNullnessAnnotationnull106 fun findNullnessAnnotation(item: Item): AnnotationItem? {
107 return item.modifiers.findAnnotation(AnnotationItem::isNullnessAnnotation)
108 }
109
isNullablenull110 private fun isNullable(item: Item): Boolean {
111 return item.modifiers.hasAnnotation(AnnotationItem::isNullable)
112 }
113
isNonNullnull114 private fun isNonNull(item: Item): Boolean {
115 return item.modifiers.hasAnnotation(AnnotationItem::isNonNull)
116 }
117 }
118 }
119
120 /**
121 * Marks the nullability of this Item as Recent. That is, replaces @Nullable/@NonNull
122 * with @RecentlyNullable/@RecentlyNonNull
123 */
Itemnull124 fun Item.markRecent() {
125 val annotation = NullnessMigration.findNullnessAnnotation(this) ?: return
126 // Nullness information change: Add migration annotation
127 val annotationClass = if (annotation.isNullable()) RECENTLY_NULLABLE else RECENTLY_NONNULL
128
129 val modifiers = mutableModifiers()
130 modifiers.removeAnnotation(annotation)
131
132 modifiers.addAnnotation(codebase.createAnnotation("@$annotationClass", this))
133 }
134