<lambda>null1 package com.google.devtools.ksp.processor
2
3 import com.google.devtools.ksp.getClassDeclarationByName
4 import com.google.devtools.ksp.getDeclaredFunctions
5 import com.google.devtools.ksp.isConstructor
6 import com.google.devtools.ksp.processing.Resolver
7 import com.google.devtools.ksp.symbol.*
8
9 @Suppress("unused") // used by generated tests
10 class AsMemberOfProcessor : AbstractTestProcessor() {
11 val results = mutableListOf<String>()
12 // keep a list of all signatures we generate and ensure equals work as expected
13 private val functionsBySignature = mutableMapOf<String, MutableSet<KSFunction>>()
14 override fun toResult(): List<String> {
15 return results
16 }
17
18 override fun process(resolver: Resolver): List<KSAnnotated> {
19 val base = resolver.getClassDeclarationByName("Base")!!
20 val child1 = resolver.getClassDeclarationByName("Child1")!!
21 addToResults(resolver, base, child1.asStarProjectedType())
22 val child2 = resolver.getClassDeclarationByName("Child2")!!
23 addToResults(resolver, base, child2.asStarProjectedType())
24 val child2WithString = resolver.getDeclaration<KSPropertyDeclaration>("child2WithString")
25 addToResults(resolver, base, child2WithString.type.resolve())
26
27 // check cases where given type is not a subtype
28 val notAChild = resolver.getClassDeclarationByName("NotAChild")!!
29 addToResults(resolver, base, notAChild.asStarProjectedType())
30 val listOfStrings = resolver.getDeclaration<KSPropertyDeclaration>("listOfStrings").type.resolve()
31 val setOfStrings = resolver.getDeclaration<KSPropertyDeclaration>("setOfStrings").type.resolve()
32 val listClass = resolver.getClassDeclarationByName("kotlin.collections.List")!!
33 val setClass = resolver.getClassDeclarationByName("kotlin.collections.Set")!!
34 val listGet = listClass.getAllFunctions().first {
35 it.simpleName.asString() == "get"
36 }
37 results.add("List#get")
38 results.add("listOfStrings: " + resolver.asMemberOfSignature(listGet, listOfStrings))
39 results.add("setOfStrings: " + resolver.asMemberOfSignature(listGet, setOfStrings))
40
41 val setContains = setClass.getAllFunctions().first {
42 it.simpleName.asString() == "contains"
43 }
44 results.add("Set#contains")
45 results.add("listOfStrings: " + resolver.asMemberOfSignature(setContains, listOfStrings))
46 results.add("setOfStrings: " + resolver.asMemberOfSignature(setContains, setOfStrings))
47
48 val javaBase = resolver.getClassDeclarationByName("JavaBase")!!
49 val javaChild1 = resolver.getClassDeclarationByName("JavaChild1")!!
50 addToResults(resolver, javaBase, javaChild1.asStarProjectedType())
51
52 val fileLevelFunction = resolver.getDeclaration<KSFunctionDeclaration>("fileLevelFunction")
53 results.add("fileLevelFunction: " + resolver.asMemberOfSignature(fileLevelFunction, listOfStrings))
54
55 // TODO we should eventually support this, probably as different asReceiverOf kind of API
56 val fileLevelExtensionFunction = resolver.getDeclaration<KSFunctionDeclaration>("fileLevelExtensionFunction")
57 results.add(
58 "fileLevelExtensionFunction: " +
59 resolver.asMemberOfSignature(fileLevelExtensionFunction, listOfStrings)
60 )
61
62 val fileLevelProperty = resolver.getDeclaration<KSPropertyDeclaration>("fileLevelProperty")
63 results.add("fileLevelProperty: " + resolver.asMemberOfSignature(fileLevelProperty, listOfStrings))
64
65 val errorType = resolver.getDeclaration<KSPropertyDeclaration>("errorType").type.resolve()
66 results.add("errorType: " + resolver.asMemberOfSignature(listGet, errorType))
67
68 // make sure values are cached
69 val first = listGet.asMemberOf(listOfStrings)
70 val second = listGet.asMemberOf(listOfStrings)
71 if (first !== second) {
72 results.add("cache error, repeated computation")
73 }
74
75 // validate equals implementation
76 // all functions with the same signature should be equal to each-other unless there is an error / incomplete
77 // type in them
78 val notEqualToItself = functionsBySignature.filter { (_, functions) ->
79 functions.size != 1
80 }.keys
81 results.add("expected comparison failures")
82 results.addAll(notEqualToItself)
83 // make sure we don't have any false positive equals
84 functionsBySignature.forEach { (signature, functions) ->
85 functionsBySignature.forEach { (otherSignature, otherFunctions) ->
86 if (signature != otherSignature && functions.any { otherFunctions.contains(it) }) {
87 results.add("Unexpected equals between $otherSignature and $signature")
88 }
89 }
90 }
91 val javaImpl = resolver.getClassDeclarationByName("JavaImpl")!!
92 val getX = javaImpl.getDeclaredFunctions().first { it.simpleName.asString() == "getX" }
93 val getY = javaImpl.getDeclaredFunctions().first { it.simpleName.asString() == "getY" }
94 val setY = javaImpl.getDeclaredFunctions().first { it.simpleName.asString() == "setY" }
95 results.add(getX.asMemberOf(javaImpl.asStarProjectedType()).toSignature())
96 results.add(getY.asMemberOf(javaImpl.asStarProjectedType()).toSignature())
97 results.add(setY.asMemberOf(javaImpl.asStarProjectedType()).toSignature())
98 return emptyList()
99 }
100
101 private inline fun <reified T : KSDeclaration> Resolver.getDeclaration(name: String): T {
102 return getNewFiles().first {
103 it.fileName == "Input.kt"
104 }.declarations.filterIsInstance<T>().first {
105 it.simpleName.asString() == name
106 }
107 }
108
109 private fun addToResults(resolver: Resolver, baseClass: KSClassDeclaration, child: KSType) {
110 results.add(child.toSignature())
111 val baseProperties = baseClass.getAllProperties()
112 val baseFunction = baseClass.getDeclaredFunctions().filterNot { it.isConstructor() }
113 results.addAll(
114 baseProperties.map { property ->
115 val typeSignature = resolver.asMemberOfSignature(
116 property = property,
117 containing = child
118 )
119 "${property.simpleName.asString()}: $typeSignature"
120 }
121 )
122 results.addAll(
123 baseFunction.map { function ->
124 val functionSignature = resolver.asMemberOfSignature(
125 function = function,
126 containing = child
127 )
128 "${function.simpleName.asString()}: $functionSignature"
129 }
130 )
131 }
132
133 private fun Resolver.asMemberOfSignature(
134 function: KSFunctionDeclaration,
135 containing: KSType
136 ): String {
137 val result = kotlin.runCatching {
138 function.asMemberOf(containing).also {
139 if (it !== function.asMemberOf(containing)) {
140 results.add("cache error, repeated computation")
141 }
142 }
143 }
144 return if (result.isSuccess) {
145 val ksFunction = result.getOrThrow()
146 val signature = ksFunction.toSignature()
147 // record it to validate equality against other signatures
148 functionsBySignature.getOrPut(signature) {
149 mutableSetOf()
150 }.add(ksFunction)
151 signature
152 } else {
153 result.exceptionOrNull()!!.toSignature()
154 }
155 }
156
157 private fun Resolver.asMemberOfSignature(
158 property: KSPropertyDeclaration,
159 containing: KSType
160 ): String {
161 val result = kotlin.runCatching {
162 property.asMemberOf(containing).also {
163 if (it !== property.asMemberOf(containing)) {
164 results.add("cache error, repeated computation")
165 }
166 }
167 }
168 return if (result.isSuccess) {
169 result.getOrThrow().toSignature()
170 } else {
171 result.exceptionOrNull()!!.toSignature()
172 }
173 }
174
175 private fun Throwable.toSignature() = "${this::class.qualifiedName}: $message"
176 private fun KSType.toSignature(): String {
177 val name = this.declaration.qualifiedName?.asString()
178 ?: this.declaration.simpleName.asString()
179 val qName = name + nullability.toSignature()
180 if (arguments.toList().isEmpty()) {
181 return qName
182 }
183 val args = arguments.joinToString(", ") {
184 it.type?.resolve()?.toSignature() ?: "no-type"
185 }
186 return "$qName<$args>"
187 }
188
189 private fun KSTypeParameter.toSignature(): String {
190 val boundsSignature = if (bounds.toList().isEmpty()) {
191 ""
192 } else {
193 bounds.joinToString(
194 separator = ", ",
195 prefix = ": "
196 ) {
197 it.resolve().toSignature()
198 }
199 }
200 val varianceSignature = if (variance.label.isBlank()) {
201 ""
202 } else {
203 "${variance.label} "
204 }
205 val name = this.name.asString()
206 return "$varianceSignature$name$boundsSignature"
207 }
208
209 private fun KSFunction.toSignature(): String {
210 val returnType = this.returnType?.toSignature() ?: "no-return-type"
211 val params = parameterTypes.joinToString(", ") {
212 it?.toSignature() ?: "no-type-param"
213 }
214 val paramTypeArgs = this.typeParameters.joinToString(", ") {
215 it.toSignature()
216 }
217 val paramTypesSignature = if (paramTypeArgs.isBlank()) {
218 ""
219 } else {
220 "<$paramTypeArgs>"
221 }
222 val receiverSignature = if (extensionReceiverType != null) {
223 extensionReceiverType!!.toSignature() + "."
224 } else {
225 ""
226 }
227 return "$receiverSignature$paramTypesSignature($params) -> $returnType"
228 }
229
230 private fun Nullability.toSignature() = when (this) {
231 Nullability.NULLABLE -> "?"
232 Nullability.NOT_NULL -> "!!"
233 Nullability.PLATFORM -> ""
234 }
235 }
236