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

Bridge method clash with value class #8001

Closed
travisbrown opened this issue Jan 15, 2020 · 10 comments
Closed

Bridge method clash with value class #8001

travisbrown opened this issue Jan 15, 2020 · 10 comments

Comments

@travisbrown
Copy link
Contributor

minimized code

trait TC[A] {
  def apply(a: A): Unit
}

class Foo[A](val value: A) extends AnyVal
object Foo {
  val tc = new TC[Foo[String]] {
    def apply(a: Foo[String]): Unit = ()
  }
}
Compilation output
-- Error: TC.scala:8:8 ---------------------------------------------------------
8 |    def apply(a: Foo[String]): Unit = ()
  |        ^
  |bridge generated for member method apply(a: Foo[String]): Unit in anonymous class Object with TC {...}
  |which overrides method apply(a: A): Unit in trait TC
  |clashes with definition of the member itself; both have erased type (a: Object): Unit."
1 error found

expectation

Another minimization from the Cats tests. Scala 2 is fine with this.

@odersky
Copy link
Contributor

odersky commented Jan 15, 2020

There's a long history about this bug which is really a shortcoming of the value class encoding. Given that value classes are not pursued further, and the bug would likely cost a person several weeks to fix, I am not sure we will ever fix it. I do hope there is a workaround.

@travisbrown
Copy link
Contributor Author

@odersky We can easily work around this, thanks.

@odersky
Copy link
Contributor

odersky commented Jan 16, 2020

@travisbrown Good to know, thanks!

@bishabosha
Copy link
Member

bishabosha commented Feb 1, 2020

minimise to this

case class Box(a: Any) extends AnyVal

or perhaps this to avoid case:

class Box(a: Any) extends AnyVal
object Box extends (Any => Box)
  def apply(a: Any): Box = new Box(a)

@Jasper-M
Copy link
Contributor

Jasper-M commented Feb 4, 2020

@bishabosha The code that Travis posted compiles fine in Scala 2, while yours will error in Scala 2 as well.

I think the bug (or just difference from scala 2, don't know...) here is that dotc thinks that Foo[String] or Foo[Int] erase to Object instead of String or Int respectively.

@smarter
Copy link
Member

smarter commented Feb 4, 2020

I think the bug (or just difference from scala 2, don't know...) here is that dotc thinks that Foo[String] or Foo[Int] erase to Object instead of String or Int respectively.

Yes, exactly. I'm working on a fix.

fthomas added a commit to fthomas/refined that referenced this issue Sep 23, 2020
The Dotty build currently fails with errors like this:
```
[error] -- Error: /home/travis/build/[secure]/refined/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/api/RefType.scala:85:19
[error] 85 |      override def unsafeWrap[T, P](t: T): Refined[T, P] =
[error]    |                   ^
[error]    |bridge generated for member method unsafeWrap[T, P](t: T): eu.timepit.refined.api.Refined[T, P] in anonymous class Object with eu.timepit.refined.api.RefType {...}
[error]    |which overrides method unsafeWrap[T, P](t: T): F[T, P] in trait RefType
[error]    |clashes with definition of the member itself; both have erased type (t: Object): Object."
```

This is a known limitation of value classes, see
scala/scala3#8001. `Refined` as opaque type
sidesteps this issue.
fthomas added a commit to fthomas/refined that referenced this issue Sep 23, 2020
* Make Refined an opaque type on Dotty

The Dotty build currently fails with errors like this:
```
[error] -- Error: /home/travis/build/[secure]/refined/modules/core/shared/src/main/scala-3.0+/eu/timepit/refined/api/RefType.scala:85:19
[error] 85 |      override def unsafeWrap[T, P](t: T): Refined[T, P] =
[error]    |                   ^
[error]    |bridge generated for member method unsafeWrap[T, P](t: T): eu.timepit.refined.api.Refined[T, P] in anonymous class Object with eu.timepit.refined.api.RefType {...}
[error]    |which overrides method unsafeWrap[T, P](t: T): F[T, P] in trait RefType
[error]    |clashes with definition of the member itself; both have erased type (t: Object): Object."
```

This is a known limitation of value classes, see
scala/scala3#8001. `Refined` as opaque type
sidesteps this issue.

* We don't need to call value inside the companion
@smarter smarter added this to the 3.0.0-RC1 milestone Nov 10, 2020
smarter added a commit to dotty-staging/dotty that referenced this issue Dec 12, 2020
Previously given:

    class Poly[A](val value: A) extends AnyVal

We always erased `Poly[X]` to `Object`, no matter the value `X`, because
the erasure was the erased underlying type as seen from its definition,
and not as seen from the current prefix. But it turns out that in Scala
2, `Foo[Int]` will be erased to `Integer` instead (it would have made
more sense to use `int` but I suspect this is more accidental than
designed).

To be binary-compatible with Scala 2 and to support the same kind of
overloads we need to replicate its behavior, more precisely the rules I
was able to reverse-engineer are:

- Given `class Foo[A](x: A) extends AnyVal`, `Foo[X]` should erase like
  `X`, except if its a primitive in which case it erases to the boxed
  version of this primitive.
- Given `class Bar[A](x: Array[A]) extends AnyVal`, `Bar[X]` will be
  erased like `Array[A]` as seen from its definition site, no matter
  the `X` (same if `A` is bounded).

I was able to adapt our implementation of value class erasure to these
new rules without too much refactoring through one compromise: a value
class can no longer wrap another value class. This was never supported
by Scala 2 so we can afford to not support it either.
smarter added a commit to dotty-staging/dotty that referenced this issue Dec 12, 2020
Previously given:

    class Poly[A](val value: A) extends AnyVal

We always erased `Poly[X]` to `Object`, no matter the value `X`, because
the erasure was the erased underlying type as seen from its definition,
and not as seen from the current prefix. But it turns out that in Scala
2, `Foo[Int]` will be erased to `Integer` instead (it would have made
more sense to use `int` but I suspect this is more accidental than
designed).

To be binary-compatible with Scala 2 and to support the same kind of
overloads we need to replicate its behavior, more precisely the rules I
was able to reverse-engineer are:

- Given `class Foo[A](x: A) extends AnyVal`, `Foo[X]` should erase like
  `X`, except if its a primitive in which case it erases to the boxed
  version of this primitive.
- Given `class Bar[A](x: Array[A]) extends AnyVal`, `Bar[X]` will be
  erased like `Array[A]` as seen from its definition site, no matter
  the `X` (same if `A` is bounded).

I was able to adapt our implementation of value class erasure to these
new rules without too much refactoring through one compromise: a value
class can no longer wrap another value class. This was never supported
by Scala 2 so we can afford to not support it either.
odersky added a commit that referenced this issue Dec 14, 2020
Fix #8001: Erase polymorphic value classes like Scala 2
@larsrh
Copy link

larsrh commented Jan 31, 2021

This is marked as fixed in M3, but there's another situation where this happens: https://scastie.scala-lang.org/g5dE8K7wT5aCPYTYFETtLA

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)
  }
}

Compiler:

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.

@smarter
Copy link
Member

smarter commented Jan 31, 2021

Hmm, interesting, if I replace the anonymous instantiation by:

object Foo extends Monad[Dual] {
  def flatMap[A,B](fa: Dual[A])(f: A => Dual[B]): Dual[B] = f(fa.getDual)
}

Then Scala 2 emits the same error as Dotty. It looks like Scala 2 has some extra magic to deal with the special-case of value class bridge clashes in anonymous classes where it can get away with renaming one of the method to avoid the clash: scala/scala#3428, feel free to open an issue about this, but I don't know if we have the bandwidth to port this in time for 3.0.

@larsrh
Copy link

larsrh commented Jan 31, 2021

I have to admit I don't understand at all what's going on, and what a possible workaround could be.

Having said that, I found this problem while minimizing another problem found by @fmsbeekmans while porting semigroups to Dotty. A similar instantiation produced the following message:

[error]    |bad parameter reference io.chrisdavenport.semigroups.Dual#A at elimOpaque
[error]    |the parameter is type A in class Dual but the prefix io.chrisdavenport.semigroups.Dual
[error]    |does not define any corresponding arguments.

... and I haven't yet minimized it in a satisfactory way, nor can I tell whether or not this is an internal error. The error goes away when removing extends AnyVal.

@smarter
Copy link
Member

smarter commented Jan 31, 2021

That bad parameter reference is definitely a bug, but I don't think it's related to the bridge issue. I've gone ahead and opened #11264 to keep track of the bridge issue.

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

7 participants