All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.evaluation.ResolveReflectEval.scala Maven / Gradle / Ivy

package dotty.tools.dotc.evaluation

import dotty.tools.dotc.ExpressionContext
import dotty.tools.dotc.ast.tpd.*
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.Names.*
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
import dotty.tools.dotc.transform.SymUtils.*
import dotty.tools.dotc.core.StdNames.*
import dotty.tools.dotc.core.NameKinds.QualifiedInfo
import dotty.tools.dotc.report
import dotty.tools.dotc.core.Phases
import dotty.tools.dotc.core.TypeErasure.ErasedValueType
import dotty.tools.dotc.transform.ValueClasses

class ResolveReflectEval(using exprCtx: ExpressionContext) extends MiniPhase:
  override def phaseName: String = ResolveReflectEval.name

  override def transformTypeDef(tree: TypeDef)(using Context): Tree =
    ExpressionTransformer.transform(tree)

  object ExpressionTransformer extends TreeMap:
    override def transform(tree: Tree)(using Context): Tree =
      tree match
        case tree: DefDef if tree.symbol == exprCtx.evaluateMethod =>
          // unbox the result of the `evaluate` method if it is a value class
          val gen = new Gen(
            Apply(
              Select(This(exprCtx.expressionClass), termName("reflectEval")),
              List(nullLiteral, nullLiteral, nullLiteral)
            )
          )
          val rhs = gen.unboxIfValueClass(exprCtx.expressionSymbol, transform(tree.rhs))
          cpy.DefDef(tree)(rhs = rhs)

        case reflectEval: Apply if isReflectEval(reflectEval.fun.symbol) =>
          val qualifier :: _ :: argsTree :: Nil = reflectEval.args.map(transform): @unchecked
          val args = argsTree.asInstanceOf[JavaSeqLiteral].elems
          val gen = new Gen(reflectEval)
          tree.attachment(EvaluationStrategy) match
            case EvaluationStrategy.This(cls) =>
              if cls.isValueClass then
                // if cls is a value class then the local $this is the erased value,
                // but we expect an instance of the value class instead
                gen.boxValueClass(cls, gen.getLocalValue("$this"))
              else gen.getLocalValue("$this")
            case EvaluationStrategy.Outer(outerCls) =>
              gen.getOuter(qualifier, outerCls)
            case EvaluationStrategy.LocalValue(variable, isByName) =>
              val variableName = JavaEncoding.encode(variable.name)
              val rawLocalValue = gen.getLocalValue(variableName)
              val localValue = if isByName then gen.evaluateByName(rawLocalValue) else rawLocalValue
              val derefLocalValue = gen.derefCapturedVar(localValue, variable)
              gen.boxIfValueClass(variable, derefLocalValue)
            case EvaluationStrategy.LocalValueAssign(variable) =>
              val value = gen.unboxIfValueClass(variable, args.head)
              val typeSymbol = variable.info.typeSymbol.asType
              val variableName = JavaEncoding.encode(variable.name)
              JavaEncoding.encode(typeSymbol) match
                case s"scala.runtime.${_}Ref" =>
                  val elemField = typeSymbol.info.decl(termName("elem")).symbol
                  gen.setField(
                    gen.getLocalValue(variableName),
                    elemField.asTerm,
                    value
                  )
                case _ => gen.setLocalValue(variableName, value)
            case EvaluationStrategy.ClassCapture(variable, cls, isByName) =>
              val rawCapture = gen
                .getClassCapture(qualifier, variable.name, cls)
                .getOrElse {
                  report.error(s"No capture found for $variable in $cls", tree.srcPos)
                  ref(defn.Predef_undefined)
                }
              val capture = if isByName then gen.evaluateByName(rawCapture) else rawCapture
              val capturedValue = gen.derefCapturedVar(capture, variable)
              gen.boxIfValueClass(variable, capturedValue)
            case EvaluationStrategy.ClassCaptureAssign(variable, cls) =>
              val capture = gen
                .getClassCapture(qualifier, variable.name, cls)
                .getOrElse {
                  report.error(s"No capture found for $variable in $cls", tree.srcPos)
                  ref(defn.Predef_undefined)
                }
              val value = gen.unboxIfValueClass(variable, args.head)
              val typeSymbol = variable.info.typeSymbol
              val elemField = typeSymbol.info.decl(termName("elem")).symbol
              gen.setField(capture, elemField.asTerm, value)
            case EvaluationStrategy.MethodCapture(variable, method, isByName) =>
              val rawCapture = gen
                .getMethodCapture(method, variable.name)
                .getOrElse {
                  report.error(s"No capture found for $variable in $method", tree.srcPos)
                  ref(defn.Predef_undefined)
                }
              val capture = if isByName then gen.evaluateByName(rawCapture) else rawCapture
              val capturedValue = gen.derefCapturedVar(capture, variable)
              gen.boxIfValueClass(variable, capturedValue)
            case EvaluationStrategy.MethodCaptureAssign(variable, method) =>
              val capture = gen
                .getMethodCapture(method, variable.name)
                .getOrElse {
                  report.error(s"No capture found for $variable in $method", tree.srcPos)
                  ref(defn.Predef_undefined)
                }
              val value = gen.unboxIfValueClass(variable, args.head)
              val typeSymbol = variable.info.typeSymbol
              val elemField = typeSymbol.info.decl(termName("elem")).symbol
              gen.setField(capture, elemField.asTerm, value)
            case EvaluationStrategy.StaticObject(obj) =>
              gen.getStaticObject(obj)
            case EvaluationStrategy.Field(field, isByName) =>
              // if the field is lazy, if it is private in a value class or a trait
              // then we must call the getter method
              val fieldValue =
                if field.is(Lazy) ||
                  field.owner.isValueClass ||
                  field.owner.is(Trait)
                then gen.callMethod(qualifier, field.getter.asTerm, Nil)
                else
                  val rawValue = gen.getField(qualifier, field)
                  if isByName then gen.evaluateByName(rawValue) else rawValue
              gen.boxIfValueClass(field, fieldValue)
            case EvaluationStrategy.FieldAssign(field) =>
              val arg = gen.unboxIfValueClass(field, args.head)
              if field.owner.is(Trait) then gen.callMethod(qualifier, field.setter.asTerm, List(arg))
              else gen.setField(qualifier, field, arg)
            case EvaluationStrategy.MethodCall(method) =>
              gen.callMethod(qualifier, method, args)
            case EvaluationStrategy.ConstructorCall(ctr, cls) =>
              gen.callConstructor(qualifier, ctr, args)
        case _ => super.transform(tree)

  private def isReflectEval(symbol: Symbol)(using Context): Boolean =
    symbol.name == termName("reflectEval") &&
      symbol.owner == exprCtx.expressionClass

  class Gen(reflectEval: Apply)(using Context):
    private val expressionThis = reflectEval.fun.asInstanceOf[Select].qualifier

    def derefCapturedVar(tree: Tree, term: TermSymbol): Tree =
      val typeSymbol = term.info.typeSymbol.asType
      JavaEncoding.encode(typeSymbol) match
        case s"scala.runtime.${_}Ref" =>
          val elemField = typeSymbol.info.decl(termName("elem")).symbol
          getField(tree, elemField.asTerm)
        case _ => tree

    def boxIfValueClass(term: TermSymbol, tree: Tree): Tree =
      atPhase(Phases.elimErasedValueTypePhase)(term.info) match
        case tpe: ErasedValueType =>
          boxValueClass(tpe.tycon.typeSymbol.asClass, tree)
        case tpe => tree

    def boxValueClass(valueClass: ClassSymbol, tree: Tree): Tree =
      // qualifier is null: a value class cannot be nested into a class
      val ctor = valueClass.primaryConstructor.asTerm
      callConstructor(nullLiteral, ctor, List(tree))

    def unboxIfValueClass(term: TermSymbol, tree: Tree): Tree =
      atPhase(Phases.elimErasedValueTypePhase)(term.info) match
        case tpe: ErasedValueType => unboxValueClass(tree, tpe)
        case tpe => tree

    private def unboxValueClass(tree: Tree, tpe: ErasedValueType): Tree =
      val cls = tpe.tycon.typeSymbol.asClass
      val unboxMethod = ValueClasses.valueClassUnbox(cls).asTerm
      callMethod(tree, unboxMethod, Nil)

    def getLocalValue(name: String): Tree =
      Apply(
        Select(expressionThis, termName("getLocalValue")),
        List(Literal(Constant(name)))
      )

    def setLocalValue(name: String, value: Tree): Tree =
      Apply(
        Select(expressionThis, termName("setLocalValue")),
        List(Literal(Constant(name)), value)
      )

    def getOuter(qualifier: Tree, outerCls: ClassSymbol): Tree =
      Apply(
        Select(expressionThis, termName("getOuter")),
        List(qualifier, Literal(Constant(JavaEncoding.encode(outerCls))))
      )

    def getClassCapture(
        qualifier: Tree,
        originalName: Name,
        cls: ClassSymbol
    ): Option[Tree] =
      cls.info.decls.iterator
        .filter(term => term.isField)
        .find { field =>
          field.name match
            case DerivedName(underlying, _) if field.isPrivate =>
              underlying == originalName
            case DerivedName(DerivedName(_, info: QualifiedInfo), _) =>
              info.name == originalName
            case _ => false
        }
        .map(field => getField(qualifier, field.asTerm))

    def getMethodCapture(
        method: TermSymbol,
        originalName: TermName
    ): Option[Tree] =
      val methodType = method.info.asInstanceOf[MethodType]
      methodType.paramNames
        .find {
          case DerivedName(n, _) => n == originalName
          case _ => false
        }
        .map { param =>
          val paramName = JavaEncoding.encode(param)
          getLocalValue(paramName)
        }

    def getStaticObject(obj: ClassSymbol): Tree =
      val className = JavaEncoding.encode(obj)
      Apply(
        Select(expressionThis, termName("getStaticObject")),
        List(Literal(Constant(className)))
      )

    def getField(qualifier: Tree, field: TermSymbol): Tree =
      val fieldName = JavaEncoding.encode(field.name)
      Apply(
        Select(expressionThis, termName("getField")),
        List(
          qualifier,
          Literal(Constant(JavaEncoding.encode(field.owner.asType))),
          Literal(Constant(fieldName))
        )
      )

    def setField(qualifier: Tree, field: TermSymbol, value: Tree): Tree =
      val fieldName = JavaEncoding.encode(field.name)
      Apply(
        Select(expressionThis, termName("setField")),
        List(
          qualifier,
          Literal(Constant(JavaEncoding.encode(field.owner.asType))),
          Literal(Constant(fieldName)),
          value
        )
      )

    def evaluateByName(function: Tree): Tree =
      val castFunction = function.cast(defn.Function0.typeRef.appliedTo(defn.AnyType))
      Apply(Select(castFunction, termName("apply")), List())

    def callMethod(
        qualifier: Tree,
        method: TermSymbol,
        args: List[Tree]
    ): Tree =
      val methodType = method.info.asInstanceOf[MethodType]
      val paramTypesNames = methodType.paramInfos.map(JavaEncoding.encode)
      val paramTypesArray = JavaSeqLiteral(
        paramTypesNames.map(t => Literal(Constant(t))),
        TypeTree(ctx.definitions.StringType)
      )
      val capturedArgs =
        methodType.paramNames.dropRight(args.size).map {
          case name @ DerivedName(underlying, _) =>
            capturedValue(method, underlying)
              .getOrElse {
                report.error(s"Unknown captured variable $name in $method", reflectEval.srcPos)
                ref(defn.Predef_undefined)
              }
          case name =>
            report
              .error(
                s"Unknown captured variable $name in $method",
                reflectEval.srcPos
              )
            ref(defn.Predef_undefined)
        }

      val erasedMethodInfo =
        atPhase(Phases.elimErasedValueTypePhase)(method.info)
          .asInstanceOf[MethodType]
      val unboxedArgs =
        erasedMethodInfo.paramInfos.takeRight(args.size).zip(args).map {
          case (tpe: ErasedValueType, arg) => unboxValueClass(arg, tpe)
          case (_, arg) => arg
        }

      val returnTypeName = JavaEncoding.encode(methodType.resType)
      val methodName = JavaEncoding.encode(method.name)
      val result = Apply(
        Select(expressionThis, termName("callMethod")),
        List(
          qualifier,
          Literal(Constant(JavaEncoding.encode(method.owner.asType))),
          Literal(Constant(methodName)),
          paramTypesArray,
          Literal(Constant(returnTypeName)),
          JavaSeqLiteral(
            capturedArgs ++ unboxedArgs,
            TypeTree(ctx.definitions.ObjectType)
          )
        )
      )
      erasedMethodInfo.resType match
        case tpe: ErasedValueType =>
          boxValueClass(tpe.tycon.typeSymbol.asClass, result)
        case _ => result

    def callConstructor(
        qualifier: Tree,
        ctr: TermSymbol,
        args: List[Tree]
    ): Tree =
      val methodType = ctr.info.asInstanceOf[MethodType]
      val paramTypesNames = methodType.paramInfos.map(JavaEncoding.encode)
      val clsName = JavaEncoding.encode(methodType.resType)

      val capturedArgs =
        methodType.paramNames.dropRight(args.size).map {
          case outer if outer.toString == "$outer" => qualifier
          case name @ DerivedName(underlying, _) =>
            // if derived then probably a capture
            capturedValue(ctr.owner, underlying)
              .getOrElse {
                report.error(s"Unknown captured variable $name in $ctr of ${ctr.owner}", reflectEval.srcPos)
                ref(defn.Predef_undefined)
              }
          case name =>
            val paramName = JavaEncoding.encode(name)
            getLocalValue(paramName)
        }

      val erasedCtrInfo = atPhase(Phases.elimErasedValueTypePhase)(ctr.info)
        .asInstanceOf[MethodType]
      val unboxedArgs =
        erasedCtrInfo.paramInfos.takeRight(args.size).zip(args).map {
          case (tpe: ErasedValueType, arg) => unboxValueClass(arg, tpe)
          case (_, arg) => arg
        }

      val paramTypesArray = JavaSeqLiteral(
        paramTypesNames.map(t => Literal(Constant(t))),
        TypeTree(ctx.definitions.StringType)
      )
      Apply(
        Select(expressionThis, termName("callConstructor")),
        List(
          Literal(Constant(clsName)),
          paramTypesArray,
          JavaSeqLiteral(
            capturedArgs ++ unboxedArgs,
            TypeTree(ctx.definitions.ObjectType)
          )
        )
      )

    private def capturedValue(
        sym: Symbol,
        originalName: TermName
    ): Option[Tree] =
      val encodedName = JavaEncoding.encode(originalName)
      if exprCtx.classOwners.contains(sym)
      then capturedByClass(sym.asClass, originalName)
      else
      // if the captured value is not a local variables
      // then it must have been captured by the outer method
      if exprCtx.localVariables.contains(encodedName)
      then Some(getLocalValue(encodedName))
      else exprCtx.capturingMethod.flatMap(getMethodCapture(_, originalName))

    private def capturedByClass(
        cls: ClassSymbol,
        originalName: TermName
    ): Option[Tree] =
      val target = exprCtx.classOwners.indexOf(cls)
      val qualifier = exprCtx.classOwners
        .drop(1)
        .take(target)
        .foldLeft(getLocalValue("$this"))((q, cls) => getOuter(q, cls))
      getClassCapture(qualifier, originalName, cls)

object ResolveReflectEval:
  val name = "resolve-reflect-eval"




© 2015 - 2025 Weber Informatics LLC | Privacy Policy