Bernhard
10/06/2026, 3:53 PMcontext(msg: String)
fun f() {
println(msg)
}
class X {
operator fun invoke(a: X.() -> Unit) {
context("yo") {
a()
}
}
}
val x = X()
context("hey") {
x {
f()
}
}CLOVIS
10/06/2026, 3:55 PMyo :
context(msg: String)
fun f() {
println(msg)
}
class X {
operator fun invoke(a: context(X) () -> Unit) {
context("yo") {
a()
}
}
}
val x = X()
context("hey") {
x {
f()
}
}
(notice the different signature of the lambda)Bernhard
10/06/2026, 3:55 PMCLOVIS
10/06/2026, 3:57 PMBernhard
10/06/2026, 3:58 PMBernhard
10/06/2026, 3:59 PMBernhard
10/06/2026, 3:59 PMCLOVIS
10/06/2026, 4:00 PMString and X are different types so they shouldn't interfere. It's something elseBernhard
10/06/2026, 4:00 PMYoussef Shoaib [MOD]
10/06/2026, 4:00 PMString along:
class X {
operator fun invoke(a: context(String) X.() -> Unit) {
context("yo") {
a()
}
}
}
val x = X()
context("hey") {
x {
f()
}
}
Context parameters are "static" or "lexical", as opposed to "dynamic". In other words, they're only passed when explicitly in the signatureCLOVIS
10/06/2026, 4:00 PMCLOVIS
10/06/2026, 4:01 PMX and String were the same type, should've double checkedBernhard
10/06/2026, 4:02 PMYoussef Shoaib [MOD]
10/06/2026, 4:02 PMfun String.f() = println(this)
class X {
operator fun invoke(a: X.() -> Unit) {
with("yo") {
a()
}
}
}
val x = X()
with("hey") {
x {
f()
}
}
prints "hey" instead of "yo"Bernhard
10/06/2026, 4:02 PMYoussef Shoaib [MOD]
10/06/2026, 4:03 PMString along. It can't magically see the `context`s inside of the function body itself; it can only see what's in the signatureCLOVIS
10/06/2026, 4:03 PMf() wants a context parameter of type String, so it looks to the outer lambda
• x {} defines this but not not define a String context parameter, so it's ignored and it looks to the outer lambda
• context("hey") {} does define a context parameter, so it's the one that's usedBernhard
10/06/2026, 4:04 PMBernhard
10/06/2026, 4:04 PMYoussef Shoaib [MOD]
10/06/2026, 4:04 PMDslMarker? I think so yes. Idk if it'd work with String though. Brb I'll try itBernhard
10/06/2026, 4:05 PMBernhard
10/06/2026, 4:06 PMCLOVIS
10/06/2026, 4:06 PMcan force this to fail to compile?Not really, the idea is "it finds the closest one in the caller code". With
@DslMarker you could force remove it, but if you forget that as well…Bernhard
10/06/2026, 4:06 PMBernhard
10/06/2026, 4:07 PMYoussef Shoaib [MOD]
10/06/2026, 4:09 PM@DslMarker annotation class MyDsl
@MyDsl
class MyString(val string: String)
context(msg: MyString)
fun f() {
println(msg.string)
}
@MyDsl
class X {
operator fun invoke(a: X.() -> Unit) {
context("yo") {
a()
}
}
}
fun main() {
val x = X()
context(MyString("hey")) {
x {
f() //
'context(msg: MyString) fun f(): Unit' cannot be called in this context with an implicit receiver. Use an explicit receiver if necessary.
}
}
}
This works!Bernhard
10/06/2026, 4:11 PMCLOVIS
10/06/2026, 4:11 PMCLOVIS
10/06/2026, 4:11 PM@DslMarker
annotation class UIMarker
@UIMarker
object NoUI
@UIMarker
interface UI {
fun button(
name: String,
onClick: () -> Unit,
)
}
// Bridge function
context(ui: UI)
fun button(
name: String,
onClick: context(NoUI) () -> Unit,
) {
ui.button(name) {
context(NoUI) {
onClick()
}
}
}
class MaterialUI : UI {
override fun button(name: String, onClick: () -> Unit) {
// …
}
}
fun foo() {
context(MaterialUI()) {
// Legal
button("foo") {
// Illegal because NoUI 'forbids' the outer context
button("test") {
}
}
}
}Youssef Shoaib [MOD]
10/06/2026, 4:11 PM@DslMarker can help prevent this issue. Importantly, contexts can't "update" if they're not passed in the signature. There's no place where a context is stored based on its type or anything.
At compile time, the compiler keeps track of your contexts and statically resolves them when needed. The compiled code just has parameters passed instead of contextsBernhard
10/06/2026, 4:12 PMCLOVIS
10/06/2026, 4:13 PMCLOVIS
10/06/2026, 4:15 PM@RestrictsSuspension but only based on regular type inference