Skip to content

compilation result depends on order of definition of context parameters #23118

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

Open
mr-git opened this issue May 7, 2025 · 4 comments
Open
Labels
area:implicits related to implicits itype:bug

Comments

@mr-git
Copy link

mr-git commented May 7, 2025

Compiler version

3.7.0, 3.6.4

Minimized code

Code is using Cats Effect 3.5.4

import cats.Parallel
import cats.syntax.all.*
import cats.effect.{Async, IO, Resource, ResourceApp}
import cats.effect.syntax.all.*

object Fail extends ResourceApp.Forever:
  override def run(args: List[String]): Resource[IO, Unit] =
    for {
      _ <- FailF.make()
    } yield ()

object FailF:
  def make[F[_]: { Async, Parallel }](): Resource[F, Unit] = // <===== fails
//  def make[F[_]: { Parallel, Async }](): Resource[F, Unit] = // <===== compiles
    (1 to 3).toList.parTraverseVoid { i => Async[F].delay { println(i) } }.toResource

Output

[error] -- [E172] Type Error: /<path>/main/src/main/scala/Fail.scala:11:23 
[error] 11 |      _ <- FailF.make()
[error]    |                       ^
[error]    |No given instance of type cats.effect.kernel.Async[F] was found for a context parameter of method make in object FailF.
[error]    |I found:
[error]    |
[error]    |    cats.effect.kernel.Async.asyncForWriterT[F², L](
[error]    |      cats.effect.kernel.Async.asyncForKleisli[F³, R](
[error]    |        cats.effect.kernel.Async.asyncForEitherT[F⁴, E](
[error]    |          cats.effect.kernel.Async.asyncForOptionT[F⁵](
[error]    |            cats.effect.kernel.Async.asyncForWriterT[F⁶, L²](
[error]    |              cats.effect.kernel.Async.asyncForIorT[F⁷, L³](
[error]    |                /* missing */summon[cats.effect.kernel.Async[F⁷]], ???),
[error]    |            ???)
[error]    |          )
[error]    |        )
[error]    |      ),
[error]    |    ???)
[error]    |
[error]    |But no implicit values were found that match type cats.effect.kernel.Async[F⁷]
[error]    |
[error]    |where:    F  is a type variable with constraint <: [_] =>> Any
[error]    |          F² is a type variable with constraint <: [_²] =>> Any
[error]    |          F³ is a type variable with constraint <: [_³] =>> Any
[error]    |          F⁴ is a type variable with constraint <: [_⁴] =>> Any
[error]    |          F⁵ is a type variable with constraint <: [_⁵] =>> Any
[error]    |          F⁶ is a type variable with constraint <: [_²] =>> Any
[error]    |          F⁷ is a type variable with constraint <: [_⁶] =>> Any
[error]    |          L  is a type variable
[error]    |          L² is a type variable
[error]    |          L³ is a type variable
[error]    |.
[error] one error found

Expectation

Compilation should succeed also with { Async, Parallel }.

@mr-git mr-git added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels May 7, 2025
@mr-git
Copy link
Author

mr-git commented May 7, 2025

I am not sure, maybe it is Cats and/or Cats Effect issues - something with monad types and their priorities?

UPDATE: I created Cats Effect issue too: typelevel/cats-effect#4401

@Gedochao Gedochao added stat:needs minimization Needs a self contained minimization area:implicits related to implicits and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels May 8, 2025
@Gedochao
Copy link
Contributor

Gedochao commented May 8, 2025

Minimization without depending on Cats Effect would help to diagnose.
Reproduced also with Cats Effect 3.6.1 and Scala 3.7.2-RC1-bin-20250507-c85982c-NIGHTLY

@prolativ
Copy link
Contributor

prolativ commented May 8, 2025

A different minimization with cats (I used scala 2 compatible syntax to check if it would work in scala 2 but there it fails in even more cases)

//> using dep org.typelevel::cats-effect:3.6.1

import cats.Parallel
import cats.effect.{Async, IO, Resource}

object FailF {
  def make1[F[_]](implicit a: Async[F]): Resource[F, Unit] = ???
  def make2[F[_]](implicit p: Parallel[F]): Resource[F, Unit] = ???
  def make3[F[_]](implicit p: Parallel[F], a: Async[F]): Resource[F, Unit] = ???
}

object Fail {
  def noMap1(): Resource[IO, Unit] = FailF.make1
  def noMap2(): Resource[IO, Unit] = FailF.make2
  def noMap3(): Resource[IO, Unit] = FailF.make3

  def withMap1(): Resource[IO, Unit] = FailF.make1.map(x => x)
  def withMap2(): Resource[IO, Unit] = FailF.make2.map(x => x)
  def withMap3(): Resource[IO, Unit] = FailF.make3.map(x => x)
}

In scala 3.7.0 only withMap1 doesn't compile but it starts to compile when we write FailF.make1[IO].map(x => x). So this looks like a an issue with type inference. When F is not known yet, implicit Async[F] cannot be found, but searching for implicit Parallel[F] fixes F so that Async an be found

@prolativ
Copy link
Contributor

prolativ commented May 8, 2025

It seems like I managed to minimize this without an external dependency:

sealed abstract class Resource[F[_], +A] {
  def map[B](f: A => B): Resource[F, B] = ???
}

trait Semigroup[A]
object Semigroup {
  implicit def instanceInt: Semigroup[Int] = ???
  implicit def instanceString: Semigroup[String] = ???
}

trait Parallel[M[_]] {
  type F[_]
}
object Parallel {
  type Aux[M[_], F0[_]] = Parallel[M] { type F[x] = F0[x] }
  implicit def fromSemigroup[E](implicit s: Semigroup[E]): Parallel.Aux[Option, Option] = ???
}

trait Async[F[_]]

class IO[+A]
object IO {
  implicit def asyncForIO: Async[IO] = ???
  implicit def parallelForIO: Parallel.Aux[IO, IO] = ???
}


object Fail {
  def make1[F[_]](implicit a: Async[F]): Resource[F, Unit] = ???
  def make2[F[_]](implicit p: Parallel[F]): Resource[F, Unit] = ???
  def make3[F[_]](implicit p: Parallel[F], a: Async[F]): Resource[F, Unit] = ???
  def make4[F[_]](implicit a: Async[F], p: Parallel[F]): Resource[F, Unit] = ???

  def test1(): Resource[IO, Unit] = make1.map(x => x) // error
  def test2(): Resource[IO, Unit] = make2.map(x => x) // OK
  def test3(): Resource[IO, Unit] = make3.map(x => x) // OK
  def test4(): Resource[IO, Unit] = make4.map(x => x) // error
}

Interestingly, the existence of Parallel.fromSemigroup is needed for test2 and test3 to compile, even though this instance is never actually selected by the implicit search. It's also needed that there are multiple implicit instances of Semigroup in the companion object (defined for types that are unrelated to the type of the implicit that is searched).
The more I think about it the more I'm surprised that test2 and test3 actually compile (none of the test methods compiles in scala 2)

@Gedochao Gedochao removed the stat:needs minimization Needs a self contained minimization label May 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:implicits related to implicits itype:bug
Projects
None yet
Development

No branches or pull requests

3 participants