Skip to content

Confusing interaction between opaque types and extension methods #9880

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

Closed
mpilquist opened this issue Sep 25, 2020 · 8 comments · Fixed by #13709
Closed

Confusing interaction between opaque types and extension methods #9880

mpilquist opened this issue Sep 25, 2020 · 8 comments · Fixed by #13709
Assignees
Labels
itype:enhancement Spree Suitable for a future Spree
Milestone

Comments

@mpilquist
Copy link
Contributor

Minimized code

opaque type Bytes = Array[Byte]
object Bytes:
  def fromArray(arr: Array[Byte]): Bytes = arr
  extension (self: Bytes):
    def toArray: Array[Byte] = self
    def length: Int = (self: Array[Byte]).length
    def size: Int = (self: Array[Byte]).size

val b = Bytes.fromArray(Array(1, 2, 3))

object Main extends App {
  println("Length: " + b.length)
  println("Size: " + b.size)
}

Output

Length is printed and then an infinite loop is encountered on b.size

Expectation

Array[Byte] doesn't have a size member so (self: Array[Byte]) is lifted to Bytes instead of picking up ArrayOps implicit conversion. This seems like a pretty big gotcha but not sure how to improve.

@odersky
Copy link
Contributor

odersky commented Oct 3, 2020

I don't see what could be done here.

@smarter
Copy link
Member

smarter commented Oct 3, 2020

We could print a warning on obviously infinite loops (scala 2 does that for def foo: Int = foo but nothing more I think).

@mpilquist
Copy link
Contributor Author

Ran in to this limitation again recently:

object Module1:
  opaque type State[S, +A] = S => (S, A)
  object State:
    extension [S, A](self: State[S, A])
      def map[B](f: A => B): State[S, B] =
        s => { val (s2, a) = self(s); (s2, f(a)) }

object Module2:
  import Module1.State
  trait RNG
  opaque type Gen[+A] = State[RNG, A]
  object Gen:
    extension [A](self: Gen[A])
      def map[B](f: A => B): Gen[B] =
        State.map(self)(f)

To implement Gen#map, I had to resort to calling the extension method directly via State.map(self)(f). Is there a better approach to workaround this?

@smarter
Copy link
Member

smarter commented Sep 6, 2021

Not that I can think of, instead I would recommend using a plain old class.

@mpilquist
Copy link
Contributor Author

I don't want to give up on using opaque types that easily. :)

Directly invoking State.map(self)(f) seems like a fine workaround assuming the compiler will always allow such a selection.

@odersky
Copy link
Contributor

odersky commented Sep 6, 2021

Directly invoking State.map(self)(f) seems like a fine workaround assuming the compiler will always allow such a selection.

Yes, that's official syntax, so no worry that it might get outlawed.

@dwijnand
Copy link
Member

Perhaps some pinhole linting can be done but otherwise this looks like a candidate FAQ.

@smarter
Copy link
Member

smarter commented Oct 5, 2021

Perhaps some pinhole linting can be done

Similar linting is now done for implicit definitions and should be easy to extend to extensions or even all definitions: #13589

@smarter smarter added the Spree Suitable for a future Spree label Oct 5, 2021
olsdavis pushed a commit to olsdavis/dotty that referenced this issue Apr 4, 2022
Broadens Martin's fix for scala#13542

Fixes scala#9880

Also, exempt Boolean's && and || methods, which aren't set up as having
by-name parameters.

Co-authored-by: Dale Wijnand <[email protected]>
@Kordyjan Kordyjan added this to the 3.1.2 milestone Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
itype:enhancement Spree Suitable for a future Spree
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants