Подтвердить что ты не робот

Как использовать тип, рассчитанный в макросе Scala в предложении reify?

Я работал с макросами Scala и имел следующий код в макросе:

    val fieldMemberType = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be another thing")
    }

    reify{
      new TypeBuilder() {
        type fieldType = fieldMemberType.type
      }
    }

Как вы можете видеть, мне удалось получить c.universe.Type fieldMemberType. Это представляет собой тип определенного поля в объекте. Как только я получу это, я хочу создать новый объект TypeBuilder в reify. TypeBuilder - абстрактный класс с абстрактным параметром. Этот абстрактный параметр fieldType. Я хочу, чтобы этот fieldType был тем типом, который я нашел раньше.

Выполнение приведенного здесь кода возвращает мне fieldMemberType not found. Есть ли способ, которым я могу заставить fieldMemberType работать внутри предложения reify?

4b9b3361

Ответ 1

Проблема в том, что код, который вы передаете в reify, по существу, будет размещен дословно в точке, где макрос расширяется, а fieldMemberType ничего не будет означать.

В некоторых случаях вы можете использовать splice, чтобы скрывать выражение, которое у вас есть в момент макрорасширения, в код, который вы обновляете. Например, если мы пытаемся создать экземпляр этого признака:

trait Foo { def i: Int }

И эта переменная имела время макрорасширения:

val myInt = 10

Мы могли бы написать следующее:

reify { new Foo { def i = c.literal(myInt).splice } }

Это не будет работать здесь, а это значит, что вам придется забыть о хорошем маленьком reify и выписать АСТ вручную. Вы обнаружите, что это случается очень много, к сожалению. Мой стандартный подход - начать новый REPL и набрать что-то вроде этого:

import scala.reflect.runtime.universe._

trait TypeBuilder { type fieldType }

showRaw(reify(new TypeBuilder { type fieldType = String }))

Это выплюнет несколько строк AST, которые вы можете вырезать и вставить в определение макроса в качестве отправной точки. Затем вы играете с ним, заменяя такие вещи:

Ident(TypeBuilder)

При этом:

Ident(newTypeName("TypeBuilder"))

И FINAL с Flag.FINAL и т.д. Я бы хотел, чтобы методы toString для типов AST более точно соответствовали коду, который требуется для их создания, но вы довольно быстро получите представление о том, что вам нужно изменить. Вы получите что-то вроде этого:

c.Expr(
  Block(
    ClassDef(
      Modifiers(Flag.FINAL),
      anon,
      Nil,
      Template(
        Ident(newTypeName("TypeBuilder")) :: Nil,
        emptyValDef,
        List(
          constructor(c),
          TypeDef(
            Modifiers(),
            newTypeName("fieldType"),
            Nil,
            TypeTree(fieldMemberType)
          )
        )
      )
    ),
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  )
)

Где anon - это имя типа, которое вы создали заранее для своего анонимного класса, а constructor - это метод удобства, который я использую, чтобы сделать такое действие менее отвратительным (вы можете найти его определение на end этот полный рабочий пример).

Теперь, если мы обернем это выражение чем-то вроде this, мы можем написать следующее:

scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = [email protected]

Так оно и работает. Мы взяли c.universe.Type (который я получаю здесь из WeakTypeTag параметра типа на builderWithType, но он будет работать точно так же, как и любой старый Type), и использовал его для определения типа член нашей черты TypeBuilder.

Ответ 2

Существует более простой подход, чем дерево для вашего использования. Действительно, я все время использую его, чтобы держать деревья в страхе, так как очень сложно программировать деревья. Я предпочитаю вычислять типы и использовать reify для генерации деревьев. Это делает гораздо более надежные и "гигиеничные" макросы и меньше ошибок времени компиляции. ИМО, использующий деревья, должен быть последним средством, только для нескольких случаев, таких как древовидные преобразования или общее программирование для семейства типов, таких как кортежи.

Здесь вы можете определить функцию, принимающую как параметры типа, типы, которые вы хотите использовать в теле reify, с привязкой к контексту WeakTypeTag. Затем вы вызываете эту функцию, явно передавая значения WeakTypeTags, которые вы можете построить из типов юниверсов, благодаря методу контекстного метода WeakTypeTag.

Итак, в вашем случае это даст следующее.

  val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be            another thing")
  }

  def genRes[T: WeakTypeTag] = reify{
    new TypeBuilder() {
      type fieldType = T
    }
  }

  genRes(c.WeakTypeTag(fieldMemberType))