Skip to content

Commit 22ed3de

Browse files
committed
references-to-java-synthetic-properties.md: Improve "Reference resolution strategy" section
Concrete example is better at explaining the rules
1 parent ddfe515 commit 22ed3de

File tree

1 file changed

+107
-15
lines changed

1 file changed

+107
-15
lines changed

proposals/references-to-java-synthetic-properties.md

Lines changed: 107 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,28 +101,120 @@ In the future, we could possibly consider introducing a separate property for th
101101

102102
## Reference resolution strategy
103103

104-
The existing reference resolution strategy seems to be consistent and doesn't require any modification, even
105-
though the logic behind it may look puzzling at a first glance.
104+
```java
105+
class Jaba {
106+
public boolean isFoo; // (0) physical val
107+
public boolean isFoo() { return true; } // (1) physical method, (2) synthetic val
106108

107-
First of all, synthetic members always have lesser priority than any "physical" declarations. If a Java synthetic property is
108-
shadowed by "physical" declaration with the same name, and both members satisfy the expected type, or no expected type constraint is present,
109-
the compiler will prefer the physical member without reporting ambiguity.
109+
public boolean clazz() { return true; } // (3) physical method
110+
public class clazz {} // (4) physical class
110111

111-
In particular, it means that for "is"-prefixed boolean synthetic Java properties a reference expression without
112-
an expected type annotation will return a reference to the getter, not the property:
113-
```kotlin
114-
val isActiveRef = widget::isActive // returns a KFunction0<Boolean> reference the getter method
112+
public String getField() { return ""; } // (5) physical method, (6) synthetic val
113+
public int field = 2; // (7) physical val
114+
115+
public int bar() { return 1; } // (8) physical method
116+
public int bar; // (9) physical val
117+
118+
public int getGetGoo() { return 1; } // (10) physical method, (11) synthetic val
119+
public int getGoo = 2; // (12) physical val
120+
121+
public String getIsBaz() { return "getIsBaz"; } // (15) physical method, (16) synthetic val
122+
public boolean isBaz() { return true; } // (17) physical method, (18) synthetic val
123+
124+
public String isDuh; // (19) physical val
125+
public CharSequence isDuh() { return "42"; } // (20) physical method, (21) synthetic val
126+
}
115127
```
116128

117-
If a situation when the expected type is known, it will be taken into account, so that only declarations satisfying the
118-
expected type will take part in resolution. This rule is not specific to synthetic Java properties and applies to all
119-
kinds of callable references in Kotlin. It makes it possible to obtain a reference to a synthetic Java property even if the
120-
latter is overshadowed by a physical member with the same name:
121129
```kotlin
122-
val isActivePropertyRef: KMutableProperty0<Boolean> = widget::isActive // now it's a reference to the property, not the getter
130+
import kotlin.reflect.KProperty
131+
132+
fun main() {
133+
// Case (a)
134+
Jaba::isFoo // Conflict between physical members (0) and (1)
135+
Jaba::clazz // Conflict between physical members (3) and (4)
136+
Jaba::bar // Conflict between physical members (8) and (9)
137+
138+
// Case (b)
139+
Jaba::field // Resolves to (7). Physical member (7) is preferred over synthetic val (6)
140+
Jaba::getGoo // Resolves to (12). physical val (12) is preferred over synthetic val (11)
141+
142+
// Case (c)
143+
val z: KProperty<Boolean> = Jaba::isFoo // Resolves to (0). The conflict is resolved
144+
145+
// Case (d)
146+
val x: KProperty<String> = Jaba::field // Resolves to (6). A different member is chosen in non-conflicting situation
147+
148+
// Case (e)
149+
val w: KProperty<*> = Jaba::isBaz // Conflict between synthetic members (16) and (18)
150+
151+
// Case (f)
152+
val v: KProperty<String> = Jaba::isBaz // Resolves to (16). The conflict is resolved
153+
val h: KProperty<Boolean> = Jaba::isBaz // Resolves to (18). The conflict is resolved
154+
155+
// Case (g)
156+
val physical: KProperty<CharSequence> = Jaba::isDuh // Resolves to (19)
157+
}
123158
```
124159

125-
We believe that it should be enough to simply explain this logic in user documentation.
160+
The following resolution rules apply in order:
161+
1. Firstly, the candidates are filtered by the expected type. Only the candidates that satisfy the expected type participate in the reference overload resolution. Cases **(c)**, **(d)**, **(e)**, **(f)**, and **(g)**
162+
2. If there is only a single physical candidate. We choose the candidate. Cases **(b)** and **(g)**
163+
2. If there are multiple physical candidates, the conflict is reported. Case **(a)**
164+
3. If there is only a single synthetic candidate. We choose the candidate. Case **(d)**
165+
4. If there are multiple synthetic candidates, the conflict is reported. Case **(e)**
166+
167+
Even though the code might look puzzling at a glance (why `Jaba::isFoo` results in a conflict, but `Jaba::field` doesn't?),
168+
the resolution rules are in fact consistent.
169+
170+
Note that similar rules apply to pure Kotlin. Replace "physical" with "member", and "synthetic" with "extension"
171+
172+
```kotlin
173+
class Kt {
174+
val prop: Int = 42 // (0)
175+
fun prop(): Double = 1.0 // (1)
176+
177+
val memberVsExtension: String = "" // (3)
178+
179+
val physVsTwoExts: String = "" // (5)
180+
181+
val duh: String = "" // (8)
182+
fun duh(): Int = 42 // (9)
183+
}
184+
185+
val Kt.prop: String get() = "extension" // (2)
186+
val Kt.memberVsExtension: Int get() = 42 // (4)
187+
188+
fun Kt.physVsTwoExts(y: String): Int = 42 // (6)
189+
fun Kt.physVsTwoExts(): Double = 1.0 // (7)
190+
191+
val Kt.duh: CharSequence get() = "extension" // (10)
192+
193+
fun main() {
194+
// Case (a)
195+
Kt::prop // Conflict between member members (0) and (1)
196+
197+
// Case (b)
198+
Kt::memberVsExtension // Resolves to (3). Member is preferred over extension
199+
Kt::physVsTwoExts // Resolves to (5). Member is preferred over extensions
200+
201+
// Case (c)
202+
val z: KProperty<Int> = Kt::prop // Resolves to (0). The conflict is resolved
203+
204+
// Case (d)
205+
val x: KProperty<Int> = Kt::memberVsExtension // Resolves to (4). A different member is chosen in non-conflicting situation
206+
207+
// Case (e)
208+
val y: KFunction<*> = Kt::physVsTwoExts // Conflict between extension (6) and (7)
209+
210+
// Case (f)
211+
val w: KFunction<Double> = Kt::physVsTwoExts // Resolves to (7). The conflict is resolved
212+
val h: KFunction<Int> = Kt::physVsTwoExts // Resolves to (6). The conflict is resolved
213+
214+
// Case (g)
215+
val physical: KProperty<CharSequence> = Kt::duh // Resolves to (8)
216+
}
217+
```
126218

127219
## The proposed solution
128220

0 commit comments

Comments
 (0)