rcd27
02/23/2023, 8:58 AMwith(x,y){...}
syntax for better context receivers experience. And that was custom function. How can I implement that multiple with()
?simon.vergauwen
02/23/2023, 9:00 AM@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
though on 1.8.xrcd27
02/23/2023, 9:00 AMthan_
02/23/2023, 10:14 AMTypePlacedHolder
as a param of the block function?simon.vergauwen
02/23/2023, 10:19 AMcontext(A, B)
into the first parameter of the lambda. So context(A, B) () -> C
resulted in context(A) (B) -> C
but it's been some time so I don't remember the details anymore.carbaj0
02/23/2023, 12:38 PMcarbaj0
02/23/2023, 12:39 PMsimon.vergauwen
02/23/2023, 12:39 PMthan_
02/23/2023, 12:41 PM@OptIn(ExperimentalContracts::class)
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
inline fun <A, B, R> with(a: A, b: B, f: context(A, B) () -> R): R {
contract {
callsInPlace(f, InvocationKind.EXACTLY_ONCE)
}
return f(a, b)
}
context (Int, String)
fun foo(){}
fun bar() = with(5, ""){
foo()
}
this seems to work for me on 1.8.0
. But I did very limited testing, so I might just not have triggered the issue.Youssef Shoaib [MOD]
02/24/2023, 4:45 AMwith
function with a lambda that that lambda has those A&B contexts.
My hunch that it was type parameter related came from the fact that if you used concrete types (say for instance Int
or List<String>
) the lambda's contexts would be recognised appropriately.
I noticed, however, after messing around a bit, that if I had passed all the context parameters also as regular parameters, the context parameters would also get recognised. In other words, calling the function inline fun <A> foo(lambda: context(A) (A) -> Unit)
with a lambda would result in the context of type A being recognised and usable inside the lambda. That meant that a multi-with function could be defined as inline fun <A, B, R> with(a: A, b: B, f: context(A, B) (A, B) -> R): R
Upon seeing that, though, it felt a little ugly that all those arguments would need to be passed to the function twice.
The magical thing is, it turned out that you only had to mention the last type parameter that was used amongst the context parameters. My guess is that mentioning that last type parameter triggered a compiler path where it saved the information for all the previous type parameters mentioned within the context parameters. Interestingly, if I only mentioned A as a normal parameter, then A would be recognised as a context, but B wouldn't. Weird compiler magic¬
Putting that all together, that meant that a multi-with could just be inline fun <A, B, R> with(a: A, b: B, f: context(A, B) (B) -> R): R
The finishing touch was to use a trick I came up with a while ago, which is `TypeWrapper`s. Basically, a `TypeWrapper`(or TypePlacedHolder
as Simon calls it) is an interface that has a type parameter that you want the compiler to move around as information. Using a type wrapper here just felt neater since you only need to pass a singleton and thus a user will see that the singleton can be safely ignored.
Side note: TypeWrappers are quite versatile in that they just expose the type-inference that the compiler has and thus allow you to access and play with types and pass them around while relying on type-inference to do the heavy lifting. For instance, long ago I made a few utilities that allowed you to be able to partially specify type arguments when calling a function (which is now obsolete because we have the underscore operator that does the same thing). A use case for those utilities was creating intersection types, since you can't explicitly specify an intersection type, but you can call a method that has A, B, C
as type parameters where C : A, C : B
and it has a normal parameter of type TypeWrapper<A, B>
thus allowing you to specify your A and B and C would be automatically inferred to A&B
.
TL;DR: compiler seemed to forget about type parameters that are used in a lambda's context, and so by mentioning the last such type parameter somewhere in its regular parameters, the compiler would retain the information about those type parameters and thus the contexts would be recognised inside the lambda. The bug is now fixed thankfully though!