如何查看 Scala 用于自动生成案例类的应用函数的代码?



定义 Scala 案例类时,会自动生成一个 apply 函数,其行为类似于 java 中默认构造函数的行为方式。如何查看自动生成应用函数的代码?我认为代码是 Scala 编译器中某个地方的宏,但我不确定。

澄清一下,我对查看给定案例类的结果应用方法不感兴趣,但对生成应用方法的宏/代码感兴趣。

它不是一个宏。方法由编译器"手动"合成。

applyunapplycopyscala.tools.nsc.typechecker.Namers中生成

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Namers.scala#L1839-L1862

/** Given a case class
*   case class C[Ts] (ps: Us)
*  Add the following methods to toScope:
*  1. if case class is not abstract, add
*   <synthetic> <case> def apply[Ts](ps: Us): C[Ts] = new C[Ts](ps)
*  2. add a method
*   <synthetic> <case> def unapply[Ts](x: C[Ts]) = <ret-val>
*  where <ret-val> is the caseClassUnapplyReturnValue of class C (see UnApplies.scala)
*
* @param cdef is the class definition of the case class
* @param namer is the namer of the module class (the comp. obj)
*/
def addApplyUnapply(cdef: ClassDef, namer: Namer): Unit = {
if (!cdef.symbol.hasAbstractFlag)
namer.enterSyntheticSym(caseModuleApplyMeth(cdef))
val primaryConstructorArity = treeInfo.firstConstructorArgs(cdef.impl.body).size
if (primaryConstructorArity <= MaxTupleArity)
namer.enterSyntheticSym(caseModuleUnapplyMeth(cdef))
}
def addCopyMethod(cdef: ClassDef, namer: Namer): Unit = {
caseClassCopyMeth(cdef) foreach namer.enterSyntheticSym
}

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Namers.scala#L1195-L1219

