use PsiFileGist instead of time-expensive indices for contract inference
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / dataFlow / ContractInferenceIndex.kt
index ee11a213b54f2dd4154b58f3a521e471fd4a1dd1..6da68ecf86e1a0151cd1b51882975ed9897b3128 100644 (file)
  */
 package com.intellij.codeInspection.dataFlow
 
-import com.intellij.ide.highlighter.JavaFileType
 import com.intellij.lang.LighterAST
 import com.intellij.lang.LighterASTNode
-import com.intellij.lang.TreeBackedLighterAST
-import com.intellij.openapi.util.Ref
 import com.intellij.psi.PsiMethod
-import com.intellij.psi.impl.source.JavaFileElementType
 import com.intellij.psi.impl.source.JavaLightStubBuilder
 import com.intellij.psi.impl.source.PsiFileImpl
 import com.intellij.psi.impl.source.PsiMethodImpl
 import com.intellij.psi.impl.source.tree.JavaElementType
+import com.intellij.psi.impl.source.tree.JavaElementType.*
 import com.intellij.psi.impl.source.tree.LightTreeUtil
 import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor
-import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.util.CachedValueProvider
-import com.intellij.psi.util.CachedValuesManager
-import com.intellij.util.indexing.*
-import com.intellij.util.io.DataExternalizer
-import com.intellij.util.io.IntInlineKeyDescriptor
+import com.intellij.util.gist.GistManager
 import java.util.*
 
 /**
  * @author peter
  */
 
-private val INDEX_ID = ID.create<Int, MethodData>("java.inferred.contracts")
+private val gist = GistManager.getInstance().newPsiFileGist("contractInference", 0, MethodDataExternalizer) { file ->
+  indexFile(file.node.lighterAST)
+}
 
-class ContractInferenceIndex : FileBasedIndexExtension<Int, MethodData>(), PsiDependentIndex {
-  override fun getName() = INDEX_ID
-  override fun getVersion() = 0
-  override fun dependsOnFileContent() = true
-  override fun getKeyDescriptor() = IntInlineKeyDescriptor()
-  override fun getValueExternalizer(): DataExternalizer<MethodData> = MethodDataExternalizer
+private fun indexFile(tree: LighterAST): Map<Int, MethodData> {
+  val result = HashMap<Int, MethodData>()
 
-  override fun getInputFilter() = FileBasedIndex.InputFilter {
-    it.fileType == JavaFileType.INSTANCE && JavaFileElementType.isInSourceContent(it)
-  }
+  object : RecursiveLighterASTNodeWalkingVisitor(tree) {
+    var methodIndex = 0
 
-  override fun getIndexer() = DataIndexer<Int, MethodData, FileContent> { fc ->
-    val result = HashMap<Int, MethodData>()
+    override fun visitNode(element: LighterASTNode) {
+      if (element.tokenType === METHOD) {
+        calcData(tree, element)?.let { data -> result[methodIndex] = data }
+        methodIndex++
+      }
 
-    val tree = (fc as FileContentImpl).lighterASTForPsiDependentIndex
-    object : RecursiveLighterASTNodeWalkingVisitor(tree) {
-      var methodIndex = 0
+      if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
 
-      override fun visitNode(element: LighterASTNode) {
-        if (element.tokenType === JavaElementType.METHOD) {
-          calcData(tree, element)?.let { data -> result[methodIndex] = data }
-          methodIndex++
-        }
+      super.visitNode(element)
+    }
+  }.visitNode(tree.root)
 
-        if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
+  return result
+}
 
-        super.visitNode(element)
-      }
-    }.visitNode(tree.root)
+private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? {
+  val body = LightTreeUtil.firstChildOfType(tree, method, CODE_BLOCK) ?: return null
+  val statements = ContractInferenceInterpreter.getStatements(body, tree)
+
+  val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts(statements)
 
-    result
+  val nullityVisitor = NullityInference.NullityInferenceVisitor(tree, body)
+  val purityVisitor = PurityInference.PurityInferenceVisitor(tree, body)
+  for (statement in statements) {
+    walkMethodBody(tree, statement) { nullityVisitor.visitNode(it); purityVisitor.visitNode(it) }
   }
 
+  return createData(body, contracts, nullityVisitor.result, purityVisitor.result)
 }
 
-private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? {
-  val body = LightTreeUtil.firstChildOfType(tree, method, JavaElementType.CODE_BLOCK) ?: return null
+private fun walkMethodBody(tree: LighterAST, root: LighterASTNode, processor: (LighterASTNode) -> Unit) {
+  object : RecursiveLighterASTNodeWalkingVisitor(tree) {
+    override fun visitNode(element: LighterASTNode) {
+      val type = element.tokenType
+      if (type === CLASS || type === FIELD || type == METHOD || type == ANNOTATION_METHOD || type === LAMBDA_EXPRESSION) return
+
+      processor(element)
+      super.visitNode(element)
+    }
+  }.visitNode(root)
+}
 
-  val nullity = NullityInference.doInferNullity(tree, body)
-  val purity = PurityInference.doInferPurity(body, tree)
-  val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts()
+private fun createData(body: LighterASTNode,
+                       contracts: List<PreContract>,
+                       nullity: NullityInferenceResult?,
+                       purity: PurityInferenceResult?): MethodData? {
   if (nullity == null && purity == null && !contracts.isNotEmpty()) return null
 
   return MethodData(nullity, purity, contracts, body.startOffset, body.endOffset)
@@ -90,22 +95,12 @@ private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? {
 
 fun getIndexedData(method: PsiMethod): MethodData? {
   if (method !is PsiMethodImpl || !InferenceFromSourceUtil.shouldInferFromSource(method)) return null
-  val vFile = method.containingFile.virtualFile ?: return calcNonPhysicalMethodData(method)
 
-  val ref = Ref<MethodData>()
-  val scope = GlobalSearchScope.fileScope(method.project, vFile)
-  FileBasedIndex.getInstance().processValues(INDEX_ID, methodIndex(method), vFile, { file, data -> ref.set(data); true }, scope)
-  return ref.get()
+  return gist.getFileData(method.containingFile)?.get(methodIndex(method))
 }
 
 private fun methodIndex(method: PsiMethodImpl): Int {
   val file = method.containingFile as PsiFileImpl
   val stubTree = file.stubTree ?: file.calcStubTree()
   return stubTree.plainList.filter { it.stubType == JavaElementType.METHOD }.map { it.psi }.indexOf(method)
-}
-
-private fun calcNonPhysicalMethodData(method: PsiMethodImpl): MethodData? {
-  return CachedValuesManager.getCachedValue(method) {
-    CachedValueProvider.Result(calcData(method.containingFile.node.lighterAST, TreeBackedLighterAST.wrap(method.node)), method)
-  }
 }
\ No newline at end of file