I’m trying to debug a transparent inline function. I’ve tried Vprint options but still cannot figure out what is going on.
This works:
transparent inline def withMarkedThreads[A](maxThreads: Int, x: Int)(code: ExecutionContext ?=> A) =
_withMarkedThreads(maxThreads)(exec => code(using exec))
transparent inline def withMarkedThreads[A](code: ExecutionContext ?=> A) =
_withMarkedThreads(0)(exec => code(using exec))
private transparent inline def _withMarkedThreads[A](maxThreads: Int)(code: ExecutionContext => A) =
val exec = ExecutionContext.global
inline code match
case futureCode: (ExecutionContext => Future[?]) => Await.result(futureCode(exec), Duration.Inf)
case _ => code(exec)
withMarkedThreads(Future("X"))
withMarkedThreads("Y")
It stops working the moment I remove the argument x from the first function. Both calls to withMarkedThreads fail, for different reasons:
[error] T/src/withMarkedThreads.scala:18:41
println(withMarkedThreads(Future("X")))
^
Cannot find an implicit ExecutionContext. You might add
an (implicit ec: ExecutionContext) parameter to your method.
[error] T/src/withMarkedThreads.scala:19:12
println(withMarkedThreads("YZ"))
^^^^^^^^^^^^^^^^^
None of the overloaded alternatives of method withMarkedThreads with types
[A](code: (scala.concurrent.ExecutionContext) ?=> A): Any
[A](maxThreads: Int)(code: (scala.concurrent.ExecutionContext) ?=> A²): Any
match arguments (("Y" : String))
If anyone can help, the code is on Scastie: Scastie - An interactive playground for Scala.
1 Like
No takers?
I still can’t figure this out. Even with the x argument removed, the signatures of the two withMarkedThreads functions are not ambiguous. (If they were, I assume I wouldn’t be able to compile them.) So what makes the compiler resolve the calls differently from before? Any useful compiler option that would let me trace this?
When it comes to overloading, Scala allows compiling ambiguous overloads if they would have a different bytecode after type erasure.
// with withMarkedThreads(10), this would be "returning" ExecutionContext ?=> A
// via currying, withMarkedThreads[A](10) _
transparent inline def withMarkedThreads[A](maxThreads: Int)(code: ExecutionContext ?=> A) =
_withMarkedThreads(maxThreads)(exec => code(using exec))
// with withMarkedThreads(10), this would be treated as A=Int,
// code = ExecutionContext ?=> 10
transparent inline def withMarkedThreads[A](code: ExecutionContext ?=> A) =
_withMarkedThreads(0)(exec => code(using exec))
When we ignore that these are transparent inline then their type-erased signatures differ:
I might be wrong, but AFAIR this is the only ambiguity when compile screams during compilation of the method (requiring @targetName) instead of compilation of its call site.
I took a look at the time but gave up quickly.
I chalked it up to overloading is evil. Knowing that it is evil, why do you pursue it?
But I sympathize that it is frustrating to have no debugging help with Scala 3 typer.
It’s necessary to recompile the project with special Printers, and then maybe you’ll get useful debugging.
That is a far cry from -Vlog:typer -Vdebug -Vtyper or whatever the invocation is with stock Scala 2 compiler.
To me, that is a grave UX regression.
What would be neat is to have a debug version of the compiler in the distro, so that a flag can load it.
What’s that you say? I should PR that suggestion? I only just thought of it.
Today I needed debug printing from inlining, so I did suffer through that. Just remembering how to ask for more debug was a hurdle. Are compiler options documented? (Snide rhetorical question.)
I did not examine whether a method in a contextual function is ambiguous with the method in Int. But desugaring for context functions happens early (where an expression e is rewritten to x ?=> e because a context function is expected), and that happens in the happy path with a certain degree of luck or planning. I’m not surprised if it doesn’t work (for a good reason) when overloading, where typechecking normally happens without an expected type. (But maybe Scala 3 has clever ways of figuring it out, so possibly it is not hopeless.)
I thought about something like that, which is why I’m using strings and not ints in the example. What’s ambiguous about withMarkedThreads("X")?
Because I’m a masochist. I’m burned semi-regularly with overloading, but I keep going back for more…
My previous design (also with overloading) used an implicit conversion instead of transparent inline (i.e., withMarkedThreads("X") was handled as withMarkedThreads(Future.successful("X"))), but the required import on the user side made me feel I was doing something even more evil than just overloading.
Maybe there’s a way to do it with evidence that A is (or is not) a subtype of Future[_], but I worry that this will lead to truly ambiguous signatures at the bytecode level.
Worst case, I’ll use withMarkedThreads(): instead of withMarkedThreads:. I can live with that.
Thanks to all for the help.