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

.scala-fortify_3.3.4.1.1.4.source-code.Translator.scala Maven / Gradle / Ivy

/*
 * Copyright © 2016-2024 Lightbend, Inc. All rights reserved.
 * No information contained herein may be reproduced or transmitted in any form
 * or by any means without the express written permission of Lightbend, Inc.
 */

package com.lightbend.tools.fortify
package plugin

import scala.language.implicitConversions

import com.fortify.frontend.nst
import nst.*
import nodes.*

import dotty.tools.dotc
import dotc.ast.tpd
import dotc.core.Contexts.Context
import dotc.core.Constants
import dotc.core.Flags.*
import dotc.core.StdNames.*
import dotc.util.SourcePosition
import dotc.core.Symbols.NoSymbol
import dotc.core.Types.{Type, TypeRef, ClassInfo, JavaArrayType}

class Translator(
    val showSourceInfo: Boolean = true,
    log: String => Unit = println,
)(using ctx: Context)
    extends TranslatorBase
    with TranslatorHelpers
    with Closure:

  def trace(s: =>String): Unit = ()

  type SourceFile = dotc.util.SourceFile
  type Tree = tpd.Tree

  def translateAll(tree: Tree): List[STClassDecl] =
    trace(s"compilation unit: ${tree.sourcePos}")
    (new tpd.TreeAccumulator[List[tpd.TypeDef]]:
      def apply(tds: List[tpd.TypeDef], tree: Tree)(using Context): List[tpd.TypeDef] =
        tree match
          // REAL
          // case PackageDef(_, stats) =>
          //   stats.foreach(recurse)
          case td @ tpd.TypeDef(_, rhs) =>
            apply(td :: tds, rhs)
          case _ =>
            foldOver(tds, tree)
    )
      .apply(Nil, tree)
      // it's maddening that the order seems to be nondeterministic and we must sort.
      // I may need to deal with this in some better way eventually?
      .sortBy(_.symbol.fullName)
      .flatMap(translate)

  def translate(classDef: tpd.TypeDef): List[STClassDecl] =
    trace(s"ClassDef: ${classDef.symbol}")
    val clazz = toClassDecl(classDef.symbol)
    if classDef.symbol.is(ModuleClass) then
      clazz.addModifiers(NSTModifiers.Synthetic)
    val clazzes = collection.mutable.ListBuffer[STClassDecl](clazz)
    if classDef.symbol.is(ModuleClass) && classDef.symbol.originalOwner.is(PackageClass) then
      addModuleInit(classDef, clazz)
      for mainClazz <- synthesizeMain(classDef) do
        clazzes += mainClazz
    val tpl = classDef.rhs.asInstanceOf[tpd.Template]
    val bodyAndConstructor =
      if classDef.symbol.is(Trait)
      then tpl.body
      else tpl.body :+ tpl.constr
    bodyAndConstructor.foreach:
      case dd: tpd.DefDef =>
        if shouldTranslate(dd) then
          clazz.addFunction(translate(dd, classDef.sourcePos))
      case vd: tpd.ValDef =>
        clazz.addField(toFieldDecl(vd.symbol))
      case x =>
        import dotty.tools.dotc.core.Decorators.i
        log(i"???: $x ${x.getClass}")
        ()
    // some of the call sites might be inside other lambda bodies, so we can't
    // just march down the list, lambdaTypes might not be full ahead of time.
    var lambdaKeys = Set[Symbol]()  // guard against accidental infinite loop (issue 478)
    while lambdaTypes.nonEmpty && lambdaKeys != lambdaTypes.keys do
      lambdaKeys = lambdaTypes.keys.toSet
      classDef.rhs.asInstanceOf[tpd.Template].body.foreach:
        case dd: tpd.DefDef if lambdaTypes.isDefinedAt(dd.symbol) =>
          if dd.symbol.is(Bridge) then
            import tpd._
            dd.rhs match
              case Block(List(Apply(sel @ Select(_, _), _)), _)
                  if lambdaTypes.isDefinedAt(dd.symbol) =>
                lambdaTypes(sel.symbol) = lambdaTypes(dd.symbol)
              case Apply(sel @ Select(_, _), _)
                  if lambdaTypes.isDefinedAt(dd.symbol) =>
                lambdaTypes(sel.symbol) = lambdaTypes(dd.symbol)
              case _ =>
                log(s"unrecognized lambda bridge at ${dd.sourcePos}: ${dd.rhs}")
          else
            clazzes += translateLambda(dd)
          lambdaTypes.remove(dd.symbol)
        case _ =>
    for key <- lambdaTypes.keys do
      log(s"couldn't find lambda for DefDef $key (at ${key.sourcePos})")
    clazzes.toList

  def translate(dd: tpd.DefDef, sourceInfo: SourceInfo): STFunDecl =
    trace(s"DefDef: ${dd.symbol}")
    resetLocalNames()
    val params = paramNames(dd)
    val fun = toFunDecl(dd.symbol, paramNames(dd), dd.paramss.flatten.map(_.name), dd.paramss.flatten.map(_.sourcePos), dd.symbol.info.paramInfoss.flatten)
    if !dd.symbol.is(Deferred) then
      // we have to pre-register the param symbols with uniquifyVariable
      // in order to prevent them from ever being renamed. (if there's a local
      // variable in the body with the same name we want to rename the
      // local, not the parameter.)
      for param <- dd.paramss.flatten.map(_.symbol) do
        uniquifyVariable(param)
      val context = TranslationContext(dd.symbol, new STBlock(sourceInfo))
      if fun.getReturnType == VoidType then
        translateStatement(dd.rhs, context)
      else
        val expr = translateExpression(dd.rhs, context)
        context.emit:
          val ret = new STReturnStmt(expr.getSourceInfo)
          ret.setExpression(expr)
          ret
      fun.setBody(context.body)
    end if
    fun

  /// top level stuff

  // kludge alert: using ListBuffer then calling distinct at the end
  // obviously isn't the optimal data structure.  if you change it
  // make sure to preserve order, so we get reproducible results,
  // which we want for end-to-end testing.  (we could use an ordered
  // set, or just sort on the way out.)
  val seenSymbols = collection.mutable.ListBuffer.empty[Symbol]

  def seen(symbol: Symbol): Unit =
    seenSymbols += (if symbol.is(JavaDefined) && symbol.is(ModuleClass)
                    then symbol.companionClass
                    else symbol)
    ()

  // when we see the lambda body, we need to know what the target type
  // was (e.g. scala.Function1), and how many of the params are actually
  // the "environment" (closed-over symbols), but we only have that information
  // at the definition site (as far as I know anyway?), so we record it when
  // we see it so we can use it later.  keys are DefDef symbols,
  // values are symbols of SAM types and the environment size
  val lambdaTypes = collection.mutable.Map.empty[Symbol, (Type, Int)]

  // includes everything we need to pass around whenever we're
  // recursing into trees:
  // * symbol is the symbol of the enclosing (Scala) DefDef
  // * body is the (NST) block we're currently adding statements to
  // * matchResult is the (NST) temporary variable used to store the result
  //   of a pattern match
  // * matchLabel is how we keep track of how to jump to the end
  //   of a pattern match
  case class TranslationContext(
      symbol: Symbol,
      body: STBlock,
      matchResult: Option[STVarAccess] = None,
      matchLabel: Symbol = NoSymbol,
  ):
    def emit(node: STNode): Unit =
      trace(s"emitting ${node.getClass.getSimpleName}: $node")
      body.add(node)

  def translateStatement(tree: Tree, context: TranslationContext): Unit =

    import tpd._
