• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.psi
18 
19 import com.android.tools.metalava.model.AnnotationItem
20 import com.android.tools.metalava.model.CallableItem
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.ClassTypeItem
23 import com.android.tools.metalava.model.PrimitiveTypeItem
24 import com.android.tools.metalava.model.ReferenceTypeItem
25 import com.android.tools.metalava.model.TypeArgumentTypeItem
26 import com.android.tools.metalava.model.TypeItem
27 import com.android.tools.metalava.model.TypeModifiers
28 import com.android.tools.metalava.model.TypeNullability
29 import com.android.tools.metalava.model.TypeParameterItem
30 import com.android.tools.metalava.model.TypeParameterScope
31 import com.android.tools.metalava.model.VariableTypeItem
32 import com.android.tools.metalava.model.WildcardTypeItem
33 import com.android.tools.metalava.model.type.ContextNullability
34 import com.android.tools.metalava.model.type.DefaultTypeItemFactory
35 import com.android.tools.metalava.model.type.DefaultTypeModifiers
36 import com.android.tools.metalava.model.type.MethodFingerprint
37 import com.intellij.psi.PsiAnnotation
38 import com.intellij.psi.PsiArrayType
39 import com.intellij.psi.PsiClassType
40 import com.intellij.psi.PsiElement
41 import com.intellij.psi.PsiEllipsisType
42 import com.intellij.psi.PsiNameHelper
43 import com.intellij.psi.PsiPrimitiveType
44 import com.intellij.psi.PsiType
45 import com.intellij.psi.PsiTypeParameter
46 import com.intellij.psi.PsiTypes
47 import com.intellij.psi.PsiWildcardType
48 import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
49 import org.jetbrains.kotlin.analysis.api.types.KaClassType
50 import org.jetbrains.kotlin.analysis.api.types.KaFunctionType
51 import org.jetbrains.kotlin.analysis.api.types.KaTypeMappingMode
52 import org.jetbrains.kotlin.psi.KtElement
53 import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
54 import org.jetbrains.uast.kotlin.isKotlin
55 
56 /**
57  * Encapsulates a [PsiType] and an optional context [PsiElement] for use with [PsiTypeItemFactory].
58  */
59 data class PsiTypeInfo(val psiType: PsiType, val context: PsiElement? = null)
60 
61 /**
62  * Creates [PsiTypeItem]s from [PsiType]s and an optional context [PsiElement], encapsulated within
63  * [PsiTypeInfo].
64  */
65 internal class PsiTypeItemFactory(
66     private val assembler: PsiCodebaseAssembler,
67     typeParameterScope: TypeParameterScope
68 ) : DefaultTypeItemFactory<PsiTypeInfo, PsiTypeItemFactory>(typeParameterScope) {
69 
70     private val codebase = assembler.codebase
71 
72     /** Construct a [PsiTypeItemFactory] suitable for creating types within [classItem]. */
73     fun from(classItem: ClassItem): PsiTypeItemFactory {
74         val scope = TypeParameterScope.from(classItem)
75         return if (scope.isEmpty()) this else PsiTypeItemFactory(assembler, scope)
76     }
77 
78     /** Construct a [PsiTypeItemFactory] suitable for creating types within [callableItem]. */
79     fun from(callableItem: CallableItem): PsiTypeItemFactory {
80         val scope = TypeParameterScope.from(callableItem)
81         return if (scope.isEmpty()) this else PsiTypeItemFactory(assembler, scope)
82     }
83 
84     override fun self() = this
85 
86     override fun createNestedFactory(scope: TypeParameterScope) =
87         PsiTypeItemFactory(assembler, scope)
88 
89     override fun getType(
90         underlyingType: PsiTypeInfo,
91         contextNullability: ContextNullability,
92         // The isVarArg is unused here as that information is encoded in the [PsiType] using the
93         // [PsiEllipsisType] extension of [PsiArrayType].
94         isVarArg: Boolean,
95     ): PsiTypeItem {
96         return getType(underlyingType.psiType, underlyingType.context, contextNullability)
97     }
98 
99     override fun getMethodParameterType(
100         underlyingParameterType: PsiTypeInfo,
101         itemAnnotations: List<AnnotationItem>,
102         fingerprint: MethodFingerprint,
103         parameterIndex: Int,
104         isVarArg: Boolean
105     ): TypeItem {
106         // Workaround for b/388030457, b/388508139: when a vararg is used in kotlin for a parameter
107         // that isn't final, it should be a regular PsiArrayType, not a PsiEllipsisType, but psi
108         // gets it wrong in some cases.
109         val fixedUnderlyingParameterType =
110             if (
111                 underlyingParameterType.context?.isKotlin() == true &&
112                     underlyingParameterType.psiType is PsiEllipsisType &&
113                     parameterIndex + 1 != fingerprint.parameterCount
114             ) {
115                 underlyingParameterType.copy(
116                     psiType = underlyingParameterType.psiType.toArrayType()
117                 )
118             } else {
119                 underlyingParameterType
120             }
121 
122         return super.getMethodParameterType(
123             fixedUnderlyingParameterType,
124             itemAnnotations,
125             fingerprint,
126             parameterIndex,
127             isVarArg
128         )
129     }
130 
131     /**
132      * Returns a [PsiTypeItem] representing the [psiType]. The [context] is used to get nullability
133      * information for Kotlin types.
134      */
135     internal fun getType(
136         psiType: PsiType,
137         context: PsiElement? = null,
138         contextNullability: ContextNullability = ContextNullability.none,
139     ): PsiTypeItem {
140         val kotlinTypeInfo =
141             if (context != null && isKotlin(context)) {
142                 KotlinTypeInfo.fromContext(context)
143             } else {
144                 null
145             }
146 
147         // Note: We do *not* cache these; it turns out that storing PsiType instances
148         // in a map is bad for performance; it has a very expensive equals operation
149         // for some type comparisons (and we sometimes end up with unexpected results,
150         // e.g. where we fetch an "equals" type from the map but its representation
151         // is slightly different to what was intended
152         return createTypeItem(psiType, kotlinTypeInfo, contextNullability)
153     }
154 
155     /** Get a [PsiClassTypeItem] to represent the [PsiClassItem]. */
156     fun getClassTypeForClass(psiClassItem: PsiClassItem): PsiClassTypeItem {
157         // Create a PsiType for the class. Specifies `PsiSubstitutor.EMPTY` so that if the class
158         // has any type parameters then the PsiType will include references to those parameters.
159         val psiTypeWithTypeParametersIfAny = assembler.getClassType(psiClassItem.psiClass)
160         // Create a PsiTypeItemFactory that will correctly resolve any references to the class's
161         // type parameters.
162         val classTypeItemFactory = from(psiClassItem)
163         return classTypeItemFactory.createTypeItem(
164             psiTypeWithTypeParametersIfAny,
165             KotlinTypeInfo.fromContext(psiClassItem.psiClass),
166             contextNullability = ContextNullability.forceNonNull,
167             creatingClassTypeForClass = true,
168         ) as PsiClassTypeItem
169     }
170 
171     /** Get a [VariableTypeItem] to represent [PsiTypeParameterItem]. */
172     fun getVariableTypeForTypeParameter(
173         psiTypeParameterItem: PsiTypeParameterItem
174     ): VariableTypeItem {
175         val psiTypeParameter = psiTypeParameterItem.psi()
176         val psiType = assembler.getClassType(psiTypeParameter)
177         return createVariableTypeItem(
178             psiType,
179             null,
180             psiTypeParameterItem,
181             ContextNullability.forceUndefined,
182         )
183     }
184 
185     /**
186      * Attempts to create a type for the [ktElement]. Returns null if the Kotlin type for the
187      * element could not be converted to a [PsiType]. This should only be used when the element has
188      * no [PsiElement] with a defined [PsiType].
189      */
190     @OptIn(KaExperimentalApi::class)
191     internal fun getTypeForKtElement(
192         ktElement: KtElement,
193     ): PsiTypeItem? {
194         val kotlinTypeInfo = KotlinTypeInfo.fromContext(ktElement)
195         val psiType =
196             kotlinTypeInfo.analysisSession?.run {
197                 kotlinTypeInfo.kaType?.asPsiType(
198                     ktElement,
199                     allowErrorTypes = false,
200                     mode = KaTypeMappingMode.DEFAULT_UAST
201                 )
202             }
203         return psiType?.let { createTypeItem(it, kotlinTypeInfo, ContextNullability.none) }
204     }
205 
206     // PsiTypeItem factory methods
207 
208     /** Creates modifiers based on the annotations of the [type]. */
209     private fun createTypeModifiers(
210         type: PsiType,
211         kotlinType: KotlinTypeInfo?,
212         contextNullability: ContextNullability,
213     ): TypeModifiers {
214         val typeAnnotations =
215             type.annotations.mapNotNull { anno ->
216                 // SLC adds JetBrain nullness annotation on types.
217                 if (anno.isJetBrainNullnessAnnotation) null
218                 else PsiAnnotationItem.create(codebase, anno)
219             }
220         // Compute the nullability, factoring in any context nullability, kotlin types and
221         // type annotations.
222         val nullability = contextNullability.compute(kotlinType?.nullability(), typeAnnotations)
223         return DefaultTypeModifiers.create(typeAnnotations, nullability)
224     }
225 
226     private val PsiAnnotation.isJetBrainNotNull: Boolean
227         get() {
228             return qualifiedName == org.jetbrains.annotations.NotNull::class.qualifiedName
229         }
230 
231     private val PsiAnnotation.isJetBrainNullable: Boolean
232         get() {
233             return qualifiedName == org.jetbrains.annotations.Nullable::class.qualifiedName
234         }
235 
236     private val PsiAnnotation.isJetBrainNullnessAnnotation: Boolean
237         get() {
238             return isJetBrainNotNull || isJetBrainNullable
239         }
240 
241     /** Create a [PsiTypeItem]. */
242     private fun createTypeItem(
243         psiType: PsiType,
244         kotlinType: KotlinTypeInfo?,
245         contextNullability: ContextNullability = ContextNullability.none,
246         creatingClassTypeForClass: Boolean = false,
247     ): PsiTypeItem {
248         return when (psiType) {
249             is PsiPrimitiveType ->
250                 createPrimitiveTypeItem(
251                     psiType = psiType,
252                     kotlinType = kotlinType,
253                 )
254             is PsiArrayType ->
255                 createArrayTypeItem(
256                     psiType = psiType,
257                     kotlinType = kotlinType,
258                     contextNullability = contextNullability,
259                 )
260             is PsiClassType -> {
261                 val typeParameterItem =
262                     when (val psiClass = psiType.resolve()) {
263                         // If the type resolves to a PsiTypeParameter then the TypeParameterItem
264                         // must exist.
265                         is PsiTypeParameter -> {
266                             val name = psiClass.simpleName
267                             typeParameterScope.getTypeParameter(name)
268                         }
269                         // If the type could not be resolved then the TypeParameterItem might
270                         // exist.
271                         null ->
272                             psiType.className?.let { name ->
273                                 typeParameterScope.findTypeParameter(name)
274                             }
275                         // Else it is not a TypeParameterItem.
276                         else -> null
277                     }
278 
279                 if (typeParameterItem != null) {
280                     // The type parameters of a class type for the class definition don't have
281                     // defined nullability (their bounds might).
282                     val correctedContextNullability =
283                         if (creatingClassTypeForClass) {
284                             ContextNullability.forceUndefined
285                         } else {
286                             contextNullability
287                         }
288                     createVariableTypeItem(
289                         psiType = psiType,
290                         kotlinType = kotlinType,
291                         typeParameterItem = typeParameterItem,
292                         contextNullability = correctedContextNullability,
293                     )
294                 } else {
295                     if (kotlinType?.kaType is KaFunctionType) {
296                         createLambdaTypeItem(
297                             psiType = psiType,
298                             kotlinType = kotlinType,
299                             contextNullability = contextNullability,
300                         )
301                     } else {
302                         val classType =
303                             createClassTypeItem(
304                                 psiType = psiType,
305                                 kotlinType = kotlinType,
306                                 contextNullability = contextNullability,
307                                 creatingClassTypeForClass = creatingClassTypeForClass,
308                             )
309                         checkForTypeAliasSubstitution(classType, contextNullability) ?: classType
310                     }
311                 }
312             }
313             is PsiWildcardType ->
314                 createWildcardTypeItem(
315                     psiType = psiType,
316                     kotlinType = kotlinType,
317                 )
318             // There are other [PsiType]s, but none can appear in API surfaces.
319             else ->
320                 throw IllegalStateException(
321                     "Invalid type in API surface: $psiType${
322                     if (kotlinType != null) {
323                         val location = PsiFileLocation.fromPsiElement(kotlinType.context)
324                         " for element ${location.baselineKey?.elementId()}" +
325                             " in file ${location.path}:${location.line}"
326                     } else ""
327                 }"
328                 )
329         }
330     }
331 
332     /**
333      * Checks if there are is a type alias matching the name of the [classTypeItem]. If there is,
334      * converts the aliased type for this usage (mapping type parameters and adjusting nullability).
335      *
336      * If there is no matching type alias, returns null.
337      */
338     private fun checkForTypeAliasSubstitution(
339         classTypeItem: PsiClassTypeItem,
340         contextNullability: ContextNullability
341     ): PsiTypeItem? {
342         // Don't bother checking for type aliases in non-KMP codebases because the substitution will
343         // already have happened in the UAST representation. Substitution won't have happened for
344         // expect/actual type aliases used from a common source set because the type is platform
345         // dependent. Metalava is just modeling the android/jvm platform, so the substitution needs
346         // to happen here.
347         if (!codebase.isMultiplatform) return null
348 
349         val typeAlias = codebase.findTypeAlias(classTypeItem.qualifiedName) ?: return null
350 
351         // Map type parameters of the type alias, if applicable.
352         val convertedType =
353             if (typeAlias.typeParameterList.isEmpty()) {
354                 typeAlias.aliasedType
355             } else {
356                 val typeParameterBindings =
357                     typeAlias.typeParameterList
358                         .zip(classTypeItem.arguments.map { it as ReferenceTypeItem })
359                         .toMap()
360                 typeAlias.aliasedType.convertType(typeParameterBindings)
361             }
362 
363         // Update type nullability: if the context requires a specific nullability, use that.
364         // If the aliased type is nullable, that propagates to the usage. Otherwise, use the
365         // nullability set by the usage site.
366         val nullability =
367             contextNullability.forcedNullability
368                 ?: if (typeAlias.aliasedType.modifiers.isNullable) {
369                     TypeNullability.NULLABLE
370                 } else {
371                     classTypeItem.modifiers.nullability
372                 }
373 
374         return convertedType.substitute(nullability) as PsiTypeItem
375     }
376 
377     /** Create a [PsiPrimitiveTypeItem]. */
378     private fun createPrimitiveTypeItem(
379         psiType: PsiPrimitiveType,
380         kotlinType: KotlinTypeInfo?,
381     ) =
382         PsiPrimitiveTypeItem(
383             psiType = psiType,
384             kind = getKind(psiType),
385             modifiers = createTypeModifiers(psiType, kotlinType, ContextNullability.forceNonNull),
386             kotlinTypeInfo = kotlinType,
387         )
388 
389     /** Get the [PrimitiveTypeItem.Primitive] enum from the [PsiPrimitiveType]. */
390     private fun getKind(type: PsiPrimitiveType): PrimitiveTypeItem.Primitive {
391         return when (type) {
392             PsiTypes.booleanType() -> PrimitiveTypeItem.Primitive.BOOLEAN
393             PsiTypes.byteType() -> PrimitiveTypeItem.Primitive.BYTE
394             PsiTypes.charType() -> PrimitiveTypeItem.Primitive.CHAR
395             PsiTypes.doubleType() -> PrimitiveTypeItem.Primitive.DOUBLE
396             PsiTypes.floatType() -> PrimitiveTypeItem.Primitive.FLOAT
397             PsiTypes.intType() -> PrimitiveTypeItem.Primitive.INT
398             PsiTypes.longType() -> PrimitiveTypeItem.Primitive.LONG
399             PsiTypes.shortType() -> PrimitiveTypeItem.Primitive.SHORT
400             PsiTypes.voidType() -> PrimitiveTypeItem.Primitive.VOID
401             else ->
402                 throw java.lang.IllegalStateException(
403                     "Invalid primitive type in API surface: $type"
404                 )
405         }
406     }
407 
408     /** Create a [PsiArrayTypeItem]. */
409     private fun createArrayTypeItem(
410         psiType: PsiArrayType,
411         kotlinType: KotlinTypeInfo?,
412         contextNullability: ContextNullability,
413     ) =
414         PsiArrayTypeItem(
415             psiType = psiType,
416             componentType =
417                 createTypeItem(
418                     psiType.componentType,
419                     kotlinType?.forArrayComponentType(),
420                     //  Pass in the [ContextNullability.forComponentType] just in case this is the
421                     // return type of an annotation method, or in other words the type of an
422                     // annotation attribute.
423                     contextNullability.forComponentType(),
424                 ),
425             isVarargs = psiType is PsiEllipsisType,
426             modifiers = createTypeModifiers(psiType, kotlinType, contextNullability),
427             kotlinTypeInfo = kotlinType,
428         )
429 
430     /** Create a [PsiClassTypeItem]. */
431     private fun createClassTypeItem(
432         psiType: PsiClassType,
433         kotlinType: KotlinTypeInfo?,
434         contextNullability: ContextNullability,
435         creatingClassTypeForClass: Boolean = false,
436     ): PsiClassTypeItem {
437         val qualifiedName = psiType.computeQualifiedName()
438         return PsiClassTypeItem(
439             codebase = codebase,
440             psiType = psiType,
441             qualifiedName = qualifiedName,
442             arguments =
443                 computeTypeArguments(
444                     psiType,
445                     kotlinType,
446                     creatingClassTypeForClass,
447                 ),
448             outerClassType =
449                 computeOuterClass(
450                     psiType,
451                     kotlinType,
452                     creatingClassTypeForClass = true,
453                 ),
454             modifiers = createTypeModifiers(psiType, kotlinType, contextNullability),
455             kotlinTypeInfo = kotlinType,
456         )
457     }
458 
459     /** Compute the [PsiClassTypeItem.arguments]. */
460     private fun computeTypeArguments(
461         psiType: PsiClassType,
462         kotlinType: KotlinTypeInfo?,
463         creatingClassTypeForClass: Boolean = false,
464     ): List<TypeArgumentTypeItem> {
465         val psiParameters =
466             psiType.parameters.toList().ifEmpty {
467                 // Sometimes, when a PsiClassType's arguments are empty it is not because there
468                 // are no arguments but due to a bug in Psi somewhere. Check to see if the
469                 // kotlin type info has a different set of type arguments and if it has then use
470                 // that to fix the type, otherwise just assume it should be empty.
471                 kotlinType?.kaType?.let { ktType ->
472                     (ktType as? KaClassType)?.typeArguments?.ifNotEmpty {
473                         fixUpPsiTypeMissingTypeArguments(psiType, kotlinType)
474                     }
475                 }
476                     ?: emptyList()
477             }
478 
479         return psiParameters.mapIndexed { i, param ->
480             val forTypeArgument = kotlinType?.forTypeArgument(i)
481             createTypeItem(
482                 param,
483                 forTypeArgument,
484                 creatingClassTypeForClass = creatingClassTypeForClass
485             )
486                 as TypeArgumentTypeItem
487         }
488     }
489 
490     /**
491      * Fix up a [PsiClassType] that is missing type arguments.
492      *
493      * This seems to happen in a very limited situation. The example that currently fails, but there
494      * may be more, appears to be due to an impedance mismatch between Kotlin collections and Java
495      * collections.
496      *
497      * Assume the following Kotlin and Java classes from the standard libraries:
498      * ```
499      * package kotlin.collections
500      * public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {
501      *     ...
502      *     public fun addAll(elements: Collection<E>): Boolean
503      *     public fun containsAll(elements: Collection<E>): Boolean
504      *     public fun removeAll(elements: Collection<E>): Boolean
505      *     public fun retainAll(elements: Collection<E>): Boolean
506      *     ...
507      * }
508      *
509      * package java.util;
510      * public interface Collection<E> extends Iterable<E> {
511      *     boolean addAll(Collection<? extends E> c);
512      *     boolean containsAll(Collection<?> c);
513      *     boolean removeAll(Collection<?> c);
514      *     boolean retainAll(Collection<?> c);
515      * }
516      * ```
517      *
518      * The given the following class this function is called for the types of the parameters of the
519      * `removeAll`, `retainAll` and `containsAll` methods but not for the `addAll` method.
520      *
521      * ```
522      * abstract class Foo<Z> : MutableCollection<Z> {
523      *     override fun addAll(elements: Collection<Z>): Boolean = true
524      *     override fun containsAll(elements: Collection<Z>): Boolean = true
525      *     override fun removeAll(elements: Collection<Z>): Boolean = true
526      *     override fun retainAll(elements: Collection<Z>): Boolean = true
527      * }
528      * ```
529      *
530      * Metalava and/or the underlying Psi model, appears to treat the `MutableCollection` in `Foo`
531      * as if it was a `java.util.Collection`, even though it is referring to
532      * `kotlin.collections.Collection`. So, both `Foo` and `MutableCollection` are reported as
533      * extending `java.util.Collection`.
534      *
535      * So, you have the following two methods (mapped into Java classes):
536      *
537      * From `java.util.Collection` itself:
538      * ```
539      *      boolean containsAll(java.util.Collection<?> c);
540      * ```
541      *
542      * And from `kotlin.collections.MutableCollection`:
543      * ```
544      *     public fun containsAll(elements: java.util.Collection<E>): Boolean
545      * ```
546      *
547      * But, strictly speaking that is not allowed for a couple of reasons:
548      * 1. `java.util.Collection` is not covariant because it is mutable. However,
549      *    `kotlin.collections.Collection` is covariant because it immutable.
550      * 2. `Collection<Z>` is more restrictive than `Collection<?>`. Java will let you try and remove
551      *    a collection of `Number` from a collection of `String` even though it is meaningless.
552      *    Kotlin's approach is more correct but only possible because its `Collection` is immutable.
553      *
554      * The [kotlinType] seems to have handled that issue reasonably well producing a type of
555      * `java.util.Collection<? extends Z>`. Unfortunately, when that is converted to a `PsiType` the
556      * `PsiType` for `Z` does not resolve to a `PsiTypeParameter`.
557      *
558      * The wildcard is correct.
559      */
560     @OptIn(KaExperimentalApi::class)
561     private fun fixUpPsiTypeMissingTypeArguments(
562         psiType: PsiClassType,
563         kotlinType: KotlinTypeInfo
564     ): List<PsiType> {
565         if (kotlinType.analysisSession == null || kotlinType.kaType == null) return emptyList()
566 
567         val kaType = kotlinType.kaType as KaClassType
568 
569         // Restrict this fix to the known issue.
570         val className = psiType.className
571         if (className != "Collection") {
572             return emptyList()
573         }
574 
575         // Convert the KtType to PsiType.
576         //
577         // Convert the whole type rather than extracting the type parameters and converting them
578         // separately because the result depends on the parameterized class, i.e.
579         // `java.util.Collection` in this case. Also, type arguments can be wildcards but
580         // wildcards cannot exist on their own. It will probably be relying on undefined
581         // behavior to try and convert a wildcard on their own.
582         val psiTypeFromKotlin =
583             kotlinType.analysisSession.run {
584                 // Use the default mode so that the resulting psiType is
585                 // `java.util.Collection<? extends Z>`.
586                 val mode = KaTypeMappingMode.DEFAULT
587                 kaType.asPsiType(kotlinType.context, false, mode = mode)
588             } as? PsiClassType
589         return psiTypeFromKotlin?.parameters?.toList() ?: emptyList()
590     }
591 
592     /** Compute the [PsiClassTypeItem.outerClassType]. */
593     private fun computeOuterClass(
594         psiType: PsiClassType,
595         kotlinType: KotlinTypeInfo?,
596         creatingClassTypeForClass: Boolean = false,
597     ): PsiClassTypeItem? {
598         // TODO(b/300081840): this drops annotations on the outer class
599         return PsiNameHelper.getOuterClassReference(psiType.canonicalText).let { outerClassName ->
600             // [PsiNameHelper.getOuterClassReference] returns an empty string if there is no
601             // outer class reference. If the type is not a nested type, it returns the package
602             // name (e.g. for "java.lang.String" it returns "java.lang").
603             if (outerClassName == "" || assembler.findPsiPackage(outerClassName) != null) {
604                 null
605             } else {
606                 val psiOuterClassType =
607                     assembler.createPsiType(
608                         outerClassName,
609                         // The context psi element allows variable types to be resolved (with no
610                         // context, they would be interpreted as class types). The [psiContext]
611                         // works in most cases, but is null when creating a type directly from a
612                         // class declaration, so the resolved [psiType] provides context then.
613                         psiType.psiContext ?: psiType.resolve()
614                     )
615                 createTypeItem(
616                     psiOuterClassType,
617                     kotlinType?.forOuterClass(),
618                     // An outer class reference can't be null.
619                     contextNullability = ContextNullability.forceNonNull,
620                     creatingClassTypeForClass = creatingClassTypeForClass,
621                 )
622                     as PsiClassTypeItem
623             }
624         }
625     }
626 
627     /** Support mapping from boxed types back to their primitive type. */
628     private val boxedToPsiPrimitiveType =
629         mapOf(
630             "java.lang.Byte" to PsiTypes.byteType(),
631             "java.lang.Double" to PsiTypes.doubleType(),
632             "java.lang.Float" to PsiTypes.floatType(),
633             "java.lang.Integer" to PsiTypes.intType(),
634             "java.lang.Long" to PsiTypes.longType(),
635             "java.lang.Short" to PsiTypes.shortType(),
636             "java.lang.Boolean" to PsiTypes.booleanType(),
637             // This is not strictly speaking a boxed -> unboxed mapping, but it fits in nicely
638             // with the others.
639             "kotlin.Unit" to PsiTypes.voidType(),
640         )
641 
642     /** If the type item is not nullable and is a boxed type then map it to the unboxed type. */
643     private fun unboxTypeWherePossible(typeItem: TypeItem): TypeItem {
644         if (
645             typeItem is ClassTypeItem && typeItem.modifiers.nullability == TypeNullability.NONNULL
646         ) {
647             boxedToPsiPrimitiveType[typeItem.qualifiedName]?.let { psiPrimitiveType ->
648                 return createPrimitiveTypeItem(psiPrimitiveType, null)
649             }
650         }
651         return typeItem
652     }
653 
654     /** An input parameter of type X is represented as a "? super X" in the `Function<X>` class. */
655     private fun unwrapInputType(typeItem: TypeItem): TypeItem {
656         return unboxTypeWherePossible((typeItem as? WildcardTypeItem)?.superBound ?: typeItem)
657     }
658 
659     /**
660      * The return type of type X can be represented as a "? extends X" in the `Function<X>` class.
661      */
662     private fun unwrapOutputType(typeItem: TypeItem): TypeItem {
663         return unboxTypeWherePossible((typeItem as? WildcardTypeItem)?.extendsBound ?: typeItem)
664     }
665 
666     /**
667      * Create a [PsiLambdaTypeItem].
668      *
669      * Extends a [PsiClassTypeItem] and then deconstructs the type arguments of Kotlin `Function<N>`
670      * to extract the receiver, input and output types. This makes heavy use of the
671      * [KotlinTypeInfo.kaType] property of [kotlinType] which must be a [KtFunctionalType]. That has
672      * the information necessary to determine which of the Kotlin `Function<N>` class's type
673      * arguments are the receiver (if any) and which are input parameters. The last type argument is
674      * always the return type.
675      */
676     private fun createLambdaTypeItem(
677         psiType: PsiClassType,
678         kotlinType: KotlinTypeInfo,
679         contextNullability: ContextNullability,
680     ): PsiLambdaTypeItem {
681         val qualifiedName = psiType.computeQualifiedName()
682 
683         val kaType = kotlinType.kaType as KaFunctionType
684 
685         val isSuspend = kaType.isSuspend
686 
687         val actualKotlinType =
688             kotlinType.copy(
689                 overrideTypeArguments =
690                     // Compute a set of [KtType]s corresponding to the type arguments in the
691                     // underlying `kotlin.jvm.functions.Function*`.
692                     buildList {
693                         // The optional lambda receiver is the first type argument.
694                         kaType.receiverType?.let { add(kotlinType.copy(kaType = it)) }
695                         // The lambda's explicit parameters appear next.
696                         kaType.parameterTypes.mapTo(this) { kotlinType.copy(kaType = it) }
697                         // A `suspend` lambda is transformed by Kotlin in the same way that a
698                         // `suspend` function is, i.e. an additional continuation parameter is added
699                         // at the end of the explicit parameters that encapsulates the return type
700                         // and the return type is changed to `Any?`.
701                         if (isSuspend) {
702                             // Create a KotlinTypeInfo for the continuation parameter that
703                             // encapsulates the actual return type.
704                             add(kotlinType.forSyntheticContinuationParameter(kaType.returnType))
705                             // Add the `Any?` for the return type.
706                             add(kotlinType.nullableAny())
707                         } else {
708                             // As it is not a `suspend` lambda add the return type last.
709                             add(kotlinType.copy(kaType = kaType.returnType))
710                         }
711                     }
712             )
713 
714         // Get the type arguments for the kotlin.jvm.functions.Function<X> class.
715         val typeArguments = computeTypeArguments(psiType, actualKotlinType)
716 
717         // If the function has a receiver then it is the first type argument.
718         var firstParameterTypeIndex = 0
719         val receiverType =
720             if (kaType.hasReceiver) {
721                 // The first parameter type is now the second type argument.
722                 firstParameterTypeIndex = 1
723                 unwrapInputType(typeArguments[0])
724             } else {
725                 null
726             }
727 
728         // The last type argument is always the return type.
729         val returnType = unwrapOutputType(typeArguments.last())
730         val lastParameterTypeIndex = typeArguments.size - 1
731 
732         // Get the parameter types, excluding the optional receiver and the return type.
733         val parameterTypes =
734             typeArguments
735                 .subList(firstParameterTypeIndex, lastParameterTypeIndex)
736                 .map { unwrapInputType(it) }
737                 .toList()
738 
739         return PsiLambdaTypeItem(
740             codebase = codebase,
741             psiType = psiType,
742             qualifiedName = qualifiedName,
743             arguments = typeArguments,
744             outerClassType = computeOuterClass(psiType, actualKotlinType),
745             modifiers = createTypeModifiers(psiType, actualKotlinType, contextNullability),
746             isSuspend = isSuspend,
747             receiverType = receiverType,
748             parameterTypes = parameterTypes,
749             returnType = returnType,
750             kotlinTypeInfo = kotlinType,
751         )
752     }
753 
754     /** Create a [PsiVariableTypeItem]. */
755     private fun createVariableTypeItem(
756         psiType: PsiClassType,
757         kotlinType: KotlinTypeInfo?,
758         typeParameterItem: TypeParameterItem,
759         contextNullability: ContextNullability,
760     ) =
761         PsiVariableTypeItem(
762             psiType = psiType,
763             modifiers = createTypeModifiers(psiType, kotlinType, contextNullability),
764             asTypeParameter = typeParameterItem,
765             kotlinTypeInfo = kotlinType,
766         )
767 
768     /** Create a [PsiWildcardTypeItem]. */
769     private fun createWildcardTypeItem(
770         psiType: PsiWildcardType,
771         kotlinType: KotlinTypeInfo?,
772     ) =
773         PsiWildcardTypeItem(
774             psiType = psiType,
775             extendsBound =
776                 createBound(
777                     psiType.extendsBound,
778                     // The kotlinType only applies to an explicit bound, not an implicit bound, so
779                     // only pass it through if this has an explicit `extends` bound.
780                     kotlinType.takeIf { psiType.isExtends },
781                     // If this is a Kotlin wildcard type with an implicit Object extends bound, the
782                     // Object bound should be nullable, not platform nullness like in Java.
783                     contextNullability =
784                         if (kotlinType != null && !psiType.isExtends) {
785                             ContextNullability(TypeNullability.NULLABLE)
786                         } else {
787                             ContextNullability.none
788                         }
789                 ),
790             superBound =
791                 createBound(
792                     psiType.superBound,
793                     // The kotlinType only applies to an explicit bound, not an implicit bound, so
794                     // only pass it through if this has an explicit `super` bound.
795                     kotlinType.takeIf { psiType.isSuper },
796                 ),
797             modifiers = createTypeModifiers(psiType, kotlinType, ContextNullability.forceUndefined),
798             kotlinTypeInfo = kotlinType,
799         )
800 
801     /**
802      * Create a [PsiWildcardTypeItem.extendsBound] or [PsiWildcardTypeItem.superBound].
803      *
804      * If a [PsiWildcardType] doesn't have a bound, the bound is represented as the null [PsiType]
805      * instead of just `null`.
806      */
807     private fun createBound(
808         bound: PsiType,
809         kotlinType: KotlinTypeInfo?,
810         contextNullability: ContextNullability = ContextNullability.none
811     ): ReferenceTypeItem? {
812         return if (bound == PsiTypes.nullType()) {
813             null
814         } else {
815             // Use the same Kotlin type, because the wildcard isn't its own level in the KtType.
816             createTypeItem(bound, kotlinType, contextNullability) as ReferenceTypeItem
817         }
818     }
819 }
820 
821 /** Compute the qualified name for a [PsiClassType].. */
computeQualifiedNamenull822 internal fun PsiClassType.computeQualifiedName(): String {
823     // It should be possible to do `psiType.rawType().canonicalText` instead, but this does not
824     // always work if psi is unable to resolve the reference.
825     // See https://youtrack.jetbrains.com/issue/KTIJ-27093 for more details.
826     return PsiNameHelper.getQualifiedClassName(canonicalText, true)
827 }
828