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

Exception during macro expansion: java.lang.ClassCastException: class scala.quoted.runtime.impl.ExprImpl cannot be cast to class scala.Product #19941

Closed
hmf opened this issue Mar 13, 2024 · 9 comments · Fixed by #19944
Assignees
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:bug
Milestone

Comments

@hmf
Copy link

hmf commented Mar 13, 2024

Compiler version

Version 3.4.0

Minimized code

package data

import scala.quoted.*
import scala.annotation.showAsInfix

object Macros9:

  sealed trait IMap
  case object IEmpT extends IMap
  case class ICons[K, V, T <: IMap](key: K, value: V, tail: T) extends IMap


  @showAsInfix
  type -:[K,V,T<:IMap] = ICons[K,V,T] 

  type ~:[V,T<:IMap] = ICons[String,V,T]

  /** A tuple of 0 elements */
  type IEmpT = IEmpT.type

  extension [K,V,T <: IMap] (a: (K,V)) def -: (t: T): ICons[K,V,T] =
    ICons(a._1, a._2, t)

  extension [V,T <: IMap] (a: V) def ~: (t: T): ICons[String,V,T] =
    ICons(a.toString(), a, t)

  object -: {
    def unapply[K, V, T <: IMap](x: -:[K,V,T]): (K, V, T) = (x.key, x.value, x.tail)
  }


  transparent inline def table[I <: Tuple](inline from:I): Any =
      ${ tableImpl0( 'from ) }

  def tableImpl0[T <: Tuple](from: Expr[T])(using qtt: Type[T], qq: Quotes): Expr[? <: IMap] =
    import quotes.reflect.*
    from match
      case '{EmptyTuple} =>
        '{
          val t: IEmpT = IEmpT
          t
        }
      case '{$v : Tuple1[t]} =>
        '{
          val v1 = $v._1
          val k1 = v1.toString()
          val map = (k1, v1) -: IEmpT
          map
        }
      case '{$v : Tuple2[t1,t2]} =>
        '{
          val v1 = $v._1
          val k1 = v1.toString()
          val v2 = $v._2
          val k2 = v2.toString()
          val map = (k1, v1) -: (k2,v2) -: IEmpT
          map
        }
      /* 
        TODO: report
        [error]     |java.lang.ClassCastException: class scala.quoted.runtime.impl.ExprImpl cannot be cast to class scala.Product (scala.quoted.runtime.impl.ExprImpl and scala.Product are in unnamed module of loader mill.api.ClassLoader$$anon$1 @75b3673)
       */
      case '{ ${hd *: tl} : *:[h,t] } => // class cast error, cannot be cast to product
        ???
      case '{$v : *:[h,t]} =>
        println(s"${from}: ${from.asTerm.show}") 
        println("h: " + Type.show[h])
        println("t: " + Type.show[t])
        ???
      case '{$expr: t} => 
        println("tableImpl0-6")
        report.errorAndAbort(s"unexpected a ${from}: ${expr.show} - ${Type.show[t]}") 

end Macros9
package data9

object Data9:

  import data.Macros9.*

  def main(args: Array[String]): Unit =
    

    val c0 = Array(100,200,300,400)
    summon[c0.type <:< Array[Int]]

    val c1 = Array(101,201,301,401)
    summon[c1.type <:< Array[Int]]

    val c2 = Array(1,2,3,4)
    summon[c2.type <:< Array[Int]]

    // Fails 
    val t4 = table(c0 *: c1 *: c2 *: EmptyTuple) 
    summon[t4.type <:< -:[String, Array[Int], -:[String, Array[Int], -:[String, Array[Int], IEmpT]]]]

end Data9

Output

[error] -- Error: /home/hmf/VSCodeProjects/sploty/meta/src/data/Data9.scala:37:18 ------
[error] 37 |    val t4 = table(
[error]    |             ^
[error]    |Exception occurred while executing macro expansion.
[error]    |java.lang.ClassCastException: class scala.quoted.runtime.impl.ExprImpl cannot be cast to class scala.Product (scala.quoted.runtime.impl.ExprImpl and scala.Product are in unnamed module of loader mill.api.ClassLoader$$anon$1 @4455f57d)
[error]    |    at data.Macros9$.tableImpl0(Macros9.scala:102)

Expectation

I was expecting to be able to match against any tuple. Note that the case '{$v : *:[h,t]}match works fine.

@hmf hmf added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 13, 2024
@nicolasstucki nicolasstucki self-assigned this Mar 13, 2024
@nicolasstucki nicolasstucki removed the stat:needs triage Every issue needs to have an "area" and "itype" label label Mar 13, 2024
@nicolasstucki
Copy link
Contributor

Minimization

import scala.quoted.*

inline def expandMacro(inline from: Tuple): Any = 
  ${ expandMacroImpl }

def expandMacroImpl(using Quotes): Expr[?] =
  '{ 1 *: EmptyTuple } match
    case '{ ${hd *: tl} : *:[h,t] } => '{ ??? }
def test: Any = expandMacro
1 |def test: Any = expandMacro
  |                ^^^^^^^^^^^
  |Exception occurred while executing macro expansion.
  |java.lang.ClassCastException: class scala.quoted.runtime.impl.ExprImpl cannot be cast to class scala.Product (scala.quoted.runtime.impl.ExprImpl and scala.Product are in unnamed module of loader 'app')
  |     at Macro_1$package$.expandMacroImpl(Macro_1.scala:7)
  |
  |-----------------------------------------------------------------------------
  |Inline stack trace
  |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  |This location contains code that was inlined from Macro_1.scala:3
3 |inline def expandMacro(inline from: Tuple): Any = ${ expandMacroImpl }
  |                                                  ^^^^^^^^^^^^^^^^^^^^
   -----------------------------------------------------------------------------

@nicolasstucki
Copy link
Contributor

The code has something wrong in ${hd *: tl}, hd *: tl is matching an Expr[Int *: EmptyTuple]. This should never match as the extractor cannot be applied to Expr.

The code should not crash, it should fail to match.

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 14, 2024
We compute the pattern using `Expr[pt]` of the prototype `pt` of the splice,
but this is not enough to reject non-matching patterns. The quoted splices
are encoded away before we get to the pattern matching phase where we could
potentially detect that they will not match. Instead we check that all
quote patterns are `Expr[..]` when typing the pattern.

Fixes scala#19941
@hmf
Copy link
Author

hmf commented Mar 14, 2024

@nicolasstucki Does this mean the code is invalid or useless for the last member of a non empty tuple? (I assumed the tail tl:t would match with the EmptyTuple.) Should one also explicitly match on the empty tuple? For example:

      case '{scala.Tuple$package.EmptyTuple.*:[t, EmptyTuple.type]($hd)} =>

For the other cases can I assume this should/will work?

TIA

@nicolasstucki
Copy link
Contributor

You probably need something like this

case '{ ($hd: h) *: ($tl: t) } =>

@hmf
Copy link
Author

hmf commented Mar 14, 2024

@nicolasstucki I had already tried that but compilation failed. Here is what I get:

[error] 96 |      case '{ ($hd: h) *: ($tl: t) } =>
[error]    |                       ^^^^^^^^^^^
[error]    |                       value *: is not a member of t

@nicolasstucki
Copy link
Contributor

Oh right, you must tell the pattern that t is a subtype of Tuple to let it figure out what *: referees to.

case '{ type t <: Tuple; ($hd: h) *: ($tl: t) } =>

@Gedochao Gedochao added the area:metaprogramming:quotes Issues related to quotes and splices label Mar 14, 2024
@hmf
Copy link
Author

hmf commented Mar 14, 2024

Thank you. Compiles. It seems obvious, but only after you have explained 8-)

One more question: Above we state t <: Tuple, but what is the relationship between T and t? I was assuming that since T <: Tuple and we match on it, any types we do match on a Tuple (say the tail) would also be a Tuple.

@hmf
Copy link
Author

hmf commented Mar 14, 2024

@nicolasstucki tried the case '{ type t <: Tuple; ($hd: h) *: ($tl: t) } => but it does not seem to be matching.

@nicolasstucki
Copy link
Contributor

That is another bug. I opened #19947 to follow that one.

nicolasstucki added a commit that referenced this issue Mar 19, 2024
We compute the pattern using `Expr[pt]` of the prototype `pt` of the
splice, but this is not enough to reject non-matching patterns. The
quoted splices are encoded away before we get to the pattern matching
phase where we could potentially detect that they will not match.
Instead we check that all quote patterns are `Expr[..]` when typing the
pattern.

Fixes #19941
@Kordyjan Kordyjan added this to the 3.4.2 milestone Mar 28, 2024
@Kordyjan Kordyjan modified the milestones: 3.4.2, 3.5.0 May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants