scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.8k stars 1.04k forks source link

Problems with implicit search involving singleton objects under 3.6 #21222

Open WojciechMazur opened 1 month ago

WojciechMazur commented 1 month ago

We can observe a strange behaviour under the new givens resolution. Currently the implicit search reports 2 ambiguous implicits: 1 is the LowercaseNonEmptyText.type so the singleton object and the second one is given instance defined withing the as trait. Removing the given instance when trying to get rid of these warning leads to compilation leads to not finding any instances of implicit value.

Based on OpenCB failure in @makiftutuncu /as - build logs

Compiler version

3.5.x and 3.6 nightly

Minimized code

infix trait as[Raw, Refined](using narrow: Raw <:< Refined, widen: Refined =:= Raw):
  extension (refined: Refined)
    def value: Raw = widen(refined)
  def undefinedValue: Raw
  lazy val undefined: Refined = narrow(undefinedValue)

  def apply(raw: Raw): Refined = narrow(raw)
  given instance: (Raw as Refined) = this // comment out to make case1 compile and make case2 fail

extension [Raw](raw: Raw)
  def as[Refined](using r: Raw as Refined): Refined = r.apply(raw)

object model:
  opaque type LowercaseNonEmptyText <: String = String
  object LowercaseNonEmptyText extends (String as LowercaseNonEmptyText):
    override val undefinedValue: String = " "

@main def Test =
  import model.LowercaseNonEmptyText
  val case1 = LowercaseNonEmptyText("foo").value
  val case2 = "bar".as[LowercaseNonEmptyText].value

Output

-source:3.5

-- Warning: /Users/wmazur/projects/sandbox/src/main/scala/test.scala:20:35 -----
20 |  val case1 = LowercaseNonEmptyText("foo").value
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |Given search preference for ?{ value: ? } between alternatives model.LowercaseNonEmptyText.type and (model.LowercaseNonEmptyText.instance : String as model.LowercaseNonEmptyText) will change
   |Current choice           : the first alternative
   |New choice from Scala 3.6: none - it's ambiguous
-- Warning: /Users/wmazur/projects/sandbox/src/main/scala/test.scala:21:14 -----
21 |  val case2 = "bar".as[LowercaseNonEmptyText].value
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |Given search preference for ?{ value: ? } between alternatives model.LowercaseNonEmptyText.type and (model.LowercaseNonEmptyText.instance : String as model.LowercaseNonEmptyText) will change
   |Current choice           : the first alternative
   |New choice from Scala 3.6: none - it's ambiguous
2 warnings found

Both warnings are elevated to errors under -source:3.6

-source:3.5 (or -source:3.5) + commented out given instance

-- [E172] Type Error: /Users/wmazur/projects/sandbox/src/main/scala/test.scala:21:45 
21 |  val case2 = "bar".as[LowercaseNonEmptyText].value
   |                                             ^
   |No given instance of type String as model.LowercaseNonEmptyText was found for parameter r of method as

Expectation

Ambigious implicits should not be reported

Workarounds

1st

Replacing

given instance: (Raw as Refined) = this

with

given instance: this.type = this

solves the problem

2nd

Replacing

object LowercaseNonEmptyText extends (String as LowercaseNonEmptyText)

with

implicit object LowercaseNonEmptyText extends (String as LowercaseNonEmptyText)

This workaround seems to be less Scala 3 idiomatic by the usage of implicit keyword that's going to probably be deprecated at some point in the future. However this version below does also work:

  given LowercaseNonEmptyText: (String as LowercaseNonEmptyText) with
    override val undefinedValue: String = " "
WojciechMazur commented 1 month ago

@odersky I'm not sure if should it actually work under the new implicits. Can I ask for deciding either it's an issue or expected behaviour (related https://github.com/epfl-lara/lisa/pull/224)