|
42 | 42 |
|
43 | 43 | Anyway, you can see there is quite a bit of detail here -- tracking |
44 | 44 | things like the keywords, the variables, references to for example the |
45 | | -package -- and higher level concepts like a class and a field which I've |
| 45 | +package -- and higher level concepts like a class and a field, which I've |
46 | 46 | marked with a thicker border. |
47 | 47 |
|
48 | 48 | Here's the corresponding Kotlin program: |
|
60 | 60 |
|
61 | 61 |  |
62 | 62 |
|
63 | | -This is for a program which is completely equivalent to the Java one. |
| 63 | +This program is equivalent to the Java one. |
64 | 64 | But notice that it has a completely different shape! They reference |
65 | 65 | different element classes, `PsiClass` versus `KtClass`, and on and on |
66 | 66 | all the way down. |
|
70 | 70 |
|
71 | 71 | ## UAST |
72 | 72 |
|
73 | | -We can construct a new AST which represents the same concepts: |
| 73 | +We can construct a new AST that represents the same concepts: |
74 | 74 |
|
75 | 75 |  |
76 | 76 |
|
|
84 | 84 |  |
85 | 85 |
|
86 | 86 | As you can see, the ASTs are not always identical. For Strings, in |
87 | | -Kotlin, we often end up with an extra parent `UiInjectionHost`. But for |
| 87 | +Kotlin, we often end up with an extra parent `UInjectionHost`. But for |
88 | 88 | our purposes, you can see that the ASTs are mostly the same, so if you |
89 | 89 | handle the Kotlin scenario, you'll handle the Java ones too. |
90 | 90 |
|
91 | 91 | ## UAST: The Java View |
92 | 92 |
|
93 | 93 | Note that “Unified” in the name here is a bit misleading. From the name |
94 | 94 | you may assume that this is some sort of superset of the ASTs across |
95 | | -languages -- and AST that can represent everything needed by all |
| 95 | +languages -- an AST that can represent everything needed by all |
96 | 96 | languages. But that's not the case! Instead, a better way to think of it |
97 | 97 | is as the **Java view** of the AST. |
98 | 98 |
|
|
106 | 106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
107 | 107 |
|
108 | 108 | This is a Kotlin data class with two properties. So you might expect |
109 | | -that UAST would have a way to represent these concepts -- properties, |
110 | | -and java classes. This should be a `UDataClass` with two `UProperty` |
111 | | -children, right? |
| 109 | +that UAST would have a way to represent these concepts. This should |
| 110 | +be a `UDataClass` with two `UProperty` children, right? |
112 | 111 |
|
113 | 112 | But Java doesn't support properties. If you try to access a `Person` |
114 | 113 | instance from Java, you'll notice that it exposes a number of public |
|
166 | 165 |
|
167 | 166 | ## UElement |
168 | 167 |
|
169 | | -Every node in UAST is a subclass of a UElement. There's a parent |
| 168 | +Every node in UAST is a subclass of a `UElement`. There's a parent |
170 | 169 | pointer, which is handy for navigating around in the AST. |
171 | 170 |
|
172 | 171 | The real skill you need for writing lint checks is understanding the |
173 | 172 | AST, and then doing pattern matching on it. And a simple trick for this |
174 | 173 | is to create the Kotlin or Java code you want, in a unit test, and then |
175 | 174 | in your detector, recursively print out the UAST as a tree. |
176 | 175 |
|
177 | | -Or in the debugger, anytime you have a UElement, you can call |
| 176 | +Or in the debugger, anytime you have a `UElement`, you can call |
178 | 177 | `UElement.asRecursiveLogString` on it, evaluate and see what you find. |
179 | 178 |
|
180 | 179 | For example, for the following Kotlin code: |
|
209 | 208 | ## Visiting |
210 | 209 |
|
211 | 210 | You generally shouldn't visit a source file on your own. Lint has a |
212 | | -special `UElementHandler` for that which is used to ensure that we don't |
| 211 | +special `UElementHandler` for that, which is used to ensure we don't |
213 | 212 | repeat visiting a source file thousands of times, one per detector. |
214 | 213 |
|
215 | 214 | But when you're doing local analysis, you sometimes need to visit a |
|
246 | 245 |
|
247 | 246 | We have our UAST tree in the top right corner. And here's the Java PSI |
248 | 247 | AST behind the scenes. We can access the underlying PSI node for a |
249 | | -UElement by accessing the sourcePsi element. So when you do need to dip |
| 248 | +`UElement` by accessing the `sourcePsi` property. So when you do need to dip |
250 | 249 | into something language specific, that's trivial to do. |
251 | 250 |
|
252 | 251 | Note that in some cases, these references are null. |
253 | 252 |
|
254 | | -Each of the UElement nodes point back into the PSI AST - whether a Java |
| 253 | +Most `UElement` nodes point back to the PSI AST - whether a Java |
255 | 254 | AST or a Kotlin AST. Here's the same AST, but with the **type** of the |
256 | | -`sourcePsi` attribute for each node added. |
| 255 | +`sourcePsi` property for each node added. |
257 | 256 |
|
258 | 257 |  |
259 | 258 |
|
260 | | -You can see that the class generated to represent the top level |
261 | | -functions here doesn't have a non-null `sourcePsi`, because in the |
| 259 | +You can see that the facade class generated to contain the top level |
| 260 | +functions has a null `sourcePsi`, because in the |
262 | 261 | Kotlin PSI, there is no real `KtClass` for a facade class. And for the |
263 | 262 | three members, the private field and the getter and the setter, they all |
264 | 263 | correspond to the exact same, single `KtProperty` instance, the single |
|
288 | 287 | across the languages. Declarations. Function calls. Super classes. |
289 | 288 | Assignments. If expressions. Return statements. And on and on. |
290 | 289 |
|
291 | | -There *are* lint checks which are language specific -- for example, if |
292 | | -you write a lint check which forbids the use of companion objects -- in |
| 290 | +There *are* lint checks that are language specific -- for example, if |
| 291 | +you write a lint check that forbids the use of companion objects -- in |
293 | 292 | that case, there's no big advantage to using UAST over PSI; it's only |
294 | 293 | ever going to run on Kotlin code. (Note however that lint's APIs and |
295 | 294 | convenience callbacks are all targeting UAST, so it's easier to write |
|
308 | 307 | language specific, and where the language details aren't exposed in UAST. |
309 | 308 |
|
310 | 309 | For example, let's say you need to determine if a `UClass` is a Kotlin |
311 | | -"companion object“. You could cheat and look at the class name to see if |
312 | | -it's ”Companion“. But that's not quite right; in Kotlin you can |
| 310 | +“companion object”. You could cheat and look at the class name to see if |
| 311 | +it's “Companion”. But that's not quite right; in Kotlin you can |
313 | 312 | specify a custom companion object name, and of course users are free |
314 | | -to create classes named ”Companion“ that aren't companion objects: |
| 313 | +to create classes named “Companion” that aren't companion objects: |
315 | 314 |
|
316 | 315 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin |
317 | 316 | class Test { |
|
323 | 322 | } |
324 | 323 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
325 | 324 |
|
326 | | -The right way to do this, is using Kotlin PSI, via the |
327 | | -`UElement.sourcePsi` attribute: |
| 325 | +The right way to do this is using Kotlin PSI, via the |
| 326 | +`UElement.sourcePsi` property: |
328 | 327 |
|
329 | 328 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin |
330 | 329 | // Skip companion objects |
|
335 | 334 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
336 | 335 |
|
337 | 336 | (To figure out how to write the above code, use a debugger on a test |
338 | | -case and look at the `UClass.sourcePsi` attribute; you'll discover that |
| 337 | +case and look at the `UClass.sourcePsi` property; you'll discover that |
339 | 338 | it's some subclass of `KtObjectDeclaration`; look up its most general |
340 | 339 | super interface or class, and then use code completion to discover |
341 | 340 | available APIs, such as `isCompanion()`.) |
|
350 | 349 | Lint doesn't actually give you access to everything you need if you want |
351 | 350 | to try to look up types in Kotlin PSI; you need something called the |
352 | 351 | "binding context”, which is not exposed anywhere! And this omission is |
353 | | -deliberate, because that was an implementation detail of the old |
| 352 | +deliberate, because this is an implementation detail of the old |
354 | 353 | compiler. The future is K2; a complete rewrite of the compiler front |
355 | 354 | end, which is no longer using the old binding context. And as part of |
356 | 355 | the tooling support for K2, there's a new API called the “Kotlin |
|
398 | 397 |
|
399 | 398 | Before the Kotlin lint analysis API, lint didn't have a way to reason |
400 | 399 | about the `Nothing` type. UAST only returns Java types, which maps to |
401 | | -void. So instead, lint had an ugly hack which just hardcoded well known |
| 400 | +void. So instead, lint had an ugly hack that just hardcoded well known |
402 | 401 | names of methods that don't return: |
403 | 402 |
|
404 | 403 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin |
|
441 | 440 | Here, we have a `KtCallExpression`, and inside the `analyze` block we |
442 | 441 | can call `resolveCall()` on it to reach the called method's symbol. |
443 | 442 |
|
444 | | -Similarly on a `KtDeclaration` (such as a named function or property) I |
| 443 | +Similarly, on a `KtDeclaration` (such as a named function or property) I |
445 | 444 | can call `getSymbol()` to get the symbol for that method or property, to |
446 | 445 | for example look up parameter information. And on a `KtExpression` (such |
447 | 446 | as an if statement) I can call `getKtType()` to get the Kotlin type. |
|
452 | 451 | so on. |
453 | 452 |
|
454 | 453 | In the new implementation of `callNeverReturns`, we resolve the call, |
455 | | -look up the corresponding function which of course is a `KtSymbol` |
| 454 | +look up the corresponding function, which of course is a `KtSymbol` |
456 | 455 | itself, and from that we get the return type, and then we can just check |
457 | 456 | if it's the `Nothing` type. |
458 | 457 |
|
|
0 commit comments