@SuppressWarnings({"unchecked"})
  @NotNull
  public static <T extends PsiType> T originalize(@NotNull T type) {
    if (!type.isValid()) {
      return type;
    }

    T result =
        new PsiTypeMapper() {
          private final Set<PsiClassType> myVisited = ContainerUtil.newIdentityTroveSet();

          @Override
          public PsiType visitClassType(final PsiClassType classType) {
            if (!myVisited.add(classType)) return classType;

            final PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics();
            final PsiClass psiClass = classResolveResult.getElement();
            final PsiSubstitutor substitutor = classResolveResult.getSubstitutor();
            if (psiClass == null) return classType;

            return new PsiImmediateClassType(
                CompletionUtil.getOriginalOrSelf(psiClass), originalizeSubstitutor(substitutor));
          }

          private PsiSubstitutor originalizeSubstitutor(final PsiSubstitutor substitutor) {
            PsiSubstitutor originalSubstitutor = PsiSubstitutor.EMPTY;
            for (final Map.Entry<PsiTypeParameter, PsiType> entry :
                substitutor.getSubstitutionMap().entrySet()) {
              final PsiType value = entry.getValue();
              originalSubstitutor =
                  originalSubstitutor.put(
                      CompletionUtil.getOriginalOrSelf(entry.getKey()),
                      value == null ? null : mapType(value));
            }
            return originalSubstitutor;
          }

          @Override
          public PsiType visitType(PsiType type) {
            return type;
          }
        }.mapType(type);
    if (result == null) {
      throw new AssertionError("Null result for type " + type + " of class " + type.getClass());
    }
    return result;
  }
  @Nullable
  public <T extends PsiExpression> PsiType getType(
      @NotNull T expr, @NotNull Function<T, PsiType> f) {
    PsiType type = getCachedType(expr);
    if (type == null) {
      final RecursionGuard.StackStamp dStackStamp = PsiDiamondType.ourDiamondGuard.markStack();
      final RecursionGuard.StackStamp gStackStamp = PsiResolveHelper.ourGraphGuard.markStack();
      type = f.fun(expr);
      if (!dStackStamp.mayCacheNow() || !gStackStamp.mayCacheNow()) {
        return type;
      }
      if (type == null) type = TypeConversionUtil.NULL_TYPE;
      Reference<PsiType> ref = new SoftReference<PsiType>(type);
      myCalculatedTypes.put(expr, ref);

      if (type instanceof PsiClassReferenceType) {
        // convert reference-based class type to the PsiImmediateClassType, since the reference may
        // become invalid
        PsiClassType.ClassResolveResult result = ((PsiClassReferenceType) type).resolveGenerics();
        PsiClass psiClass = result.getElement();
        type =
            psiClass == null
                ? type // for type with unresolved reference, leave it in the cache
                // for clients still might be able to retrieve its getCanonicalText() from the
                // reference text
                : new PsiImmediateClassType(
                    psiClass,
                    result.getSubstitutor(),
                    ((PsiClassReferenceType) type).getLanguageLevel(),
                    type.getAnnotations());
      }
    }

    if (!type.isValid()) {
      if (expr.isValid()) {
        PsiJavaCodeReferenceElement refInside =
            type instanceof PsiClassReferenceType
                ? ((PsiClassReferenceType) type).getReference()
                : null;
        @NonNls
        String typeinfo =
            type
                + " ("
                + type.getClass()
                + ")"
                + (refInside == null
                    ? ""
                    : "; ref inside: "
                        + refInside
                        + " ("
                        + refInside.getClass()
                        + ") valid:"
                        + refInside.isValid());
        LOG.error(
            "Type is invalid: "
                + typeinfo
                + "; expr: '"
                + expr
                + "' ("
                + expr.getClass()
                + ") is valid");
      } else {
        LOG.error("Expression: '" + expr + "' is invalid, must not be used for getType()");
      }
    }

    return type == TypeConversionUtil.NULL_TYPE ? null : type;
  }