1 /*
<lambda>null2 * Copyright (C) 2018 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 package com.squareup.moshi.kotlin.codegen.api
17
18 import com.squareup.kotlinpoet.ANY
19 import com.squareup.kotlinpoet.ARRAY
20 import com.squareup.kotlinpoet.BOOLEAN
21 import com.squareup.kotlinpoet.BYTE
22 import com.squareup.kotlinpoet.CHAR
23 import com.squareup.kotlinpoet.ClassName
24 import com.squareup.kotlinpoet.CodeBlock
25 import com.squareup.kotlinpoet.DOUBLE
26 import com.squareup.kotlinpoet.DelicateKotlinPoetApi
27 import com.squareup.kotlinpoet.FLOAT
28 import com.squareup.kotlinpoet.INT
29 import com.squareup.kotlinpoet.KModifier
30 import com.squareup.kotlinpoet.LONG
31 import com.squareup.kotlinpoet.LambdaTypeName
32 import com.squareup.kotlinpoet.NOTHING
33 import com.squareup.kotlinpoet.ParameterizedTypeName
34 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
35 import com.squareup.kotlinpoet.SHORT
36 import com.squareup.kotlinpoet.STAR
37 import com.squareup.kotlinpoet.TypeName
38 import com.squareup.kotlinpoet.TypeVariableName
39 import com.squareup.kotlinpoet.UNIT
40 import com.squareup.kotlinpoet.WildcardTypeName
41 import com.squareup.kotlinpoet.asClassName
42 import com.squareup.kotlinpoet.asTypeName
43 import java.lang.reflect.Array
44
45 internal fun TypeName.rawType(): ClassName {
46 return findRawType() ?: throw IllegalArgumentException("Cannot get raw type from $this")
47 }
48
findRawTypenull49 internal fun TypeName.findRawType(): ClassName? {
50 return when (this) {
51 is ClassName -> this
52 is ParameterizedTypeName -> rawType
53 is LambdaTypeName -> {
54 var count = parameters.size
55 if (receiver != null) {
56 count++
57 }
58 val functionSimpleName = if (count >= 23) {
59 "FunctionN"
60 } else {
61 "Function$count"
62 }
63 ClassName("kotlin.jvm.functions", functionSimpleName)
64 }
65 else -> null
66 }
67 }
68
defaultPrimitiveValuenull69 internal fun TypeName.defaultPrimitiveValue(): CodeBlock =
70 when (this) {
71 BOOLEAN -> CodeBlock.of("false")
72 CHAR -> CodeBlock.of("0.toChar()")
73 BYTE -> CodeBlock.of("0.toByte()")
74 SHORT -> CodeBlock.of("0.toShort()")
75 INT -> CodeBlock.of("0")
76 FLOAT -> CodeBlock.of("0f")
77 LONG -> CodeBlock.of("0L")
78 DOUBLE -> CodeBlock.of("0.0")
79 UNIT, Void::class.asTypeName(), NOTHING -> throw IllegalStateException("Parameter with void, Unit, or Nothing type is illegal")
80 else -> CodeBlock.of("null")
81 }
82
83 @OptIn(DelicateKotlinPoetApi::class)
asTypeBlocknull84 internal fun TypeName.asTypeBlock(): CodeBlock {
85 if (annotations.isNotEmpty()) {
86 return copy(annotations = emptyList()).asTypeBlock()
87 }
88 when (this) {
89 is ParameterizedTypeName -> {
90 return if (rawType == ARRAY) {
91 val componentType = typeArguments[0]
92 if (componentType is ParameterizedTypeName) {
93 // "generic" array just uses the component's raw type
94 // java.lang.reflect.Array.newInstance(<raw-type>, 0).javaClass
95 CodeBlock.of(
96 "%T.newInstance(%L, 0).javaClass",
97 Array::class.java.asClassName(),
98 componentType.rawType.asTypeBlock()
99 )
100 } else {
101 CodeBlock.of("%T::class.java", copy(nullable = false))
102 }
103 } else {
104 rawType.asTypeBlock()
105 }
106 }
107 is TypeVariableName -> {
108 val bound = bounds.firstOrNull() ?: ANY
109 return bound.asTypeBlock()
110 }
111 is LambdaTypeName -> return rawType().asTypeBlock()
112 is ClassName -> {
113 // Check against the non-nullable version for equality, but we'll keep the nullability in
114 // consideration when creating the CodeBlock if needed.
115 return when (copy(nullable = false)) {
116 BOOLEAN, CHAR, BYTE, SHORT, INT, FLOAT, LONG, DOUBLE -> {
117 if (isNullable) {
118 // Remove nullable but keep the java object type
119 CodeBlock.of("%T::class.javaObjectType", copy(nullable = false))
120 } else {
121 CodeBlock.of("%T::class.javaPrimitiveType", this)
122 }
123 }
124 UNIT, Void::class.asTypeName(), NOTHING -> throw IllegalStateException("Parameter with void, Unit, or Nothing type is illegal")
125 else -> CodeBlock.of("%T::class.java", copy(nullable = false))
126 }
127 }
128 else -> throw UnsupportedOperationException("Parameter with type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, or type variables are allowed.")
129 }
130 }
131
checkIsVisibilitynull132 internal fun KModifier.checkIsVisibility() {
133 require(ordinal <= ordinal) {
134 "Visibility must be one of ${(0..ordinal).joinToString { KModifier.values()[it].name }}. Is $name"
135 }
136 }
137
stripTypeVarVariancenull138 internal fun TypeName.stripTypeVarVariance(resolver: TypeVariableResolver): TypeName {
139 return when (this) {
140 is ClassName -> this
141 is ParameterizedTypeName -> {
142 deepCopy { it.stripTypeVarVariance(resolver) }
143 }
144 is TypeVariableName -> resolver[name]
145 is WildcardTypeName -> deepCopy { it.stripTypeVarVariance(resolver) }
146 else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.")
147 }
148 }
149
deepCopynull150 internal fun ParameterizedTypeName.deepCopy(
151 transform: (TypeName) -> TypeName
152 ): ParameterizedTypeName {
153 return rawType.parameterizedBy(typeArguments.map { transform(it) })
154 .copy(nullable = isNullable, annotations = annotations, tags = tags)
155 }
156
deepCopynull157 internal fun TypeVariableName.deepCopy(
158 variance: KModifier? = this.variance,
159 transform: (TypeName) -> TypeName
160 ): TypeVariableName {
161 return TypeVariableName(name = name, bounds = bounds.map { transform(it) }, variance = variance)
162 .copy(nullable = isNullable, annotations = annotations, tags = tags)
163 }
164
deepCopynull165 internal fun WildcardTypeName.deepCopy(transform: (TypeName) -> TypeName): TypeName {
166 // TODO Would be nice if KotlinPoet modeled these easier.
167 // Producer type - empty inTypes, single element outTypes
168 // Consumer type - single element inTypes, single ANY element outType.
169 return when {
170 this == STAR -> this
171 outTypes.isNotEmpty() && inTypes.isEmpty() -> {
172 WildcardTypeName.producerOf(transform(outTypes[0]))
173 .copy(nullable = isNullable, annotations = annotations)
174 }
175 inTypes.isNotEmpty() -> {
176 WildcardTypeName.consumerOf(transform(inTypes[0]))
177 .copy(nullable = isNullable, annotations = annotations)
178 }
179 else -> throw UnsupportedOperationException("Not possible.")
180 }
181 }
182
deepCopynull183 internal fun LambdaTypeName.deepCopy(transform: (TypeName) -> TypeName): TypeName {
184 return LambdaTypeName.get(
185 receiver?.let(transform),
186 parameters.map { it.toBuilder(type = transform(it.type)).build() },
187 transform(returnType)
188 ).copy(nullable = isNullable, annotations = annotations, suspending = isSuspending)
189 }
190
191 internal interface TypeVariableResolver {
192 val parametersMap: Map<String, TypeVariableName>
getnull193 operator fun get(index: String): TypeVariableName
194 }
195
196 internal fun List<TypeName>.toTypeVariableResolver(
197 fallback: TypeVariableResolver? = null,
198 sourceType: String? = null,
199 ): TypeVariableResolver {
200 val parametersMap = LinkedHashMap<String, TypeVariableName>()
201 val typeParamResolver = { id: String ->
202 parametersMap[id]
203 ?: fallback?.get(id)
204 ?: throw IllegalStateException("No type argument found for $id! Anaylzing $sourceType")
205 }
206
207 val resolver = object : TypeVariableResolver {
208 override val parametersMap: Map<String, TypeVariableName> = parametersMap
209
210 override operator fun get(index: String): TypeVariableName = typeParamResolver(index)
211 }
212
213 // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
214 for (typeVar in this) {
215 check(typeVar is TypeVariableName)
216 // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
217 // replacement later that may add bounds referencing this.
218 val id = typeVar.name
219 parametersMap[id] = TypeVariableName(id)
220 }
221
222 for (typeVar in this) {
223 check(typeVar is TypeVariableName)
224 // Now replace it with the full version.
225 parametersMap[typeVar.name] = typeVar.deepCopy(null) { it.stripTypeVarVariance(resolver) }
226 }
227
228 return resolver
229 }
230