<commit_before>package org.jetbrains.plugins.scala.debugger.evaluation

import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.evaluation._
import com.intellij.debugger.engine.evaluation.expression._
import com.intellij.lang.java.JavaLanguage
import com.intellij.psi._
import org.jetbrains.plugins.scala.ScalaBundle
import org.jetbrains.plugins.scala.debugger.evaluation.evaluator._
import org.jetbrains.plugins.scala.debugger.evaluation.util.DebuggerUtil
import org.jetbrains.plugins.scala.lang.psi.api.base._
import org.jetbrains.plugins.scala.lang.psi.api.expr._
import org.jetbrains.plugins.scala.lang.psi.api.statements._
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScMember

/**
 * Nikolay.Tropin
 * 2014-09-28
 */
object ScalaEvaluatorBuilder extends EvaluatorBuilder {
  def build(codeFragment: PsiElement, position: SourcePosition): ExpressionEvaluator = {
    if (codeFragment.getLanguage.isInstanceOf[JavaLanguage])
      return EvaluatorBuilderImpl.getInstance().build(codeFragment, position) //java builder (e.g. SCL-6117)

    val scalaFragment = codeFragment match {
      case sf: ScalaCodeFragment => sf
      case _ => throw EvaluationException(ScalaBundle.message("non-scala.code.fragment"))
    }

    val project = codeFragment.getProject

    val cache = ScalaEvaluatorCache.getInstance(project)
    val cached: Option[ExpressionEvaluator] = {
      try cache.get(position, codeFragment)
      catch {
        case e: Exception =>
          cache.clear()
          None
      }
    }

    def buildSimpleEvaluator = {
      cached.getOrElse {
        val newEvaluator = new ScalaEvaluatorBuilder(scalaFragment, position).getEvaluator
        cache.add(position, scalaFragment, new ExpressionEvaluatorImpl(newEvaluator))
      }
    }

    def buildCompilingEvaluator: ExpressionEvaluator = {
      val compilingEvaluator = new ScalaCompilingEvaluator(position.getElementAt, scalaFragment)
      cache.add(position, scalaFragment, compilingEvaluator)
    }

    try buildSimpleEvaluator
    catch {
      case e: NeedCompilationException =>
        buildCompilingEvaluator
      case e: EvaluateException => throw e
    }
  }
}

private[evaluation] class NeedCompilationException(message: String) extends EvaluateException(message)

private[evaluation] class ScalaEvaluatorBuilder(val codeFragment: ScalaCodeFragment, val position: SourcePosition)
        extends ScalaEvaluatorBuilderUtil with SyntheticVariablesHelper {

  import org.jetbrains.plugins.scala.debugger.evaluation.ScalaEvaluatorBuilderUtil._

  val contextClass =  {
    if (position == null) null
    else getContextClass(position.getElementAt, strict = false)
  }

  def getEvaluator: Evaluator = new UnwrapRefEvaluator(fragmentEvaluator(codeFragment))

  protected def evaluatorFor(element: PsiElement): Evaluator = {
    element match {
      case implicitlyConvertedTo(expr) => evaluatorFor(expr)
      case needsCompilation(message) => throw new NeedCompilationException(message)
      case expr: ScExpression =>
        val innerEval = expr match {
          case lit: ScLiteral => literalEvaluator(lit)
          case mc: ScMethodCall => scMethodCallEvaluator(mc)
          case ref: ScReferenceExpression => refExpressionEvaluator(ref)
          case t: ScThisReference => thisOrSuperEvaluator(t.reference, isSuper = false)
          case t: ScSuperReference => thisOrSuperEvaluator(t.reference, isSuper = true)
          case tuple: ScTuple => tupleEvaluator(tuple)
          case newTd: ScNewTemplateDefinition => newTemplateDefinitionEvaluator(newTd)
          case inf: ScInfixExpr => infixExpressionEvaluator(inf)
          case ScParenthesisedExpr(inner) => evaluatorFor(inner)
          case p: ScPrefixExpr => prefixExprEvaluator(p)
          case p: ScPostfixExpr => postfixExprEvaluator(p)
          case stmt: ScIfStmt => ifStmtEvaluator(stmt)
          case ws: ScWhileStmt => whileStmtEvaluator(ws)
          case doSt: ScDoStmt => doStmtEvaluator(doSt)
          case block: ScBlock => blockExprEvaluator(block)
          case call: ScGenericCall => methodCallEvaluator(call, Nil, Map.empty)
          case stmt: ScAssignStmt => assignmentEvaluator(stmt)
          case stmt: ScTypedStmt => evaluatorFor(stmt.expr)
          case e => throw EvaluationException(s"This type of expression is not supported: ${e.getText}")
        }
        postProcessExpressionEvaluator(expr, innerEval)
      case pd: ScPatternDefinition => patternDefinitionEvaluator(pd)
      case vd: ScVariableDefinition => variableDefinitionEvaluator(vd)
      case e => throw EvaluationException(s"This type of element is not supported: ${e.getText}")
    }
  }

  def fragmentEvaluator(fragment: ScalaCodeFragment): Evaluator = {
    val childrenEvaluators = fragment.children.collect {
      case e @ (_: ScBlockStatement | _: ScMember) => evaluatorFor(e)
    }
    new BlockStatementEvaluator(childrenEvaluators.toArray)
  }
}

private[evaluation] trait SyntheticVariablesHelper {
  private var currentHolder = new SyntheticVariablesHolderEvaluator(null)

  protected def withNewSyntheticVariablesHolder(evaluatorComputation: => Evaluator): Evaluator = {
    val old = currentHolder
    val newEvaluator = new SyntheticVariablesHolderEvaluator(currentHolder)
    currentHolder = newEvaluator
    var result: Evaluator = null
    try {
      result = evaluatorComputation
    }
    finally {
      currentHolder = old
    }
    result
  }

  protected def createSyntheticVariable(name: String) = currentHolder.setInitialValue(name, null)
  protected def syntheticVariableEvaluator(name: String) = new SyntheticVariableEvaluator(currentHolder, name)
}

private object needsCompilation {
  def message(kind: String) = Some(s"Evaluation of $kind needs compilation")

  def unapply(elem: PsiElement): Option[String] = elem match {
    case expr if ScUnderScoreSectionUtil.isUnderscoreFunction(expr) => message("anonymous function")
    case funExpr: ScFunctionExpr =>
      message("anonymous function")
    case forSt: ScForStatement =>
      message("for expression")
    case tryStmt: ScTryStmt =>
      message("try statement")
    case ret: ScReturnStmt =>
      message("return statement")
    case ms: ScMatchStmt =>
      message("match statement")
    case throwStmt: ScThrowStmt =>
      message("throw statement")
    case v @ (_: ScVariableDeclaration | _: ScValueDeclaration) => message("variable declaration")
    case t: ScTypeAlias => message("type alias")
    case newTd: ScNewTemplateDefinition if DebuggerUtil.generatesAnonClass(newTd) =>
      message("anonymous class")
    case _ => None
  }
}<commit_msg>evaluate expression:  use compiling evaluator for function and template definitions (regression fixed)<commit_after>package org.jetbrains.plugins.scala.debugger.evaluation

import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.evaluation._
import com.intellij.debugger.engine.evaluation.expression._
import com.intellij.lang.java.JavaLanguage
import com.intellij.psi._
import org.jetbrains.plugins.scala.ScalaBundle
import org.jetbrains.plugins.scala.debugger.evaluation.evaluator._
import org.jetbrains.plugins.scala.debugger.evaluation.util.DebuggerUtil
import org.jetbrains.plugins.scala.extensions.LazyVal
import org.jetbrains.plugins.scala.lang.psi.api.base._
import org.jetbrains.plugins.scala.lang.psi.api.expr._
import org.jetbrains.plugins.scala.lang.psi.api.expr.xml.ScXmlExpr
import org.jetbrains.plugins.scala.lang.psi.api.statements._
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef._

/**
 * Nikolay.Tropin
 * 2014-09-28
 */
object ScalaEvaluatorBuilder extends EvaluatorBuilder {
  def build(codeFragment: PsiElement, position: SourcePosition): ExpressionEvaluator = {
    if (codeFragment.getLanguage.isInstanceOf[JavaLanguage])
      return EvaluatorBuilderImpl.getInstance().build(codeFragment, position) //java builder (e.g. SCL-6117)

    val scalaFragment = codeFragment match {
      case sf: ScalaCodeFragment => sf
      case _ => throw EvaluationException(ScalaBundle.message("non-scala.code.fragment"))
    }

    val project = codeFragment.getProject

    val cache = ScalaEvaluatorCache.getInstance(project)
    val cached: Option[ExpressionEvaluator] = {
      try cache.get(position, codeFragment)
      catch {
        case e: Exception =>
          cache.clear()
          None
      }
    }

    def buildSimpleEvaluator = {
      cached.getOrElse {
        val newEvaluator = new ScalaEvaluatorBuilder(scalaFragment, position).getEvaluator
        cache.add(position, scalaFragment, new ExpressionEvaluatorImpl(newEvaluator))
      }
    }

    def buildCompilingEvaluator: ExpressionEvaluator = {
      val compilingEvaluator = new ScalaCompilingEvaluator(position.getElementAt, scalaFragment)
      cache.add(position, scalaFragment, compilingEvaluator)
    }

    try buildSimpleEvaluator
    catch {
      case e: NeedCompilationException =>
        buildCompilingEvaluator
      case e: EvaluateException => throw e
    }
  }
}

private[evaluation] class NeedCompilationException(message: String) extends EvaluateException(message)

private[evaluation] class ScalaEvaluatorBuilder(val codeFragment: ScalaCodeFragment, val position: SourcePosition)
        extends ScalaEvaluatorBuilderUtil with SyntheticVariablesHelper {

  import org.jetbrains.plugins.scala.debugger.evaluation.ScalaEvaluatorBuilderUtil._

  val contextClass =  {
    if (position == null) null
    else getContextClass(position.getElementAt, strict = false)
  }

  def getEvaluator: Evaluator = new UnwrapRefEvaluator(fragmentEvaluator(codeFragment))

  protected def evaluatorFor(element: PsiElement): Evaluator = {
    element match {
      case implicitlyConvertedTo(expr) => evaluatorFor(expr)
      case needsCompilation(message) => throw new NeedCompilationException(message)
      case expr: ScExpression =>
        val innerEval = expr match {
          case lit: ScLiteral => literalEvaluator(lit)
          case mc: ScMethodCall => scMethodCallEvaluator(mc)
          case ref: ScReferenceExpression => refExpressionEvaluator(ref)
          case t: ScThisReference => thisOrSuperEvaluator(t.reference, isSuper = false)
          case t: ScSuperReference => thisOrSuperEvaluator(t.reference, isSuper = true)
          case tuple: ScTuple => tupleEvaluator(tuple)
          case newTd: ScNewTemplateDefinition => newTemplateDefinitionEvaluator(newTd)
          case inf: ScInfixExpr => infixExpressionEvaluator(inf)
          case ScParenthesisedExpr(inner) => evaluatorFor(inner)
          case p: ScPrefixExpr => prefixExprEvaluator(p)
          case p: ScPostfixExpr => postfixExprEvaluator(p)
          case stmt: ScIfStmt => ifStmtEvaluator(stmt)
          case ws: ScWhileStmt => whileStmtEvaluator(ws)
          case doSt: ScDoStmt => doStmtEvaluator(doSt)
          case block: ScBlock => blockExprEvaluator(block)
          case call: ScGenericCall => methodCallEvaluator(call, Nil, Map.empty)
          case stmt: ScAssignStmt => assignmentEvaluator(stmt)
          case stmt: ScTypedStmt => evaluatorFor(stmt.expr)
          case e => throw EvaluationException(s"This type of expression is not supported: ${e.getText}")
        }
        postProcessExpressionEvaluator(expr, innerEval)
      case pd: ScPatternDefinition => patternDefinitionEvaluator(pd)
      case vd: ScVariableDefinition => variableDefinitionEvaluator(vd)
      case e => throw EvaluationException(s"This type of element is not supported: ${e.getText}")
    }
  }

  def fragmentEvaluator(fragment: ScalaCodeFragment): Evaluator = {
    val childrenEvaluators = fragment.children.collect {
      case e @ (_: ScBlockStatement | _: ScMember) => evaluatorFor(e)
    }
    new BlockStatementEvaluator(childrenEvaluators.toArray)
  }
}

private[evaluation] trait SyntheticVariablesHelper {
  private var currentHolder = new SyntheticVariablesHolderEvaluator(null)

  protected def withNewSyntheticVariablesHolder(evaluatorComputation: => Evaluator): Evaluator = {
    val old = currentHolder
    val newEvaluator = new SyntheticVariablesHolderEvaluator(currentHolder)
    currentHolder = newEvaluator
    var result: Evaluator = null
    try {
      result = evaluatorComputation
    }
    finally {
      currentHolder = old
    }
    result
  }

  protected def createSyntheticVariable(name: String) = currentHolder.setInitialValue(name, null)
  protected def syntheticVariableEvaluator(name: String) = new SyntheticVariableEvaluator(currentHolder, name)
}

private object needsCompilation {
  def message(kind: String) = Some(s"Evaluation of $kind needs compilation")

  def unapply(elem: PsiElement): Option[String] = elem match {
    case m: ScMember => m match {
      case td: ScTemplateDefinition =>
        td match {
          case o: ScObject => message("object")
          case c: ScClass => message("class")
          case t: ScTrait => message("trait")
          case newTd: ScNewTemplateDefinition if DebuggerUtil.generatesAnonClass(newTd) =>
            message("anonymous class")
          case _ => None
        }
      case t: ScTypeAlias => message("type alias")
      case f: ScFunction => message("function definition")
      case v @ (_: ScVariableDeclaration | _: ScValueDeclaration) => message("variable declaration")
      case LazyVal(_) => message("lazy val definition")
      case _ => None
    }
    case expr if ScalaEvaluatorBuilderUtil.isGenerateAnonfun(expr) => message("anonymous function")
    case forSt: ScForStatement => message("for expression")
    case tryStmt: ScTryStmt => message("try statement")
    case ret: ScReturnStmt => message("return statement")
    case ms: ScMatchStmt => message("match statement")
    case throwStmt: ScThrowStmt => message("throw statement")
    case xml: ScXmlExpr => message("xml expression")
    case _ => None
  }
}
{'fraction_non_alphanumeric': 0.08808700662633247, 'fraction_numerical': 0.001728608470181504, 'mean_word_length': 3.9377667140825037, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 98, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 3}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4267253', 'n_tokens_mistral': 4013, 'n_tokens_neox': 3925, 'n_words': 871}