• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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