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

1.9.8 regression: copy with different signature than constructor produces error #256

Closed
OndrejSpanel opened this issue Nov 26, 2024 · 6 comments · Fixed by #257
Closed

Comments

@OndrejSpanel
Copy link
Contributor

Following code fails with a compile error with 1.9.8 and 1.9.9 in Scala 3. It works fine with quicklens 1.9.7 or with Scala 2.

The error is:

Multiple methods named copy found in Frozen

import com.softwaremill.quicklens.*

object Main {
  case class Frozen(state: String, ext: Int) {
    def copy(state: String): Frozen = Frozen(state, ext)
  }

  def main(args: Array[String]): Unit = {
    val f = Frozen("A", 0)
    f.modify(_.state).setTo("B")
  }
}

My guess is the regression was caused by #236

@KacperFKorban
Copy link
Collaborator

Hmmm, we might need a heuristic to pick the correct copy method. I'll have to check what Scala 2 does.
Should we always choose the synthetic one, if there are multiple alternatives? Or filter by the ones with the desired name first?

@OndrejSpanel
Copy link
Contributor Author

OndrejSpanel commented Nov 27, 2024

Should we always choose the synthetic one,

When there is a user provided copy, there should be no synthetic one available:

https://www.scala-lang.org/files/archive/spec/3.4/05-classes-and-objects.html?utm_source=chatgpt.com

A method named copy is implicitly added to every case class unless the class already has a member (directly defined or inherited) with that name,

@KacperFKorban
Copy link
Collaborator

Hmmm, so this looks like a dotty or spec issue, because this compiles:

//> using scala 3.nightly
//> using dep "com.softwaremill.quicklens::quicklens:1.9.9"
//> using options "-Xprint:typer"

import com.softwaremill.quicklens.*

object Main {
  case class Frozen(state: String, ext: Int) {
    def copy(state: Char): Frozen = Frozen(state.toString, ext)
  }

  def main(args: Array[String]): Unit = {
    val f = Frozen("A", 0)
    // f.modify(_.state).setTo("B")
    f.copy('B')
    f.copy("B", 2)
  }
}

Scala-cli one-liner: scala-cli https://gist.github.com/KacperFKorban/b808cfa80ac20dbb4b85b4d1b6a253a2

@OndrejSpanel
Copy link
Contributor Author

Interesting. Do you want to post this on scala3, or should I? Meanwhile, picking some particular copy when multiple are available sounds like a good choice. It would be best to mimic what Scala 3 compiler does, but as this seems not to be well specified, picking any should do.

@KacperFKorban
Copy link
Collaborator

You can post it if it's not a problem 😄

@OndrejSpanel
Copy link
Contributor Author

OndrejSpanel commented Nov 27, 2024

They decided it is a spec issue, the behaviour is correct.

Now as for quicklens, here is a bit different repro, with no synthetic methods at all. Again, having two copy variants prevents the code from compiling on Scala 3:

object Main {
  class Frozen(val state: String, val ext: Int) {
    def copy(state: String = state, ext: Int = ext): Frozen = new Frozen(state, ext)
    def copy(state: String): Frozen = new Frozen(state, ext)
  }

  def main(args: Array[String]): Unit = {
    val f = new Frozen("A", 0)
    f.modify(_.state).setTo("B")
  }
}

The error is a bit confusing:

Unsupported source object: must be a case class or sealed trait, but got: val of type Main.Frozen (f)

If you remove the 2nd copy, it compiles fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants