scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

Compiler cannot choose correct overloaded method with default argument

Open kpodsiad opened this issue 3 years ago • 1 comments

Compiler version

3.2.0, 3.2.1-RC1, haven't checked earlier versions

Minimized code

Original code
//> using scala "3.2.1-RC1"

//> using lib "org.typelevel::cats-core:2.8.0"
//> using lib "org.typelevel::cats-effect:3.3.11"

import cats.Functor
import cats.Monad
import cats.Parallel
import cats.effect.Concurrent
import cats.effect.IO
import cats.effect.Resource
import cats.effect.Temporal
import cats.syntax.all._

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._

trait Cache[F[_], K, V]

object Cache {

  final case class Refresh[-K, +V](interval: FiniteDuration, value: K => V)

  final case class Config[F[_], -K, V](
      expireAfterRead: FiniteDuration,
      expireAfterWrite: Option[FiniteDuration] = None,
      maxSize: Option[Int] = None,
      refresh: Option[Refresh[K, F[Option[V]]]] = None
  )

  def expiring[F[_]: Concurrent: Temporal: Parallel, K, V](
      expireAfter: FiniteDuration
  ): Resource[F, Cache[F, K, V]] = ???

  def expiring[F[_]: Temporal: Parallel, K, V](
      config: Config[F, K, V],
      partitions: Option[Int] = None
  ): Resource[F, Cache[F, K, V]] = ???

  /* Without partitions being specified, error is yielded */

  val notCompiling = expiring[IO, Int, Int](
    config = Config[IO, Int, Int](expireAfterRead = 1.minute),
  )

  val compiling = expiring[IO, Int, Int](
    config = Config[IO, Int, Int](expireAfterRead = 1.minute),
    partitions = None
  )
}


Minimized reproduction

//> using scala "3.2.1-RC1"

//> using lib "org.typelevel::cats-effect:3.3.11"

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._
import cats.effect.kernel.Resource
import cats.effect.IO

trait Cache[F[_], K, V]

object Cache {

  final case class Config(expireAfterRead: FiniteDuration)

  def expiring[F[_], K, V](
      expireAfter: FiniteDuration
  ): Resource[F, Cache[F, K, V]] = ???

  def expiring[F[_], K, V](
      config: Config,
      partitions: Option[Int] = None
  ): Resource[F, Cache[F, K, V]] = ???

  /* Without partitions being specified, error is yielded */

  val notCompiling = expiring[IO, Int, Int](
    config = Config(expireAfterRead = 1.minute)
  )

  val compiling = expiring[IO, Int, Int](
    config = Config(expireAfterRead = 1.minute),
    partitions = None
  )
}

Output

None of the overloaded alternatives of method expiring in object Cache with types
 [F[_$3], K, V]
  (config: Cache.Config, partitions: Option[Int]): 
    cats.effect.kernel.Resource[F, Cache[F, K, V]]
 [F[_$2], K, V]
  (expireAfter: concurrent.duration.FiniteDuration): 
    cats.effect.kernel.Resource[F, Cache[F, K, V]]
match type arguments [cats.effect.IO, Int, Int] and arguments (Cache.Config)

Expectation

Compiler is able to pick correct overloaded method even without partitions specified explicitly.

kpodsiad avatar Sep 09 '22 08:09 kpodsiad

It seems like this code was able to compile (I've checked 3.0, 3.1 versions too.). A possible workaround until fixing it in compiler:

  val x: Resource[IO, Cache[IO, Int, Int]] = expiring(
    config = Config(expireAfterRead = 1.minute)
  )

WojciechMazur avatar Sep 09 '22 09:09 WojciechMazur

works on 3.2.2, 3.3.x, 2.13.11

som-snytt avatar Aug 20 '23 19:08 som-snytt

Here's a minimization without the Cats Effect dependency. Maybe it can be added as a regression test and we can close this issue?

//> using scala "3.2.1-RC1"

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._

trait Resource[F[_], A]
trait IO[A]

trait Cache[F[_], K, V]

object Cache {

  final case class Config(expireAfterRead: FiniteDuration)

  def expiring[F[_], K, V](
      expireAfter: FiniteDuration
  ): Resource[F, Cache[F, K, V]] = ???

  def expiring[F[_], K, V](
      config: Config,
      partitions: Option[Int] = None
  ): Resource[F, Cache[F, K, V]] = ???

  /* Without partitions being specified, error is yielded */

  val notCompiling = expiring[IO, Int, Int](
    config = Config(expireAfterRead = 1.minute)
  )

  val compiling = expiring[IO, Int, Int](
    config = Config(expireAfterRead = 1.minute),
    partitions = None
  )
}

armanbilge avatar Aug 21 '23 03:08 armanbilge

Ha, I didn't understand where you were going in chat. Yes, I had that minimization locally last year. Today I was trying out the OP before deleting my local test.

It never hurts to add a test (generally). Possibly this progression must surely be covered, but I did not investigate what fixed it.

By coincidence, another issue deserves a test and it's less trivial https://github.com/lampepfl/dotty/issues/17601#issuecomment-1685478600

So either the test is trivial and must already exist, or it's too non-trivial to be worth the maintenance?

Probably ChatGPT could tell us what existing tests look similar.

som-snytt avatar Aug 21 '23 03:08 som-snytt