can anyone explain why the context is not updated ...
# getting-started
b
can anyone explain why the context is not updated here? Shouldn't this print "yo"?
Copy code
context(msg: String)
fun f() {
    println(msg)
}

class X {
    operator fun invoke(a: X.() -> Unit) {
        context("yo") {
            a()
        }
    }
}

val x = X()
context("hey") {
    x {
        f()
    }
}
✅ 1
c
Extension receivers have priority over context. This should write
yo
:
Copy code
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)
b
aah
c
In general: • If it's the thing doing the action, it should be a regular receiver • If it's information or work required for the action, it should be a context parameter so a function shouldn't have the same type appear in both places, so you should never encounter this in the real world
b
yeah, makes total sense
once again bitten by the difference of function definition syntax and lambda definitions
it took a while to understand (f: T.() -> Unit) as well
c
wait no I'm wrong,
String
and
X
are different types so they shouldn't interfere. It's something else
b
because if I had defined the lambda as a function, I certainly would have added a context() fun
y
I don't think I'm understanding this the same way as @CLOVIS. I think your problem is just that you didn't pass the
String
along:
Copy code
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 signature
c
Yeah @Youssef Shoaib [MOD] is right, I just saw it
Sorry, I thought
X
and
String
were the same type, should've double checked
b
is that a missing DslMarker problem?
y
Btw this is true for extensions too:
Copy code
fun 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"
b
that the context of the inner f() call resolves to the outer most one?
y
Yes because you didn't pass the
String
along. It can't magically see the `context`s inside of the function body itself; it can only see what's in the signature
c
If you enable inlay hints, it's easier to understand: •
f()
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 used
➕ 1
b
as in: can force this to fail to compile?
if the context is not passed along all lambda nestings?
y
With
DslMarker
? I think so yes. Idk if it'd work with
String
though. Brb I'll try it
b
maybe I can slap it on my custom context class that I pass along
nah, that produces completely different issues
c
can 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…
b
image.png
that is with @TheDsl on the Context object
y
Copy code
@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!
b
nvm, needed to prefix this.
c
Ahah once again Youssef is faster than I am
❤️ 1
Copy code
@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") {

			}
		}
	}
}
❤️ 1
thank you color 1
y
So yes
@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 contexts
b
thank you so much
❤️ 1
c
It's basically a custom implementation of
@RestrictsSuspension
but only based on regular type inference
👀 1