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

Scala 3不透明类型扩展方法类型擦除编译差异疑问

问题背景

我需要定义两个底层实现类型均为Double的不透明类型(opaque type)别名,同时为这两个别名定义同名扩展方法。
如下写法的代码可以正常编译:

object MyMath1:
  object Logarithms1:
    opaque type Logarithm1 = Double
    extension (l1: Logarithm1) def foo: Unit = ()
  object Logarithms2:
    opaque type Logarithm2 = Double
    extension (l2: Logarithm2) def foo: Unit = ()
  import Logarithms1.Logarithm1
  import Logarithms2.Logarithm2

但如果将扩展方法定义在不透明类型别名的定义作用域之外,也就是统一放在MyMath2对象中,对应代码如下:

object MyMath2:
  object Logarithms1:
    opaque type Logarithm1 = Double
  object Logarithms2:
    opaque type Logarithm2 = Double
  import Logarithms1.Logarithm1
  import Logarithms2.Logarithm2
  extension (l1: Logarithm1) def foo: Unit = ()
  extension (l2: Logarithm2) def foo: Unit = ()

这段代码会触发如下编译错误:

Double definition:
def foo(l1: MyMath.Logarithms1.Logarithm1): Unit in object MyMath at line 52 and
def foo(l2: MyMath.Logarithms2.Logarithm2): Unit in object MyMath at line 53
have the same type after erasure.

Consider adding a @targetName annotation to one of the conflicting definitions
for disambiguation.

给其中一个方法添加@targetName注解确实可以修复该问题。
核心疑问:为什么第一段代码可以正常编译,第二段代码却编译失败?按直觉判断,第一段代码里的两个扩展方法在类型擦除后也应该是相同签名。

原因解释

这个差异是两个规则共同作用的结果:

  • 不透明类型的别名等价性仅在自身定义作用域内生效,出了作用域后编译器会将其视为独立的抽象类型,但字节码生成阶段的类型擦除仍会将其映射回底层实现类型
  • JVM层面方法签名冲突的判断仅关心「同一个类中,是否存在方法名相同、擦除后参数类型列表完全一致的方法」,跨类的同名方法不会产生冲突

两种写法的具体差异:

  1. 可编译的第一种写法
    两个foo扩展方法分别定义在Logarithms1、Logarithms2两个独立的单例对象内部,属于两个不同类的成员。哪怕擦除后两个方法的参数类型都是Double,但因为所属类完全不同,JVM层面根本不存在签名冲突,编译器自然不会拦截。
  2. 编译报错的第二种写法
    两个foo扩展方法都直接定义在MyMath2的根作用域下,属于MyMath2单例对象的直接成员。在Scala的类型检查阶段,编译器会将出了定义作用域的Logarithm1、Logarithm2视为两个不同的类型,因此表面上看两个方法的参数类型并不重复;但到字节码生成的擦除阶段,两个不透明类型都会被映射为底层的Double,最终生成的两个方法签名完全一致,触发了同类下的重名方法校验,因此编译器直接抛出错误。

内容的提问来源于stack exchange,提问作者Maximilien Tirard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:21:28