Skip to content

Commit ebd432b

Browse files
committed
fixup! Update KEEP on Explicit backing fields feature
1 parent 969e678 commit ebd432b

File tree

1 file changed

+53
-4
lines changed

1 file changed

+53
-4
lines changed

Diff for: proposals/explicit-backing-fields.md

+53-4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class SomeViewModel : ViewModel() {
5656
* [Java interoperability](#java-interoperability)
5757
* [Reflection](#reflection)
5858
* [Future enhancements](#future-enhancements)
59+
* [Underscore operator in type parameters](#underscore-operator-in-type-parameters)
5960

6061
<!--- END -->
6162

@@ -64,6 +65,9 @@ class SomeViewModel : ViewModel() {
6465
This proposal caters to a variety of use-cases that are currently met via a
6566
[backing property pattern](https://kotlinlang.org/docs/properties.html#backing-properties).
6667

68+
To better understand pattern usage and to help in decision-making,
69+
for each pattern occurrence statistics were collected based on open repositories on GitHub.
70+
6771
### Use cases targeted by the explicit backing field feature
6872

6973
#### Expose read-only supertype
@@ -118,6 +122,23 @@ val city: StateFlow<String> = _city.asStateFlow()
118122
It is usually more desirable for the primary property to be stored, as in the code snippet above or [this example](https://github.com/wikimedia/apps-android-wikipedia/blob/604007f38e834667f037c475b05f362b92a5575c/app/src/main/java/org/wikipedia/talk/template/TalkTemplatesViewModel.kt#L26-L30).
119123
However, computed property pattern is also quite popular ([example](https://github.com/jellyfin/jellyfin-androidtv/blob/b46f1acc99fc848abb9ef896c9bb2941e9c6e3ff/playback/core/src/main/kotlin/PlayerState.kt#L75-L88)).
120124

125+
##### Convenient type cast without spelling the full type
126+
127+
Also, conversion method like `asStateFlow` is used when we want to cast backing property to supertype,
128+
but we don't want to explicitly spell the full type (which is also was [one of the reasons of introducing `asStateFlow`](https://github.com/Kotlin/kotlinx.coroutines/issues/1973#issuecomment-621660861)):
129+
130+
```kotlin
131+
private val _item = MutableStateFlow(ExtremelyLongTypeName.DefaultValue)
132+
val item = _city.asStateFlow()
133+
// shorter than
134+
val item: StateFlow<ExtremelyLongTypeName> = _city
135+
// or
136+
val item = _city as StateFlow<ExtremelyLongTypeName>
137+
```
138+
139+
Apart from rewriting it using explicit backing fields, this use case could be reduced to the simpler form of [previous use-case](#expose-read-only-supertype),
140+
if [underscore operator in type parameters](#underscore-operator-in-type-parameters) was supported.
141+
121142
##### Hide complex value storage logic
122143

123144
Sometimes we want to provide a public API for getting the immediate value of a variable,
@@ -232,7 +253,8 @@ Visibility of property must be more permissive than explicit backing field visib
232253
### Resolution
233254

234255
Calls of properties with explicit backing field are resolved to
235-
- the backing field, if call is made from inside the class (within private scope),
256+
- the backing field, if property is accessed from the same scope it is declared in
257+
(actually, follows `private` visibility rules as per [specification](https://kotlinlang.org/spec/declarations.html#declaration-visibility)),
236258
- getter, otherwise.
237259

238260
```kotlin
@@ -250,7 +272,8 @@ fun outside(vm: SomeViewModel) {
250272
}
251273
```
252274

253-
There is no possibility to call a getter inside the class
275+
There is no possibility to call a getter instead of field
276+
when the property is accessed from the same scope it is declared in
254277
(might be reconsidered in [Future enhancements](#future-enhancements)).
255278

256279
### Accessors
@@ -292,6 +315,12 @@ immediately after the backing field is initialized and then returned every time
292315

293316
If property with explicit backing field has initializer, it's prohibited to declare accessors.
294317

318+
> This use case stands out a little from the rest,
319+
since it implies the simultaneous existence of two different stored values for one property,
320+
which sounds dissonant with the current mental image of properties in Kotlin.
321+
However, this is a case where the desire for consistency gives way to the importance of supporting a popular pattern
322+
(see [Expose different object](#expose-different-object)).
323+
295324
### Accessing field inside accessors and initializers
296325

297326
It's possible to access explicit backing field by calling `field` from inside accessors (like it works now for ordinary
@@ -334,6 +363,8 @@ Neither property with explicit backing field:
334363
* nor explicit backing field itself can be `lateinit` (added to [Future enhancements](#future-enhancements))
335364
* nor explicit backing field itself can be delegated (added to [Future enhancements](#future-enhancements))
336365

366+
It is not prohibited for a top-level property to have an explicit backing field.
367+
337368
## Technical details
338369

339370
### Grammar changes
@@ -410,7 +441,7 @@ public final StateFlow<String> getCity() {
410441
### Reflection
411442

412443
Callable reference to property with explicit backing field has type `KProperty<V>` (or its subtypes) where `V`
413-
is type of property (not backing field) regardless of whether it is called inside class or not.
444+
is type of property (not backing field) regardless of wherever it is accessed from.
414445

415446
On JVM backing field can be obtained using [`javaField` extension](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect.jvm/java-field.html).
416447

@@ -423,11 +454,29 @@ We strive to keep the design simple and uncluttered, so at this point we put asi
423454
Of course, we may revise some decisions in the future based on community feedback.
424455
Here is a list of the most obvious future enhancements for the feature:
425456

426-
1. Make it possible to access getter instead of explicit backing field when referenced within the scope of visibility of explicit backing field. This is not supported because usually there is no need for calling getter (see [Access backing property inside](#access-backing-property-inside)).
457+
1. Make it possible to access getter instead of explicit backing field when the property is accessed from the same scope the property is declared in. This is not supported because usually there is no need for calling getter (see [Access backing property inside](#access-backing-property-inside)).
427458
2. Support other visibilities of explicit backing field (`protected` or `internal`).
428459
3. Support delegation of explicit backing field or property.
429460
4. Support `lateinit` explicit backing field.
430461
5. Support combining mutable explicit backing field and non-mutable property.
431462
Despite its popularity (see [`var` backing property](#var-backing-property)), this use case is not supported in this proposal, because it is impossible for one property to behave as mutable + nullable and
432463
at the same time non-mutable and non-nullable.
433464
6. Add API in Reflection to retrieve information about explicit backing field.
465+
466+
### Underscore operator in type parameters
467+
468+
It's proposed to make [underscore operator](https://kotlinlang.org/docs/generics.html#underscore-operator-for-type-arguments)
469+
more powerful and support it in type parameters.
470+
471+
This is a separate language feature, yet worth mentioning here,
472+
as it can help rewrite properties with explicit backing field in a better way in some cases
473+
(see [Convenient type cast without spelling the full type](#convenient-type-cast-without-spelling-the-full-type)).
474+
475+
```kotlin
476+
val item = field.asStateFlow() // if we don't need read-only wrapper...
477+
field = MutableStateFlow(ExtremelyLongTypeName.Default)
478+
479+
// ...we could rewrite it like that without losing conciseness
480+
val item: StateFlow<_> // inferred StateFlow<ExtremelyLongTypeName>
481+
field = MutableStateFlow(ExtremelyLongTypeName.Default)
482+
```

0 commit comments

Comments
 (0)