• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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.psi
18 
19 import com.android.tools.metalava.model.TypeNullability
20 import com.intellij.psi.PsiElement
21 import org.jetbrains.kotlin.analysis.api.KaSession
22 import org.jetbrains.kotlin.analysis.api.analyze
23 import org.jetbrains.kotlin.analysis.api.symbols.KaNamedClassSymbol
24 import org.jetbrains.kotlin.analysis.api.types.KaClassType
25 import org.jetbrains.kotlin.analysis.api.types.KaType
26 import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
27 import org.jetbrains.kotlin.analysis.api.types.KaTypeParameterType
28 import org.jetbrains.kotlin.psi.KtCallableDeclaration
29 import org.jetbrains.kotlin.psi.KtClass
30 import org.jetbrains.kotlin.psi.KtElement
31 import org.jetbrains.kotlin.psi.KtFunction
32 import org.jetbrains.kotlin.psi.KtParameter
33 import org.jetbrains.kotlin.psi.KtProperty
34 import org.jetbrains.kotlin.psi.KtPropertyAccessor
35 import org.jetbrains.kotlin.psi.KtTypeAlias
36 import org.jetbrains.kotlin.psi.KtTypeReference
37 import org.jetbrains.kotlin.psi.psiUtil.hasSuspendModifier
38 import org.jetbrains.uast.UElement
39 import org.jetbrains.uast.UField
40 import org.jetbrains.uast.UMethod
41 import org.jetbrains.uast.UParameter
42 import org.jetbrains.uast.getContainingUMethod
43 
44 /**
45  * A wrapper for a [KtType] and the [KtAnalysisSession] needed to analyze it and the [PsiElement]
46  * that is the use site.
47  */
48 internal class KotlinTypeInfo
49 private constructor(
50     val analysisSession: KaSession?,
51     kaType: KaType?,
52     val context: PsiElement,
53     /**
54      * Override list of type arguments that should have been, but for some reason could not be,
55      * encapsulated within [kaType].
56      */
57     val overrideTypeArguments: List<KotlinTypeInfo>? = null,
58 ) {
59     constructor(context: PsiElement) : this(null, null, context)
60 
61     /** Make sure that any typealiases are fully expanded. */
62     val kaType =
63         analysisSession?.run { kaType?.fullyExpandedType }
64             ?: kaType?.let {
65                 error("cannot have non-null kaType ($kaType) with a null analysisSession")
66             }
67 
68     override fun toString(): String {
69         return "KotlinTypeInfo(${this@KotlinTypeInfo.kaType} for $context)"
70     }
71 
72     fun copy(
73         kaType: KaType? = this.kaType,
74         overrideTypeArguments: List<KotlinTypeInfo>? = this.overrideTypeArguments,
75     ) = KotlinTypeInfo(analysisSession, kaType, context, overrideTypeArguments)
76 
77     /**
78      * Finds the nullability of the [kaType]. If there is no [analysisSession] or [kaType], defaults
79      * to `null` to allow for other sources, like annotations and inferred nullability to take
80      * effect.
81      */
82     fun nullability(): TypeNullability? {
83         return if (analysisSession != null && kaType != null) {
84             analysisSession.run {
85                 if (useSiteSession.isInheritedGenericType(kaType)) {
86                     TypeNullability.UNDEFINED
87                 } else if (kaType.nullability == KaTypeNullability.NULLABLE) {
88                     TypeNullability.NULLABLE
89                 } else if (kaType.nullability == KaTypeNullability.NON_NULLABLE) {
90                     TypeNullability.NONNULL
91                 } else {
92                     // No nullability information, possibly a propagated platform type.
93                     null
94                 }
95             }
96         } else {
97             null
98         }
99     }
100 
101     /** Checks whether the [kaType] is a value class type. */
102     fun isValueClassType(): Boolean {
103         return kaType?.let { analysisSession?.typeForValueClass(it) } ?: false
104     }
105 
106     /**
107      * Creates [KotlinTypeInfo] for the component type of this [kaType], assuming it is an array.
108      */
109     fun forArrayComponentType(): KotlinTypeInfo {
110         return KotlinTypeInfo(
111             analysisSession,
112             analysisSession?.run { kaType?.arrayElementType },
113             context,
114         )
115     }
116 
117     /**
118      * Creates [KotlinTypeInfo] for the type argument at [index] of this [KotlinTypeInfo], assuming
119      * it is a class type.
120      */
121     fun forTypeArgument(index: Int): KotlinTypeInfo {
122         overrideTypeArguments?.getOrNull(index)?.let {
123             return it
124         }
125         return KotlinTypeInfo(
126             analysisSession,
127             analysisSession?.run {
128                 when (kaType) {
129                     is KaClassType -> kaType.typeArguments.getOrNull(index)?.type
130                     else -> null
131                 }
132             },
133             context,
134         )
135     }
136 
137     /**
138      * Creates [KotlinTypeInfo] for the outer class type of this [kaType], assuming it is a class.
139      */
140     fun forOuterClass(): KotlinTypeInfo {
141         return KotlinTypeInfo(
142             analysisSession,
143             analysisSession?.run {
144                 (kaType as? KaClassType)?.classId?.outerClassId?.let { outerClassId ->
145                     buildClassType(outerClassId) {
146                         // Add the parameters of the class type with nullability information.
147                         kaType.qualifiers
148                             .firstOrNull { it.name == outerClassId.shortClassName }
149                             ?.typeArguments
150                             ?.forEach { argument(it) }
151                     }
152                 }
153             },
154             context,
155         )
156     }
157 
158     /** Get a [KotlinTypeInfo] that represents a suspend function's `Continuation` parameter. */
159     fun forSyntheticContinuationParameter(returnType: KaType): KotlinTypeInfo {
160         // This cast is safe as this will only be called for a lambda function whose context will
161         // be [KtFunction].
162         val ktElement = context as KtElement
163         return analyze(ktElement) { syntheticContinuationParameter(context, returnType) }
164     }
165 
166     /** Get a [KotlinTypeInfo] that represents `Any?`. */
167     fun nullableAny(): KotlinTypeInfo {
168         // This cast is safe as this will only be called for a lambda function whose context will
169         // be [KtFunction].
170         val ktElement = context as KtElement
171         return analyze(ktElement) { KotlinTypeInfo(this, builtinTypes.nullableAny, context) }
172     }
173 
174     companion object {
175         /**
176          * Creates a [KotlinTypeInfo] instance from the given [context], with null values if the
177          * [KtType] for the [context] can't be resolved.
178          */
179         fun fromContext(context: PsiElement): KotlinTypeInfo {
180             return if (context is KtElement) {
181                 fromKtElement(context, context)
182             } else {
183                 when (val sourcePsi = (context as? UElement)?.sourcePsi) {
184                     is KtElement -> fromKtElement(sourcePsi, context)
185                     else -> {
186                         typeFromSyntheticElement(context)
187                     }
188                 }
189             }
190                 ?: KotlinTypeInfo(context)
191         }
192 
193         /**
194          * Try and compute [KotlinTypeInfo] from a [KtElement].
195          *
196          * Multiple different [PsiElement] subclasses can be generated from the same [KtElement] and
197          * require different views of its types. The [context] is provided to differentiate between
198          * them.
199          */
200         private fun fromKtElement(ktElement: KtElement, context: PsiElement): KotlinTypeInfo? =
201             when (ktElement) {
202                 is KtProperty -> {
203                     analyze(ktElement) {
204                         val ktType =
205                             when {
206                                 // If the context is the backing field then use the type of the
207                                 // delegate, if any.
208                                 context is UField -> ktElement.delegateExpression?.expressionType
209                                 else -> null
210                             }
211                                 ?: ktElement.returnType
212                         KotlinTypeInfo(this, ktType, ktElement)
213                     }
214                 }
215                 is KtCallableDeclaration -> {
216                     analyze(ktElement) {
217                         val ktType =
218                             if (ktElement is KtFunction && ktElement.isSuspend()) {
219                                 // A suspend function is transformed by Kotlin to return Any?
220                                 // instead of its actual return type.
221                                 builtinTypes.nullableAny
222                             } else {
223                                 ktElement.returnType
224                             }
225                         KotlinTypeInfo(this, ktType, ktElement)
226                     }
227                 }
228                 is KtTypeReference ->
229                     analyze(ktElement) { KotlinTypeInfo(this, ktElement.type, ktElement) }
230                 is KtPropertyAccessor ->
231                     analyze(ktElement) { KotlinTypeInfo(this, ktElement.returnType, ktElement) }
232                 is KtClass -> {
233                     analyze(ktElement) {
234                         // If this is a named class or object then return a KotlinTypeInfo for the
235                         // class. If it is generic then the type parameters will be used as the
236                         // type arguments.
237                         (ktElement.symbol as? KaNamedClassSymbol)?.let { symbol ->
238                             KotlinTypeInfo(this, symbol.defaultType, ktElement)
239                         }
240                     }
241                 }
242                 is KtTypeAlias -> {
243                     analyze(ktElement) {
244                         KotlinTypeInfo(this, ktElement.getTypeReference()?.type, ktElement)
245                     }
246                 }
247                 else -> null
248             }
249 
250         /**
251          * Try and compute the type from a synthetic elements, e.g. a property setter.
252          *
253          * In order to get this far the [context] is either not a [UElement], or it has a null
254          * [UElement.sourcePsi]. That means it is most likely a parameter in a synthetic method
255          * created for use by code that operates on a "Psi" view of the source, i.e. java code. This
256          * method will attempt to reverse engineer the "Kt" -> "Psi" mapping to find the real Kotlin
257          * types.
258          */
259         private fun typeFromSyntheticElement(context: PsiElement): KotlinTypeInfo? {
260             // If this is not a UParameter in a UMethod then it is an unknown synthetic element so
261             // just return.
262             val containingMethod = (context as? UParameter)?.getContainingUMethod() ?: return null
263 
264             // Get the parameter index from the containing methods `uastParameters` as the parameter
265             // is a `UParameter`.
266             val parameterIndex = containingMethod.uastParameters.indexOf(context)
267 
268             return when (val sourcePsi = containingMethod.sourcePsi) {
269                 is KtProperty -> {
270                     // This is the parameter of a synthetic setter, so get its type from the
271                     // containing method.
272                     fromContext(containingMethod)
273                 }
274                 is KtParameter -> {
275                     // The underlying source representation of the synthetic method is a parameter,
276                     // most likely a parameter of the primary constructor. In which case the
277                     // synthetic method is most like a property setter. Whatever it may be, use the
278                     // type of the parameter as it is most likely to be the correct type.
279                     fromKtElement(sourcePsi, context)
280                 }
281                 is KtClass -> {
282                     // The underlying source representation of the synthetic method is a whole
283                     // class.
284                     typeFromKtClass(parameterIndex, containingMethod, sourcePsi)
285                 }
286                 is KtFunction -> {
287                     if (
288                         sourcePsi.isSuspend() &&
289                             parameterIndex == containingMethod.parameters.size - 1
290                     ) {
291                         // Compute the [KotlinTypeInfo] for the suspend function's synthetic
292                         // [kotlin.coroutines.Continuation] parameter.
293                         analyze(sourcePsi) {
294                             val returnKtType = sourcePsi.returnType
295                             syntheticContinuationParameter(sourcePsi, returnKtType)
296                         }
297                     } else null
298                 }
299                 is KtPropertyAccessor ->
300                     analyze(sourcePsi) {
301                         // Getters and setters are always the same type as the property so use its
302                         // type.
303                         fromKtElement(sourcePsi.property, context)
304                     }
305                 else -> null
306             }
307         }
308 
309         /** Check if this is a `suspend` function. */
310         private fun KtFunction.isSuspend() = modifierList?.hasSuspendModifier() == true
311 
312         /**
313          * Create a [KotlinTypeInfo] that represents the continuation parameter of a `suspend`
314          * function with [returnKtType].
315          *
316          * Ideally, this would create a [KtNonErrorClassType] for `Continuation<$returnType$>`,
317          * where `$returnType$` is the return type of the Kotlin suspend function but while that
318          * works in K2 it fails in K1 as it cannot resolve the `Continuation` type even though it is
319          * in the Kotlin stdlib which will be on the classpath.
320          *
321          * Fortunately, doing that is not strictly necessary as the [KtType] is only used to
322          * retrieve nullability for the `Continuation` type and its type argument. So, this uses
323          * non-nullable [Any] as the fake type for `Continuation` (as that is always non-nullable)
324          * and stores the suspend function's return type in [KotlinTypeInfo.overrideTypeArguments]
325          * from where it will be retrieved.
326          */
327         internal fun KaSession.syntheticContinuationParameter(
328             context: PsiElement,
329             returnKtType: KaType
330         ): KotlinTypeInfo {
331             val returnTypeInfo = KotlinTypeInfo(this, returnKtType, context)
332             val fakeContinuationKtType = builtinTypes.any
333             return KotlinTypeInfo(this, fakeContinuationKtType, context, listOf(returnTypeInfo))
334         }
335 
336         /** Try and get the type for [parameterIndex] in [containingMethod] from the [ktClass]. */
337         private fun typeFromKtClass(
338             parameterIndex: Int,
339             containingMethod: UMethod,
340             ktClass: KtClass
341         ) =
342             when {
343                 ktClass.isData() && containingMethod.name == "copy" -> {
344                     // The parameters in the copy constructor correspond to the parameters in the
345                     // primary constructor so find the corresponding parameter in the primary
346                     // constructor and use its type.
347                     ktClass.primaryConstructor?.let { primaryConstructor ->
348                         val ktParameter = primaryConstructor.valueParameters[parameterIndex]
349                         analyze(ktParameter) {
350                             KotlinTypeInfo(
351                                 this,
352                                 ktParameter.returnType,
353                                 ktParameter,
354                             )
355                         }
356                     }
357                 }
358                 else -> null
359             }
360 
361         // Mimic `hasInheritedGenericType` in `...uast.kotlin.FirKotlinUastResolveProviderService`
362         fun KaSession.isInheritedGenericType(ktType: KaType): Boolean {
363             return ktType is KaTypeParameterType &&
364                 // explicitly nullable, e.g., T?
365                 !ktType.isMarkedNullable &&
366                 // non-null upper bound, e.g., T : Any
367                 ktType.canBeNull
368         }
369 
370         // Mimic `typeForValueClass` in
371         // `org.jetbrains.kotlin.light.classes.symbol.classes.symbolLightClassUtils.kt`
372         private fun KaSession.typeForValueClass(type: KaType): Boolean {
373             val symbol = type.expandedSymbol as? KaNamedClassSymbol ?: return false
374             return symbol.isInline
375         }
376     }
377 }
378