Repository: intellij-rust/intellij-rust
Issue: Clippy checker throws exception

## Environment

* **Intellij-Rust plugin version:** Unrelated
* **Rust toolchain version:** intellij-rust-0.2.94.2579-191-nightly
* **IDE name and version:** CL-191.6183.6
* **Operating system:** Ubuntu Linux 18.04

## Problem description

```
java.lang.Throwable: Incorrect CachedValue use: same CV with different captured context, this can cause unstable results and invalid PSI access.
Field $owner in class org.rust.ide.annotator.RsExternalLinterUtils$checkLazily$1 has non-equivalent values:
  Dispose on PSI change (com.intellij.openapi.util.Disposer$2) and
  Dispose on PSI change (com.intellij.openapi.util.Disposer$2)
Either make `equals()` hold for these values, or avoid this dependency, e.g. by extracting CV provider into a static method.
	at com.intellij.openapi.diagnostic.Logger.error(Logger.java:145)
	at com.intellij.util.CachedValueStabilityChecker.complain(CachedValueStabilityChecker.java:163)
	at com.intellij.util.CachedValueStabilityChecker.checkFieldEquivalence(CachedValueStabilityChecker.java:134)
	at com.intellij.util.CachedValueStabilityChecker.checkProvidersEquivalent(CachedValueStabilityChecker.java:87)
	at com.intellij.util.CachedValuesManagerImpl.getCachedValueFromExHolder(CachedValuesManagerImpl.java:60)
	at com.intellij.util.CachedValuesManagerImpl.getCachedValue(CachedValuesManagerImpl.java:45)
	at com.intellij.psi.util.CachedValuesManager.getCachedValue(CachedValuesManager.java:119)
	at org.rust.ide.annotator.RsExternalLinterUtils.checkLazily(RsExternalLinterUtils.kt:71)
	at org.rust.ide.annotator.RsExternalLinterPass.doCollectInformation(RsExternalLinterPass.kt:57)
	at com.intellij.codeHighlighting.TextEditorHighlightingPass.collectInformation(TextEditorHighlightingPass.java:69)
	at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$null$1(PassExecutorService.java:430)
	at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1166)
	at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$doRun$2(PassExecutorService.java:423)
	at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:586)
	at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:532)
	at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:86)
	at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.doRun(PassExecutorService.java:422)
	at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.lambda$run$0(PassExecutorService.java:398)
	at com.intellij.openapi.application.impl.ReadMostlyRWLock.executeByImpatientReader(ReadMostlyRWLock.java:164)
	at com.intellij.openapi.application.impl.ApplicationImpl.executeByImpatientReader(ApplicationImpl.java:218)
	at com.intellij.codeInsight.daemon.impl.PassExecutorService$ScheduledPass.run(PassExecutorService.java:396)
	at com.intellij.concurrency.JobLauncherImpl$VoidForkJoinTask$1.exec(JobLauncherImpl.java:174)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
```

## Steps to reproduce

Cannot reproduce, but I get the stack trace.

This happens after:

+ I changed checker from "cargo check" to "clippy" and enabled "run cargo check on the fly"
+ ij-rs said I don't have clippy, I clicked "install"
+ I triggered a build, get error

Changed files:
src/main/kotlin/org/rust/openapiext/ProjectCache.kt, src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt

diff --git a/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt b/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt
index 8fe0e7a86..d007d13e4 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt
@@ -26,8 +26,6 @@ import com.intellij.openapi.util.TextRange
 import com.intellij.psi.PsiFile
 import com.intellij.psi.impl.AnyPsiChangeListener
 import com.intellij.psi.impl.PsiManagerImpl
-import com.intellij.psi.util.CachedValueProvider
-import com.intellij.psi.util.CachedValuesManager
 import com.intellij.psi.util.PsiModificationTracker
 import com.intellij.util.PathUtil
 import com.intellij.util.messages.MessageBus
@@ -40,10 +38,7 @@ import org.rust.ide.annotator.fixes.ApplySuggestionFix
 import org.rust.lang.RsConstants
 import org.rust.lang.core.psi.RsFile
 import org.rust.lang.core.psi.ext.containingCargoPackage
-import org.rust.openapiext.checkReadAccessAllowed
-import org.rust.openapiext.checkReadAccessNotAllowed
-import org.rust.openapiext.isUnitTestMode
-import org.rust.openapiext.saveAllDocumentsAsTheyAre
+import org.rust.openapiext.*
 import java.nio.file.Path
 import java.util.*
 
@@ -67,32 +62,28 @@ object RsExternalLinterUtils {
         packageName: String?
     ): Lazy<RsExternalLinterResult?> {
         checkReadAccessAllowed()
-        return CachedValuesManager.getManager(project)
-            .getCachedValue(project) {
-                // We want to run external linter in background thread and *without* read action.
-                // And also we want to cache result of external linter because it is cargo package-global,
-                // but annotator can be invoked separately for each file.
-                // With `CachedValuesManager` our cached value should be invalidated on any PSI change.
-                // Important note about this cache is that modification count will be stored AFTER computation
-                // of a value. If we aren't in read action, PSI can be modified during computation of the value
-                // and so an outdated value will be cached. So we can't use the cache without read action.
-                // What we really want:
-                // 1. Store current PSI modification count;
-                // 2. Run external linter and retrieve results (in background thread and without read action);
-                // 3. Try to cache result use modification count stored in (1). Result can be already outdated here.
-                // We get such behavior by storing `Lazy` computation to the cache. Cache result is created in read
-                // action, so it will be stored within correct PSI modification count. Then, we will retrieve the value
-                // from `Lazy` in a background thread. The value will be computed or retrieved from the already computed
-                // `Lazy` value.
-                CachedValueProvider.Result.create(
-                    lazy {
-                        // This code will be executed out of read action in background thread
-                        if (!isUnitTestMode) checkReadAccessNotAllowed()
-                        checkWrapped(toolchain, project, owner, workingDirectory, packageName)
-                    },
-                    PsiModificationTracker.MODIFICATION_COUNT
-                )
+        return externalLinterLazyResultCache.getOrPut(project, Key(toolchain, workingDirectory, packageName)) {
+            // We want to run external linter in background thread and *without* read action.
+            // And also we want to cache result of external linter because it is cargo package-global,
+            // but annotator can be invoked separately for each file.
+            // With `CachedValuesManager` our cached value should be invalidated on any PSI change.
+            // Important note about this cache is that modification count will be stored AFTER computation
+            // of a value. If we aren't in read action, PSI can be modified during computation of the value
+            // and so an outdated value will be cached. So we can't use the cache without read action.
+            // What we really want:
+            // 1. Store current PSI modification count;
+            // 2. Run external linter and retrieve results (in background thread and without read action);
+            // 3. Try to cache result use modification count stored in (1). Result can be already outdated here.
+            // We get such behavior by storing `Lazy` computation to the cache. Cache result is created in read
+            // action, so it will be stored within correct PSI modification count. Then, we will retrieve the value
+            // from `Lazy` in a background thread. The value will be computed or retrieved from the already computed
+            // `Lazy` value.
+            lazy {
+                // This code will be executed out of read action in background thread
+                if (!isUnitTestMode) checkReadAccessNotAllowed()
+                checkWrapped(toolchain, project, owner, workingDirectory, packageName)
             }
+        }
     }
 
     private fun checkWrapped(
@@ -139,6 +130,17 @@ object RsExternalLinterUtils {
         if (output.isCancelled) return null
         return RsExternalLinterResult(output.stdoutLines)
     }
+
+    private data class Key(
+        val toolchain: RustToolchain,
+        val workingDirectory: Path,
+        val packageName: String?
+    )
+
+    private val externalLinterLazyResultCache =
+        ProjectCache<Key, Lazy<RsExternalLinterResult?>>("externalLinterLazyResultCache") {
+            PsiModificationTracker.MODIFICATION_COUNT
+        }
 }
 
 fun MessageBus.createDisposableOnAnyPsiChange(): Disposable {
diff --git a/src/main/kotlin/org/rust/openapiext/ProjectCache.kt b/src/main/kotlin/org/rust/openapiext/ProjectCache.kt
index 9b275f76f..a71d6967c 100644
--- a/src/main/kotlin/org/rust/openapiext/ProjectCache.kt
+++ b/src/main/kotlin/org/rust/openapiext/ProjectCache.kt
@@ -14,7 +14,10 @@ import com.intellij.util.containers.ContainerUtil
 import org.rust.lang.core.psi.rustStructureModificationTracker
 import java.util.concurrent.ConcurrentMap
 
-class ProjectCache<in T, R>(cacheName: String) {
+class ProjectCache<in T, R>(
+    cacheName: String,
+    private val dependencyGetter: (Project) -> Any = { it.rustStructureModificationTracker }
+) {
     init {
         if (!registered.add(cacheName)) {
             error("""
@@ -31,7 +34,7 @@ class ProjectCache<in T, R>(cacheName: String) {
             .getCachedValue(project, cacheKey, {
                 CachedValueProvider.Result.create(
                     ContainerUtil.newConcurrentMap<T, R>(),
-                    project.rustStructureModificationTracker
+                    dependencyGetter(project)
                 )
             }, false)
         return cache.getOrPut(key) { defaultValue() }