You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala 3编译期Tuple实现Typeclass推导遇VerifyError问题

Scala 3 Tuple类型类推导触发java.lang.VerifyError的原因解析

问题场景

在学习Scala 3编译期操作时,尝试基于*:和EmptyTuple的类型匹配推导cats.Show类型类,遇到如下问题:原代码对HList风格的Tuple(如Int *: String *: EmptyTuple)能正常工作,但对Tuple语法糖(如(Int, String))调用时触发java.lang.VerifyError。

原代码

import scala.compiletime.*
import cats.Show // 修正原代码的拼写错误`imort`

transparent inline def showForTuple[T <: Tuple]: Show[T] =
  inline erasedValue[T] match
    case _: EmptyTuple => (new Show[EmptyTuple] {
      override def show(n: EmptyTuple): String = ""
    }).asInstanceOf[Show[T]]

    case _: (t *: EmptyTuple) => (new Show[t *: EmptyTuple] {
      val showHead = summonInline[Show[t]]

      override def show(tup: t *: EmptyTuple): String = showHead.show(tup.head)
    }).asInstanceOf[Show[T]]

    case _: (t *: ts) => (new Show[t *: ts] {
      val showHead = summonInline[Show[t]]
      val showTail = showForTuple[ts]

      override def show(tup: t *: ts): String =
        showHead.show(tup.head) + ", " + showTail.show(tup.tail)
    }).asInstanceOf[Show[T]]

正常调用案例

showForTuple[Int *: String *: EmptyTuple].show((1, "hola mundo"))
// 输出: val res2: String = 1, hola mundo

报错调用案例

showForTuple[(Int, String)].show((1, "hola mundo"))

触发错误:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    rs$line$28$$anon$1.show(Lscala/Product;)Ljava/lang/String; @16: invokevirtual
  Reason:
    Type 'scala/Product' (current frame, stack[2]) is not assignable to 'scala/Tuple2'
  Current Frame:
    bci: @16
    flags: { }
    locals: { 'rs$line$28$$anon$1', 'scala/Product', 'scala/Product' }
    stack: { 'java/lang/StringBuilder', 'cats/Show', 'scala/Product' }
  Bytecode:
    0000000: bb00 2c59 122d b700 302a b600 322b 4d2c
    0000010: b600 38b8 003e b800 42b9 0045 0200 b600
    0000020: 4912 4bb6 0049 2ab6 004d 2b4e b200 522d
    0000030: b600 55b6 0059 b900 4502 00b6 0049 b600
    0000040: 5cb0                                   

  ... 66 elided

社区提供的解决方案

import scala.compiletime.*
import cats.Show

given Show[EmptyTuple] = _ => ""
lazy val given_Show_EmptyTuple: cats.Show[EmptyTuple]

given [A, T <: Tuple](using showA: Show[A], showT: Show[T]): Show[A *: T] =
  _ match
    case h *: EmptyTuple => showA.show(h)
    case h *: t => showA.show(h) + ", " + showT.show(t)

transparent inline def showForTuple[T <: Tuple]: Show[T] =
  inline erasedValue[T] match
    case _: EmptyTuple => summonInline[Show[EmptyTuple]].asInstanceOf[Show[T]]
    case _: (t *: EmptyTuple) => summonInline[Show[t *: EmptyTuple]].asInstanceOf[Show[T]]
    case _: (t *: ts) => summonInline[Show[t *: ts]].asInstanceOf[Show[T]]

// 两种调用均正常工作
showForTuple[Int *: String *: EmptyTuple].show((1, "hola mundo"))
showForTuple[(Int, String)].show((1, "hola mundo"))

错误原因解析

核心问题出在Tuple语法糖的类型擦除与HList风格Tuple的字节码兼容性:

  1. Scala中(Int, String)是Tuple2[Int, String]的语法糖,而Int *: String *: EmptyTuple是HList风格的Tuple表示,二者在编译期逻辑上等价,但字节码层面的实现细节不同。
  2. 原代码中手动创建匿名Show实例时,inline推导生成的字节码会将方法参数类型擦除为Product(所有Tuple都继承自Product)。但代码中直接调用tup.head和tup.tail——这两个方法对于Tuple2来说是通过扩展方法实现的,而非原生方法。JVM的字节码验证器会检查类型兼容性,发现Product类型无法直接转换为Tuple2来调用这些方法,因此抛出VerifyError。
  3. 解决方案改用Scala隐式系统(given)来定义类型类实例,隐式系统会自动处理Tuple2与HList风格Tuple的类型等价性,生成的字节码会正确适配实际的Tuple类型,避免了手动创建匿名类时的类型擦除不兼容问题。

内容的提问来源于stack exchange,提问作者Fabián Heredia Montiel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 06:17:03