Add receiver-stack function types
This commit is contained in:
1 parent
1bababa058
commit
e107296bca
11 files changed
+415
-46
No files matched your search
+25
@@ -454,6 +454,31 @@ Key rules and features:
|
||||
- For arbitrary receivers, use casts: `(expr as Type).member(...)` or `(expr as? Type)?.member(...)`.
|
||||
- Qualified access does not relax visibility.
|
||||
|
||||
### Receiver-stack lambdas
|
||||
|
||||
Qualified `this@Type` is also used outside inheritance when a lambda has multiple visible receivers.
|
||||
This is common in DSL-style builders.
|
||||
|
||||
- `A & B` means one receiver value that implements both types.
|
||||
- `context(A, B) C.()->R` means a receiver stack:
|
||||
- primary `this` is `C`
|
||||
- outer/context receivers are `A`, then `B`
|
||||
- Unqualified lookup checks the primary receiver first.
|
||||
- If the primary receiver does not define a member and several outer/context receivers do, Lyng reports a compile-time ambiguity. Use `this@Type` to select one explicitly.
|
||||
|
||||
Example:
|
||||
|
||||
```lyng
|
||||
class Html { fun title() = "html" }
|
||||
class Head { fun title() = "head" }
|
||||
class Body
|
||||
|
||||
val block: context(Html, Head) Body.()->String = {
|
||||
// title() // compile-time ambiguity: Html vs Head
|
||||
this@Html.title()
|
||||
}
|
||||
```
|
||||
|
||||
- Field inheritance (`val`/`var`) and collisions
|
||||
- Instance storage is kept per declaring class, internally disambiguated; unqualified read/write resolves to the first match in the resolution order (leftmost base).
|
||||
- Qualified read/write (via `this@Type` or casts) targets the chosen ancestor’s storage.
|
||||
|
||||
@@ -164,7 +164,12 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
|
||||
- unions `A | B`
|
||||
- intersections `A & B`
|
||||
- function types `(A, B)->R` and receiver form `Receiver.(A)->R`
|
||||
- receiver-stack function types via `context(A, B) Receiver.(P)->R`
|
||||
- variadics in function type via ellipsis (`T...`)
|
||||
- `A & B` means one value implementing both types.
|
||||
- `context(A, B) Receiver.(P)->R` is different: it declares an ordered implicit-receiver stack where `Receiver` is primary `this`, then `A`, then `B`.
|
||||
- Nested receiver lambdas keep outer receivers in scope; unqualified lookup prefers the innermost receiver, and `this@Type` can select an outer/context receiver explicitly.
|
||||
- If the primary receiver does not provide a member and multiple outer/context receivers do, the lookup is a compile-time ambiguity and must be disambiguated with `this@Type`.
|
||||
- Generics:
|
||||
- type params on classes/functions/type aliases
|
||||
- bounds via `:` with union/intersection expressions
|
||||
@@ -217,6 +222,7 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
|
||||
- Disambiguation helpers are supported:
|
||||
- qualified this: `this@Base.member()`
|
||||
- cast view: `(obj as Base).member()`
|
||||
- In nested receiver lambdas, `this@Type` can target any receiver visible through the receiver stack, not just inheritance ancestors.
|
||||
- On unknown receiver types, compiler allows only Object-safe members:
|
||||
- `toString`, `toInspectString`, `let`, `also`, `apply`, `run`
|
||||
- Other members require known receiver type or explicit cast.
|
||||
|
||||
@@ -352,6 +352,40 @@ Sets `this` to the first argument and executes the block. Returns the value retu
|
||||
assertEquals(3, sum)
|
||||
>>> void
|
||||
|
||||
Receiver lambdas can also keep outer receivers in scope. The primary receiver wins for unqualified lookup, and `this@Type`
|
||||
selects an outer receiver explicitly:
|
||||
|
||||
class Html { fun lang() = "en" }
|
||||
class Body { fun lang() = "body" }
|
||||
|
||||
fun html(block: Html.()->String) = with(Html()) { block(this) }
|
||||
fun body(block: Body.()->String) = with(Body()) { block(this) }
|
||||
|
||||
val result = html {
|
||||
body {
|
||||
lang() + ":" + this@Html.lang()
|
||||
}
|
||||
}
|
||||
assertEquals("body:en", result)
|
||||
>>> void
|
||||
|
||||
You can declare the same requirement in a function type:
|
||||
|
||||
val block: context(Html) Body.()->String = {
|
||||
lang() + ":" + this@Html.lang()
|
||||
}
|
||||
|
||||
If the primary receiver does not define a member and multiple outer/context receivers do, Lyng reports an ambiguity instead of picking one silently:
|
||||
|
||||
class A { fun title() = "a" }
|
||||
class B { fun title() = "b" }
|
||||
class C
|
||||
|
||||
val block: context(A, B) C.()->String = {
|
||||
// title() // compile-time ambiguity
|
||||
this@A.title()
|
||||
}
|
||||
|
||||
## run
|
||||
|
||||
Executes a block after it returning the value passed by the block. for example, can be used with elvis operator:
|
||||
|
||||
Reference in new issue
Block a user