<commit_before>package org.jetbrains.plugins.scala.lang.psi.dataFlow.impl.reachingDefs

import org.jetbrains.plugins.scala.lang.psi.api.base.{ScStableCodeReferenceElement, ScReferenceElement}
import _root_.org.jetbrains.plugins.scala.lang.psi.api.{ScalaRecursiveElementVisitor, ScControlFlowOwner}
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.plugins.scala.lang.psi.dataFlow.DfaEngine
import org.jetbrains.plugins.scala.lang.psi.controlFlow.Instruction
import collection.mutable.ArrayBuffer
import com.intellij.psi.{PsiClass, PsiMethod, PsiNamedElement, PsiElement}
import org.jetbrains.plugins.scala.lang.psi.controlFlow.impl.{ReadWriteVariableInstruction, DefineValueInstruction}
import org.jetbrains.plugins.scala.lang.psi.{ScalaPsiUtil, ScalaPsiElement}
import org.jetbrains.plugins.scala.lang.psi.api.statements.{ScValueDeclaration, ScTypeAlias, ScFun, ScFunction}
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScFunctionExpr
import scala.collection.mutable
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiElementFactory
import org.jetbrains.plugins.scala.extensions._
import com.intellij.codeInsight.PsiEquivalenceUtil
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScTypeDefinition, ScObject}
import org.jetbrains.plugins.scala.lang.psi.api.base.types.ScSimpleTypeElement
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic.{SyntheticNamedElement, ScSyntheticFunction}

/**
 * @author ilyas
 */

object ReachingDefintionsCollector {
  import ReachingDefinitions.{A => RDSet, _}

  def collectVariableInfo(elements: Seq[PsiElement], place: ScalaPsiElement): FragmentVariableInfos =
    collectVariableInfo(elements, visibilityFilter(place))
  /**
   * @param elements a fragment to analyze
   * @param isVisible since Extract Method refactoring is in fact RDC's main client, it should define if we need to
   *                  use captured variable as an argument
   */
  def collectVariableInfo(elements: Seq[PsiElement], isVisible: (PsiNamedElement) => Boolean): FragmentVariableInfos = {
    import PsiTreeUtil._
    val isInFragment = ancestorFilter(elements)
    // for every reference element, define is it's definition in scope
    val inputInfos = getInputInfo(elements, isInFragment, isVisible)

    // CFG -> DFA
    val commonParent = findCommonParent(elements: _*)
    val cfowner = getParentOfType(commonParent, classOf[ScControlFlowOwner])
    if (cfowner == null) {
      val message = "cfowner == null: " + elements.map(_.getText).mkString("(", ", ", ")") + "\n" + "files: " +
        elements.map(_.getContainingFile.getName).mkString("(", ", ", ")")
      throw new RuntimeException(message)
    }
    val cfg = cfowner.getControlFlow(cached = false) //todo: make cache more right to not get PsiInvalidAccess
    val engine = new DfaEngine(cfg, ReachingDefinitionsInstance, ReachingDefinitionsLattice)
    val dfaResult = engine.performDFA

    // instructions in given fragment
    val fragmentInstructions = filterByFragment(cfg, isInFragment)

    // for every WRITE or VAL define, if it escapes `elements' or not
    // i.e. if there instructions with greater numbers, which are "reached" by WRITE
    // instructions from the fragment

    val outputInfos = computeOutputVariables(fragmentInstructions, dfaResult)

    // take into account scope
    // todo implement

    FragmentVariableInfos(inputInfos, outputInfos)
  }

  private def ancestorFilter(elements: Seq[PsiElement]) = new ((PsiElement) => Boolean) {
    val cache: mutable.Map[PsiElement, Boolean] = new mutable.HashMap[PsiElement, Boolean]

    def apply(elem: PsiElement): Boolean = {
      if (elem == null) return false
      cache.getOrElseUpdate(elem, elements.exists(e => PsiTreeUtil.isAncestor(e, elem, false)))
    }
  }

  //defines if the given PsiNamedElement is visible at `place`
  private def visibilityFilter(place: ScalaPsiElement) = new ((PsiNamedElement) => Boolean) {
    val cache: mutable.Map[PsiElement, Boolean] = new mutable.HashMap[PsiElement, Boolean]

    def apply(elem: PsiNamedElement): Boolean = {
      def checkResolve(ref: PsiElement) = cache.getOrElseUpdate(ref, ref match {
        case r: ScReferenceElement =>
          r.multiResolve(false).map(_.getElement).exists(PsiEquivalenceUtil.areElementsEquivalent(_, elem))
        case _ => false
      })
      val isInstanceMethod = elem match {
        case fun: ScFunction => fun.isInstance && !fun.containingClass.isInstanceOf[ScObject]
        case m: PsiMethod => !m.hasModifierPropertyScala("static")
        case _ => false
      }
      val isSynthetic = elem match {
        case _: SyntheticNamedElement => true
        case fun: ScFunction => fun.isSynthetic
        case _ => false
      }
      import ScalaPsiElementFactory.{createExpressionWithContextFromText, createDeclarationFromText}
      val resolvesAtNewPlace = elem match {
        case _: PsiMethod | _: ScFun =>
          checkResolve(createExpressionWithContextFromText(elem.name + " _", place.getContext, place).getFirstChild)
        case _: ScTypeAlias | _: ScTypeDefinition =>
          val decl = createDeclarationFromText(s"val dummyVal: ${elem.name}", place.getContext, place).asInstanceOf[ScValueDeclaration]
          decl.typeElement match {
            case Some(st: ScSimpleTypeElement) => st.reference.exists(checkResolve)
            case _ => false
          }
        case _ =>
          checkResolve(createExpressionWithContextFromText(elem.name, place.getContext, place))
      }
      isInstanceMethod || isSynthetic || resolvesAtNewPlace
    }
  }

  private def filterByFragment(cfg: Seq[Instruction], checker: (PsiElement) => Boolean) = cfg.filter(i =><commit_msg>collection of variables for extract method improved<commit_after>package org.jetbrains.plugins.scala.lang.psi.dataFlow.impl.reachingDefs

import org.jetbrains.plugins.scala.lang.psi.api.base.{ScStableCodeReferenceElement, ScReferenceElement}
import _root_.org.jetbrains.plugins.scala.lang.psi.api.{ScalaRecursiveElementVisitor, ScControlFlowOwner}
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.plugins.scala.lang.psi.dataFlow.DfaEngine
import org.jetbrains.plugins.scala.lang.psi.controlFlow.Instruction
import collection.mutable.ArrayBuffer
import com.intellij.psi.{PsiClass, PsiMethod, PsiNamedElement, PsiElement}
import org.jetbrains.plugins.scala.lang.psi.controlFlow.impl.{ReadWriteVariableInstruction, DefineValueInstruction}
import org.jetbrains.plugins.scala.lang.psi.{ScalaPsiUtil, ScalaPsiElement}
import org.jetbrains.plugins.scala.lang.psi.api.statements.{ScValueDeclaration, ScTypeAlias, ScFun, ScFunction}
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScFunctionExpr
import scala.collection.mutable
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiElementFactory
import org.jetbrains.plugins.scala.extensions._
import com.intellij.codeInsight.PsiEquivalenceUtil
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScTypeDefinition, ScObject}
import org.jetbrains.plugins.scala.lang.psi.api.base.types.ScSimpleTypeElement
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic.{SyntheticNamedElement, ScSyntheticFunction}

/**
 * @author ilyas
 */

object ReachingDefintionsCollector {
  import ReachingDefinitions.{A => RDSet, _}

  def collectVariableInfo(elements: Seq[PsiElement], place: ScalaPsiElement): FragmentVariableInfos =
    collectVariableInfo(elements, visibilityFilter(place))
  /**
   * @param elements a fragment to analyze
   * @param isVisible since Extract Method refactoring is in fact RDC's main client, it should define if we need to
   *                  use captured variable as an argument
   */
  def collectVariableInfo(elements: Seq[PsiElement], isVisible: (PsiNamedElement) => Boolean): FragmentVariableInfos = {
    import PsiTreeUtil._
    val isInFragment = ancestorFilter(elements)
    // for every reference element, define is it's definition in scope
    val inputInfos = getInputInfo(elements, isInFragment, isVisible)

    // CFG -> DFA
    val commonParent = findCommonParent(elements: _*)
    val cfowner = getParentOfType(commonParent, classOf[ScControlFlowOwner])
    if (cfowner == null) {
      val message = "cfowner == null: " + elements.map(_.getText).mkString("(", ", ", ")") + "\n" + "files: " +
        elements.map(_.getContainingFile.getName).mkString("(", ", ", ")")
      throw new RuntimeException(message)
    }
    val cfg = cfowner.getControlFlow(cached = false) //todo: make cache more right to not get PsiInvalidAccess
    val engine = new DfaEngine(cfg, ReachingDefinitionsInstance, ReachingDefinitionsLattice)
    val dfaResult = engine.performDFA

    // instructions in given fragment
    val fragmentInstructions = filterByFragment(cfg, isInFragment)

    // for every WRITE or VAL define, if it escapes `elements' or not
    // i.e. if there instructions with greater numbers, which are "reached" by WRITE
    // instructions from the fragment

    val outputInfos = computeOutputVariables(fragmentInstructions, dfaResult)

    // take into account scope
    // todo implement

    FragmentVariableInfos(inputInfos, outputInfos)
  }

  private def ancestorFilter(elements: Seq[PsiElement]) = new ((PsiElement) => Boolean) {
    val cache: mutable.Map[PsiElement, Boolean] = new mutable.HashMap[PsiElement, Boolean]

    def apply(elem: PsiElement): Boolean = {
      if (elem == null) return false
      cache.getOrElseUpdate(elem, elements.exists(e => PsiTreeUtil.isAncestor(e, elem, false)))
    }
  }

  //defines if the given PsiNamedElement is visible at `place`
  private def visibilityFilter(place: ScalaPsiElement) = new ((PsiNamedElement) => Boolean) {
    val cache: mutable.Map[PsiElement, Boolean] = new mutable.HashMap[PsiElement, Boolean]

    def apply(elem: PsiNamedElement): Boolean = {
      def checkResolve(ref: PsiElement) = cache.getOrElseUpdate(ref, ref match {
        case r: ScReferenceElement =>
          r.multiResolve(false).map(_.getElement).exists(PsiEquivalenceUtil.areElementsEquivalent(_, elem))
        case _ => false
      })
      val isInstanceMethod = elem match {
        case fun: ScFunction => fun.isInstance
        case m: PsiMethod => !m.hasModifierPropertyScala("static")
        case _ => false
      }
      val isSynthetic = elem match {
        case _: SyntheticNamedElement => true
        case fun: ScFunction => fun.isSynthetic
        case _ => false
      }
      import ScalaPsiElementFactory.{createExpressionWithContextFromText, createDeclarationFromText}
      val resolvesAtNewPlace = elem match {
        case _: PsiMethod | _: ScFun =>
          checkResolve(createExpressionWithContextFromText(elem.name + " _", place.getContext, place).getFirstChild)
        case _: ScObject =>
          checkResolve(createExpressionWithContextFromText(elem.name, place.getContext, place))
        case _: ScTypeAlias | _: ScTypeDefinition =>
          val decl = createDeclarationFromText(s"val dummyVal: ${elem.name}", place.getContext, place).asInstanceOf[ScValueDeclaration]
          decl.typeElement match {
            case Some(st: ScSimpleTypeElement) => st.reference.exists(checkResolve)
            case _ => false
          }
        case _ =>
          checkResolve(createExpressionWithContextFromText(elem.name, place.getContext, place))
      }
      isInstanceMethod || isSynthetic || resolvesAtNewPlace
    }
  }

  private def filterByFragment(cfg: Seq[Instruction], checker: (PsiElement) => Boolean) = cfg.filter(i =>
{'fraction_non_alphanumeric': 0.10071942446043165, 'fraction_numerical': 0.0, 'mean_word_length': 4.1188997338065665, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '7156114', 'n_tokens_mistral': 3386, 'n_tokens_neox': 3286, 'n_words': 726}