private def templateSig(templ: Template): Type = {
//...
// add apply and unapply methods to companion objects of case classes,
// unless they exist already; here, "clazz" is the module class
if (clazz.isModuleClass) {
clazz.attachments.get[ClassForCaseCompanionAttachment] foreach { cma =>
val cdef = cma.caseClass
assert(cdef.mods.isCase, "expected case class: "+ cdef)
addApplyUnapply(cdef, templateNamer)
}
}
// add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because
// the namer phase must traverse this copy method to create default getters for its parameters.
// here, clazz is the ClassSymbol of the case class (not the module). (!clazz.hasModuleFlag) excludes
// the moduleClass symbol of the companion object when the companion is a "case object".
if (clazz.isCaseClass && !clazz.hasModuleFlag) {
val modClass = companionSymbolOf(clazz, context).moduleClass
modClass.attachments.get[ClassForCaseCompanionAttachment] foreach { cma =>
val cdef = cma.caseClass
def hasCopy = (decls containsName nme.copy) || parents.exists(_.member(nme.copy).exists)
// scala/bug#5956 needs (cdef.symbol == clazz): there can be multiple class symbols with the same name
if (cdef.symbol == clazz && !hasCopy)
addCopyMethod(cdef, templateNamer)
}
}

equalshashCodetoStringscala.tools.nsc.typechecker.SyntheticMethods中生成

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala

/** Synthetic method implementations for case classes and case objects.
*
*  Added to all case classes/objects:
*    def productArity: Int
*    def productElement(n: Int): Any
*    def productPrefix: String
*    def productIterator: Iterator[Any]
*
*  Selectively added to case classes/objects, unless a non-default
*  implementation already exists:
*    def equals(other: Any): Boolean
*    def hashCode(): Int
*    def canEqual(other: Any): Boolean
*    def toString(): String
*
*  Special handling:
*    protected def writeReplace(): AnyRef
*/
trait SyntheticMethods extends ast.TreeDSL {
//...

访问器的符号是在scala.reflect.internal.Symbols中创建

的https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/internal/Symbols.scala#L2103-L2128

/** For a case class, the symbols of the accessor methods, one for each
*  argument in the first parameter list of the primary constructor.
*  The empty list for all other classes.
*
* This list will be sorted to correspond to the declaration order
* in the constructor parameter
*/
final def caseFieldAccessors: List[Symbol] = {
// We can't rely on the ordering of the case field accessors within decls --
// handling of non-public parameters seems to change the order (see scala/bug#7035.)
//
// Luckily, the constrParamAccessors are still sorted properly, so sort the field-accessors using them
// (need to undo name-mangling, including the sneaky trailing whitespace)
//
// The slightly more principled approach of using the paramss of the
// primary constructor leads to cycles in, for example, pos/t5084.scala.
val primaryNames = constrParamAccessors map (_.name.dropLocal)
def nameStartsWithOrigDollar(name: Name, prefix: Name) =
name.startsWith(prefix) && name.length > prefix.length + 1 && name.charAt(prefix.length) == '$'
caseFieldAccessorsUnsorted.sortBy { acc =>
primaryNames indexWhere { orig =>
(acc.name == orig) || nameStartsWithOrigDollar(acc.name, orig)
}
}
}
private final def caseFieldAccessorsUnsorted: List[Symbol] = info.decls.toList.filter(_.isCaseAccessorMethod)

也许我可以指出代码库中可能相关的几点。

首先,有一种方法可以将Scala语言规范语法直接与源代码相关联。例如,案例类规则

TmplDef  ::=  ‘case’ ‘class’ ClassDef

Parser.tmplDef有关

/** {{{
*  TmplDef ::= [case] class ClassDef
*            |  [case] object ObjectDef
*            |  [override] trait TraitDef
*  }}}
*/
def tmplDef(pos: Offset, mods: Modifiers): Tree = {
...
in.token match {
...
case CASECLASS =>
classDef(pos, (mods | Flags.CASE) withPosition (Flags.CASE, tokenRange(in.prev /*scanner skips on 'case' to 'class', thus take prev*/)))
...
}
}

规格继续

具有类型参数的[tps](ps1)…(ps )的事例类定义 TPS 和值参数 ps 表示伴侣的定义 对象,用作提取器对象。

object    {   
def apply[tps](ps1)…(ps  ):   [tps] = new   [Ts](xs1)…(xs  )   
def unapply[tps](  :   [tps]) =
if (x eq null) scala.None
else scala.Some(  .xs11,…,  .xs1  ) 
} 

,让我们尝试寻找隐含的定义

def apply[tps](ps1)…(ps  ):   [tps] = new   [Ts](xs1)…(xs  )

这是综合定义的另一种说法。有希望的是,存在MethodSynthesis.scala

/** Logic related to method synthesis which involves cooperation between
*  Namer and Typer.
*/
trait MethodSynthesis {

因此,我们发现了另外两个潜在的线索NamerTyper.我想知道里面有什么?但是首先MethodSynthesis.scala只有大约 300 个 LOC,所以让我们略读一下。我们偶然发现了一条有前途的线

val methDef = factoryMeth(classDef.mods & (AccessFlags | FINAL) | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef)

"factoryMeth"...它有一个戒指。查找用法!我们很快被引导到

/** The apply method corresponding to a case class
*/
def caseModuleApplyMeth(cdef: ClassDef): DefDef = {
val inheritedMods = constrMods(cdef)
val mods =
if (applyShouldInheritAccess(inheritedMods))
(caseMods | (inheritedMods.flags & PRIVATE)).copy(privateWithin = inheritedMods.privateWithin)
else
caseMods
factoryMeth(mods, nme.apply, cdef)
}

看来我们走在正确的轨道上。我们还注意到名称

nme.apply

这是

val apply: NameType                = nameType("apply")

我们急切地找到了caseModuleApplyMeth的用法,我们被蠕虫洞Namer.addApplyUnapply

/** Given a case class
*   case class C[Ts] (ps: Us)
*  Add the following methods to toScope:
*  1. if case class is not abstract, add
*   <synthetic> <case> def apply[Ts](ps: Us): C[Ts] = new C[Ts](ps)
*  2. add a method
*   <synthetic> <case> def unapply[Ts](x: C[Ts]) = <ret-val>
*  where <ret-val> is the caseClassUnapplyReturnValue of class C (see UnApplies.scala)
*
* @param cdef is the class definition of the case class
* @param namer is the namer of the module class (the comp. obj)
*/
def addApplyUnapply(cdef: ClassDef, namer: Namer): Unit = {
if (!cdef.symbol.hasAbstractFlag)
namer.enterSyntheticSym(caseModuleApplyMeth(cdef))
val primaryConstructorArity = treeInfo.firstConstructorArgs(cdef.impl.body).size
if (primaryConstructorArity <= MaxTupleArity)
namer.enterSyntheticSym(caseModuleUnapplyMeth(cdef))
}

呜呼!文档指出

<synthetic> <case> def apply[Ts](ps: Us): C[Ts] = new C[Ts](ps)

这似乎与SLS版本惊人地相似

def apply[tps](ps1)…(ps  ):   [tps] = new   [Ts](xs1)…(xs  )

我们在黑暗中磕磕绊绊似乎让我们发现了一个发现。

我注意到,虽然其他人已经发布了生成方法名称的代码片段、签名类型、符号表中的相应符号以及几乎所有其他内容,但到目前为止,还没有人发布生成 case 类伴侣对象apply方法的实际正文的代码段。

该代码在src/compiler/scala/tools/nsc/typechecker/Unapplies.scala中定义的scala.tools.nsc.typechecker.Unapplies.factoryMeth(mods: Global.Modifiers, name: Global.TermName, cdef: Global.ClassDef): Global.DefDef中,相关部分如下:

atPos(cdef.pos.focus)(
DefDef(mods, name, tparams, cparamss, classtpe,
New(classtpe, mmap(cparamss)(gen.paramToArg)))
)

使用TreeDSL内部域特定语言在抽象语法树中生成语法节点,并且(大致)表示:

  • 在树中的当前位置 (atPos(cdef.pos.focus))
  • 方法定义节点中的拼接 (DefDef)
  • 其主体只是一个New节点,即构造函数调用。

TreeDSL性状的描述指出:

目标是生成代码的代码看起来应该很像它生成的代码。

我认为这是真的,即使您不熟悉编译器内部结构,也使代码易于阅读。

再次将生成代码与生成代码进行比较:

DefDef(mods, name, tparams, cparamss, classtpe,
New(classtpe, mmap(cparamss)(gen.paramToArg)))
def apply[Tparams](constructorParams): CaseClassType =
new CaseClassType(constructorParams)

相关内容

  • 没有找到相关文章

最新更新