1 /*
<lambda>null2  * Copyright (C) 2024 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.model.type
18 
19 import com.android.tools.metalava.model.AnnotationItem
20 import com.android.tools.metalava.model.BoundsTypeItem
21 import com.android.tools.metalava.model.ClassTypeItem
22 import com.android.tools.metalava.model.ExceptionTypeItem
23 import com.android.tools.metalava.model.Item
24 import com.android.tools.metalava.model.PrimitiveTypeItem
25 import com.android.tools.metalava.model.TypeItem
26 import com.android.tools.metalava.model.TypeNullability
27 import com.android.tools.metalava.model.TypeParameterItem
28 import com.android.tools.metalava.model.TypeParameterScope
29 import com.android.tools.metalava.model.WildcardTypeItem
30 import com.android.tools.metalava.model.typeNullability
31 
32 /**
33  * Factory for building [TypeItem] from the underlying model specific representation of the type.
34  *
35  * The purpose of this is to abstract away all the model specific details of type construction in a
36  * consistent manner across the models so that the types can be constructed in a consistent manner.
37  * It provides specialist functions for different type uses that can ensure the specific constraints
38  * of those type uses are enforced. e.g. types used for super class and interfaces cannot be
39  * nullable.
40  *
41  * At the moment the specialist functions are limited to just a few different types but over time it
42  * will be expanded as more type creation logic is moved inside.
43  *
44  * @param T the underlying model specific representation of a type.
45  * @param F the type of the factory implementation.
46  */
47 interface TypeItemFactory<in T, F : TypeItemFactory<T, F>> {
48 
49     /**
50      * The [TypeParameterScope] that is used by this factory to resolve references to
51      * [TypeParameterItem]s.
52      */
53     val typeParameterScope: TypeParameterScope
54 
55     /**
56      * Create a [TypeItemFactory] that can resolve references to the [typeParameters].
57      *
58      * Returns `this` if [typeParameters] is empty, otherwise returns a new [TypeItemFactory] with a
59      * new [typeParameterScope] with the specified [scopeDescription] and containing the supplied
60      * [typeParameters].
61      */
62     fun nestedFactory(scopeDescription: String, typeParameters: List<TypeParameterItem>): F
63 
64     /** Get a type suitable for use in a wildcard type bounds clause. */
65     fun getBoundsType(underlyingType: T): BoundsTypeItem
66 
67     /** Get a type suitable for use in a `throws` list. */
68     fun getExceptionType(underlyingType: T): ExceptionTypeItem
69 
70     /**
71      * Get a general type suitable for use anywhere not covered by one of the more specific type
72      * methods in this.
73      */
74     fun getGeneralType(underlyingType: T): TypeItem
75 
76     /**
77      * Get a type suitable for use in an `implements` list of a concrete class, or an `extends` list
78      * of an interface class.
79      */
80     fun getInterfaceType(underlyingType: T): ClassTypeItem
81 
82     /** Get a type suitable for use in an `extends` clause of a concrete class. */
83     fun getSuperClassType(underlyingType: T): ClassTypeItem
84 
85     // Item specific type methods.
86 
87     /**
88      * Get the type for a field.
89      *
90      * This considers a number of factors, in addition to the declared type, to determine the
91      * appropriate [TypeNullability] for the field type, i.e.:
92      * * Any [AnnotationItem.typeNullability] annotations in [itemAnnotations].
93      * * Setting of [isEnumConstant]; if it is `true` then it is always [TypeNullability.NONNULL].
94      * * If the field is `final` then the nullability of the field's value can be considered
95      *   ([isInitialValueNonNull]). Otherwise, it cannot as it may change over the lifetime of the
96      *   field.
97      *
98      * @param underlyingType the underlying model's type.
99      * @param itemAnnotations the annotations on the field (not the type).
100      * @param isFinal `true` if the field is `final`.
101      * @param isEnumConstant `true` if the field is actually an enum constant.
102      * @param isInitialValueNonNull a lambda that will be invoked on `final` fields to determine
103      *   whether its initial value is non-null. This is a lambda as the determination of the initial
104      *   value may be expensive.
105      */
106     fun getFieldType(
107         underlyingType: T,
108         itemAnnotations: List<AnnotationItem>,
109         isEnumConstant: Boolean,
110         isFinal: Boolean,
111         isInitialValueNonNull: () -> Boolean,
112     ): TypeItem = error("unsupported")
113 
114     /**
115      * Get the parameter type for a method (or constructor).
116      *
117      * This considers a number of factors, in addition to the declared type, to determine the
118      * appropriate [TypeNullability] for the method parameter type, i.e.:
119      * * Any [AnnotationItem.typeNullability] annotations in [itemAnnotations].
120      * * Method [fingerprint], which may match a known method whose return type has a known
121      *   [TypeNullability].
122      *
123      * @param underlyingParameterType the underlying model's type.
124      * @param itemAnnotations the annotations on the method parameter (not the type).
125      * @param fingerprint method fingerprint
126      * @param parameterIndex the index of the parameter in the method's list of parameters.
127      * @param isVarArg whether this parameter is a vararg parameter. This is provided separately to
128      *   the [underlyingParameterType] because while some models encapsulate that information within
129      *   the type not all do.
130      */
131     fun getMethodParameterType(
132         underlyingParameterType: T,
133         itemAnnotations: List<AnnotationItem>,
134         fingerprint: MethodFingerprint,
135         parameterIndex: Int,
136         isVarArg: Boolean,
137     ): TypeItem = error("unsupported")
138 
139     /**
140      * Get the return type for a method.
141      *
142      * This considers a number of factors, in addition to the declared type, to determine the
143      * appropriate [TypeNullability] for the field type, i.e.:
144      * * If it [isAnnotationElement], they must be [TypeNullability.NONNULL].
145      * * Method [fingerprint], which may match a known method whose return type has a known
146      *   [TypeNullability].
147      *
148      * @param underlyingReturnType the underlying model's return type.
149      * @param itemAnnotations the annotations on the field (not the type).
150      * @param fingerprint method fingerprint
151      * @param isAnnotationElement true for a non-static method of an annotation class.
152      */
153     fun getMethodReturnType(
154         underlyingReturnType: T,
155         itemAnnotations: List<AnnotationItem>,
156         fingerprint: MethodFingerprint,
157         isAnnotationElement: Boolean,
158     ): TypeItem = error("unsupported")
159 }
160 
161 /**
162  * A fingerprint of a method that is used to determined if it is a known method with known
163  * nullability.
164  */
165 data class MethodFingerprint(
166     /** The name of the method. */
167     val name: String,
168     /** The number of parameters. */
169     val parameterCount: Int,
170 )
171 
172 /**
173  * Encapsulates the information necessary to compute the [TypeNullability] from a variety of
174  * different sources with differing priorities.
175  *
176  * The priorities are:
177  * 1. Forced by specification, e.g. enum constants, super class types, primitives.
178  * 2. Kotlin; ignoring [TypeNullability.PLATFORM].
179  * 3. Annotations.
180  * 4. Nullability inferred from context, e.g. constant field with non-null value.
181  * 4. [TypeNullability.PLATFORM]
182  */
183 class ContextNullability(
184     /**
185      * The [TypeNullability] that a [TypeItem] MUST have by virtue of what the type is, or where it
186      * is used; e.g. [PrimitiveTypeItem]s and super class types MUST be [TypeNullability.NONNULL]
187      * while [WildcardTypeItem]s MUST be [TypeNullability.UNDEFINED].
188      *
189      * This CANNOT be overridden by a nullability annotation.
190      */
191     val forcedNullability: TypeNullability? = null,
192 
193     /**
194      * The [TypeNullability] that a [TypeItem] that is a component of an array MUST have.
195      *
196      * This is used to ensure that annotation attributes, i.e. methods on an annotation class, that
197      * return arrays cannot return arrays of a nullable component type.
198      *
199      * This CANNOT be overridden by a nullability annotation.
200      */
201     val forcedComponentNullability: TypeNullability? = null,
202 
203     /**
204      * The annotations from the [Item] whose type this is.
205      *
206      * If supplied then this may be used by [compute] to determine the [TypeNullability] of the
207      * constructed type.
208      */
209     val itemAnnotations: List<AnnotationItem>? = null,
210 
211     /**
212      * A [TypeNullability] that can be inferred from the context.
213      *
214      * It is passed as a lambda as it may be expensive to compute.
215      */
216     val inferNullability: (() -> TypeNullability?)? = null,
217 ) {
218     /**
219      * Compute the [TypeNullability] according to the priority in the documentation for this class.
220      */
computenull221     fun compute(
222         kotlinNullability: TypeNullability?,
223         typeAnnotations: List<AnnotationItem>
224     ): TypeNullability =
225         // If forced is set then use that as the top priority.
226         forcedNullability
227         // If kotlin provides it then use that as it is most accurate, ignore PLATFORM though
228         // as that may be overridden by annotations or the default.
229         ?: kotlinNullability?.takeIf { nullability -> nullability != TypeNullability.PLATFORM }
230             // If annotations provide it then use them as the developer requested.
231             ?: typeAnnotations.typeNullability
232             // If item annotations are found then check them.
233             ?: itemAnnotations?.typeNullability
234             // If an inferred nullability is provided then use it.
235             ?: inferNullability?.invoke()
236             // Finally default to [TypeNullability.PLATFORM].
237             ?: TypeNullability.PLATFORM
238 
239     /**
240      * Get a [ContextNullability] instance for components of arrays.
241      *
242      * If [forcedComponentNullability] is null then this returns [ContextNullability.none],
243      * otherwise it returns a [ContextNullability] whose [forcedNullability] is set to
244      * [forcedComponentNullability].
245      */
forComponentTypenull246     fun forComponentType() =
247         forcedComponentNullability?.let { ContextNullability(forcedNullability = it) } ?: none
248 
249     companion object {
250         val none = ContextNullability()
251         val forceNonNull = ContextNullability(TypeNullability.NONNULL)
252         val forceUndefined = ContextNullability(TypeNullability.UNDEFINED)
253     }
254 }
255 
256 abstract class DefaultTypeItemFactory<in T, F : DefaultTypeItemFactory<T, F>>(
257     final override val typeParameterScope: TypeParameterScope
258 ) : TypeItemFactory<T, F> {
259 
nestedFactorynull260     final override fun nestedFactory(
261         scopeDescription: String,
262         typeParameters: List<TypeParameterItem>
263     ): F {
264         val scope = typeParameterScope.nestedScope(scopeDescription, typeParameters)
265         return if (scope === typeParameterScope) self() else createNestedFactory(scope)
266     }
267 
getBoundsTypenull268     override fun getBoundsType(underlyingType: T) = getType(underlyingType) as BoundsTypeItem
269 
270     override fun getExceptionType(underlyingType: T) = getType(underlyingType) as ExceptionTypeItem
271 
272     override fun getGeneralType(underlyingType: T) = getType(underlyingType)
273 
274     override fun getInterfaceType(underlyingType: T) = getSuperType(underlyingType)
275 
276     override fun getSuperClassType(underlyingType: T) = getSuperType(underlyingType)
277 
278     /**
279      * Creates a [ClassTypeItem] that is suitable for use as a super type, e.g. in an `extends` or
280      * `implements` list.
281      */
282     private fun getSuperType(underlyingType: T): ClassTypeItem {
283         return getType(underlyingType, contextNullability = ContextNullability.forceNonNull)
284             as ClassTypeItem
285     }
286 
getFieldTypenull287     override fun getFieldType(
288         underlyingType: T,
289         itemAnnotations: List<AnnotationItem>,
290         isEnumConstant: Boolean,
291         isFinal: Boolean,
292         isInitialValueNonNull: () -> Boolean
293     ): TypeItem {
294         // Get the context nullability. Enum constants are always non-null, item annotations and
295         // whether a field is final and has a non-null value are used only if no other source of
296         // information about nullability is available.
297         val contextNullability =
298             if (isEnumConstant) ContextNullability.forceNonNull
299             else {
300                 ContextNullability(
301                     itemAnnotations = itemAnnotations,
302                     inferNullability = {
303                         // Treat the field as non-null if it is final and has a non-null value.
304                         TypeNullability.NONNULL.takeIf { isFinal && isInitialValueNonNull() }
305                     }
306                 )
307             }
308 
309         // Get the field's type, passing in the context nullability.
310         return getType(underlyingType, contextNullability = contextNullability)
311     }
312 
getMethodParameterTypenull313     override fun getMethodParameterType(
314         underlyingParameterType: T,
315         itemAnnotations: List<AnnotationItem>,
316         fingerprint: MethodFingerprint,
317         parameterIndex: Int,
318         isVarArg: Boolean
319     ): TypeItem {
320         val contextNullability =
321             ContextNullability(
322                 itemAnnotations = itemAnnotations,
323                 inferNullability = {
324                     // Check for a known method's nullability.
325                     getMethodParameterNullability(fingerprint, parameterIndex)
326                 }
327             )
328 
329         return getType(
330             underlyingParameterType,
331             contextNullability = contextNullability,
332             isVarArg = isVarArg,
333         )
334     }
335 
getMethodReturnTypenull336     override fun getMethodReturnType(
337         underlyingReturnType: T,
338         itemAnnotations: List<AnnotationItem>,
339         fingerprint: MethodFingerprint,
340         isAnnotationElement: Boolean,
341     ): TypeItem {
342         // Annotation elements, aka attributes, or methods on an annotation class cannot have a null
343         // value.
344         val annotationElementNullability = TypeNullability.NONNULL.takeIf { isAnnotationElement }
345         val contextNullability =
346             ContextNullability(
347                 forcedNullability = annotationElementNullability,
348                 forcedComponentNullability = annotationElementNullability,
349                 itemAnnotations = itemAnnotations,
350                 inferNullability = {
351                     // Check for a known method's nullability.
352                     getMethodReturnTypeNullability(fingerprint)
353                 }
354             )
355 
356         // Get the method's return type, passing in the context nullability.
357         return getType(underlyingReturnType, contextNullability = contextNullability)
358     }
359 
360     /** Type safe access to `this`. */
selfnull361     protected abstract fun self(): F
362 
363     /** Create a nested factory that is a copy of this one, except using [scope]. */
364     protected abstract fun createNestedFactory(scope: TypeParameterScope): F
365 
366     /**
367      * Get the [TypeItem] corresponding to the [underlyingType] and within the [contextNullability].
368      *
369      * The [isVarArg] is provided separately to the [underlyingType] because not all models
370      * encapsulate that information within the type.
371      */
372     protected abstract fun getType(
373         underlyingType: T,
374         contextNullability: ContextNullability = ContextNullability.none,
375         isVarArg: Boolean = false,
376     ): TypeItem
377 
378     companion object {
379         /**
380          * Get known [TypeNullability] for parameter [parameterIndex] of method with [fingerprint]
381          * if available, or `null`.
382          */
383         private fun getMethodParameterNullability(
384             fingerprint: MethodFingerprint,
385             parameterIndex: Int
386         ): TypeNullability? {
387             val (name, parameterCount) = fingerprint
388             return when {
389                 name == "equals" && parameterCount == 1 ->
390                     TypeNullability.NULLABLE.takeIf { parameterIndex == 0 }
391                 else -> null
392             }
393         }
394 
395         /**
396          * Get [TypeNullability], if known, for the return type of the method with [fingerprint], or
397          * `null` if the method is not known.
398          */
399         private fun getMethodReturnTypeNullability(
400             fingerprint: MethodFingerprint
401         ): TypeNullability? {
402             val (name, parameterCount) = fingerprint
403             return when {
404                 name == "toString" && parameterCount == 0 -> TypeNullability.NONNULL
405                 else -> null
406             }
407         }
408     }
409 }
410