定义 Scala 案例类时,会自动生成一个 apply 函数,其行为类似于 java 中默认构造函数的行为方式。如何查看自动生成应用函数的代码?我认为代码是 Scala 编译器中某个地方的宏,但我不确定。
澄清一下,我对查看给定案例类的结果应用方法不感兴趣,但对生成应用方法的宏/代码感兴趣。
它不是一个宏。方法由编译器"手动"合成。
apply
、unapply
、copy
在scala.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)
}
}
equals
、hashCode
、toString
在scala.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 {
因此,我们发现了另外两个潜在的线索Namer
和Typer
.我想知道里面有什么?但是首先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)