scala / scala3

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

Default value of implicit parameter defined in package object is not found without explicit import #11507

Closed aeons closed 3 years ago

aeons commented 3 years ago

Compiler version

3.0.0-RC1

Minimized code

The issue was found while looking at upgrading the sttp integration for circe to Scala 3 here: https://github.com/softwaremill/sttp/issues/864

The code works as is for Scala 2.

There's a reproduction here: https://github.com/aeons/default-implicit-repro

It looks like something has changed with how default implicit arguments work if they are used from a package object. There's a lot of moving parts here.

In the repro, these are the parts:

package repro.circe
trait SttpCirceApi {
  implicit def circeBodySerializer[B](implicit
      encoder: Encoder[B],
      printer: Printer = Printer.noSpaces
  ): BodySerializer[B] =
    b => encoder(b).printWith(printer)
}
package repro
package object circe extends SttpCirceApi

And then we have a test

package repro.circe

import io.circe._
import repro.{BodySerializer, serialize}

case class Inner(a: Int, b: Boolean, c: String)
object Inner {
  implicit val encoder: Encoder[Inner] =
    Encoder.forProduct3("a", "b", "c")(i => (i.a, i.b, i.c))
}

class CirceTestsWorks {
  // This works
  import repro.circe._
  implicitly[BodySerializer[Inner]]
}

class CirceTestsFails {
  // This fails with a missing implicit for Printer
  implicitly[BodySerializer[Inner]]
}

Output

[error] -- Error: /home/user/code/default-implicit-repro/src/test/scala/repro/circe/CirceTests.scala:20:35
[error] 20 |  implicitly[BodySerializer[Inner]]
[error]    |                                   ^
[error]    |no implicit argument of type repro.BodySerializer[repro.circe.Inner] was found for parameter e of method implicitly in object Predef.
[error]    |I found:
[error]    |
[error]    |    repro.circe.circeBodySerializer[repro.circe.Inner](repro.circe.Inner.encoder,
[error]    |      /* missing */summon[io.circe.Printer]
[error]    |    )
[error]    |
[error]    |But no implicit values were found that match type io.circe.Printer.

Expectation

It compiles and works, as in Scala 2.

odersky commented 3 years ago

It would be good to get a minimization of this. Implicit resolution has changed in Scala 3, so the expectation that any Scala 2 code should compile is invalid. Notably, implicits in packages or package objects or no longer in the implicit scope for the types that they contain. I wonder whether this might have something to do with the failure?

aeons commented 3 years ago

Here's a (more) minimal example:

package object test {
    implicit def summonInt[T](implicit i: Int = 42): Boolean = i == 42
}

package test {
    // Fails
    val a = implicitly[Boolean]

    {
        // Works
        import test.*
        val b = implicitly[Boolean]
    }
}

I would think that the implicit, being in the package object, would be in scope in the package.

som-snytt commented 3 years ago

I tried this the other day and assumed odersky's explanation.

The minimization looks like the opposite case, since it fails with the import.

That looks like something else, where it doesn't consider the default arg:

-- Error: i11507.scala:9:23 ----------------------------------------------------
9 |    implicitly[Boolean]
  |                       ^
  |no implicit argument of type Boolean was found for parameter e of method implicitly in object Predef.
  |I found:
  |
  |    test.summonInt[T](/* missing */summon[Int])
  |
  |But no implicit values were found that match type Int.
result of i11507.scala after typer:
package <empty> {
  package test {
    final lazy module val package: test.package$ = new test.package$()
    final module class package$() extends Object() { this: test.package.type =>
      implicit def summonInt[T >: Nothing <: Any](implicit i: Int): Boolean =
        i.==(42)
      def summonInt$default$1[T >: Nothing <: Any]: Int = 42
    }
  }
  package test {
    class Test1() extends Object() {
      implicitly[Boolean](test.summonInt[T](/* missing */summon[Int]))
    }
    class Test2() extends Object() {
      import test.*
      implicitly[Boolean](test.summonInt[Any](test.summonInt$default$1[Any]))
    }
  }

The reproduction repository succeeds for me. Was it supposed to fail, to demonstrate the issue?

aeons commented 3 years ago

Argh, I switched up the comments. The one without the import fails, the one with succeeds. I edited the comment above to be correct.

The repository should fail when doing Test / compile, since that is very un-minimized and that's how we found the error in the first place.

aeons commented 3 years ago

Does it still need more minimization than https://github.com/lampepfl/dotty/issues/11507#issuecomment-784918576 ?

Not sure it can get much smaller :)

smarter commented 3 years ago

Indeed, I'll remove the tag.

smarter commented 3 years ago

Good news: this got fixed just a few days ago by https://github.com/lampepfl/dotty/pull/13018, so I'll be closing this (you can try a nightly to check that it's fixed in the original code too: scalaVersion := "3.0.2-RC1-bin-20210708-7627583-NIGHTLY")