• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
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  *      http://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 
17 package com.android.tools.metalava.model.snapshot
18 
19 import com.android.tools.metalava.model.ApiVariantSelectors
20 import com.android.tools.metalava.model.CallableItem
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.ClassTypeItem
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.ConstructorItem
25 import com.android.tools.metalava.model.DefaultTypeParameterList
26 import com.android.tools.metalava.model.DelegatedVisitor
27 import com.android.tools.metalava.model.FieldItem
28 import com.android.tools.metalava.model.Item
29 import com.android.tools.metalava.model.ItemDocumentationFactory
30 import com.android.tools.metalava.model.ItemLanguage
31 import com.android.tools.metalava.model.ItemVisitor
32 import com.android.tools.metalava.model.MethodItem
33 import com.android.tools.metalava.model.ModifierList
34 import com.android.tools.metalava.model.PackageItem
35 import com.android.tools.metalava.model.ParameterItem
36 import com.android.tools.metalava.model.PropertyItem
37 import com.android.tools.metalava.model.SelectableItem
38 import com.android.tools.metalava.model.Showability
39 import com.android.tools.metalava.model.TypeAliasItem
40 import com.android.tools.metalava.model.TypeItem
41 import com.android.tools.metalava.model.TypeParameterList
42 import com.android.tools.metalava.model.TypeParameterListAndFactory
43 import com.android.tools.metalava.model.item.DefaultClassItem
44 import com.android.tools.metalava.model.item.DefaultCodebase
45 import com.android.tools.metalava.model.item.DefaultCodebaseAssembler
46 import com.android.tools.metalava.model.item.DefaultItemFactory
47 import com.android.tools.metalava.model.item.DefaultPackageItem
48 import com.android.tools.metalava.model.item.DefaultTypeParameterItem
49 import com.android.tools.metalava.model.item.MutablePackageDoc
50 import com.android.tools.metalava.model.item.PackageDoc
51 import com.android.tools.metalava.model.item.PackageDocs
52 
53 /** Constructs a [Codebase] by taking a snapshot of another [Codebase] that is being visited. */
54 class CodebaseSnapshotTaker
55 private constructor(referenceVisitorFactory: (DelegatedVisitor) -> ItemVisitor) :
56     DefaultCodebaseAssembler(), DelegatedVisitor {
57 
58     /**
59      * The [Codebase] that is under construction.
60      *
61      * Initialized in [visitCodebase].
62      */
63     private lateinit var snapshotCodebase: DefaultCodebase
64 
65     /**
66      * The [ItemVisitor] to use in [createClassFromUnderlyingModel] to create a [ClassItem] that is
67      * not emitted as part of the snapshot but is included because it is referenced from a
68      * [ClassItem] that is emitted from the snapshot.
69      */
70     private val referenceVisitor = referenceVisitorFactory(this)
71 
72     override val itemFactory: DefaultItemFactory by
73         lazy(LazyThreadSafetyMode.NONE) {
74             DefaultItemFactory(
75                 snapshotCodebase,
76                 // Snapshots currently only support java.
77                 defaultItemLanguage = ItemLanguage.JAVA,
78                 // Snapshots have already been separated by API surface variants, so they can use
79                 // the same immutable ApiVariantSelectors.
80                 ApiVariantSelectors.IMMUTABLE_FACTORY,
81             )
82         }
83 
84     /**
85      * The original [Codebase] that is being snapshotted construction.
86      *
87      * Initialized in [visitCodebase].
88      */
89     private lateinit var originalCodebase: Codebase
90 
91     private val globalTypeItemFactory by
92         lazy(LazyThreadSafetyMode.NONE) { SnapshotTypeItemFactory(snapshotCodebase) }
93 
94     /** Take a snapshot of this [ModifierList] for [snapshotCodebase]. */
95     private fun ModifierList.snapshot() = snapshot(snapshotCodebase)
96 
97     /**
98      * Snapshots need to preserve class nesting when visiting otherwise [ClassItem.containingClass]
99      * will not be initialized correctly.
100      */
101     override val requiresClassNesting: Boolean
102         get() = false
103 
104     override fun visitCodebase(codebase: Codebase) {
105         this.originalCodebase = codebase
106         val newCodebase =
107             DefaultCodebase(
108                 location = codebase.location,
109                 description = "snapshot of ${codebase.description}",
110                 preFiltered = true,
111                 config = codebase.config,
112                 trustedApi = true,
113                 // Supports documentation if the copied codebase does.
114                 supportsDocumentation = codebase.supportsDocumentation(),
115                 assembler = this,
116             )
117 
118         this.snapshotCodebase = newCodebase
119     }
120 
121     /**
122      * Construct a [PackageDocs] that contains a [PackageDoc] that in turn contains information
123      * extracted from [packageItem] that can be used to create a new [PackageItem] that is a
124      * snapshot of [packageItem].
125      */
126     private fun packageDocsForPackageItem(packageItem: PackageItem) =
127         MutablePackageDoc(
128                 qualifiedName = packageItem.qualifiedName(),
129                 fileLocation = packageItem.fileLocation,
130                 modifiers = packageItem.modifiers.snapshot(),
131                 commentFactory = packageItem.documentation::snapshot,
132                 overview = packageItem.overviewDocumentation,
133             )
134             .let { PackageDocs(mapOf(it.qualifiedName to it)) }
135 
136     /** Get the [PackageItem] corresponding to this [PackageItem] in the snapshot codebase. */
137     private fun PackageItem.getSnapshotPackage(): PackageItem {
138         // Check to see if the package already exists to avoid unnecessarily creating PackageDocs.
139         val packageName = qualifiedName()
140         snapshotCodebase.findPackage(packageName)?.let {
141             return it
142         }
143 
144         // Get a PackageDocs that contains a PackageDoc that contains information extracted from the
145         // PackageItem being visited. This is needed to ensure that the findOrCreatePackage(...)
146         // call below will use the correct information when creating the package. As only a single
147         // PackageDoc is provided for this package it means that if findOrCreatePackage(...) had to
148         // created a containing package that package would not have a PackageDocs and might be
149         // incorrect. However, that should not be a problem as the packages are visited in order
150         // such that a containing package is visited before any contained packages.
151         val packageDocs = packageDocsForPackageItem(this)
152         val newPackageItem = snapshotCodebase.findOrCreatePackage(packageName, packageDocs)
153         newPackageItem.copySelectedApiVariants(this)
154         return newPackageItem
155     }
156 
157     /**
158      * Take a snapshot of the documentation.
159      *
160      * If necessary revert the documentation change that accompanied a deprecation change.
161      *
162      * Deprecating an API requires adding an `@Deprecated` annotation and an `@deprecated` Javadoc
163      * tag with text that explains why it is being deprecated and what will replace it. When the
164      * deprecation change is being reverted then this will remove the `@deprecated` tag and its
165      * associated text to avoid warnings when compiling and misleading information being written
166      * into the Javadoc.
167      */
168     private fun snapshotDocumentation(
169         itemToSnapshot: Item,
170         documentedItem: Item,
171     ): ItemDocumentationFactory {
172         // The documentation does not need to be reverted if...
173         if (
174             // the item is not being reverted
175             itemToSnapshot === documentedItem
176             // or if the deprecation status has not changed
177             ||
178                 itemToSnapshot.effectivelyDeprecated == documentedItem.effectivelyDeprecated
179                 // or if the item was previously deprecated
180                 ||
181                 itemToSnapshot.effectivelyDeprecated
182         )
183             return documentedItem.documentation::snapshot
184 
185         val documentation = documentedItem.documentation
186         return { item -> documentation.snapshot(item).apply { removeDeprecatedSection() } }
187     }
188 
189     /** Get the [ClassItem] corresponding to this [ClassItem] in the [snapshotCodebase]. */
190     private fun ClassItem.getSnapshotClass(): DefaultClassItem =
191         snapshotCodebase.resolveClass(qualifiedName()) as DefaultClassItem
192 
193     /** Copy [SelectableItem.selectedApiVariants] from [original] to this. */
194     private fun <T : SelectableItem> T.copySelectedApiVariants(original: T) {
195         selectedApiVariants = original.selectedApiVariants
196     }
197 
198     override fun visitClass(cls: ClassItem) {
199         val classToSnapshot = cls.actualItemToSnapshot
200 
201         // Get the snapshot of the containing package.
202         val containingPackage = cls.containingPackage().getSnapshotPackage()
203 
204         // Get the snapshot of the containing class, if any.
205         val containingClass = cls.containingClass()?.getSnapshotClass()
206 
207         // Create a TypeParameterList and SnapshotTypeItemFactory for the class.
208         val (typeParameterList, classTypeItemFactory) =
209             globalTypeItemFactory.from(containingClass).inScope {
210                 classToSnapshot.typeParameterList.snapshot(
211                     "class ${classToSnapshot.qualifiedName()}"
212                 )
213             }
214 
215         // Snapshot the super class type, if any.
216         val snapshotSuperClassType =
217             classToSnapshot.superClassType()?.let { superClassType ->
218                 classTypeItemFactory.getSuperClassType(superClassType)
219             }
220         val snapshotInterfaceTypes =
221             classToSnapshot.interfaceTypes().map { classTypeItemFactory.getInterfaceType(it) }
222 
223         // Create the class and register it in the codebase.
224         val newClass =
225             itemFactory.createClassItem(
226                 fileLocation = classToSnapshot.fileLocation,
227                 itemLanguage = classToSnapshot.itemLanguage,
228                 modifiers = classToSnapshot.modifiers.snapshot(),
229                 documentationFactory = snapshotDocumentation(classToSnapshot, cls),
230                 source = cls.sourceFile(),
231                 classKind = classToSnapshot.classKind,
232                 containingClass = containingClass,
233                 containingPackage = containingPackage,
234                 qualifiedName = classToSnapshot.qualifiedName(),
235                 typeParameterList = typeParameterList,
236                 origin = classToSnapshot.origin,
237                 superClassType = snapshotSuperClassType,
238                 interfaceTypes = snapshotInterfaceTypes,
239             )
240         newClass.copySelectedApiVariants(classToSnapshot)
241     }
242 
243     /** Execute [body] within [SnapshotTypeItemFactoryContext]. */
244     private inline fun <T> SnapshotTypeItemFactory.inScope(
245         body: SnapshotTypeItemFactoryContext.() -> T
246     ) = SnapshotTypeItemFactoryContext(this).body()
247 
248     override fun visitConstructor(constructor: ConstructorItem) {
249         val constructorToSnapshot = constructor.actualItemToSnapshot
250 
251         val containingClass = constructor.containingClass().getSnapshotClass()
252 
253         // Create a TypeParameterList and SnapshotTypeItemFactory for the constructor.
254         val (typeParameterList, constructorTypeItemFactory) =
255             globalTypeItemFactory.from(containingClass).inScope {
256                 constructorToSnapshot.typeParameterList.snapshot(constructorToSnapshot.describe())
257             }
258 
259         val newConstructor =
260             // Resolve any type parameters used in the constructor's return type and parameter items
261             // within the scope of the constructor's SnapshotTypeItemFactory.
262             constructorTypeItemFactory.inScope {
263                 itemFactory.createConstructorItem(
264                     fileLocation = constructorToSnapshot.fileLocation,
265                     itemLanguage = constructorToSnapshot.itemLanguage,
266                     modifiers = constructorToSnapshot.modifiers.snapshot(),
267                     documentationFactory =
268                         snapshotDocumentation(constructorToSnapshot, constructor),
269                     name = constructorToSnapshot.name(),
270                     containingClass = containingClass,
271                     typeParameterList = typeParameterList,
272                     returnType = constructorToSnapshot.returnType().snapshot(),
273                     parameterItemsFactory = { containingCallable ->
274                         constructorToSnapshot.parameters().snapshot(containingCallable, constructor)
275                     },
276                     throwsTypes =
277                         constructorToSnapshot.throwsTypes().map {
278                             typeItemFactory.getExceptionType(it)
279                         },
280                     callableBodyFactory = constructorToSnapshot.body::snapshot,
281                     implicitConstructor = constructorToSnapshot.isImplicitConstructor(),
282                     isPrimary = constructorToSnapshot.isPrimary,
283                 )
284             }
285         newConstructor.copySelectedApiVariants(constructorToSnapshot)
286 
287         containingClass.addConstructor(newConstructor)
288     }
289 
290     override fun visitMethod(method: MethodItem) {
291         val methodToSnapshot = method.actualItemToSnapshot
292 
293         val containingClass = method.containingClass().getSnapshotClass()
294 
295         // Create a TypeParameterList and SnapshotTypeItemFactory for the method.
296         val (typeParameterList, methodTypeItemFactory) =
297             globalTypeItemFactory.from(containingClass).inScope {
298                 methodToSnapshot.typeParameterList.snapshot(methodToSnapshot.describe())
299             }
300 
301         val newMethod =
302             // Resolve any type parameters used in the method's return type and parameter items
303             // within the scope of the method's SnapshotTypeItemFactory.
304             methodTypeItemFactory.inScope {
305                 itemFactory.createMethodItem(
306                     fileLocation = methodToSnapshot.fileLocation,
307                     itemLanguage = methodToSnapshot.itemLanguage,
308                     modifiers = methodToSnapshot.modifiers.snapshot(),
309                     documentationFactory = snapshotDocumentation(methodToSnapshot, method),
310                     name = methodToSnapshot.name(),
311                     containingClass = containingClass,
312                     typeParameterList = typeParameterList,
313                     returnType = methodToSnapshot.returnType().snapshot(),
314                     parameterItemsFactory = { containingCallable ->
315                         methodToSnapshot.parameters().snapshot(containingCallable, method)
316                     },
317                     throwsTypes =
318                         methodToSnapshot.throwsTypes().map { typeItemFactory.getExceptionType(it) },
319                     callableBodyFactory = methodToSnapshot.body::snapshot,
320                     annotationDefault = methodToSnapshot.legacyDefaultValue(),
321                 )
322             }
323         newMethod.copySelectedApiVariants(methodToSnapshot)
324 
325         containingClass.addMethod(newMethod)
326     }
327 
328     override fun visitField(field: FieldItem) {
329         val fieldToSnapshot = field.actualItemToSnapshot
330 
331         val containingClass = field.containingClass().getSnapshotClass()
332         val newField =
333             // Resolve any type parameters used in the field's type within the scope of the
334             // containing class's SnapshotTypeItemFactory.
335             globalTypeItemFactory.from(containingClass).inScope {
336                 itemFactory.createFieldItem(
337                     fileLocation = fieldToSnapshot.fileLocation,
338                     itemLanguage = fieldToSnapshot.itemLanguage,
339                     modifiers = fieldToSnapshot.modifiers.snapshot(),
340                     documentationFactory = snapshotDocumentation(fieldToSnapshot, field),
341                     name = fieldToSnapshot.name(),
342                     containingClass = containingClass,
343                     type = fieldToSnapshot.type().snapshot(),
344                     isEnumConstant = fieldToSnapshot.isEnumConstant(),
345                     fieldValue = fieldToSnapshot.legacyFieldValue?.snapshot(),
346                 )
347             }
348         newField.copySelectedApiVariants(fieldToSnapshot)
349 
350         containingClass.addField(newField)
351     }
352 
353     override fun visitProperty(property: PropertyItem) {
354         val propertyToSnapshot = property.actualItemToSnapshot
355         val containingClass = property.containingClass().getSnapshotClass()
356 
357         // Create a TypeParameterList and SnapshotTypeItemFactory for the property.
358         val (typeParameterList, propertyTypeItemFactory) =
359             globalTypeItemFactory.from(containingClass).inScope {
360                 propertyToSnapshot.typeParameterList.snapshot(propertyToSnapshot.describe())
361             }
362 
363         val newProperty =
364             // Resolve any type parameters used in the property's type within the scope of the
365             // containing class's SnapshotTypeItemFactory.
366             propertyTypeItemFactory.inScope {
367                 itemFactory.createPropertyItem(
368                     fileLocation = propertyToSnapshot.fileLocation,
369                     itemLanguage = propertyToSnapshot.itemLanguage,
370                     modifiers = propertyToSnapshot.modifiers.snapshot(),
371                     documentationFactory = snapshotDocumentation(propertyToSnapshot, property),
372                     name = propertyToSnapshot.name(),
373                     containingClass = containingClass,
374                     type = propertyToSnapshot.type().snapshot(),
375                     getter = property.getter,
376                     setter = property.setter,
377                     constructorParameter = property.constructorParameter,
378                     backingField = property.backingField,
379                     receiver = property.receiver?.snapshot(),
380                     typeParameterList = typeParameterList
381                 )
382             }
383         newProperty.copySelectedApiVariants(propertyToSnapshot)
384 
385         containingClass.addProperty(newProperty)
386     }
387 
388     override fun visitTypeAlias(typeAlias: TypeAliasItem) {
389         val typeAliasToSnapshot = typeAlias.actualItemToSnapshot
390         val containingPackage = typeAlias.containingPackage().getSnapshotPackage()
391 
392         val (typeParameterList, typeAliasTypeItemFactory) =
393             globalTypeItemFactory.inScope {
394                 typeAliasToSnapshot.typeParameterList.snapshot(typeAliasToSnapshot.describe())
395             }
396 
397         val newTypeAlias =
398             typeAliasTypeItemFactory.inScope {
399                 itemFactory.createTypeAliasItem(
400                     fileLocation = typeAliasToSnapshot.fileLocation,
401                     modifiers = typeAliasToSnapshot.modifiers.snapshot(),
402                     qualifiedName = typeAliasToSnapshot.qualifiedName,
403                     containingPackage = containingPackage as DefaultPackageItem,
404                     aliasedType = typeAliasToSnapshot.aliasedType.snapshot(),
405                     typeParameterList = typeParameterList,
406                     documentationFactory = snapshotDocumentation(typeAliasToSnapshot, typeAlias),
407                 )
408             }
409         newTypeAlias.copySelectedApiVariants(typeAliasToSnapshot)
410     }
411 
412     /** Take a snapshot of [qualifiedName]. */
413     override fun createClassFromUnderlyingModel(qualifiedName: String): ClassItem? {
414         // Resolve the class in the original codebase, if possible.
415         val originalClass = originalCodebase.resolveClass(qualifiedName) ?: return null
416 
417         // Take a snapshot of a class that is referenced from, but not defined within, the snapshot.
418         originalClass.accept(referenceVisitor)
419 
420         // Find the newly added class.
421         val classItem =
422             snapshotCodebase.findClass(originalClass.qualifiedName())
423                 ?: error("Could not snapshot class $qualifiedName")
424 
425         // Any class that is created only when resolving references is by definition not part of the
426         // codebase and so will not be emitted.
427         classItem.emit = false
428 
429         return classItem
430     }
431 
432     companion object {
433         /**
434          * Take a snapshot of [codebase].
435          *
436          * @param definitionVisitorFactory a factory for creating an [ItemVisitor] that delegates to
437          *   a [DelegatedVisitor]. The [ItemVisitor] is used to determine which parts of [codebase]
438          *   will be defined within and emitted from the snapshot.
439          * @param referenceVisitorFactory a factory for creating an [ItemVisitor] that delegates to
440          *   a [DelegatedVisitor]. The [ItemVisitor] is used to determine which parts of [codebase]
441          *   will be referenced from within but not emitted from the snapshot.
442          */
443         fun takeSnapshot(
444             codebase: Codebase,
445             definitionVisitorFactory: (DelegatedVisitor) -> ItemVisitor,
446             referenceVisitorFactory: (DelegatedVisitor) -> ItemVisitor,
447         ): Codebase {
448             // Create a snapshot taker that will construct the snapshot. Pass in the
449             // referenceVisitorFactory so it can create the reference visitor for use in creating
450             // Items that are referenced from the snapshot.
451             val taker = CodebaseSnapshotTaker(referenceVisitorFactory)
452 
453             // Wrap it in a visitor that will determine which Items are defined in the snapshot and
454             // then apply that visitor to the input codebase.
455             val definitionVisitor = definitionVisitorFactory(taker)
456             codebase.accept(definitionVisitor)
457 
458             // Return the constructed snapshot.
459             return taker.snapshotCodebase
460         }
461     }
462 
463     /** Encapsulates state and methods needed to take a snapshot of [TypeItem]s. */
464     internal inner class SnapshotTypeItemFactoryContext(
465         val typeItemFactory: SnapshotTypeItemFactory
466     ) {
467         /**
468          * Create a snapshot of this [TypeParameterList] and an associated
469          * [SnapshotTypeItemFactory].
470          *
471          * @param description the description to use when failing to resolve a type parameter by
472          *   name.
473          */
474         internal fun TypeParameterList.snapshot(description: String) =
475             if (this == TypeParameterList.NONE) TypeParameterListAndFactory(this, typeItemFactory)
476             else
477                 DefaultTypeParameterList.createTypeParameterItemsAndFactory(
478                     typeItemFactory,
479                     description,
480                     this,
481                     { typeParameterItem ->
482                         DefaultTypeParameterItem(
483                             codebase = snapshotCodebase,
484                             modifiers = typeParameterItem.modifiers.snapshot(),
485                             name = typeParameterItem.name(),
486                             isReified = typeParameterItem.isReified()
487                         )
488                     },
489                     // Create, set and return the [BoundsTypeItem] list.
490                     { typeItemFactory, typeParameterItem ->
491                         typeParameterItem.typeBounds().map { typeItemFactory.getBoundsType(it) }
492                     },
493                 )
494         /** General [TypeItem] specific snapshot. */
495         internal fun TypeItem.snapshot() = typeItemFactory.getGeneralType(this)
496 
497         /** [ClassTypeItem] specific snapshot. */
498         internal fun ClassTypeItem.snapshot() =
499             typeItemFactory.getGeneralType(this) as ClassTypeItem
500 
501         /** Create a snapshot of this list of [ParameterItem]s. */
502         internal fun List<ParameterItem>.snapshot(
503             containingCallable: CallableItem,
504             currentCallable: CallableItem
505         ): List<ParameterItem> {
506             return map { parameterItem ->
507                 // Retrieve the public name immediately to remove any dependencies on this in the
508                 // lambda passed to publicNameProvider.
509                 val publicName = parameterItem.publicName()
510 
511                 // The parameter being snapshot may be from a previously released API, which may not
512                 // track parameter names and so may have to auto-generate them. This code tries to
513                 // avoid using the auto-generated names if possible. If the `publicName()` of the
514                 // parameter being snapshot is not `null` then get its `name()` as that will either
515                 // be set to the public name or another developer supplied name. Either way it will
516                 // not be auto-generated. However, if its `publicName()` is `null` then its `name()`
517                 // will be auto-generated so try and avoid that is possible. Instead, use the name
518                 // of the corresponding parameter from `currentCallable` as that is more likely to
519                 // have a developer supplied name, although it will be the same as `parameterItem`
520                 // if `currentCallable` is not being reverted.
521                 val name =
522                     if (publicName != null) parameterItem.name()
523                     else {
524                         val namedParameter =
525                             currentCallable.parameters()[parameterItem.parameterIndex]
526                         namedParameter.name()
527                     }
528 
529                 itemFactory.createParameterItem(
530                     fileLocation = parameterItem.fileLocation,
531                     itemLanguage = parameterItem.itemLanguage,
532                     modifiers = parameterItem.modifiers.snapshot(),
533                     name = name,
534                     publicNameProvider = { publicName },
535                     containingCallable = containingCallable,
536                     parameterIndex = parameterItem.parameterIndex,
537                     type = parameterItem.type().snapshot(),
538                     defaultValueFactory = parameterItem.defaultValue::snapshot,
539                 )
540             }
541         }
542     }
543 }
544 
545 /**
546  * Get the actual item to snapshot, this takes into account whether the item has been reverted.
547  *
548  * The [Showability.revertItem] is only set to a non-null value if changes to this [SelectableItem]
549  * have been reverted AND this [SelectableItem] existed in the previously released API.
550  *
551  * This casts the [Showability.revertItem] to the same type as this is called upon. That is safe as,
552  * if set to a non-null value the [Showability.revertItem] will always point to a [SelectableItem]
553  * of the same type.
554  */
555 private val <reified T : SelectableItem> T.actualItemToSnapshot: T
556     inline get() = (showability.revertItem ?: this) as T
557