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

Anonymous trait mixin breaks inline match + erasedValue + summonInline #17222

Closed
kavedaa opened this issue Apr 10, 2023 · 10 comments · Fixed by #21954
Closed

Anonymous trait mixin breaks inline match + erasedValue + summonInline #17222

kavedaa opened this issue Apr 10, 2023 · 10 comments · Fixed by #21954

Comments

@kavedaa
Copy link

kavedaa commented Apr 10, 2023

Compiler version

3.2.2, 3.3.0-RC3

Minimized code

//> using scala "3.3.0-RC3"

import scala.deriving.Mirror
import scala.compiletime.*

trait Reader[-In, Out]

trait A:
  type T
  type F[X]
  type Q = F[T]

object Reader:

  given [X]: Reader[A { type Q = X }, X] with {}    

  type Map2[Tup1 <: Tuple, Tup2 <: Tuple, F[_, _]] <: Tuple = (Tup1, Tup2) match
    case (h1 *: t1, h2 *: t2) => F[h1, h2] *: Map2[t1, t2, F]
    case (EmptyTuple, EmptyTuple) => EmptyTuple    

  inline given productReader[In <: Product, Out <: Product](using mi: Mirror.ProductOf[In])(using mo: Mirror.ProductOf[Out]): Reader[In, Out] = 
    summonAll[Map2[mi.MirroredElemTypes, mo.MirroredElemTypes, Reader]]
    ???

object Test:
  
  trait B[X] extends A:
    type T = X

  trait C extends A:
    type F[X] = X
  
  val bc = new B[Int] with C

  summon[Reader[(bc.type, bc.type), (Int, Int)]]    // fails

Output

Compiling project (Scala 3.3.0-RC3, JVM)
[error] .\fails.scala:35:49: No given instance of type Reader[(Test.bc : Test.B[Int] & Test.C), Int] was found.
[error] I found:
[error]
[error]     Reader.productReader[In, Out](/* missing */summon[deriving.Mirror.ProductOf[In]]
[error]       )
[error]
[error] But Failed to synthesize an instance of type deriving.Mirror.ProductOf[In]: class Nothing is not a generic product because it is not a case class.
[error]   summon[Reader[(bc.type, bc.type), (Int, Int)]]    // fails
[error]                                                 ^
Error compiling project (Scala 3.3.0-RC3, JVM)
Compilation failed

Expectation

Successful compilation

@kavedaa kavedaa added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 10, 2023
@kavedaa
Copy link
Author

kavedaa commented Apr 10, 2023

If the last couple of lines are replaced with this, it works:

trait D[X] extends B[X] with C

val d = new D[Int] {}

summon[Reader[(d.type, d.type), (Int, Int)]]    // works

@bishabosha
Copy link
Member

this issue is incorrectly titled, in fact the mirror synthesis works fine (can be observed with -Xprint:typer), what is failing is summonAll[(Reader[bc.type, Int], Reader[bc.type, Int])], see the following code, where I manually inline Reader. productReader:

//> using scala "3.3.0-RC3"

import scala.compiletime.*

trait Reader[-In, Out]

trait A:
  type T
  type F[X]
  type Q = F[T]

object Reader:

  given [X]: Reader[A { type Q = X }, X] with {}

object Test:

  trait B[X] extends A:
    type T = X

  trait C extends A:
    type F[X] = X

  trait D[X] extends B[X] with C

  val d = new D[Int] {}
  val bc = new B[Int] with C

  summonAll[(Reader[d.type, Int], Reader[d.type, Int])] // works
  summonAll[(Reader[bc.type, Int], Reader[bc.type, Int])] // error
  summonInline[Reader[d.type, Int]] // works
  summonInline[Reader[bc.type, Int]] // works??

so it appears the problem is summonAll

@bishabosha bishabosha changed the title Anonymous trait mixin breaks mirror synthesis Anonymous trait mixin breaks summonAll Apr 11, 2023
@bishabosha bishabosha added area:inline and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 11, 2023
@bishabosha
Copy link
Member

bishabosha commented Apr 11, 2023

If I reimplement summonAll as summonOne, with a single case, then we get the same problem, so is the issue inline match with erasedValue?

object Test:
  // ...

  case class Box[T]()

  /** compiletime.summonAll, but with one case */
  inline def summonOne[T <: Box[?]]: T =
    val res =
      inline erasedValue[T] match
        case _: Box[t] => summonInline[t]
      end match
    Box(res).asInstanceOf[T]
  end summonOne

  summonOne[Box[Reader[d.type, Int]]] // works
  summonOne[Box[Reader[bc.type, Int]]] // errors

@bishabosha bishabosha changed the title Anonymous trait mixin breaks summonAll Anonymous trait mixin breaks inline match + erasedValue + summonInline Apr 11, 2023
@kavedaa
Copy link
Author

kavedaa commented Jun 9, 2024

Here is a complete minimized example. There are many ways to make it compile, but only one way to make it fail:

//> using scala 3.3.3

import scala.compiletime.*

trait Reader[-In, Out]

trait A:
  type T
  type F[X]
  type Q = F[T]

given [X]: Reader[A { type Q = X }, X] with {}    

case class Box[T](x: T)

/** compiletime.summonAll, but with one case */
inline def summonOne[T]: T =
  val res =
    inline erasedValue[T] match
      case _: Box[t] => summonInline[t]
    end match
  Box(res).asInstanceOf[T]
end summonOne


@main def main = 


  trait B[X] extends A:
    type T = X

  trait C extends A:
    type F[X] = X

  
  val bc = new B[Int] with C
  
  summonOne[Box[Reader[bc.type, Int]]] // errors


  val bc2: A { type Q = Int } = new B[Int] with C
  
  summonOne[Box[Reader[bc2.type, Int]]] // works


  object BC extends B[Int] with C

  summonOne[Box[Reader[BC.type, Int]]] // works


  val a = new A:
    type T = Int
    type F[X] = X
  
  summonOne[Box[Reader[a.type, Int]]] // works


  val b = new B[Int]:
    type F[X] = X
  
  summonOne[Box[Reader[b.type, Int]]] // works


  val ac = new A with C:
    type T = Int

  summonOne[Box[Reader[ac.type, Int]]] // works


  trait D[X] extends B[X] with C
  val d = new D[Int] {}

  summonOne[Box[Reader[d.type, Int]]] // works

@jchyb
Copy link
Contributor

jchyb commented Jul 9, 2024

Spent some time investigating this, further minimization:

//> using scala 3.3.3
import scala.compiletime.*

trait Reader[-In, Out]

trait A:
  type T
  type F[X]
  type Q = F[T]

given [X]: Reader[A { type Q = X }, X] with {}    

case class Box[T](x: T)

inline def summonOne[T]: T =
  summonInline[T]
end summonOne

@main def main = 
  trait B[X] extends A:
    type T = X
  trait C extends A:
    type F[X] = X
  
  val bc = new B[Int] with C
  summonInline[Reader[bc.type, Int]] // (I) Works
  summonOne[Reader[bc.type, Int]] // (II) Errors

Looks like the only difference between the working call (I) and the erroring call (II) is that the failing call has a slightly different Context, the type argument is unchanged after the inlining of the enclosing method. In the failing call (II) the compiler is able to correctly identify the implicit method, however a later TypeComparer.testSubType check fails.

This is where it gets difficult, in both calls the TypeComparer tries to check the subtyping of Reader[A{type Q = X}, X] <:< Reader[(bc : B[Int] & C), Int], later checking B[Int] & C <:< A{type Q = X}, later checking B[Int].Q <:< X.

  • In the successful check (I), it resolves B[Int].Q into a denotation representing TypeBounds(Nothing, Any).
  • In the failing check (II), it resolves B[Int].Q into a denotation representing B[Int]#F[B[Int]#T].

I have no idea why the things differ here. In the failing check this causes the X type variable to "become" B[Int]#F[B[Int]#T], later failing X <: Int check. Type comparer is not an area of the compiler I usually touch, so I have no idea why the B[Int].Q denotations differ in those 2 calls and I am a bit stuck (any help/ideas would be appreciated).

@jchyb
Copy link
Contributor

jchyb commented Sep 2, 2024

Hi @noti0na1,
as mentioned above, I ran into some problems while investigating this issue relating to the type comparer, which I do not fully understand. While reviewing the current cycle, @Gedochao recommended you as a TypeComparer expert. Any help/mentoring/hints would be appreciated (if you have time of course!, in my opinion this seems like a lower priority issue, but an annoying one nonetheless).
Later this week I'll try to prepare a branch with type comparer printouts to show exactly what I mean in the message above, and so that you have a potentially easier time helping me dig there while ignoring the metaprogramming/implicit stuff. Even if you end up not being able to help/mentor, a branch like this will still be useful for the future me eventually, so do not feel pressured!

@noti0na1
Copy link
Member

noti0na1 commented Sep 2, 2024

I took a quick look, and it appears that the two summonInline calls are completed during different phases:

  • summonInline[Reader[bc.type, Int]] is inlined during typer,
  • summonInline inside summonOne is inlined during inlining.

I tried modifying the evCtx within searchImplicitOrError to ctx.fresh.setTyper(evTyper).setPhase(ctx.base.typerPhase) (which is not the correct fix!), and with this change, the tests in this issue pass.

I know that the TypeComparer behaves differently before and after the typer phase, but I haven't look into details why the type variable can't be instantiated in the second case.

@EugeneFlesselle
Copy link
Contributor

EugeneFlesselle commented Sep 6, 2024

Further minimized to:

import scala.compiletime.*

trait Reader[-In, Out]

trait A:
  type F
  type Q = F

given [X]: Reader[A { type Q = X }, X] with {}

def main =
//  type BC = A { type F = Int } & A // ok
  type BC = A & A { type F = Int } // fail, also ok when manually de-aliased

  inline def summonOne: Unit = summonInline[Reader[BC, Int]]

  summonInline[Reader[BC, Int]] // ok
  summonOne // error

It is from hasMatchingMember that we get results depending on the current phase.

@jchyb
Copy link
Contributor

jchyb commented Oct 2, 2024

@noti0na1 @EugeneFlesselle Thank you for all of the help!

The different behavior is indeed caused by the current phase. With that knowledge I tried digging a little further, and where it starts to differ is where it tries to return the denotation for A#Q. In typer it returns <SingleDenotation of type TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Nothing),TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any))>, in inlining it returns <SingleDenotation of type TypeAlias(TypeRef(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),trait A),type F))>. It makes sense that the denotations are different for different phases, and I don't think I can change anything there, so I'll try to look at this from another angle.

@EugeneFlesselle
Copy link
Contributor

EugeneFlesselle commented Oct 4, 2024

We had also noticed the issue is avoided by disabling the optimizations in def compareRefined that try to avoid going through def compareRefinedSlow, which might be useful to look into as well @jchyb

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

Successfully merging a pull request may close this issue.

7 participants