//    println(tree)
    tree match

      case EmptyTree =>
        trace("statement: empty tree (eliding)")

      case Literal(value) if value.tag == Constants.UnitTag =>
        trace("statement: literal Unit (eliding)")

      case tree @ ValDef(_, _, _) =>
        trace("statement: local val")
        val name = variableName(tree.symbol, uniquify = true)
        val decl = new STVarDecl(tree.sourcePos, name, typeForType(tree.symbol.info))
        context.emit(decl)
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(new STVarAccess(tree.sourcePos, name))
        assign.setRight(translateExpression(tree.rhs, context))
        context.emit(assign)

      case Assign(id @ Ident(_), rhs) if !id.symbol.owner.isClass =>
        trace("statement: assign to local val")
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(
          new STVarAccess(tree.sourcePos, variableName(id.symbol, uniquify = true)))
        assign.setRight(translateExpression(rhs, context))
        context.emit(assign)

      case Assign(sel @ Select(th @ This(id @ Ident(_)), _), rhs) =>
        trace("statement: assign to field (kind 1 of 3)")
        seen(sel.symbol)
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(
          new STFieldAccess(
            tree.sourcePos,
            translateExpression(th, context),
            variableName(sel.symbol),
            typeForType(th.tpe, ref = false)))
        assign.setRight(translateExpression(rhs, context))
        context.emit(assign)

      case Assign(sel @ Select(id @ Ident(_), _), rhs) =>
        trace("statement: assign to field (kind 2 of 3)")
        seen(sel.symbol)
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(
          new STFieldAccess(
            tree.sourcePos,
            translateExpression(id, context),
            variableName(sel.symbol),
            typeForType(id.tpe, ref = false)))
        assign.setRight(translateExpression(rhs, context))
        context.emit(assign)

      case Assign(id @ Ident(_), rhs) =>
        trace("statement: assign to field (kind 3 of 3)")
        seen(id.symbol)
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(
          new STFieldAccess(
            tree.sourcePos,
            new STVarAccess(id.sourcePos, "this~"),
            variableName(id.symbol),
            typeForType(id.symbol.owner.info, ref = false)))
        assign.setRight(translateExpression(rhs, context))
        context.emit(assign)

      case If(condTree, thenTree, EmptyTree) =>
        trace("statement: if (one-legged)")
        val statement = new STIfElse(tree.sourcePos)
        statement.setPredicate(translateExpression(condTree, context))
        val result = new STBlock(thenTree.sourcePos)
        translateStatement(thenTree, context.copy(body = result))
        statement.setIfBody(result)
        context.emit(statement)

      case If(condTree, thenTree, elseTree) =>
        trace("statement: if + else")
        val result = new STIfElse(tree.sourcePos)
        result.setPredicate(translateExpression(condTree, context))
        val thenBlock = new STBlock(thenTree.sourcePos)
        translateStatement(thenTree, context.copy(body = thenBlock))
        result.setIfBody(thenBlock)
        elseTree match
          case Literal(Constants.Constant(_: Unit)) =>
            // omit
          case _ =>
            val elseBlock = new STBlock(elseTree.sourcePos)
            translateStatement(elseTree, context.copy(body = elseBlock))
            result.setElseBody(elseBlock)
        context.emit(result)

      case WhileDo(condition, body) =>
        trace("statement: while")
        // condition can be empty tree in tail recursion encoding
        val generatedCondition =
          if condition == EmptyTree
          then STLiteralExp.create(tree.sourcePos, true) // :true:
          else translateExpression(condition, context)
        val result = new STWhileStmt(tree.sourcePos)
        result.setPredicate(generatedCondition)
        val whileBody = new STBlock(body.sourcePos)
        val innerContext = context.copy(body = whileBody)
        translateStatement(body, innerContext)
        result.setBody(whileBody)
        context.emit(result)

      // array update
      case Apply(select @ Select(qualifier, nme.primitive.arrayUpdate), List(index, rhs))
          if isArrayType(qualifier.tpe) =>
        trace("statement: array update")
        val loc = translateExpression(qualifier, context)
        val deref = new STDereference(qualifier.sourcePos, loc)
        val access = new STArrayAccess(tree.sourcePos)
        access.setBase(deref)
        access.setIndex(translateExpression(index, context))
        val result = new STAssignmentStmt(tree.sourcePos)
        result.setLeft(access)
        result.setRight(translateExpression(rhs, context))
        context.emit(result)

      case Apply(TypeApply(Select(obj, nme.synchronized_), _), List(body)) =>
        trace("statement: synchronized")
        context.emit:
          val call = new STFunCall(tree.sourcePos)
          call.setVirtual()
          call.setName("__synchronize")
          call.addArgument(translateExpression(obj, context))
          call
        context.emit:
          val block = new STBlock(body.sourcePos)
          block.setFoldable(false)
          block.setLabel(nextLabel("__synchronized____L__"))
          translateStatement(body, context.copy(body = block))
          block

      case Try(block, catches, finalizer) =>
        trace("statement: try")
        // Java translator has the label numbers in 1-2-0 order.
        // surely doesn't matter? might as well just match it, though
        val finallyLabel = nextLabel("__finally____L__")
        val tryLabel = nextLabel("__try____L__")
        context.emit:
          val body = new STBlock(block.sourcePos)
          body.setFoldable(false)
          body.setLabel(tryLabel)
          translateStatement(block, context.copy(body = body))
          body.add(new STGoto(block.sourcePos, finallyLabel))
          body
        def translateCatch(cd: CaseDef): STBlock =
          (cd: @unchecked) match
            case CaseDef(bind @ Bind(_, rhs), _, body) =>
              generateCatch(cd.sourcePos, variableName(bind.symbol, uniquify = true), rhs.tpe, body)
            case CaseDef(Typed(Ident(nme.WILDCARD), rhs), EmptyTree, body) =>
              generateCatch(cd.sourcePos, temporaries.next(), rhs.tpe, body)
            case CaseDef(Ident(nme.WILDCARD), EmptyTree, body) =>
              generateCatch(cd.sourcePos, temporaries.next(), defn.ThrowableType, body)
        def generateCatch(info: SourceInfo, name: String, tpe: Type, body: Tree): STBlock =
          val result = new STBlock(body.sourcePos)
          result.setLabel(nextLabel("__catch____L__"))
          result.setFoldable(false)
          result.add(new STVarDecl(info, name, typeForType(tpe)))
          translateStatement(body, context.copy(body = result))
          result.add(new STGoto(info, finallyLabel))
          result
        catches
          .map(translateCatch)
          .foreach(context.emit(_))
        context.emit:
          val finalBlock = new STBlock(
            if finalizer == EmptyTree
            then finalizer.sourcePos
            else tree.sourcePos)
          finalBlock.setFoldable(false)
          finalBlock.setLabel(finallyLabel)
          if finalizer != EmptyTree then
            translateStatement(finalizer, context.copy(body = finalBlock))
          finalBlock

      case Apply(Ident(nme.throw_), List(expr)) =>
        trace("statement: throw")
        context.emit(
          new STThrow(tree.sourcePos, translateExpression(expr, context)))

      case Labeled(bind @ Bind(name, _), expr) =>
        trace("statement: general pattern match")
        translateStatement(expr,
          context.copy(matchLabel = bind.symbol))
        context.emit:
          val block = new STBlock(tree.sourcePos)
          block.setFoldable(true)
          block.setLabel(bind.symbol.name.toString)
          block

      case Return(expr, target) if context.matchLabel eq target.symbol =>
        trace("statement: return from pattern match")
        context.matchResult match
          case Some(access) =>
            val assign = new STAssignmentStmt(expr.sourcePos)
            assign.setLeft(access)
            assign.setRight(translateExpression(expr, context))
            context.emit(assign)
          case None =>
            translateStatement(expr, context)
        context.emit(
          new STGoto(tree.sourcePos, context.matchLabel.name.toString))

      case Return(Literal(Constants.Constant(())), _)
          if context.symbol.info.resultType.typeSymbol == defn.UnitClass =>
        trace("statement: return unit")
        context.emit(new STReturnStmt(tree.sourcePos))

      case Return(expr, target) =>
        trace("statement: return")
        if expr.tpe.typeSymbol == defn.UnitClass then
          translateStatement(expr, context)
          context.emit(new STReturnStmt(tree.sourcePos))
        else
          val result = new STReturnStmt(tree.sourcePos)
          result.setExpression(translateExpression(expr, context))
          context.emit(result)

      case block @ Block(stats, expr) => // if !isPatternMatch(block) =>
        trace("statement: block")
        (stats :+ expr).foreach(translateStatement(_, context))

      case Match(expr, cases) if cases.nonEmpty =>
        trace("statement: switch-style pattern match")
        val (inputAccess, inputDecl) =
          useTemporary(typeForType(expr.tpe), expr.sourcePos)
        context.emit(inputDecl)
        val assign = new STAssignmentStmt(expr.sourcePos)
        assign.setLeft(inputAccess)
        assign.setRight(translateExpression(expr, context))
        context.emit(assign)
        def ifElseChain(cases: List[Tree], context: TranslationContext): Unit =
          cases match
            case List(CaseDef(Ident(nme.WILDCARD), EmptyTree, rhs)) =>
              translateStatement(rhs, context)
            case List(firstCase, moreCases @ _*) =>
              val CaseDef(lhs, EmptyTree, rhs) = cases.head: @unchecked
              val alternatives: List[Tree] =
                lhs match
                  case Alternative(alternatives) =>
                    alternatives
                  case expr =>
                    List(expr)
              val ifelse = new STIfElse(firstCase.sourcePos)
              val compare =
                combineAlternatives(alternatives, inputAccess, context)
              ifelse.setPredicate(compare)
              val thenBlock = new STBlock(rhs.sourcePos)
              translateStatement(rhs, context.copy(body = thenBlock))
              ifelse.setIfBody(thenBlock)
              val elseBlock = new STBlock(tree.sourcePos)
              ifElseChain(cases.tail, context.copy(body = elseBlock))
              ifelse.setElseBody(elseBlock)
              context.emit(ifelse)
            case _ =>
              context.emit(unknown(context, tree))
        ifElseChain(cases, context)

      case _ =>
        trace("statement: treating as expression")
        val expr = translateExpression(tree, context)
        // drop dangling references to (usually) temporaries
        if !expr.isInstanceOf[STVarAccess] then
          context.emit(expr)

  // helper for switch-style pattern matches
  private def combineAlternatives(
      alternatives: List[Tree],
      access: STVarAccess,
      context: TranslationContext): STExpression =
    alternatives
      .map: alt =>
        new STOpExp(alt.sourcePos, NSTOperators.Equal, access, translateExpression(alt, context))
      .reduceLeft: (sofar, next) =>
        new STOpExp(sofar.getSourceInfo, NSTOperators.Or, sofar, next)

  def translateExpression(tree: Tree, context: TranslationContext): STExpression =
    import tpd._
