japgolly / scalajs-react

Facebook's React on Scala.JS
https://japgolly.github.io/scalajs-react/
Apache License 2.0
1.64k stars 231 forks source link

renderBackend[B] fails compilation in Scala 3 when the backend takes implicit parameters #1052

Closed DanielMoss closed 2 years ago

DanielMoss commented 2 years ago

Hi. Thanks for the great library.

I'm experimenting with upgrading a project I've got to Scala 3, and had some code stop compiling unexpectedly. Here's what I'm playing around with:

import japgolly.scalajs.react.component.Scala.{BackendScope, Component}
import japgolly.scalajs.react.vdom.VdomNode
import japgolly.scalajs.react.{CtorType, ScalaComponent}

import scala.language.implicitConversions

object Test {
  /* Successfully compiles in both Scala 2.13 and Scala 3.0 */
  def buildV1[N : Numeric]: Component[N, N, Backend[N], CtorType.Props] =
    ScalaComponent
      .builder[N]
      .initialState(Numeric[N].zero)
      .backend(new Backend(_))
      .renderBackend
      .build

  /* Successfully compiles in Scala 2.13, but fails in Scala 3.0
   * [error] 30 |    ScalaComponent
   * [error] 31 |      .builder[N]
   * [error] 32 |      .initialState(Numeric[N].zero)
   * [error] 33 |      .renderBackend[Backend[N]]
   * [error]    |    ^
   * [error]    |    no implicit values were found that match type Numeric[N]
   * [error]    | This location contains code that was inlined from Test.scala:14
   * [error]    | This location contains code that was inlined from ComponentBuilder.scala:145
   */
  def buildV2[N : Numeric]: Component[N, N, Backend[N], CtorType.Props] =
    ScalaComponent
      .builder[N]
      .initialState(Numeric[N].zero)
      .renderBackend[Backend[N]]
      .build

  final class Backend[N : Numeric](scope: BackendScope[N, N]) {
    def render(props: N, state: N): VdomNode =
      ???
  }

I've included this code in a small project over here, in case anyone wants to play about with it.

I imagine this is related to the macro changes that have happened, but I'm only just dipping my toes into Scala 3 so I'm not sure what exactly is going wrong at the second.

DanielMoss commented 2 years ago

I've done some more testing and traced the problem back to the compile-time project - specifically to how summoning the implicit terms works in NewInstance. It seems to be a problem when the class you're constructing has an implicit parameter whose type is dependent on one of the class' type parameters.

For example, if we add the following code to the compilation tests, then we'll receive a compilation failure:

/* Results in
 * [error] -- Error: microlibs-scala\compile-time\shared\src\test\scala-3\japgolly\microlibs\compiletime\Scala3CompilationTests.scala:21:13
 * [error] 21 |  newInstance[Test[Int]]
 * [error]    |  ^^^^^^^^^^^^^^^^^^^^^^
 * [error]    |  no implicit values were found that match type T
 * [error]    | This location contains code that was inlined from Scala3CompilationTests.scala:21
 *
 */
implicit val i: Int = 10
class Test[T](implicit param: T)
newInstance[Test[Int]]
japgolly commented 2 years ago

Hi @DanielMoss, thanks for the debugging help! I've got some bad news for you.

  1. I can't implement this because a feature I need still hasn't been added to Scala 3 non-experimentally (see 1, 2)
  2. I can make it work for simple substitutions but I can't do higher-kinded substitutions due to another missing piece of functionality in Scala 3

Unfortunately you'll have to persist with your workaround for a while longer I'm afraid

DanielMoss commented 2 years ago

No problem at all @japgolly. Thanks very much for all the context - I wasn't even aware that type substitution was something we have to take care of ourselves when in macro land.

japgolly commented 2 years ago

No worries at all and I wish I couldn't come back to you with a better result. It's still early days for Scala 3 but we'll get there eventually 🙂