@Override
  public boolean isValid() {
    if (myManager.getProject().isDisposed()) {
      // normally FileManager.dispose would call markInvalidated
      // but there's temporary disposed project in tests, which doesn't actually dispose its components :(
      return false;
    }
    if (!myViewProvider.getVirtualFile().isValid()) {
      // PSI listeners receive VFS deletion events and do markInvalidated
      // but some VFS listeners receive the same events before that and ask PsiFile.isValid
      return false;
    }

    if (!myPossiblyInvalidated) return true;

    /*
    Originally, all PSI was invalidated on root change, to avoid UI freeze (IDEA-172762),
    but that has led to too many PIEAEs (like IDEA-191185, IDEA-188292, IDEA-184186, EA-114990).

    Ideally those clients should all be converted to smart pointers, but that proved to be quite hard to do, especially without breaking API.
    And they mostly worked before those batch invalidations.

    So now we have a smarter way of dealing with this issue. On root change, we mark
    PSI as "potentially invalid", and then, when someone calls "isValid"
    (hopefully not for all cached PSI at once, and hopefully in a background thread),
    we check if the old PSI is equivalent to the one that would be re-created in its place.
    If yes, we return valid. If no, we invalidate the old PSI forever and return the new one.
    */

    // synchronized by read-write action
    if (((FileManagerImpl)myManager.getFileManager()).evaluateValidity(this)) {
      myPossiblyInvalidated = false;
      PsiInvalidElementAccessException.setInvalidationTrace(this, null);
      return true;
    }
    return false;
  }
@Override
  public boolean isValid()