Olaf Gottschalk
02/17/2026, 2:33 PMResult<T> as the result, the other using Arrow's Raise context using context parameters. It looks like this:
context(_: Raise<NotFound>)
fun extractFrom(data: Any?): Any?
fun extractFrom(data: Any?): Result<Any?> =
recover({
Result.success(extractFrom(data))
}) {
Result.failure(NotFoundException(it.message))
}
When switching from Kotlin 2.3.10 to 2.3.20-Beta2, I get this error:
e: JsonPointer.kt:86:24 Overload resolution ambiguity between candidates:
context(_: Raise<NotFound>) fun extractFrom(data: Any?): Any?
fun extractFrom(data: Any?): Result<Any?>
This one, I could work around by changing one function's name. Not nice, but okay, I could. But WHY is the newer compiler not able to distinguish the two functions which do have different signatures?
Plus: it all works in IntelliJ, it is just when compiling using gradle build that this fails!
The next problem I get when working around this is even more tricky and gives me real headaches. I am deliberately overloading well-known Java functions with my own stubs to give users proper error messages. As an example, here I am "overwriting" `System.currentTimeMillis()`:
object System {
@Deprecated(ERROR_NOW_OUTSIDE_CONTEXT, level = ERROR)
fun currentTimeMillis(): Long = error(ERROR_NOW_OUTSIDE_CONTEXT)
/**
* Returns the current time in milliseconds since the epoch or a fixed time in test environment.
*/
context(_: TransformationTimeAccess)
fun currentTimeMillis(): Long = instantNow.toEpochMilli()
}
Basically, in my environment users are NOT allowed to use import statements, so if somebody uses System.currentTimeMillis() outside of a context that is required, it deliberately fails with a compiler error, but when calling IN the context it's allowed, it will work.
With Kotlin up to 2.3.10 this works just fine, with Kotlin 2.3.20-Beta2 I get this error:
e: Overload resolution ambiguity between candidates:
fun currentTimeMillis(): Long
context(_: TransformationTimeAccess) fun currentTimeMillis(): Long
If this gets released like this, I would really be in deep trouble.
WHY does the new compiler fail on this when the old ones worked?
Thanks!dmitriy.novozhilov
02/17/2026, 2:35 PMkirillrakhman
02/17/2026, 2:37 PMOlaf Gottschalk
02/17/2026, 2:37 PMdmitriy.novozhilov
02/17/2026, 2:38 PMOlaf Gottschalk
02/17/2026, 2:39 PMOlaf Gottschalk
02/17/2026, 2:40 PMOlaf Gottschalk
02/17/2026, 2:42 PMfun foo() {}
context(_: Bar)
fun foo() {}
object Bar
fun main() {
foo() // should select the first foo()
context(Bar) {
foo() // should select the second foo with context
}
}Youssef Shoaib [MOD]
02/17/2026, 2:42 PM§7.8 _(most specific candidate)_: When choosing the most specific candidate we follow the Kotlin specification. Context parameters play no role in this choice, unless they are explicitly given (see KEEP-448 for details).While before it used to say:
* Candidates with context parameters are considered more specific than those without them.
* But there is no other prioritization coming from the length of the context parameter list or their types.Changed in this commit
Olaf Gottschalk
02/17/2026, 2:42 PMOlaf Gottschalk
02/17/2026, 2:43 PMOlaf Gottschalk
02/17/2026, 2:44 PMkirillrakhman
02/17/2026, 2:45 PMOlaf Gottschalk
02/17/2026, 2:45 PMOlaf Gottschalk
02/17/2026, 2:45 PMOlaf Gottschalk
02/17/2026, 2:46 PMYoussef Shoaib [MOD]
02/17/2026, 2:46 PM@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.LowPriorityInOverloadResolution
fun foo() { println("no context") }
context(_: Bar)
fun foo() { println("context") }
object Bar
fun main() {
foo() // no context
context(Bar) {
foo() // context
}
}
Edit: even better:
fun foo(unit: Unit = Unit) { println("no context") }
context(_: Bar)
fun foo() { println("context") }
object Bar
fun main() {
foo() // no context
context(Bar) {
foo() // context
}
}Olaf Gottschalk
02/17/2026, 2:47 PMOlaf Gottschalk
02/17/2026, 2:47 PMOlaf Gottschalk
02/17/2026, 2:47 PMOlaf Gottschalk
02/17/2026, 2:48 PMOlaf Gottschalk
02/17/2026, 2:48 PMYoussef Shoaib [MOD]
02/17/2026, 2:49 PMkirillrakhman
02/17/2026, 3:00 PMYoussef Shoaib [MOD]
02/17/2026, 3:11 PMunit: Unit trick already fixes your solution, but I think some alternative solutions can be helpful, too:
For your first problem, it sounds like you're transitioning from using Result to using Arrow. Hence, you can write a little wrapper that encapsulates your recover logic, and use that whenever you're in code that still relies on Result. You thus won't have 2 different methods anymore
OptIn annotations can let you mark where things like currentTimeMillis are used, so you can easily find them
You can remove the @Deprecated overload, and instead rely on the fact that the compiler will already report a NO_CONTEXT_ARGUMENT error for TransformationTimeAccess. I understand your users may be non technical, but teaching them that No context argument for TransformationTimeAccess found means "you can't use time-based APIs without having TransformationTimeAccess in scope" sounds pretty doable to me. If you explain the concept of a "context" to them, and explain what TransformationTimeAccess lets you do, then the error message is very clear.
Maybe the real feature request here is to allow for a custom message to be reported when a context is not found in scope (similar to what OptIn allows you to do). Scala has something similar with their implicitNotFound annotationOlaf Gottschalk
02/17/2026, 3:27 PMYoussef Shoaib [MOD]
02/17/2026, 3:36 PMLowPriorityInOverloadResolution) is worth considering sometimes. I wish these annotations were available with an OptIn or something. Even with the ugly hacky `Suppression`s, I think they're worth it as a last resort.Olaf Gottschalk
02/17/2026, 3:38 PMYoussef Shoaib [MOD]
02/17/2026, 3:41 PM@OnlyInputTypes to have a reasonable assertEquals). Then, I look often at the file they're defined in when I'm at my wits end with a DSL design problem. I've also been messing around with compiler plugins, and while debugging, I end up seeing references to annotations like @Exact and @HidesMembers, with interesting code paths in compiler code.
Mostly, though, it's just trial and error. The annotations aren't super self-explanatory, so I have some approximate ideas on when they might be helpful, but I just have to try them and see if they workAlejandro Serrano.Mena
02/18/2026, 9:25 AMphldavies
02/18/2026, 10:21 AMfun String.greet() = println("Hello, $this!")
fun greet() = println("Hello there!")
There's no way to call greet() once you have a String in scope as a receiver.
I can't help but feel that this will limit the usability of context parameters (by requiring the crutch of explicit context arguments at the callsite to disambiguate) or result in overly verbose function naming to avoid conflicts.
Would a general anti-scope function be more useful - some way to enforce explicit references/context from the outer scope:
with("Kotlin") {
greet() // no way to call (1)
explicitScope {
greet() // calls greet()
this@with.greet() // explicitly call String.greet()
context(contextOf<String>()) { greet() } // explicitly (re)declare context
}
}dmitriy.novozhilov
02/18/2026, 10:22 AMThere's no way to call greet() once you have a String in scope as a receiver.
my.package.name.greet()Youssef Shoaib [MOD]
02/18/2026, 10:22 AMphldavies
02/18/2026, 10:32 AMYoussef Shoaib [MOD]
02/18/2026, 10:37 AMfoo with a context and non-context overload. If we're inside the context, not all hope is lost for calling non-context `foo`: you can call some foo2 that itself calls non-context foo. So, ultimately, you're not 100% prevented from calling something simply because of the scope you're in, since you can always "recreate" your scope but with something (receiver, context) removed. However, it's a lot of boilerplate, so I understand why it's a good rule of thumb to either error with ambiguity or have some way to choose a lower-priority overloadphldavies
02/18/2026, 10:53 AMRaise context, i.e.
context(_: Raise<MalformedId>)
fun String.parseId(): Id = ensureNotNull(toIntOrNull()) { MalformedId(this) }
fun String.parseId(): Either<MalformedId, Id> = either { parseId() }
Which this change is going to impact. In this case it's very unlikely to want to use the no-context overload when the context is available. We also had similar overloads with with or without context(json: Json) with similar intent (although these also had other context parameters and stopped working recently due to context shadowing restrictions.Olaf Gottschalk
02/18/2026, 10:54 AMYoussef Shoaib [MOD]
02/18/2026, 10:56 AMunit: Unit trick, mentioned earlier in the thread, would serve you well, then.
Personally, I think you should get rid of the Either overload. Instead, wrap your calls with either { } when you need to. It then makes it easier to find places where you should refactor your code to use Raise more extensively. It's a lot easier for me, personally, to reason about code that looks like either { foo.parseId() }.flatMap { either { it.toString().parseId() } } and understand that it's equivalent to either { foo.parseId().toString().parseId() } than code like foo.parseId().flatMap { it.toString().parseId() } because the latter goes through Either in a way that's hidden away from me. Such transformations can absolutely be automated (e.g. using structural find and replace), so it can make refactoring even easier (good luck doing that with the multiple-overloads version without accidentally breaking code)
context(_: Raise<MalformedId>)
fun String.parseId(): Id = ensureNotNull(toIntOrNull()) { MalformedId(this) }
fun String.parseId(unit: Unit = Unit): Either<MalformedId, Id> = either { parseId() }
The no-context overload has lower priority as a resultOlaf Gottschalk
03/19/2026, 9:23 AMimport arrow.core.* and recently import arrow.core.raise.context.* for using Raise with contexts. The problem now is that Arrow provides most of its methods twice: one with using extension style (in arrow.core) and another one with using context(r: Raise<..>) in arrow.core.raise.context.. It requires a lot of tweaking now to convince the compiler to chose the correct one.
Even worse: all of these errors are not shown in IntelliJ (yet?). Everything looks fine, but when doing a gradle build, it fails. @Alejandro Serrano.Mena should know a lot about this problem... 😉phldavies
03/19/2026, 9:25 AMOlaf Gottschalk
03/19/2026, 9:26 AMimport arrow.core.* made a lot of sense and mostly this was done automatically as of the star import rule. Adding the newer style context lambdas in its own package now leads to this problem.Olaf Gottschalk
03/19/2026, 9:45 AMimport arrow.core.* it will always select
public inline fun <Error, A, B> Iterable<A>.mapOrAccumulate(
@BuilderInference transform: RaiseAccumulate<Error>.(A) -> B,
): Either<NonEmptyList<Error>, List<B>> = either {
mapOrAccumulate(this@mapOrAccumulate, transform)
}
in Iterable.kt. But this automatically creates an Either - which is what I do not want any more. I want to "stay in Raise-land" and use the newer
context(raise: Raise<NonEmptyList<Error>>)
@RaiseDSL public inline fun <Error, A, B> Iterable<A>.mapOrAccumulate(
@BuilderInference transform: context(RaiseAccumulate<Error>) (A) -> B
): List<B> = raise.mapOrAccumulateExt(this, transform)
defined in RaiseAccumulateNelContext.kt in package arrow.core.raise.context.
This is awkward now and yes, @phldavies, I would like to do one OR the other, but since many functions are defined at top level, there is not a good way to chose one over the other.Olaf Gottschalk
03/19/2026, 9:46 AMkirillrakhman
03/19/2026, 9:46 AMOlaf Gottschalk
03/19/2026, 9:48 AMarrow.core if you need them.Olaf Gottschalk
03/19/2026, 9:49 AMkirillrakhman
03/19/2026, 9:49 AMkirillrakhman
03/19/2026, 9:50 AMOlaf Gottschalk
03/19/2026, 9:50 AMOlaf Gottschalk
03/19/2026, 9:50 AMkirillrakhman
03/19/2026, 9:52 AMOlaf Gottschalk
03/19/2026, 9:52 AMEither that come with extension style. If you now want to use contexts, I don't know whether importing the new extension function with context style really works.Olaf Gottschalk
03/19/2026, 9:53 AMkirillrakhman
03/19/2026, 9:58 AMimport arrow.core.*
import arrow.core.raise.context.Raise
import arrow.core.raise.context.mapOrAccumulate
context(_: Raise<NonEmptyList<Throwable>>)
fun testContextual(x: Iterable<String>) {
x.mapOrAccumulate { it }
}
fun testWithoutContext(x: Iterable<String>) {
x.mapOrAccumulate {
if ("false".toBoolean()) raise(Exception())
it
}
}
On my Nightly version of IJ IDEA, import optimizer doesn't remove the explicit import here.Olaf Gottschalk
03/19/2026, 10:01 AMimport arrow.core.*
import arrow.core.raise.context.*
import arrow.core.raise.recover
everything used to work with Kotlin 2.3.10
Switching to Kotlin 2.3.20 this leads to compile errors (not shown in IDE):
e: file:///Calculator.kt:238:50 Overload resolution ambiguity between candidates:
fun <Error, A, B> Iterable<A>.mapOrAccumulate(transform: RaiseAccumulate<Error>.(A) -> B): Either<NonEmptyList<Error>, List<B>>
context(raise: Raise<NonEmptyList<Error>>)
fun <Error, A, B> Iterable<A>.mapOrAccumulate(transform: context(RaiseAccumulate<Error>) (A) -> B): List<B>
The fix for this is to rework the imports to those:
import arrow.core.*
import arrow.core.raise.Raise
import arrow.core.raise.context.bind
import arrow.core.raise.context.either
import arrow.core.raise.context.ensure
import arrow.core.raise.context.mapOrAccumulate
import arrow.core.raise.context.withError
import arrow.core.raise.recover
But when I do my clean up (Option-Cmd-L), IntelliJ does this automatically:
import arrow.core.*
import arrow.core.raise.Raise
import arrow.core.raise.context.*
import arrow.core.raise.recover
and this breaks it again. 😞Olaf Gottschalk
03/19/2026, 10:02 AMkirillrakhman
03/19/2026, 10:04 AMOlaf Gottschalk
03/19/2026, 10:05 AMimport arrow.core.*
import arrow.core.raise.Raise
import arrow.core.raise.context.*
import arrow.core.raise.context.mapOrAccumulate
Also fix the issue for the compiler - but cleaning up imports thinks "oh, there's a star import on context, let's remove the explicit one for the function".
This seems to be a problem. The IDE tools do not know that this explicit import does something "special". They look at this as I also would have done: "it's all imported, so let's remove the unnecessary ones".Olaf Gottschalk
03/19/2026, 10:06 AMOlaf Gottschalk
03/19/2026, 10:06 AMkirillrakhman
03/19/2026, 10:09 AMOlaf Gottschalk
03/19/2026, 10:11 AMkirillrakhman
03/19/2026, 10:12 AMOlaf Gottschalk
03/19/2026, 10:14 AMkirillrakhman
03/19/2026, 10:19 AMOlaf Gottschalk
03/23/2026, 5:54 AMOlaf Gottschalk
03/23/2026, 5:56 AMkirillrakhman
03/23/2026, 8:04 AMOlaf Gottschalk
03/23/2026, 9:14 AMkirillrakhman
03/23/2026, 9:16 AMMiguelDecimal128
03/25/2026, 11:51 PMval a = b + c * d
Advanced users who need rounding control (for example) could perform arithmetic operations within a context parameter.
with(mySpecializedContext) {
val a = b + c * d
}
.OR.
mySpecializedContext.context {
val t = (w * x) / (y + z)
}
where:
fun <T> DecContext.context(block: context(DecContext) () -> T): T = block(this)
As of 2.3.20 this does not work because of 'overload resolution ambiguity'
Where is the ambiguity?
Is the ability to have a context-parameter-implementation and a bare-implementation of operator funs really going away?Youssef Shoaib [MOD]
03/26/2026, 1:36 AMfun main() {
MyFloat() + MyFloat() // called without context
with(MyContext) { MyFloat() + MyFloat() } // called with context
}
object MyContext
class MyFloat
operator fun <T: MyFloat> T.plus(other: T) = println("called without context")
context(_: MyContext)
operator fun MyFloat.plus(other: MyFloat) = println("called with context")
the type parameter makes the context-less overload have a lower priority in overload resolution. Thus, the compiler tries the context overload first, and if it fails, tries the context-less one, which is exactly what you want.
There's a different trick for non-operator functions, namely adding dummy default parameters. You can do some magic to get it working for operators, though:
fun main() {
MyFloat() + MyFloat() // called without context
with(MyContext) { MyFloat() + MyFloat() } // called with context
}
object MyContext
class MyFloat
@Suppress("INAPPLICABLE_OPERATOR_MODIFIER")
operator fun MyFloat.plus(other: MyFloat, unit: Unit = Unit) = println("called without context")
context(_: MyContext)
operator fun MyFloat.plus(other: MyFloat) = println("called with context")
There are other tricks you can use here. These tricks have been known about and used way before contexts were ever introduced. As a result, I personally don't mind this new ambiguity change, since I have tricks that can get me the old behavior back when I want itphldavies
03/26/2026, 11:08 AMOlaf Gottschalk
03/26/2026, 11:12 AMkirillrakhman
03/26/2026, 11:13 AMAnd I feel that the newest change to how imports workThis rule is not new
Olaf Gottschalk
03/26/2026, 11:14 AMphldavies
03/26/2026, 11:14 AMOlaf Gottschalk
03/26/2026, 11:15 AMphldavies
03/26/2026, 11:15 AMAlejandro Serrano.Mena
03/26/2026, 11:19 AMI can't help but feel this ambiguity change is detrimental to Kotlin's design goal of being pragmatic and not getting in the way of developers.Actually, it's the opposite. With the new rule, you don't need to keep tracking in your head what it's and what it's not in scope, and that it's a huge win for those using those libraries, at the expense of the API author having to come up with different names for contextual vs. non-contextual operations.
phldavies
03/26/2026, 11:30 AMblahContextually suffix a la coEvery or everySuspend which becomes general noise. It also wouldn't work as a solution for operators (as with the float library discussed previously in this thread).Olaf Gottschalk
03/26/2026, 11:34 AMkirillrakhman
03/26/2026, 11:51 AMIntelliJ auto import action are misbehavingThis is exclusively due to a version mismatch between the compiler and the IDE. With experimental features, you can only expect reasonable IDE behavior by using a compatible version of the IDE and as I wrote earlier, you need to use 2026.1 with 2.3.20.
Olaf Gottschalk
03/26/2026, 11:53 AMkirillrakhman
03/26/2026, 11:55 AMOlaf Gottschalk
03/26/2026, 11:56 AMOlaf Gottschalk
03/26/2026, 11:57 AMYoussef Shoaib [MOD]
03/26/2026, 12:48 PMOlaf Gottschalk
03/26/2026, 12:56 PMYoussef Shoaib [MOD]
03/26/2026, 12:58 PMOlaf Gottschalk
03/26/2026, 12:59 PMOlaf Gottschalk
03/26/2026, 1:00 PMOlaf Gottschalk
03/26/2026, 1:01 PMYoussef Shoaib [MOD]
03/26/2026, 1:05 PMobject System {
@Deprecated(ERROR_NOW_OUTSIDE_CONTEXT, level = ERROR)
fun currentTimeMillis(unit: Unit = Unit): Long = error(ERROR_NOW_OUTSIDE_CONTEXT)
context(_: TransformationTimeAccess)
fun currentTimeMillis(): Long = instantNow.toEpochMilli()
}
Your initial strategy didn't give errors when using java.lang.System anyway, so this isn't much different. The exact signature of Java methods thus seems irrelevant, since the whole point is that you're shadowing them in Kotlin and using error deprecation. Perhaps I am mistaken, though!Olaf Gottschalk
03/26/2026, 1:08 PM