Skip to content

Commit 08e1806

Browse files
committed
[Context parameters] New specificity rule + shadowed receiver error
1 parent 0a0c4ac commit 08e1806

File tree

2 files changed

+114
-48
lines changed

2 files changed

+114
-48
lines changed

proposals/KEEP-0367-context-parameters.md

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
* **Type**: Design proposal
44
* **Author**: Alejandro Serrano
5-
* **Contributors**: Marat Akhin, Nikita Bobko, Ilya Gorbunov, Mikhail Zarechenskii, Denis Zharkov
5+
* **Contributors**: Marat Akhin, Nikita Bobko, Ilya Gorbunov, Kirill Rakhman, Mikhail Zarechenskii, Denis Zharkov
66
* **Status**: In progress
77
* **Discussion**: [KEEP-367](https://github.com/Kotlin/KEEP/issues/367)
88

@@ -34,12 +34,14 @@ This is an updated proposal for [KEEP-259](https://github.com/Kotlin/KEEP/issues
3434
* [For extending DSLs](#for-extending-dsls)
3535
* [Context-oriented dispatch / externally-implemented interface / type classes](#context-oriented-dispatch--externally-implemented-interface--type-classes)
3636
* [Dependency injection](#dependency-injection)
37+
* [Explicit context arguments](#explicit-context-arguments)
3738
* [Callable references](#callable-references)
3839
* [Context and classes](#context-and-classes)
3940
* [Technical design](#technical-design)
4041
* [Syntax](#syntax)
4142
* [Extended resolution algorithm](#extended-resolution-algorithm)
4243
* [Extended type inference algorithm](#extended-type-inference-algorithm)
44+
* [Extended scoping rules](#extended-scoping-rules)
4345
* [ABI compatibility](#abi-compatibility)
4446
* [Q\&A about design decisions](#qa-about-design-decisions)
4547
* [Acknowledgments](#acknowledgments)
@@ -108,7 +110,21 @@ context(logger: Logger) fun User.doAction() {
108110
* The type and order of context parameters must coincide.
109111
* It is allowed (yet discouraged) to change the name of a context parameter.
110112

111-
It is a conflict to declare overloads which only differ in the number of context parameters. It is allowed to declare one overload with _no_ context parameters and one with _some_ of them (this is aligned with §7.8).
113+
If is a conflict to declare overloads with the exact same set of context parameters.
114+
115+
It is allowed to declare multiple overloads if they differ in their set of context parameters. However, a warning should be reported if all of the set of context parameters of one overload are supertypes of those of another overload, since by §7.8 only the latter would be callable in a scope in which the more specific context is available.
116+
117+
```kotlin
118+
open class Parent
119+
class Child : Parent()
120+
121+
fun foo() { ... } // warning
122+
context(x: Child) fun foo() { ... } // warning
123+
context(x: Child, a: A) fun foo() { ... }
124+
125+
context(x: Parent) fun bar() { ... } // warning
126+
context(x: Child) fun bar() { ... }
127+
```
112128

113129
**§1.6** *(naming ambiguity)*: We use the term **context** with two meanings:
114130

@@ -165,16 +181,16 @@ fun foo(y: Int) {
165181

166182
**§1.8** *(lambdas)*: If a lambda is assigned a function type with context parameters, those behave as if declared with `_` as its name.
167183

168-
* They participate in context resolution but are only accessible through the `implicit` function (defined below).
184+
* They participate in context resolution but are only accessible through the `contextOf` function (defined below).
169185

170186
```kotlin
171187
fun <A> withConsoleLogger(block: context(Logger) () -> A) = ...
172188

173189
withConsoleLogger {
174190
// you can call functions with Logger as context parameter
175191
logWithTime("doing something")
176-
// you can use 'implicit' to access the context parameter
177-
implicit<Logger>().log("hello")
192+
// you can use 'contextOf' to access the context parameter
193+
contextOf<Logger>().log("hello")
178194
}
179195
```
180196

@@ -414,6 +430,10 @@ context(logger, userService) {
414430
}
415431
```
416432

433+
## Explicit context arguments
434+
435+
[KEEP-448](./KEEP-0448-explicit-context-arguments.md) should be taken as an integral part of this design.
436+
417437
## Callable references
418438

419439
**§5.1** *(callable references, eager resolution)*: References to callables declared with context parameters are resolved **eagerly**:
@@ -592,7 +612,7 @@ fun <A, T> withExampleReceiver(value: A, block: ExampleScope<A>.() -> T): T = ..
592612
fun <A, T> withExampleContext(value: A, block: context(example: ExampleScope<A>) () -> T): T =
593613
withExampleReceiver(value) { block() }
594614

595-
context(ExampleScope<A>) fun <A> similarExampleTo(other: A): A = ...
615+
context(example: ExampleScope<A>) fun <A> similarExampleTo(other: A): A = ...
596616

597617
fun dslMarkerExample() =
598618
withExampleContext(3) { // (1)
@@ -618,42 +638,70 @@ fun dslMarkerExample() =
618638
}
619639
```
620640

621-
**§7.8** *(most specific candidate)*: When choosing the **most specific candidate** we follow the [Kotlin specification](https://kotlinlang.org/spec/overload-resolution.html#choosing-the-most-specific-candidate-from-the-overload-candidate-set), with one addition:
622-
623-
* Candidates with context parameters are considered more specific than those without them.
624-
* But there is no other prioritization coming from the length of the context parameter list or their types.
641+
**§7.8** *(most specific candidate)*: When choosing the **most specific candidate** we follow the [Kotlin specification](https://kotlinlang.org/spec/overload-resolution.html#choosing-the-most-specific-candidate-from-the-overload-candidate-set). Context parameters play **no role** in this choice, unless they are explicitly given (see [KEEP-448](./KEEP-0448-explicit-context-arguments.md) for details).
625642

626643
For example, the following call to `foo` is declared ambiguous, since `"hello"` may work both as `String` or `Any` context parameter.
627644

628645
```kotlin
629-
context(Any) fun foo() {}
630-
context(String) fun foo() {}
646+
context(value: Any) fun foo() {}
647+
context(string: String) fun foo() {}
631648

632-
fun test() = with("hello") {
649+
fun test() = context("hello") {
633650
foo()
634651
}
635652
```
636653

637654
The reasoning for this particular rule is that, since contexts are implicit, there is no way for the user to resolve to the other function if required. This implicitness also means that it's harder to figure out which overload is called from the program code -- once again, there's no value you can easily point to. In contrast, in the case of an explicit parameter, you can always use `f("hello" as Any)` to force using `(Any) -> R` over `(String) -> R`.
638655

656+
> [!NOTE]
657+
> [Explicit context arguments](./KEEP-0448-explicit-context-arguments.md) provide a way to disambiguate between overloads if the name of the context parameter is different.
658+
>
659+
> ```kotlin
660+
> fun test() = foo(value = "hello") // calls the 'Any' overload
661+
> ```
662+
639663
### Extended type inference algorithm
640664
641665
**§7.9** *(lambda literal inference)*: the type inference process in the [Kotlin specification](https://kotlinlang.org/spec/type-inference.html#statements-with-lambda-literals) should take context parameters into account. Note that unless a function type with context is "pushed" as a type for the lambda, context parameters are never inferred.
642666
643667
Context parameters take part in [builder-style type inference](https://kotlinlang.org/spec/type-inference.html#builder-style-type-inference), or any similar process defined by the implementation and whose goal is to infer better types for receivers.
644668
669+
### Extended scoping rules
670+
671+
**§7.10** *(receiver shadowed by context)*: it is an error to call a function that binds to an implicit _receiver_ in the context, whenever an implicit _context value_ of the same type is available in a more nested scope. The reasoning behind this error is that in many cases a function defined over the implicit receiver (either as member or as extension function) is chosen instead of a similar function using a context parameter; even if the context value is in a (lexically) nested scope. This leads to surprising behavior.
672+
673+
Note that this error should be reported even if there's no function defined with context parameters of that type in scope. For example, in the code below both calls to `moo` are marked as errors.
674+
675+
```kotlin
676+
class Cow {
677+
fun moo() { }
678+
679+
fun test1() = context(Cow()) {
680+
moo() // error: receiver shadowed by context
681+
}
682+
}
683+
684+
fun test2() {
685+
with(Cow()) {
686+
context(Cow()) {
687+
moo() // error: receiver shadowed by context
688+
}
689+
}
690+
}
691+
```
692+
645693
### ABI compatibility
646694
647-
**§7.10** *(JVM and Java compatibility, functions)*: In the JVM a function with context parameters is represented as a function with additional parameters. In particular, the order is:
695+
**§7.11** *(JVM and Java compatibility, functions)*: In the JVM a function with context parameters is represented as a function with additional parameters. In particular, the order is:
648696
1. Context parameters, if present;
649697
2. Extension receiver, if present;
650698
3. Regular value parameters.
651699
652700
Note that parameter names do not impact JVM ABI compatibility, but we use the names given in parameter declarations as far as possible.
653701
654-
**§7.11** *(JVM and Java compatibility, properties)*: In the JVM a property with context parameters is represented by its corresponding getter and/or setter. This representation follows the same argument order described in §7.10.
702+
**§7.12** *(JVM and Java compatibility, properties)*: In the JVM a property with context parameters is represented by its corresponding getter and/or setter. This representation follows the same argument order described in §7.10.
655703
656-
**§7.12** *(other targets)*: Targets may not follow the same ABI compatibility guarantees as those described for the JVM.
704+
**§7.13** *(other targets)*: Targets may not follow the same ABI compatibility guarantees as those described for the JVM.
657705
658706
## Q&A about design decisions
659707

proposals/KEEP-0448-explicit-context-arguments.md

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -89,32 +89,66 @@ time as other named arguments to the function, _not_ during the context
8989
resolution phase. This choice has consequences for type inference and overload
9090
resolution, that may now use additional information from those arguments.
9191

92-
**More specific candidate.** There are no changes to the
92+
**Context parameters naming.**
93+
Context parameters whose name is declared as `_` may not be given as explicit
94+
context argument. We strongly recommend to give names to context parameters,
95+
and make them _unique_ among similar overloads, so the name can be used for
96+
disambiguation purposes.
97+
98+
**More specific candidate.** The
9399
[most specific candidate rule](./KEEP-0367-context-parameters.md#extended-resolution-algorithm)
94-
with respect to context arguments. In particular, a function with context
95-
parameters (even if all of them are explicitly given) is considered more
96-
specific than one without.
100+
is updated. In contrast to implicitly-resolved context arguments,
101+
explicit context arguments **do** participate in the selection of the most
102+
specific candidate. This aligns with how the
103+
[algorithm](https://kotlinlang.org/spec/overload-resolution.html#algorithm-of-msc-selection)
104+
treats optional parameters, that is, they are only accounted for when
105+
explicitly given.
106+
107+
Specifically, when we do step 1 of the
108+
[most specific candidate selection algorithm](https://kotlinlang.org/spec/overload-resolution.html#algorithm-of-msc-selection),
109+
110+
> For every non-default argument of the call and their corresponding
111+
> declaration-site parameter types...
97112
98-
For example, in the following example,
113+
now handles not only regular value parameters, but also context parameters and
114+
their declaration-site types whenever they are passed explicitly.
115+
116+
For example,
99117

100118
```kotlin
101-
context(a: A) fun foo() { ... } // (1)
102-
fun foo(a: A) { ... } // (2)
119+
open class Parent
120+
class Child : Parent()
121+
122+
context(x: Parent) fun foo() { ... } // (1)
123+
context(x: Child) fun foo() { ... } // (2)
103124

104125
fun test() {
105-
foo(a = A) // resolves to (1)
126+
context(Child()) {
127+
foo() // ambiguity between (1) and (2)
128+
}
129+
130+
foo(x = Child()) // resolves to (2)
106131
}
107132
```
108133

109-
Note that you can still disambiguate between each of them: you can use
110-
`context` to be explicit about (1), or you can give the arguments positionally
111-
to choose (2).
134+
Note that explicit context arguments may "compete" with regular value
135+
arguments during most specific candidate selection.
112136

113-
**Context parameters naming.**
114-
Context parameters whose name is declared as `_` may not be given as explicit
115-
context argument. We strongly recommend to give names to context parameters,
116-
and make them _unique_ among similar overloads, so the name can be used for
117-
disambiguation purposes.
137+
```kotlin
138+
open class Parent
139+
class Child : Parent()
140+
141+
fun foo(x: Parent) { ... } // (1)
142+
context(x: Child) fun foo() { ... } // (2)
143+
144+
fun test() {
145+
context(Child()) {
146+
foo() // resolves to (2) -- we miss arguments for (1)
147+
}
148+
149+
foo(x = Child()) // resolves to (2) -- 'Child' is more specific than 'Parent'
150+
}
151+
```
118152

119153
**Contextual properties.** This proposal does not introduce syntax for
120154
explicitly providing context arguments to contextual properties.
@@ -183,19 +217,3 @@ Finally, we think that a call in which only some context arguments are given
183217
explicitly is not more difficult to understand than one in which all of them
184218
are explicitly given. The intuitive model (those not explicitly given are
185219
implicitly resolved) is still simple to understand.
186-
187-
**More specific candidate.** We have considered a variation of the rules
188-
for _more specific candidate_ in which a function call in which all context
189-
arguments are explicitly given would count as having no contexts.
190-
191-
```kotlin
192-
context(a: A) fun foo() { ... } // (1)
193-
fun foo(a: A) { ... } // (2)
194-
195-
fun test() {
196-
foo(a = A) // ambiguity
197-
}
198-
```
199-
200-
However, the benefits of such variation are unclear, whereas it introduces
201-
even more complication to a somehow convoluted rule.

0 commit comments

Comments
 (0)