|
| 1 | +# Migration guide |
| 2 | + |
| 3 | +The purpose of this migration guide is to help clients of the analyzer package |
| 4 | +migrate to version 7.4. |
| 5 | + |
| 6 | +The biggest change in version 7.4 is the introduction of the new element model |
| 7 | +API. Unfortunately, it isn’t possible to automate the migration, so we wanted to |
| 8 | +make the process as easy as possible by explaining why we made the changes, what |
| 9 | +changes were made, and what you need to do in order to migrate your code. |
| 10 | + |
| 11 | +## The reason for the change |
| 12 | + |
| 13 | +The changes to the element model were necessary in order for the analyzer to |
| 14 | +support both the [enhanced parts][enhanced_parts] and |
| 15 | +[augmentations][augmentations] language features, both of which extend the |
| 16 | +semantics of the language in significant ways. |
| 17 | + |
| 18 | +[augmentations]: https://github.com/dart-lang/language/blob/main/working/augmentation-libraries/feature-specification.md |
| 19 | +[enhanced_parts]: https://github.com/dart-lang/language/blob/main/working/augmentation-libraries/parts_with_imports.md |
| 20 | + |
| 21 | +There are also a few small syntactic changes as a result of these features, but |
| 22 | +the changes to the AST structure are all experimental and don’t require any |
| 23 | +migration work at this point, so we won’t discuss them here. |
| 24 | + |
| 25 | +### Overview of the language features |
| 26 | + |
| 27 | +While you should probably read the details of the language features before you |
| 28 | +attempt to support them, this section attempts to describe the aspects of those |
| 29 | +features that impact the element model API. Note that it is not necessary for |
| 30 | +you to support the new language features in order to migrate to the new element |
| 31 | +model APIs. In fact, given that the analyzer package doesn’t yet support the |
| 32 | +augmentations feature you probably can’t support it yet even if you want to. |
| 33 | + |
| 34 | +As you know, the element model describes the semantic (as opposed to syntactic) |
| 35 | +structure of Dart code. Generally speaking, an element represents something that |
| 36 | +is defined in the code, such as a class, method, or variable. That hasn't |
| 37 | +changed, but there are two things that have changed. |
| 38 | + |
| 39 | +It has always been possible to break a library into multiple files (the |
| 40 | +"defining" compilation unit and zero or more "parts"). The old model represented |
| 41 | +these parts as a list. The enhanced_parts feature makes it possible for parts to |
| 42 | +have not only their own imports but also sub-parts. A list is no longer |
| 43 | +sufficient to represent the semantics, so in the new model these parts are |
| 44 | +represented as a tree. |
| 45 | + |
| 46 | +It used to be the case that every element was fully defined by a single lexical |
| 47 | +declaration. With the introduction of augmentations, some elements can now be |
| 48 | +defined by multiple declarations, and those declarations can be located in one |
| 49 | +or more parts, with each part in the library contributing zero, one, or many |
| 50 | +pieces of the element's definition. This has led to the need to represent both |
| 51 | +the individual declarations as well as the element that is defined by those |
| 52 | +declarations. |
| 53 | + |
| 54 | +## Changes to the element model |
| 55 | + |
| 56 | +This section describes some of the changes made to the element model in order to |
| 57 | +accommodate these language features. It is not intended to be comprehensive. |
| 58 | + |
| 59 | +### Name changes |
| 60 | + |
| 61 | +In order to make it easier to incrementally migrate code we have made it |
| 62 | +possible to have both the old and new APIs imported into a single library |
| 63 | +without conflict. We did this by changing the names of the classes in the new |
| 64 | +API. In most cases we did this by appending the digit `2` to the end of the name |
| 65 | +of the corresponding class in the old API. For example, the class |
| 66 | +`LibraryElement` has been replaced by the class `LibraryElement2`, |
| 67 | +`ClassElement` by `ClassElement2`, etc. There are a couple of exceptions, |
| 68 | +described in the section below titled “Specific model changes”. |
| 69 | + |
| 70 | +To make the implementation of the new model easier we also changed the names of |
| 71 | +the members of those classes whose signature changed. Most of the time this |
| 72 | +follows the same pattern of adding a digit to the old name, but in a few cases |
| 73 | +we made a more comprehensive change to the name in order to end up with a more |
| 74 | +consistent API. |
| 75 | + |
| 76 | +Additional details of the name changes are available in the `@deprecated` |
| 77 | +annotations. It might be worthwhile migrating to version 7.4 in order to have |
| 78 | +those annotations available during the migration. |
| 79 | + |
| 80 | +### Introduction of fragments |
| 81 | + |
| 82 | +Some information that used to be associated with an element, specifically |
| 83 | +information related to the single declaration site, no longer makes sense to |
| 84 | +have on the elements because there are now potentially multiple declaration |
| 85 | +sites. For example, every element used to know the offset of the element's name |
| 86 | +in the declaration, but with multiple declaration sites the name can now appear |
| 87 | +at multiple offsets in multiple files. |
| 88 | + |
| 89 | +Instead, we have introduced a new set of classes, rooted at `Fragment`, to |
| 90 | +represent the information related to a single declaration site. For consistency, |
| 91 | +every element has one or more fragments associated with it, even if that |
| 92 | +particular kind of element, such as a local variable, can never have multiple |
| 93 | +declaration sites. Information that isn't specific to a declaration site is |
| 94 | +still accessed through the element. |
| 95 | + |
| 96 | +Just as elements exist in a hierarchy, the corresponding fragments also form a |
| 97 | +parallel hierarchy. For example, just as every method element is a child of a |
| 98 | +class-like element (class, mixin, etc.), every method fragment is a child of a |
| 99 | +class-like fragment. |
| 100 | + |
| 101 | +Some information is available through both the element and the fragments, but |
| 102 | +with slightly different semantics. For example, you can ask a class fragment |
| 103 | +(representing a single class declaration) for the member fragments contained in |
| 104 | +it, but you can also ask a class element for all of the member elements defined |
| 105 | +for it and get the results of merging all of the member fragments from all of |
| 106 | +the declaration sites. |
| 107 | + |
| 108 | +### Compilation units |
| 109 | + |
| 110 | +A `CompilationUnitElement` is no longer an element. It's now a fragment and its |
| 111 | +name has changed to `LibraryFragment` to reflect this change. That means that, |
| 112 | +for example, a class element is now contained in a library, not in a compilation |
| 113 | +unit. But, as expected, a class fragment _is_ contained in a library fragment. |
| 114 | + |
| 115 | +Libraries have always been the merge of the declarations in all of the parts, |
| 116 | +this just makes the treatment of parts be consistent with the way the rest of |
| 117 | +the declarations are now handled. In other words, just as one or more |
| 118 | +`ClassFragment`s are merged to define a class, one or more `LibraryFragment`s |
| 119 | +are merged to define a library. |
| 120 | + |
| 121 | +And, as noted above, `LibraryFragment`s form a tree structure. |
| 122 | + |
| 123 | +### Getters and setters |
| 124 | + |
| 125 | +The class `PropertyAccessorElement` has been replaced by the classes |
| 126 | +`GetterElement` and `SetterElement`. |
| 127 | + |
| 128 | +Getters and setters are different enough that it makes sense for them to have |
| 129 | +different APIs, so we decided to have different classes to represent them. |
| 130 | + |
| 131 | +### Formal parameters |
| 132 | + |
| 133 | +Rather than rename `ParameterElement` to `ParameterElement2`, we renamed it to |
| 134 | +`FormalParameterElement`. We did this to make a more clear distinction between |
| 135 | +_formal_ parameters associated with functions and methods (appearing between |
| 136 | +`(` and `)`) and _type_ parameters associated with generic declarations |
| 137 | +(appearing between `<` and `>`). |
| 138 | + |
| 139 | +### Functions |
| 140 | + |
| 141 | +The class `FunctionElement` has been replaced by the classes |
| 142 | +`TopLevelFunctionElement` and `LocalFunctionElement`. |
| 143 | + |
| 144 | +Top-level functions can have multiple declarations, but local functions can’t. |
| 145 | + |
| 146 | +### Local declarations |
| 147 | + |
| 148 | +Unlike most other elements, the elements representing local declarations (local |
| 149 | +variables, local functions, and statement labels) can only ever have a single |
| 150 | +declaration site (that is, a single fragment). |
| 151 | + |
| 152 | +While it makes sense to ask a method element for the class-like element that |
| 153 | +it’s defined in, it doesn’t make sense to ask a local variable element for the |
| 154 | +method element it’s defined in, nor does it make sense to ask a method element |
| 155 | +for all of the local variables in all of the method’s fragments. Therefore, if |
| 156 | +you ask a local variable element for its enclosing element it will return |
| 157 | +`null`. You can, however, ask a local variable fragment for its enclosing |
| 158 | +fragment. |
| 159 | + |
| 160 | +### Directives |
| 161 | + |
| 162 | +In the old model there were subclasses of `Element` providing information about |
| 163 | +the directives in a library. In the new model there are similar classes, but |
| 164 | +they are no longer subclasses of `Element` because directives don’t define |
| 165 | +anything that can be referenced. (Import directives can include the declaration |
| 166 | +of an import prefix, and an import prefix is still represented as an element, |
| 167 | +but the import containing the prefix declaration isn’t an element.) |
| 168 | + |
| 169 | +### Class member changes |
| 170 | + |
| 171 | +Some members of the element classes have been removed because they no longer |
| 172 | +make sense to have on the element. Those members have been moved to the |
| 173 | +corresponding fragment. |
| 174 | + |
| 175 | +### Accessing metadata |
| 176 | + |
| 177 | +In the old API you could ask any element for its `metadata` and get back a list |
| 178 | +of the annotations associated with the declaration and there were a number of |
| 179 | +helper getters of the form `hasSomeAnnotation` for annotations defined in the |
| 180 | +SDK or the `meta` package. |
| 181 | + |
| 182 | +In the new API you can ask either a fragment or an element for `metadata2` to |
| 183 | +get an instance of `Metadata`. That instance can be used to access the list, and |
| 184 | +it’s also where the helper getters are now defined. It adds a level of |
| 185 | +indirection, but by reducing the number of getters defined on `Element` we hope |
| 186 | +to have made it easier to discover other more commonly used members. |
| 187 | + |
| 188 | +## Changes outside the element model |
| 189 | + |
| 190 | +The APIs used to access the element model haven't changed significantly in most |
| 191 | +cases. The names of the members used to access the new element model are, by |
| 192 | +necessity, different from the deprecated methods used to access the old model, |
| 193 | +usually by adding a `2` at the end of the name (though in some cases we already |
| 194 | +had a `2` at the end of the name, so in those cases we used a different digit). |
| 195 | + |
| 196 | +There are a few places where we made a more significant change. For example, it |
| 197 | +used to be possible to ask some AST nodes for the `staticElement` associated |
| 198 | +with them, but to access the element from the new model you should use |
| 199 | +`element`. In some cases the name change is a reflection of the fact that the |
| 200 | +member returns a fragment rather than an element, as is the case for declaration |
| 201 | +nodes where the getter `declaredElement` has been replaced by |
| 202 | +`declaredFragment`. |
| 203 | + |
| 204 | +## Migrating from the old element model |
| 205 | + |
| 206 | +The most difficult part of migrating code to the new element model is deciding |
| 207 | +whether an element was being used in order to get information about the full |
| 208 | +definition of the element or whether it was being used to access information |
| 209 | +about a single declaration site. It is, of course, possible that the answer is |
| 210 | +"both". |
| 211 | + |
| 212 | +As you've probably already figured out, the question is important because it |
| 213 | +tells you whether you need to use the element, the fragment, or both after the |
| 214 | +migration. |
| 215 | + |
| 216 | +After you’ve figured out where the information you need lives in the new model |
| 217 | +it should generally be fairly easy to figure out how to access it. |
| 218 | + |
| 219 | +If you aren’t attempting to support augmentations as part of this migration |
| 220 | +(which is our recommendation), then anywhere you need to access information that |
| 221 | +has moved from the element to a fragment, you can use `Element.firstFragment` to |
| 222 | +get to the information. That’s because, until the experiment is enabled, every |
| 223 | +element will have exactly one fragment. |
| 224 | + |
| 225 | +## Migration examples |
| 226 | + |
| 227 | +Let's look at two examples of migrating some code. The examples are taken from |
| 228 | +the analysis_server package, so they’re real code, and should be fairly |
| 229 | +epresentative without being overly complex. |
| 230 | + |
| 231 | +### Add missing enum case clauses |
| 232 | + |
| 233 | +This is a fix that will add case clauses to a switch over an enumerated type. It |
| 234 | +uses the element model in a couple of ways. |
| 235 | + |
| 236 | +To start, we need to understand how the code works, which we’ll do by looking at |
| 237 | +the pre-migrated code. It starts by getting the type of the value being switched |
| 238 | +ver. If the type is an `InterfaceType` then it gets the element associated with |
| 239 | +the type. |
| 240 | + |
| 241 | +```dart |
| 242 | +var enumElement = expressionType.element; |
| 243 | +``` |
| 244 | + |
| 245 | +It then checks to see whether the element is an `EnumElement`. |
| 246 | + |
| 247 | +```dart |
| 248 | +if (enumElement is EnumElement) { |
| 249 | + // ... |
| 250 | +} |
| 251 | + ``` |
| 252 | + |
| 253 | +If it is, the list of enum constants is iterated over and each constant is added |
| 254 | +to a collection. |
| 255 | + |
| 256 | +It then iterates over the list of switch cases in the switch statement (or |
| 257 | +expression), getting the elements associated with each switch case and removing |
| 258 | +those elements from the collection. |
| 259 | + |
| 260 | +```dart |
| 261 | +var element = expression.staticElement; |
| 262 | +if (element is PropertyAccessorElement) { |
| 263 | + unhandledEnumCases.remove(element.name); |
| 264 | +} |
| 265 | +``` |
| 266 | + |
| 267 | +At the end, the collection contains a list of the missing constants and new |
| 268 | +switch cases are added for those constants. |
| 269 | + |
| 270 | +Now let's look at what we need to do to translate the fix. |
| 271 | + |
| 272 | +When it's getting the list of constants it's fairly clear that we want all of |
| 273 | +the constants, no matter where they're declared. That means we want the merged |
| 274 | +view, so we need the new element, an `EnumElement2`, and we can get that by |
| 275 | +using a different getter. |
| 276 | + |
| 277 | +```dart |
| 278 | +var enumElement = expressionType.element3; |
| 279 | +``` |
| 280 | + |
| 281 | +We also need to update the condition that tests the type to use the type from |
| 282 | +the new model. |
| 283 | + |
| 284 | +```dart |
| 285 | +if (enumElement is EnumElement2) { |
| 286 | + // ... |
| 287 | +} |
| 288 | +``` |
| 289 | + |
| 290 | +When we ask the element for the constants we'll get back instances of |
| 291 | +`FieldElement2`. That means that when we're iterating over the switch cases we |
| 292 | +need to also get the elements, which we can do by rewriting the code to the |
| 293 | +following: |
| 294 | + |
| 295 | +```dart |
| 296 | +var element = expression.element; |
| 297 | +if (element is GetterElement) { |
| 298 | + unhandledEnumCases.remove(element.name); |
| 299 | +} |
| 300 | +``` |
| 301 | + |
| 302 | +There are a couple of other places where the names of types or members need to |
| 303 | +be updated, and the import needs to be changed to include |
| 304 | +`package:analyzer/dart/element/element2` rather than |
| 305 | +`package:analyzer/dart/element/element`, but that’s the majority of the changes. |
| 306 | + |
| 307 | +### Add enum constant |
| 308 | + |
| 309 | +This is a fix that will add a declaration of an enum constant to an existing |
| 310 | +enum. |
| 311 | + |
| 312 | +It works by first getting the element associated with the name of the enum. |
| 313 | + |
| 314 | +```dart |
| 315 | +var targetElement = target.staticElement; |
| 316 | +``` |
| 317 | + |
| 318 | +It then checks to make sure that the `targetElement` exists and that it isn’t |
| 319 | +defined in the SDK. |
| 320 | + |
| 321 | +```dart |
| 322 | +if (targetElement == null) return; |
| 323 | +if (targetElement.library?.isInSdk == true) return; |
| 324 | +``` |
| 325 | + |
| 326 | +Then it finds the declaration to which the constant will be added. |
| 327 | + |
| 328 | +```dart |
| 329 | +var targetDeclarationResult = |
| 330 | + await sessionHelper.getElementDeclaration(targetElement); |
| 331 | +``` |
| 332 | + |
| 333 | +It then figures out which file the declaration is in. |
| 334 | + |
| 335 | +```dart |
| 336 | +var targetSource = targetElement.source; |
| 337 | +``` |
| 338 | + |
| 339 | +And the rest is using the AST to figure out where to insert the new declaration, |
| 340 | +so that part doesn’t need to change (and we’ll ignore it for the sake of |
| 341 | +brevity). |
| 342 | + |
| 343 | +Now let's look at what we need to do to translate the fix. |
| 344 | + |
| 345 | +It will still need to get the element associated with the name of the enum |
| 346 | +(because no other information is available), so we’ll use the new API to do |
| 347 | +that. |
| 348 | + |
| 349 | +```dart |
| 350 | +var targetElement = target.element; |
| 351 | +``` |
| 352 | + |
| 353 | +We’ll update the way it validates that we have a good enum to work with. By |
| 354 | +testing the type we’re doing a null check and we’re also promoting the variable. |
| 355 | + |
| 356 | +```dart |
| 357 | +if (targetElement is! EnumElement2) return; |
| 358 | +if (targetElement.library2.isInSdk) return; |
| 359 | +``` |
| 360 | + |
| 361 | +We’ll need to update the way we get the declaration result, because declarations |
| 362 | +are associated with fragments, not with elements. For the purposes of the |
| 363 | +migration, we’ll just use the first fragment. |
| 364 | + |
| 365 | +```dart |
| 366 | +var targetFragment = targetElement.firstFragment; |
| 367 | +var targetDeclarationResult = await sessionHelper.getFragmentDeclaration( |
| 368 | + targetFragment, |
| 369 | +); |
| 370 | +``` |
| 371 | + |
| 372 | +Finally, it needs to use the fragment, rather than the element, in order to find |
| 373 | +out which file to update |
| 374 | + |
| 375 | +```dart |
| 376 | +var targetSource = targetFragment.libraryFragment.source; |
| 377 | +``` |
| 378 | + |
| 379 | +And that’s pretty much it. |
| 380 | + |
| 381 | +Note that this has the same behavior it had before, but doesn’t support |
| 382 | +augmentations. If we wanted to support augmentations we’d need to ask if and how |
| 383 | +having multiple declarations should impact the behavior of the fix. |
0 commit comments