String (boolean presentable, PsiTypeParameterListOwner owner, PsiSubstitutor substitutor) { PsiTypeParameter[] parameters = owner.getTypeParameters(); if (parameters.length == 0) return null; List<PsiType> substituted = ContainerUtil.map(parameters, parameter -> { PsiType type = substitutor.substitute(parameter); if (type instanceof PsiWildcardType) type = ((PsiWildcardType)type).getExtendsBound(); return PsiUtil.resolveClassInClassTypeOnly(type) == parameter ? null : type; }); if (ContainerUtil.exists(substituted, t -> t == null || t instanceof PsiCapturedWildcardType)) return null; if (substituted.equals(ContainerUtil.map(parameters, TypeConversionUtil::typeParameterErasure))) return null; String result = "<" + StringUtil.join(substituted, presentable ? PsiType::getPresentableText : PsiType::getCanonicalText, ", ") + ">"; return result.contains("?") ? null : result; }