Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid bridge clashes involving value classes for anonymous classes #11264

Open
smarter opened this issue Jan 31, 2021 · 3 comments
Open

Avoid bridge clashes involving value classes for anonymous classes #11264

smarter opened this issue Jan 31, 2021 · 3 comments

Comments

@smarter
Copy link
Member

smarter commented Jan 31, 2021

The following compiles with Scala 2 but not with Scala 3:

trait Monad[F[_]] {
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}

final case class Dual[A](getDual: A) extends AnyVal

object Dual {
  implicit val dualInstances: Monad[Dual] = new Monad[Dual] {
    def flatMap[A,B](fa: Dual[A])(f: A => Dual[B]): Dual[B] = f(fa.getDual) // error in Scala 3, OK in Scala 2
  }
}

The error we get is:

bridge generated for member method flatMap[A, B](fa: Dual[A])(f: A => Dual[B]): Dual[B] in anonymous class Object with Monad {...}
which overrides method flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] in trait Monad
clashes with definition of the member itself; both have erased type (fa: Object, f: Function1): Object.

This is a known fundamental problem with value classes: we need two flatMap methods depending on whether we're in a context where we know we're dealing with Dual[A] (and thus can erase it to its underlying type) or in a generic context (and thus need to keep Dual[A] boxed), but the generic type parameter erases to Object and the unboxed representation of this value class is also Object, so we end up with two methods with the same signature. In fact, Scala 2 will emit the same error if we make the subclass non-anonymous:

object Dual {
  class MonadDual extends Monad[Dual] {
    def flatMap[A,B](fa: Dual[A])(f: A => Dual[B]): Dual[B] = f(fa.getDual)
  }
  implicit val dualInstances: Monad[Dual] = new MonadDual // error in Scala 2 and 3
}

So how come the first example works in Scala 2? Turns out there's a special-case just for anonymous classes, where the non-generic method gets renamed because it cannot be called anyway: scala/scala#3428. We could do the same to make cross-compilation easier, this can be done post-3.0 since it doesn't change anything for code that currently compiles.

@sirthias
Copy link
Contributor

Just tripped across this while setting up cross-building to Scala 3 for borer.
Currently solved by turning the value class into a regular case class, which is sub-optimal.

@Rich2
Copy link

Rich2 commented Jul 23, 2021

@smarter Is this likely to be fixed now in Scala 3.0.2, or is it still planned to leave it post 3.0?

@smarter
Copy link
Member Author

smarter commented Jul 23, 2021

It can be fixed at anytime, it just requires someone with some time and motivation to port scala/scala#3428 over, I personally don't have the time.

hearnadam pushed a commit to getkyo/kyo that referenced this issue Aug 8, 2024
I'm porting the other modules to the new design in `kyo-prelude` but
there's a performance regression that I can't see how to avoid with the
new encoding in the current version of the Scala 3 compiler:

- Given the lack of specialization, the generated bytecode contains a
significant amount of unexpected `<` boxing. Fortunately, the JIT is
able to avoid most of these allocations but profiling sessions show `<`
allocations in several benchmarks. It isn't a bottleneck in the
benchmarks I analyzed but introduces some overhead.

- The compiler doesn't allow implementing interfaces with `AnyVal`
generic type parameters. As a workaround, I was planning to use a
wrapper class like in `MonadLawsTest` but the approach is too expensive
in integrations like in `kyo-sttp`. The wrapper class essentially
introduces a new allocation to all methods in
[KyoSttpMonad](https://github.com/getkyo/kyo/blob/0279f30163759d834b1793cbba5bcf7d08f9f0c3/kyo-sttp/shared/src/main/scala/kyo/internal/KyoSttpMonad.scala#L9),
which are heavily used by sttp's transformations. Given how critical the
integrations with sttp and tapir are, this regression doesn't seem
ideal.

As described in #531, the purpose of
the new `AnyVal` encoding was to allow arbitrary nesting of Kyo
computations. Although it's a nice usability improvement, the current
limitations of the Scala 3 compiler doesn't seem to make it worth it and
finalizing the migration to the new design seems a much higher priority.
I can create a ticket so we can try again later.

Related compiler tickets: scala/scala3#11264
scala/scala3#15532
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants