1 /*
2  * Copyright (C) 2015 Square, Inc.
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  * https://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 @file:JvmName("TypeVariableNames")
17 
18 package com.squareup.kotlinpoet
19 
20 import java.lang.reflect.Type
21 import java.util.Collections
22 import javax.lang.model.element.TypeParameterElement
23 import javax.lang.model.type.TypeMirror
24 import javax.lang.model.type.TypeVariable
25 import kotlin.reflect.KClass
26 import kotlin.reflect.KType
27 import kotlin.reflect.KTypeParameter
28 import kotlin.reflect.KVariance
29 
30 public class TypeVariableName private constructor(
31   public val name: String,
32   public val bounds: List<TypeName>,
33 
34   /** Either [KModifier.IN], [KModifier.OUT], or null. */
35   public val variance: KModifier? = null,
36   public val isReified: Boolean = false,
37   nullable: Boolean = false,
38   annotations: List<AnnotationSpec> = emptyList(),
39   tags: Map<KClass<*>, Any> = emptyMap(),
40 ) : TypeName(nullable, annotations, TagMap(tags)) {
41 
copynull42   override fun copy(
43     nullable: Boolean,
44     annotations: List<AnnotationSpec>,
45     tags: Map<KClass<*>, Any>,
46   ): TypeVariableName {
47     return copy(nullable, annotations, this.bounds, this.isReified, tags)
48   }
49 
copynull50   public fun copy(
51     nullable: Boolean = this.isNullable,
52     annotations: List<AnnotationSpec> = this.annotations.toList(),
53     bounds: List<TypeName> = this.bounds.toList(),
54     reified: Boolean = this.isReified,
55     tags: Map<KClass<*>, Any> = this.tagMap.tags,
56   ): TypeVariableName {
57     return TypeVariableName(
58       name,
59       bounds.withoutImplicitBound(),
60       variance,
61       reified,
62       nullable,
63       annotations,
64       tags,
65     )
66   }
67 
withoutImplicitBoundnull68   private fun List<TypeName>.withoutImplicitBound(): List<TypeName> {
69     return if (size == 1) this else filterNot { it == NULLABLE_ANY }
70   }
71 
emitnull72   override fun emit(out: CodeWriter) = out.emit(name)
73 
74   override fun equals(other: Any?): Boolean {
75     if (this === other) return true
76     if (javaClass != other?.javaClass) return false
77     if (!super.equals(other)) return false
78 
79     other as TypeVariableName
80 
81     if (name != other.name) return false
82     if (bounds != other.bounds) return false
83     if (variance != other.variance) return false
84     if (isReified != other.isReified) return false
85 
86     return true
87   }
88 
hashCodenull89   override fun hashCode(): Int {
90     var result = super.hashCode()
91     result = 31 * result + name.hashCode()
92     result = 31 * result + bounds.hashCode()
93     result = 31 * result + (variance?.hashCode() ?: 0)
94     result = 31 * result + isReified.hashCode()
95     return result
96   }
97 
98   public companion object {
ofnull99     internal fun of(
100       name: String,
101       bounds: List<TypeName>,
102       variance: KModifier?,
103     ): TypeVariableName {
104       require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) {
105         "$variance is an invalid variance modifier, the only allowed values are in and out!"
106       }
107       require(bounds.isNotEmpty()) {
108         "$name has no bounds"
109       }
110       // Strip Any? from bounds if it is present.
111       return TypeVariableName(name, bounds, variance)
112     }
113 
114     /** Returns type variable named `name` with `variance` and without bounds. */
115     @JvmStatic
116     @JvmName("get")
117     @JvmOverloads
invokenull118     public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName =
119       of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance)
120 
121     /** Returns type variable named `name` with `variance` and `bounds`. */
122     @JvmStatic
123     @JvmName("get")
124     @JvmOverloads
125     public operator fun invoke(
126       name: String,
127       vararg bounds: TypeName,
128       variance: KModifier? = null,
129     ): TypeVariableName =
130       of(
131         name = name,
132         bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST),
133         variance = variance,
134       )
135 
136     /** Returns type variable named `name` with `variance` and `bounds`. */
137     @JvmStatic
138     @JvmName("get")
139     @JvmOverloads
140     public operator fun invoke(
141       name: String,
142       vararg bounds: KClass<*>,
143       variance: KModifier? = null,
144     ): TypeVariableName =
145       of(
146         name = name,
147         bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
148         variance = variance,
149       )
150 
151     /** Returns type variable named `name` with `variance` and `bounds`. */
152     @JvmStatic
153     @JvmName("get")
154     @JvmOverloads
155     public operator fun invoke(
156       name: String,
157       vararg bounds: Type,
158       variance: KModifier? = null,
159     ): TypeVariableName =
160       of(
161         name = name,
162         bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
163         variance = variance,
164       )
165 
166     /** Returns type variable named `name` with `variance` and `bounds`. */
167     @JvmStatic
168     @JvmName("get")
169     @JvmOverloads
170     public operator fun invoke(
171       name: String,
172       bounds: List<TypeName>,
173       variance: KModifier? = null,
174     ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance)
175 
176     /** Returns type variable named `name` with `variance` and `bounds`. */
177     @JvmStatic
178     @JvmName("getWithClasses")
179     @JvmOverloads
180     public operator fun invoke(
181       name: String,
182       bounds: Iterable<KClass<*>>,
183       variance: KModifier? = null,
184     ): TypeVariableName =
185       of(
186         name,
187         bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
188         variance,
189       )
190 
191     /** Returns type variable named `name` with `variance` and `bounds`. */
192     @JvmStatic
193     @JvmName("getWithTypes")
194     @JvmOverloads
invokenull195     public operator fun invoke(
196       name: String,
197       bounds: Iterable<Type>,
198       variance: KModifier? = null,
199     ): TypeVariableName =
200       of(
201         name,
202         bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
203         variance,
204       )
205 
206     /**
207      * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
208      * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a
209      * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables`
210      * map before looking up the bounds. Then if we encounter this TypeVariable again while
211      * constructing the bounds, we can just return it from the map. And, the code that put the entry
212      * in `variables` will make sure that the bounds are filled in before returning.
213      */
getnull214     internal fun get(
215       mirror: javax.lang.model.type.TypeVariable,
216       typeVariables: MutableMap<TypeParameterElement, TypeVariableName>,
217     ): TypeVariableName {
218       val element = mirror.asElement() as TypeParameterElement
219       var typeVariableName: TypeVariableName? = typeVariables[element]
220       if (typeVariableName == null) {
221         // Since the bounds field is public, we need to make it an unmodifiableList. But we control
222         // the List that that wraps, which means we can change it before returning.
223         val bounds = mutableListOf<TypeName>()
224         val visibleBounds = Collections.unmodifiableList(bounds)
225         typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds)
226         typeVariables[element] = typeVariableName
227         for (typeMirror in element.bounds) {
228           bounds += get(typeMirror, typeVariables)
229         }
230         bounds.remove(ANY)
231         bounds.remove(JAVA_OBJECT)
232         if (bounds.isEmpty()) {
233           bounds.add(NULLABLE_ANY)
234         }
235       }
236       return typeVariableName
237     }
238 
239     /** Returns type variable equivalent to `type`.  */
getnull240     internal fun get(
241       type: java.lang.reflect.TypeVariable<*>,
242       map: MutableMap<Type, TypeVariableName> = mutableMapOf(),
243     ): TypeVariableName {
244       var result: TypeVariableName? = map[type]
245       if (result == null) {
246         val bounds = mutableListOf<TypeName>()
247         val visibleBounds = Collections.unmodifiableList(bounds)
248         result = TypeVariableName(type.name, visibleBounds)
249         map[type] = result
250         for (bound in type.bounds) {
251           bounds += get(bound, map)
252         }
253         bounds.remove(ANY)
254         bounds.remove(JAVA_OBJECT)
255         if (bounds.isEmpty()) {
256           bounds.add(NULLABLE_ANY)
257         }
258       }
259       return result
260     }
261 
262     internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY)
263     private val JAVA_OBJECT = ClassName("java.lang", "Object")
264   }
265 }
266 
267 /** Returns type variable equivalent to `mirror`. */
268 @DelicateKotlinPoetApi(
269   message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
270     " the kotlinpoet-metadata APIs instead.",
271 )
272 @JvmName("get")
asTypeVariableNamenull273 public fun TypeVariable.asTypeVariableName(): TypeVariableName =
274   (asElement() as TypeParameterElement).asTypeVariableName()
275 
276 /** Returns type variable equivalent to `element`. */
277 @DelicateKotlinPoetApi(
278   message = "Element APIs don't give complete information on Kotlin types. Consider using" +
279     " the kotlinpoet-metadata APIs instead.",
280 )
281 @JvmName("get")
282 public fun TypeParameterElement.asTypeVariableName(): TypeVariableName {
283   val name = simpleName.toString()
284   val boundsTypeNames = bounds.map(TypeMirror::asTypeName)
285     .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST)
286   return TypeVariableName.of(name, boundsTypeNames, variance = null)
287 }
288 
asTypeVariableNamenull289 public fun KTypeParameter.asTypeVariableName(): TypeVariableName {
290   return TypeVariableName.of(
291     name = name,
292     bounds = upperBounds.map(KType::asTypeName)
293       .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST),
294     variance = when (variance) {
295       KVariance.INVARIANT -> null
296       KVariance.IN -> KModifier.IN
297       KVariance.OUT -> KModifier.OUT
298     },
299   )
300 }
301