Как я могу просмотреть код, который Scala использует для автоматической генерации функции применения для классов дел? - PullRequest
1 голос
/ 02 апреля 2020

При определении класса case Scala автоматически генерируется функция apply, которая ведет себя так же, как и конструктор по умолчанию в java. Как я могу увидеть код, который автоматически генерирует функцию apply? Я предполагаю, что код является макросом в компиляторе Scala где-то, но я не уверен.

Чтобы пояснить, я не заинтересован в просмотре результирующего метода apply данного класса case, но заинтересован в макросе / код, который генерирует метод apply.

Ответы [ 3 ]

3 голосов
/ 03 апреля 2020

Это не макрос. Методы синтезируются компилятором "вручную".

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)
2 голосов
/ 03 апреля 2020

Возможно, я мог бы указать на несколько моментов в кодовой базе, которые могли бы иметь отношение.

Во-первых, есть способ соотнести грамматику 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*/)))
        ...
      }
    }

Спецификация продолжается

Определение класса case ?[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 LO C, так что давайте немного просмотрим. Мы натыкаемся на многообещающую линию

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))
    }

Woohoo! Документация гласит:

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

, которая кажется очень похожей на версию SLS

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

Наш камень в темноте, кажется, привел нас к открытию.

1 голос
/ 03 апреля 2020

Я заметил, что, в то время как другие опубликовали фрагменты кода, которые генерируют имя метода, подпись , тип , соответствующие символы в таблице символов и почти во всем остальном, пока никто не опубликовал фрагмент кода, который генерирует фактическое тело метода объекта сопутствующего класса apply.

Этот код находится в scala.tools.nsc.typechecker.Unapplies.factoryMeth(mods: Global.Modifiers, name: Global.TermName, cdef: Global.ClassDef): Global.DefDef, который определен в src/compiler/scala/tools/nsc/typechecker/Unapplies.scala, и соответствующая часть такова:

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

, который использует TreeDSL внутренняя спецификация домена c Язык для генерации синтаксических узлов в абстрактном синтаксическом дереве, и (примерно) это означает:

  • В текущей позиции в дереве ( atPos(cdef.pos.focus))
  • Соединение в узле определения метода (DefDef)
  • Чье тело является просто New узлом, то есть вызовом конструктора.

Описание TreeDSL черт состояния:

Цель состоит в том, чтобы генерировать код g код должен быть очень похож на код, который он генерирует.

И я думаю, что это правда, и делает код легким для чтения, даже если вы не знакомы с внутренними компонентами компилятора.

Сравните полученный код ing еще раз с генерируемым кодом ed :

DefDef(mods, name, tparams, cparamss, classtpe,
 New(classtpe, mmap(cparamss)(gen.paramToArg)))
def apply[Tparams](constructorParams): CaseClassType =
  new CaseClassType(constructorParams)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...