//    println(tree)
    val result = tree match

      case Apply(select @ Select(qualifier, nme.And), List(arg))
          if isBooleanType(qualifier.tpe) =>
        trace("expression: &&")
        val (access, decl) =
          useTemporary(typeForType(qualifier.tpe), qualifier.sourcePos)
        context.emit(decl)
        context.emit:
          val statement = new STIfElse(tree.sourcePos)
          statement.setPredicate(translateExpression(qualifier, context))
          statement.setIfBody {
            val block = new STBlock(arg.sourcePos)
            val assign = new STAssignmentStmt(arg.sourcePos)
            assign.setLeft(access)
            assign.setRight(
              translateExpression(arg, context.copy(body = block)))
            block.add(assign)
            block
          }
          statement.setElseBody {
            val block = new STBlock(qualifier.sourcePos)
            val assign = new STAssignmentStmt(qualifier.sourcePos)
            assign.setLeft(access)
            assign.setRight(STLiteralExp.create(qualifier.sourcePos, false))
            block.add(assign)
            block
          }
          statement
        access

      case Apply(select @ Select(qualifier, nme.Or), List(arg))
          if isBooleanType(qualifier.tpe) =>
        trace("expression: ||")
        val (access, decl) =
          useTemporary(typeForType(qualifier.tpe), qualifier.sourcePos)
        context.emit(decl)
        context.emit:
          val statement = new STIfElse(tree.sourcePos)
          statement.setPredicate(translateExpression(qualifier, context))
          statement.setIfBody:
            val block = new STBlock(qualifier.sourcePos)
            val assign = new STAssignmentStmt(qualifier.sourcePos)
            assign.setLeft(access)
            assign.setRight(STLiteralExp.create(qualifier.sourcePos, true))
            block.add(assign)
            block
          statement.setElseBody:
            val block = new STBlock(arg.sourcePos)
            val assign = new STAssignmentStmt(arg.sourcePos)
            assign.setLeft(access)
            assign.setRight(
              translateExpression(arg, context.copy(body = block)))
            block.add(assign)
            block
          statement
        access

      // if + else
      case If(condTree, thenTree, elseTree) =>
        trace("expression: if + else")
        val statement = new STIfElse(tree.sourcePos)
        statement.setPredicate(translateExpression(condTree, context))
        val (access, decl) = useTemporary(typeForType(tree.tpe), tree.sourcePos)
        context.emit(decl)
        def subBody(subtree: Tree): STBlock =
          val result = new STBlock(subtree.sourcePos)
          val assign = new STAssignmentStmt(subtree.sourcePos)
          assign.setLeft(access)
          assign.setRight(
            translateExpression(subtree, context.copy(body = result)))
          result.add(assign)
          result
        statement.setIfBody(subBody(thenTree))
        statement.setElseBody(subBody(elseTree))
        context.emit(statement)
        access

      case WhileDo(condition, body) =>
        // note: occurs in expression position (with empty condition)
        // in lazy val encoding
        trace("expression: while")
        val result = new STWhileStmt(tree.sourcePos)
        result.setPredicate(
          condition match
            case EmptyTree =>
              STLiteralExp.create(tree.sourcePos, true) // :true:
            case _ =>
              translateExpression(condition, context)
        )
        val whileBody = new STBlock(body.sourcePos)
        val innerContext = context.copy(body = whileBody)
        translateStatement(body, innerContext)
        result.setBody(whileBody)
        context.emit(result)
        STLiteralExp.create(tree.sourcePos) // :null:

      case Select(qualifier, selector) if tree.symbol.is(JavaStatic) =>
        trace("expression: static field access")
        seen(tree.symbol)
        new STStaticFieldAccess(
          tree.sourcePos,
          selector.toString,
          typeForSymbol(qualifier.tpe.typeSymbol.companionClass, ref = false))

      case Labeled(bind @ Bind(name, _), expr) =>
        trace("expression: general pattern match")
        val (access, decl) = useTemporary(typeForType(tree.tpe), tree.sourcePos)
        context.emit(decl)
        val block = new STBlock(tree.sourcePos)
        val blockContext = context.copy(
          body = block,
          matchResult = Some(access),
          matchLabel = bind.symbol)
        translateStatement(expr, blockContext)
        context.emit(block)
        context.emit:
          val block = new STBlock(tree.sourcePos)
          block.setFoldable(true)
          block.setLabel(blockContext.matchLabel.name.toString)
          block
        access

      case Return(Literal(Constants.Constant(())), target) if context.matchLabel eq target.symbol =>
        trace("expression: return (jump) to label, no value")
        context.emit(
          new STGoto(tree.sourcePos, context.matchLabel.name.toString))
        STLiteralExp.create(tree.sourcePos) // :null:

      case Return(expr, target) if context.matchLabel eq target.symbol =>
        trace("expression: return value from pattern match")
        val assign = new STAssignmentStmt(expr.sourcePos)
        if context.matchResult.isDefined then
          assign.setLeft(context.matchResult.get)
          assign.setRight(translateExpression(expr, context))
          context.emit(assign)
          context.emit(
            new STGoto(tree.sourcePos, context.matchLabel.name.toString))
        STLiteralExp.create(tree.sourcePos) // :null:

      case Return(expr, target) =>
        trace("expression: return")
        translateStatement(tree, context)
        STLiteralExp.create(tree.sourcePos) // :null:

      // lambda
      case Closure(env, fun @ Select(This(_), _), functionalInterface) =>
        trace("expression: lambda")
        lambdaTypes(fun.symbol) = (tree.tpe, env.size)
        val lamName =
          s"""${className(context.symbol.owner)}${fun.symbol.name}"""
            .replaceFirst("\\$adapted(\\$\\w+)$", "$1")
        val lamType = new STType.STClassType(tree.sourcePos, lamName)
        val lamRefType = new STType.STPointerType(tree.sourcePos, lamType)
        val (access, decl) = useTemporary(lamRefType, tree.sourcePos)
        context.emit(decl)
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(access)
        assign.setRight:
          val rhs = new STAllocation(tree.sourcePos)
          rhs.setType(lamType)
          rhs
        context.emit(assign)
        context.emit:
          val call = new STFunCall(tree.sourcePos)
          call.setName(
            s"$lamName~~innerinit^~L$lamName^" +
              fun.symbol.info.paramInfoss.flatten.take(env.size)
                .map(typeString)
                .map(unDollar)
                .mkString
          )
          call.addArgument(access)
          call.addArgument(new STVarAccess(tree.sourcePos, "this~"))
          // pass in the values we closed over
          for arg <- env do
            call.addArgument(translateExpression(arg, context))
          call.setVirtual()
          call
        access

      case This(_) if tree.tpe.typeSymbol == context.symbol.owner =>
        trace("expression: this")
        new STVarAccess(tree.sourcePos, "this~")

      case Select(_, selector) if tree.symbol.is(Module) =>
        trace("expression: module access (kind 1)")
        seen(tree.symbol)
        new STStaticFieldAccess(
          tree.sourcePos,
          "MODULE$",
          new STType.STClassType(tree.sourcePos, className(tree.symbol) + "$"))

      case This(_) if tree.symbol.is(ModuleClass) =>
        trace("expression: module access (kind 2)")
        seen(tree.symbol)
         new STStaticFieldAccess(
           tree.sourcePos,
           "MODULE$",
           new STType.STClassType(tree.sourcePos, className(tree.symbol)))

      case Ident(_) if tree.symbol.is(Module) =>
        trace("expression: module access (kind 3)")
        seen(tree.symbol)
        new STStaticFieldAccess(
          tree.sourcePos,
          "MODULE$",
          new STType.STClassType(tree.sourcePos, className(tree.symbol) + "$"))

      case Select(sel @ This(id @ Ident(_)), _) =>
        trace("expression: field access (kind 1)")
        seen(id.symbol)
        val result = new STFieldAccess(
          tree.sourcePos,
          new STDereference(id.sourcePos, translateExpression(sel, context)),
          variableName(tree.symbol),
          typeForType(sel.tpe, ref = false))
        result

      case Select(qualifier, _) =>
        trace("expression: field access (kind 2)")
        seen(qualifier.symbol)
        val result = new STFieldAccess(
          tree.sourcePos,
          new STDereference(qualifier.sourcePos, translateExpression(qualifier, context)),
          variableName(tree.symbol),
          typeForType(qualifier.tpe, ref = false))
        result

      case Ident(_) if tree.symbol.owner.isClass =>
        trace("expression: field access (kind 3)")
        seen(tree.symbol)
        val result = new STFieldAccess(
          tree.sourcePos,
          new STVarAccess(tree.sourcePos, "this~"),
          variableName(tree.symbol),
          typeForType(tree.symbol.owner.info, ref = false))
        result

      // super call
      case Apply(sel @ Select(sup @ Super(_, _), _), args) =>
        trace("expression: super call")
        val call = new STFunCall(tree.sourcePos)
        call.setVirtual()
        call.setName(methodName(sel.symbol))
        call.addArgument(new STVarAccess(tree.sourcePos, "this~"))
        for arg <- args do
          call.addArgument(translateExpression(arg, context))
        call

      // synchronized
      case Apply(TypeApply(Select(obj, nme.synchronized_), _), List(body)) =>
        trace("expression: synchronized")
        val (access, decl) = useTemporary(typeForType(tree.tpe), tree.sourcePos)
        context.emit(decl)
        context.emit:
          val call = new STFunCall(tree.sourcePos)
          call.setVirtual()
          call.setName("__synchronize")
          call.addArgument(translateExpression(obj, context))
          call
        context.emit:
          val block = new STBlock(body.sourcePos)
          block.setFoldable(false)
          block.setLabel(nextLabel("__synchronized____L__"))
          block.add {
            val assign = new STAssignmentStmt(tree.sourcePos)
            assign.setLeft(access)
            assign.setRight(
              translateExpression(body, context.copy(body = block)))
            assign
          }
          block
        access

      case jsl: JavaSeqLiteral =>
        trace("expression: array creation (kind 1)")
        val JavaArrayType(eltType) = jsl.tpe: @unchecked
        translateArrayCreation(context, tree.sourcePos, jsl.elems, jsl.tpe, eltType)

      case Apply(Select(Ident(Arrays), NewArrayMethod), args :+ (jsl: JavaSeqLiteral)) =>
        trace("expression: array creation (kind 2)")
        // `Array.ofDim` calls are rewritten to this by the arrayConstructors transform
        val call = new STFunCall(tree.sourcePos)
        call.setVirtual()
        call.setName(methodName(defn.newArrayMethod))
        seen(defn.newArrayMethod)
        for arg <- args do
          call.addArgument(translateExpression(arg, context))
        // `newArray` wants an `Array` of dimensions
        val JavaArrayType(eltType) = jsl.tpe: @unchecked
        call.addArgument(
          translateArrayCreation(context, jsl.sourcePos,jsl.elems, jsl.tpe, eltType))
        call

      case TypeApply(Select(obj, nme.asInstanceOf_), List(typeArg)) =>
        trace("expression: asInstanceOf")
        new STTypeCast(tree.sourcePos, typeForType(typeArg.tpe), translateExpression(obj, context))

      case TypeApply(Select(obj, nme.isInstanceOf_), List(typeArg)) =>
        trace("expression: isInstanceOf")
        val call = new STFunCall(tree.sourcePos)
        call.setVirtual()
        call.setName("__instanceof")
        call.addArgument(translateExpression(obj, context))
        call.addArgument(
          new STTypeCast(obj.sourcePos, typeForType(typeArg.tpe), STLiteralExp.create(tree.sourcePos))
        ) // :null:
        call

      case Ident(nme.UNIT) =>
        trace("expression: literal Unit")
        new STStaticFieldAccess(tree.sourcePos, "UNIT", typeForType(tree.tpe, ref = false))

      case Ident(_) =>
        trace("expression: access local variable")
        new STVarAccess(tree.sourcePos, variableName(tree.symbol, uniquify = true))

      case Typed(expr, _) =>
        trace("expression: Typed")
        // not completely sure we don't sometimes need to insert a
        // typecast here -- something to keep an eye out for
        translateExpression(expr, context)

      /// operators

      case Apply(select @ Select(qualifier, nme.PLUS), List(arg))
          if qualifier.tpe <:< defn.StringType =>
        trace("expression: string concatenation")
        new STOpExp(
          tree.sourcePos,
          NSTOperators.Add,
          translateExpression(qualifier, context),
          if arg.tpe <:< defn.StringType
          then translateExpression(arg, context)
          else
            val call = new STFunCall(arg.sourcePos)
            call.setName(methodName(defn.Any_toString))
            call.addArgument(translateExpression(arg, context))
            call
        )

      case Apply(select @ Select(qualifier, name), List(arg))
          if arithmeticOps.isDefinedAt(name) && (isNumericType(qualifier.tpe) || isBooleanType(
            qualifier.tpe)) =>
        trace("expression: binary operator")  // numeric or boolean
        new STOpExp(
          tree.sourcePos,
          arithmeticOps(name),
          translateExpression(qualifier, context),
          translateExpression(arg, context))

      case Apply(Select(lhs, nme.Equals | NmeEq), List(Literal(Constants.Constant(null)))) =>
        trace("expression: equal, null on right")
        new STOpExp(
          tree.sourcePos,
          NSTOperators.Equal,
          translateExpression(lhs, context),
          STLiteralExp.create(tree.sourcePos)
        ) // :null:

      case Apply(Select(lhs, nme.NotEquals | NmeNe), List(Literal(Constants.Constant(null)))) =>
        trace("expression: not equal, null on right")
        new STOpExp(
          tree.sourcePos,
          NSTOperators.NotEqual,
          translateExpression(lhs, context),
          STLiteralExp.create(tree.sourcePos)
        ) // :null:

      case Apply(Select(Apply(Select(lhs, nme.Equals | NmeEq), List(Literal(Constants.Constant(null)))), nme.UNARY_!),List()) =>
        trace("expression: equal, null on right")
        // for some reason in Scala 3, unlike 2, != has been rewritten to ! plus ==
        // by the time we see it. regardless I've left the previous case in
        // just in case it doesn't always happen that way?
        new STOpExp(
          tree.sourcePos,
          NSTOperators.NotEqual,
          translateExpression(lhs, context),
          STLiteralExp.create(tree.sourcePos)
        ) // :null:

      case Apply(Select(Literal(Constants.Constant(null)), nme.Equals | NmeEq), List(rhs)) =>
        trace("expression: equal, null on left")
        new STOpExp(
          tree.sourcePos,
          NSTOperators.Equal,
          STLiteralExp.create(tree.sourcePos), // :null:
          translateExpression(rhs, context))

      case Apply(Select(Literal(Constants.Constant(null)), nme.NotEquals | NmeNe), List(rhs)) =>
        trace("expression: not equal, null on left")
        new STOpExp(
          tree.sourcePos,
          NSTOperators.NotEqual,
          STLiteralExp.create(tree.sourcePos), // :null:
          translateExpression(rhs, context))

      case Apply(Select(Apply(Select(Literal(Constants.Constant(null)), nme.Equals | NmeEq),List(rhs)), nme.UNARY_!), List()) =>
        trace("expression: equal")
        // for some reason in Scala 3, unlike 2, != has been rewritten to ! plus ==
        // by the time we see it. regardless I've left the previous case in
        // just in case it doesn't always happen that way?
        new STOpExp(
          tree.sourcePos,
          NSTOperators.NotEqual,
          STLiteralExp.create(tree.sourcePos), // :null:
          translateExpression(rhs, context))

      case Apply(sel @ Select(lhs @ Literal(Constants.Constant(_: String)), nme.Equals | nme.NotEquals), List(rhs)) =>
        trace("expression: reference equal (or not), string literal on left")
        val call = new STFunCall(tree.sourcePos)
        call.setVirtual()
        call.setName(methodName(defn.Any_equals))
        seen(defn.Any_equals)
        call.addArgument(translateExpression(lhs, context))
        call.addArgument(translateExpression(rhs, context))
        if sel.name == nme.Equals
        then call
        else new STOpExp(tree.sourcePos, NSTOperators.Not, call)

      case Apply(select @ Select(lhs, nme.Equals), List(rhs)) =>
        trace("expression: reference equal")
        translateEqOp(context, tree.sourcePos, tree.tpe, select, lhs, rhs, negate = false)

      case Apply(select @ Select(lhs, nme.NotEquals), List(rhs)) =>
        trace("expression: reference not equal")
        translateEqOp(context, tree.sourcePos, tree.tpe, select, lhs, rhs, negate = true)

      case Apply(select @ Select(qualifier, name), List(arg))
          if referenceOps.isDefinedAt(name) && isReferenceType(qualifier.tpe) =>
        trace("expression: reference operator")
        new STOpExp(
          tree.sourcePos,
          referenceOps(name),
          translateExpression(qualifier, context),
          translateExpression(arg, context))

      case Apply(select @ Select(qualifier, nme.UNARY_!), List()) if isBooleanType(qualifier.tpe) =>
        trace("expression: not")
        new STOpExp(tree.sourcePos, NSTOperators.Not, translateExpression(qualifier, context))

      case Apply(select @ Select(qualifier, nme.primitive.arrayLength), List())
          if isArrayType(qualifier.tpe) =>
        trace("expression: array length")
        new STOpExp(tree.sourcePos, NSTOperators.Arraylen, translateExpression(qualifier, context))

      case Apply(select @ Select(qualifier, nme.primitive.arrayApply), List(arg))
          if isArrayType(qualifier.tpe) =>
        trace("expression: array access")
        val loc = translateExpression(qualifier, context)
        val deref = new STDereference(qualifier.sourcePos, loc)
        val result = new STArrayAccess(tree.sourcePos)
        result.setBase(deref)
        result.setIndex(translateExpression(arg, context))
        result

      case Apply(sel @ Select(n @ New(tpe), nme.CONSTRUCTOR), args) =>
        trace("expression: constructor call")
        seen(sel.symbol)
        val (access, decl) = useTemporary(typeForType(tpe.tpe), n.sourcePos)
        context.emit(decl)
        val assign = new STAssignmentStmt(tree.sourcePos)
        assign.setLeft(access)
        assign.setRight:
          val rhs = new STAllocation(tree.sourcePos)
          rhs.setType(typeForSymbol(tpe.symbol, ref = false))
          rhs
        context.emit(assign)
        context.emit:
          val call = new STFunCall(tree.sourcePos)
          call.setName(methodName(sel.symbol))
          call.addArgument(access)
          for arg <- args do
            call.addArgument(translateExpression(arg, context))
          call
        access

      /// literals

      case Literal(Constants.Constant(plugin.Constants.KnownUnknown)) =>
        // for testing handling of unknown trees
        trace("expression: special testing constant")
        unknown(context, tree, Some("additional infos for testing"))

      case Literal(Constants.Constant(s: String)) =>
        trace("expression: constant String")
        STLiteralExp.create(tree.sourcePos, s)

      case Literal(Constants.Constant(i: Int)) =>
        trace("expression: constant Int")
        STLiteralExp.create(tree.sourcePos, i)

      case Literal(Constants.Constant(l: Long)) =>
        trace("expression: constant Long")
        STLiteralExp.create(tree.sourcePos, l)

      case Literal(Constants.Constant(c: Char)) =>
        trace("expression: constant Char")
        STLiteralExp.create(tree.sourcePos, c)

      case Literal(Constants.Constant(b: Boolean)) =>
        trace("expression: constant Boolean")
        STLiteralExp.create(tree.sourcePos, b)

      case Literal(Constants.Constant(d: Double)) =>
        trace("expression: constant Double")
        STLiteralExp.create(tree.sourcePos, d)

      case Literal(Constants.Constant(f: Float)) =>
        trace("expression: constant Float")
        STLiteralExp.create(tree.sourcePos, f.toDouble)

      case Literal(Constants.Constant(s: Short)) =>
        trace("expression: constant Short")
        new STTypeCast(tree.sourcePos, typeForType(tree.tpe), STLiteralExp.create(tree.sourcePos, s.toInt))

      case Literal(Constants.Constant(b: Byte)) =>
        trace("expression: constant Byte")
        new STTypeCast(tree.sourcePos, typeForType(tree.tpe), STLiteralExp.create(tree.sourcePos, b.toInt))

      case Literal(Constants.Constant(())) =>
        trace("expression: literal Unit")
        new STStaticFieldAccess(tree.sourcePos, "UNIT", typeForType(tree.tpe, ref = true))

      case Literal(Constants.Constant(null)) =>
        trace("expression: literal null")
        STLiteralExp.create(tree.sourcePos) // :null:

      case Literal(Constants.Constant(TypeRef(_, symbol: Symbol))) =>
        trace("expression: class literal (TypeRef)")
        translateClassLiteral(tree.sourcePos, symbol)

      case Literal(Constants.Constant(ClassInfo(_, symbol, _, _, _))) =>
        trace("expression: class literal (ClassInfo)")
        translateClassLiteral(tree.sourcePos, symbol)

      case Literal(Constants.Constant(JavaArrayType(elemType))) =>
        trace("expression: class literal (JavaArrayType)")
        translateClassLiteral(tree.sourcePos, elemType.typeSymbol)

      // unknown literal
      case Literal(Constants.Constant(x)) =>
        trace("expression: unknown literal")
        unknown(context, tree, Some(s"unknown literal type: ${x.getClass}"))

      // try (expression position)
      case Try(block, catches, finalizer) =>
        val (access, decl) = useTemporary(typeForType(block.tpe), block.sourcePos)
        context.emit(decl)
        // Java translator has the label numbers in 1-2-0 order.
        // surely doesn't matter? might as well just match it, though
        val finallyLabel = nextLabel("__finally____L__")
        val tryLabel = nextLabel("__try____L__")
        context.emit:
          val body = new STBlock(block.sourcePos)
          body.setFoldable(false)
          body.setLabel(tryLabel)
          val assign = new STAssignmentStmt(block.sourcePos)
          assign.setLeft(access)
          assign.setRight(
            translateExpression(block, context.copy(body = body)))
          body.add(assign)
          body.add(new STGoto(block.sourcePos, finallyLabel))
          body
        def translateCatch(cd: CaseDef): STBlock =
          (cd: @unchecked) match
            case CaseDef(bind @ Bind(_, rhs), _, body) =>
              generateCatch(cd.sourcePos, variableName(bind.symbol, uniquify = true), rhs.tpe, body)
            case CaseDef(Typed(Ident(nme.WILDCARD), rhs), EmptyTree, body) =>
              generateCatch(cd.sourcePos, temporaries.next(), rhs.tpe, body)
            case CaseDef(Ident(nme.WILDCARD), EmptyTree, body) =>
              generateCatch(cd.sourcePos, temporaries.next(), defn.ThrowableType, body)
        def generateCatch(info: SourceInfo, name: String, tpe: Type, body: Tree): STBlock =
          val result = new STBlock(body.sourcePos)
          result.setLabel(nextLabel("__catch____L__"))
          result.setFoldable(false)
          result.add(new STVarDecl(info, name, typeForType(tpe)))
          val assign = new STAssignmentStmt(block.sourcePos)
          assign.setLeft(access)
          assign.setRight(
            translateExpression(body, context.copy(body = result)))
          result.add(assign)
          result.add(new STGoto(info, finallyLabel))
          result
        catches
          .map(translateCatch)
          .foreach(context.emit(_))
        context.emit:
          val finalBlock = new STBlock(
            if finalizer == EmptyTree
            then finalizer.sourcePos
            else tree.sourcePos)
          finalBlock.setFoldable(false)
          finalBlock.setLabel(finallyLabel)
          if finalizer != EmptyTree then
            translateStatement(finalizer, context.copy(body = finalBlock))
          finalBlock
        access

      case Apply(Ident(nme.throw_), List(expr)) =>
        trace("expression: throw")
        context.emit(
          new STThrow(tree.sourcePos, translateExpression(expr, context)))
        STLiteralExp.create(tree.sourcePos) // :null:

      case Apply(select @ Select(qualifier, _), args) =>
        trace("expression: method call (Select)")
        seen(select.symbol)
        val call = new STFunCall(tree.sourcePos)
        call.setVirtual()
        call.setName(methodName(select.symbol))
        if qualifier.symbol.exists then
          seen(qualifier.symbol)
        if !select.symbol.is(JavaDefined) || select.symbol.isConstructor || !select.symbol.isStatic || ctx.platform.isMainMethod(select.symbol) then
          call.addArgument(translateExpression(qualifier, context))
        for arg <- args do
          call.addArgument(translateExpression(arg, context))
        call

      case Apply(ident @ Ident(_), args) =>
        // (Scala 3 specific)
        trace("expression: method call (Ident)")
        val select @ Select(qualifier, _) = tpd.desugarIdent(ident): @unchecked
        seen(select.symbol)
        val call = new STFunCall(tree.sourcePos)
        call.setVirtual()
        call.setName(methodName(ident.symbol))
        if qualifier.symbol.exists then
          seen(qualifier.symbol)
        call.addArgument(translateExpression(qualifier, context))
        for arg <- args do
          call.addArgument(translateExpression(arg, context))
        call

      case block @ Block(stats, expr) =>
        trace("expression: block")
        stats.foreach(translateStatement(_, context))
        translateExpression(expr, context)

      case Match(expr, cases) if cases.nonEmpty =>
        trace("expression: switch-style pattern match")
        val (inputAccess, inputDecl) =
          useTemporary(typeForType(expr.tpe), expr.sourcePos)
        val (resultAccess, resultDecl) =
          useTemporary(typeForType(tree.tpe), tree.sourcePos)
        context.emit(inputDecl)
        val assign = new STAssignmentStmt(expr.sourcePos)
        assign.setLeft(inputAccess)
        assign.setRight(translateExpression(expr, context))
        context.emit(assign)
        context.emit(resultDecl)

        val newContext = context.copy(
          matchLabel = cases
            .collectFirst:
              case CaseDef(Ident(nme.WILDCARD), EmptyTree, body @ Labeled(_, _)) =>
                body.symbol
            .getOrElse(NoSymbol))

        def ifElseChain(cases: List[Tree]): STNode =
          cases match
            case List(
                  lastCase @ CaseDef(
                    Ident(nme.WILDCARD),
                    EmptyTree,
                    Labeled(Bind(_, _), rhs))) =>
              val block = new STBlock(lastCase.sourcePos)
              block.setFoldable(true)
              block.setLabel(newContext.matchLabel.name.toString)
              val assign = new STAssignmentStmt(lastCase.sourcePos)
              assign.setLeft(resultAccess)
              assign.setRight(
                translateExpression(rhs, newContext.copy(body = block)))
              block.add(assign)
              block
            case List(CaseDef(Ident(nme.WILDCARD), EmptyTree, rhs)) =>
              val assign = new STAssignmentStmt(cases.head.sourcePos)
              assign.setLeft(resultAccess)
              assign.setRight(translateExpression(rhs, newContext))
              assign
            case List(firstCase, moreCases @ _*) =>
              val CaseDef(lhs, EmptyTree, rhs) = cases.head: @unchecked
              val alternatives: List[Tree] =
                lhs match
                  case Alternative(alternatives) =>
                    alternatives
                  case expr =>
                    List(expr)
              val ifelse = new STIfElse(firstCase.sourcePos)
              val compare =
                combineAlternatives(alternatives, inputAccess, newContext)
              ifelse.setPredicate(compare)
              val thenBlock = new STBlock(rhs.sourcePos)
              val assign = new STAssignmentStmt(rhs.sourcePos)
              assign.setLeft(resultAccess)
              assign.setRight(
                translateExpression(rhs, newContext.copy(body = thenBlock)))
              thenBlock.add(assign)
              ifelse.setIfBody(thenBlock)
              val elseBlock = new STBlock(tree.sourcePos)
              elseBlock.add(ifElseChain(cases.tail))
              ifelse.setElseBody(elseBlock)
              ifelse
            case _ =>
              unknown(context, tree)
        context.emit(ifElseChain(cases))
        resultAccess

      case x =>
        trace("expression: unknown")
        unknown(context, x)

    // println(s"$tree => \n  $result")
    result

  end translateExpression

  /// lambdas

  def translateLambda(dd: tpd.DefDef): STClassDecl =
    trace(s"lambda: ${dd.symbol}")
    val name = s"""${className(dd.symbol.owner)}${dd.symbol.name}"""
    val (lambdaType, envSize) = lambdaTypes(dd.symbol)
    val (closedOver, params) = paramNames(dd).splitAt(envSize)
    val clazz =
      val result = new STClassDecl(dd.symbol.sourcePos)
      result.addModifiers(NSTModifiers.Private, NSTModifiers.Final)
      result.setSimpleName(s"lambda [${typeForType(lambdaType, ref = false)}]")
      result.setName(name)
      result.addExtends(typeForType(lambdaType, ref = false))
      result.addField:
        val field = new STFieldDecl(dd.sourcePos)
        field.setName("outer~this")
        field.setType(typeForType(dd.symbol.owner.info))
        field
      for param <- closedOver do
        result.addField:
          val field = new STFieldDecl(dd.sourcePos)
          field.setName(variableName(param))
          field.setType(typeForType(param.info))
          field
      result

    // lambda body
    clazz.addFunction:
      val result = translate(dd, dd.sourcePos)
      // we're calling addFront repeatedly here, so we have to add
      // things in reverse order of how we want them to appear
      for param <- closedOver.reverse do
        // expected:   ~t~x@1 :=: [LambdaVar$anonfun$incrementer$1] (:*: lambda~this) :.: ~t~x@1;
        result.addFront:
          val assign = new STAssignmentStmt(dd.sourcePos)
          assign.setLeft(new STVarAccess(dd.sourcePos, variableName(param)))
          assign.setRight(
            new STFieldAccess(
              dd.sourcePos,
              new STVarAccess(dd.sourcePos, "lambda~this"),
              variableName(param),
              new STType.STClassType(dd.sourcePos, clazz.getName)))
          assign
        result.addFront(
          new STVarDecl(dd.sourcePos, variableName(param), typeForType(param.info)))
      result.addFront:
        val assign = new STAssignmentStmt(dd.sourcePos)
        assign.setLeft(new STVarAccess(dd.sourcePos, "this~"))
        assign.setRight(
          new STFieldAccess(
            dd.sourcePos,
            new STVarAccess(dd.sourcePos, "lambda~this"),
            "outer~this",
            new STType.STClassType(dd.sourcePos, clazz.getName)))
        assign
      result.addFront(
        new STVarDecl(dd.sourcePos, "this~", typeForType(dd.symbol.owner.info)))
      // drop closed-over parameters, leave real parameters
      while result.getArglist.size > params.size do
        result.popArg()
      result.pushArg(
        new STVarDecl(
          dd.sourcePos,
          "lambda~this",
          new STType.STPointerType(
            dd.sourcePos,
            new STType.STClassType(dd.sourcePos, clazz.getName))))
      val (samType, _) = lambdaTypes(dd.symbol)
      val samMethod =
        val Seq(samMethodDenot) = samType.possibleSamMethods
        samMethodDenot.symbol
      result.setName(
        s"$name~~${unDollar(samMethod.name.toString)}~L$name^${params.map(p => typeString(p.info)).map(unDollar).mkString}")
      result.setSimpleName(toSimpleName(samMethod))
      result.addOverride(methodName(samMethod))
      result
    // constructor
    clazz.addFunction:
      val result = new STFunDecl(dd.sourcePos)
      result.setReturnType(VoidType)
      result.setModifiers(NSTModifiers.Public)
      result.setSimpleName("innerinit^")
      result.addParameter(
        new STVarDecl(
          dd.sourcePos,
          "this~",
          new STType.STPointerType(
            dd.sourcePos,
            new STType.STClassType(dd.sourcePos, clazz.getName))))
      result.addParameter(
        new STVarDecl(dd.sourcePos, "outer~this", typeForType(dd.symbol.owner.info)))
      result.setName:
        val argTypes =
          closedOver
            .map(_.info)
            .map(typeString)
            .map(unDollar)
            .mkString
        s"${clazz.getName}~~innerinit^~L${clazz.getName}^$argTypes"
      for param <- closedOver do
        result.addParameter(
          new STVarDecl(dd.sourcePos, variableName(param), typeForType(param.info)))
      result.setBody:
        val body = new STBlock(dd.sourcePos)
        body.add:
          val assign = new STAssignmentStmt(dd.sourcePos)
          assign.setLeft(
            new STFieldAccess(
              dd.sourcePos,
              new STVarAccess(dd.sourcePos, "this~"),
              "outer~this",
              new STType.STClassType(dd.sourcePos, clazz.getName)))
          assign.setRight(new STVarAccess(dd.sourcePos, "outer~this"))
          assign
        for param <- closedOver do
          body.add:
            val assign = new STAssignmentStmt(dd.sourcePos)
            assign.setLeft(
              new STFieldAccess(
                dd.sourcePos,
                new STVarAccess(dd.sourcePos, "this~"),
                variableName(param),
                new STType.STClassType(dd.sourcePos, clazz.getName)))
            assign.setRight(new STVarAccess(dd.sourcePos, variableName(param)))
            assign
        body
      result
    clazz

  /// array creation

  def translateArrayCreation(
      context: TranslationContext,
      pos: SourcePosition,
      exprs: Seq[Tree],
      resultType: Type,
      eltType: Type): STExpression =
    val (access, decl) = useTemporary(typeForType(resultType), pos)
    context.emit(decl)
    context.emit:
      val assign = new STAssignmentStmt(pos)
      assign.setLeft(access)
      assign.setRight:
        val rhs = new STAllocation(pos)
        rhs.addIndex(STLiteralExp.create(pos, exprs.size))
        rhs.setType(new STType.STArrayType(pos, typeForType(eltType, false)))
        rhs
      assign
    for (expr, index) <- exprs.zipWithIndex do
      val assign = new STAssignmentStmt(pos)
      val itemAccess = new STArrayAccess(pos)
      itemAccess.setBase(new STDereference(pos, access))
      itemAccess.setIndex(STLiteralExp.create(pos, index))
      assign.setLeft(itemAccess)
      assign.setRight(translateExpression(expr, context))
      context.emit(assign)
    access

  /// class literals

  def translateClassLiteral(pos: SourcePosition, symbol: Symbol): STExpression =
    val call = new STFunCall(pos)
    call.setVirtual()
    call.setName("~classliteral")
    call.addArgument(
      new STTypeCast(pos, typeForSymbol(symbol, ref = true), STLiteralExp.create(pos))
    ) // :null:
    new STTypeCast(
      pos,
      new STType.STPointerType(pos, new STType.STClassType("java.lang.Class")),
      call)

  /// equality & inequality

  // `l == r` becomes `if (l.eq(null)) r.eq(null) else l.equals(r)`
  def translateEqOp(
      context: TranslationContext,
      pos: SourcePosition,
      tpe: Type,
      select: Tree,
      lhs: Tree,
      rhs: Tree,
      negate: Boolean): STExpression =
    val (lhsAccess, lhsDecl) = useTemporary(typeForType(lhs.tpe), pos)
    context.emit(lhsDecl)
    context.emit:
      val assign = new STAssignmentStmt(pos)
      assign.setLeft(lhsAccess)
      assign.setRight(translateExpression(lhs, context))
      assign
    val (rhsAccess, rhsDecl) = useTemporary(typeForType(rhs.tpe), pos)
    context.emit(rhsDecl)
    context.emit:
      val assign = new STAssignmentStmt(pos)
      assign.setLeft(rhsAccess)
      assign.setRight(translateExpression(rhs, context))
      assign
    val (resultAccess, resultDecl) =
      useTemporary(typeForType(tpe), pos)
    context.emit(resultDecl)
    context.emit:
      val ifElse = new STIfElse(pos)
      ifElse.setPredicate(
        new STOpExp(pos, NSTOperators.Equal, lhsAccess, STLiteralExp.create(pos))
      ) // :null:
      ifElse.setIfBody:
        val block = new STBlock(pos)
        val assign = new STAssignmentStmt(pos)
        assign.setLeft(resultAccess)
        assign.setRight(
          new STOpExp(pos, NSTOperators.Equal, rhsAccess, STLiteralExp.create(pos))
        ) // :null:
        block.add(assign)
        block
      ifElse.setElseBody:
        val block = new STBlock(pos)
        val assign = new STAssignmentStmt(pos)
        assign.setLeft(resultAccess)
        assign.setRight {
          val call = new STFunCall(pos)
          call.setVirtual()
          call.setName(methodName(defn.Any_equals))
          call.addArgument(lhsAccess)
          call.addArgument(rhsAccess)
          call
        }
        block.add(assign)
        block
      ifElse
    if negate
    then STOpExp(pos, NSTOperators.Not, resultAccess)
    else resultAccess

  /// temporaries

  val temporaries = Iterator.from(0).map(n => s"~t$n")
  def useTemporary(tpe: STType, pos: SourcePosition): (STVarAccess, STVarDecl) =
    val temp = temporaries.next()
    val access = new STVarAccess(pos, temp)
    val decl = new STVarDecl(pos, temp, tpe)
    (access, decl)

  /// throwing up our hands

  def unknown(context: TranslationContext, tree: Tree, more: Option[String] = None): STExpression =
    log(s"unknown code at ${tree.sourcePos}: $tree")
    for msg <- more do
      log(s"additional info: $msg")
    context.emit(STLiteralExp.create(tree.sourcePos, s"???: $tree")) // s"???: ${showRaw(tree)}"))
    new STTypeCast(tree.sourcePos, typeForType(tree.tpe), STLiteralExp.create(tree.sourcePos)) // :null:

  /// these aren't stable identifiers, sigh. so make them so.

  val NmeEq = nme.eq
  val NmeNe = nme.ne
  val Arrays = defn.DottyArraysModule.name
  val NewArrayMethod = defn.newArrayMethod.name

end Translator

class TracingTranslator(
    showSourceInfo: Boolean = true,
    log: String => Unit = println,
)(using ctx: Context) extends Translator(showSourceInfo, log):

  override def trace(s: =>String): Unit =
    println("  " * indent + s)

  var indent = 0
  val width = 90

  private def escapedChar(ch: Char): String = (ch: @annotation.switch) match
    case '\b' => "\\b"
    case '\t' => "\\t"
    case '\n' => "\\n"
    case '\f' => "\\f"
    case '\r' => "\\r"
    case '"' => "\\\""
    case '\'' => "\\\'"
    case '\\' => "\\\\"
    case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn

  private def escapedString(str: String): String =
    str.flatMap(escapedChar)

  def recurse[T1, T2](msg: String, body: => T1, input: T2): T1 =
    val inputMessage =
      val inputString = escapedString(input.toString)
      if inputString.length > width
      then s"${inputString.take(width - 3)}..."
      else inputString
    trace(s"$msg==> $inputMessage")
    try
      indent += 1
      val result = body
      if result != () then
        trace(s"<== ${escapedString(result.toString)}")
      result
    finally indent -= 1

  override def translate(classDef: tpd.TypeDef): List[STClassDecl] =
    recurse("ClassDef", super.translate(classDef), classDef)
  override def translate(dd: tpd.DefDef, sourceInfo: SourceInfo): STFunDecl =
    recurse("DefDef", super.translate(dd, sourceInfo), dd)
  override def translateStatement(tree: Tree, context: TranslationContext): Unit =
    recurse("", super.translateStatement(tree, context), tree)
  override def translateExpression(tree: Tree, context: TranslationContext): STExpression =
    recurse("", super.translateExpression(tree, context), tree)
  override def translateLambda(dd: tpd.DefDef): STClassDecl =
    recurse("lambda", super.translateLambda(dd), dd)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy