[Java] IDEA-375132 refactoring: move type parameters substitution to the PsiUtil to make API more consistent

GitOrigin-RevId: 85463e3c3093aec168fb1871de3dd306dc270351
This commit is contained in:
Georgii Ustinov
2025-10-27 10:40:41 +00:00
committed by intellij-monorepo-bot
parent 233f2c8bd5
commit 877c7ad663
3 changed files with 70 additions and 58 deletions
@@ -6,7 +6,6 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -321,58 +320,6 @@ public final class JavaGenericsUtil {
return getCollectionItemType(expression.getType(), expression.getResolveScope());
}
/**
* Substitutes all values for type parameters of the {@code baseType} from the {@code derivedType}.
*
* @return null if the calculation wasn't successful, list of substituted types otherwise.
*
* @see JavaGenericsUtil#getCollectionItemType(PsiExpression)
*/
public static @Nullable List<@NotNull PsiType> getParentParameterTypeListFromDerivedType(@Nullable PsiType baseType, @Nullable PsiType derivedType) {
if (derivedType instanceof PsiClassType) {
PsiClass baseClass = PsiTypesUtil.getPsiClass(baseType);
if (baseClass == null) return null;
final PsiClassType.ClassResolveResult resolveResult = getDerivedClassTypeResolveResult((PsiClassType)derivedType);
PsiClass derivedClass = resolveResult.getElement();
if (derivedClass == null) return null;
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
PsiTypeParameter[] parameters = baseClass.getTypeParameters();
PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getClassSubstitutor(baseClass, derivedClass, substitutor);
if (superClassSubstitutor == null) return null;
return ContainerUtil.map(
parameters, typeParameter -> {
PsiType substitutedType = superClassSubstitutor.substitute(typeParameter);
return substitutedType == null ? PsiType.getJavaLangObject(derivedClass.getManager(), derivedClass.getResolveScope()) : substitutedType;
}
);
} else if (derivedType instanceof PsiIntersectionType) {
for (PsiType conjunct : ((PsiIntersectionType)derivedType).getConjuncts()) {
List<@NotNull PsiType> candidates = getParentParameterTypeListFromDerivedType(baseType, conjunct);
if (candidates != null) return candidates;
}
}
return null;
}
/**
* Retrieves the resolve result corresponding to the given {@code derivedType}. If the initial resolved
* result is a type parameter with an upper bound, then the upper bound is returned, otherwise the initial resolve result.
*/
private static @NotNull PsiClassType.ClassResolveResult getDerivedClassTypeResolveResult(PsiClassType derivedType) {
final PsiClassType.ClassResolveResult resolveResult = derivedType.resolveGenerics();
PsiClass derivedClass = resolveResult.getElement();
if (derivedClass instanceof PsiTypeParameter) {
PsiTypeParameter typeParameter = (PsiTypeParameter)derivedClass;
PsiClassType[] types = typeParameter.getExtendsListTypes();
if (types.length > 1) return PsiClassType.ClassResolveResult.EMPTY;
else if (types.length == 1) {
return types[0].resolveGenerics();
}
}
return resolveResult;
}
public static @Nullable PsiType getCollectionItemType(@Nullable PsiType type, @NotNull GlobalSearchScope scope) {
if (type instanceof PsiArrayType) {
return ((PsiArrayType)type).getComponentType();
@@ -1207,6 +1207,49 @@ public final class PsiUtil extends PsiUtilCore {
return type != null ? type : substituteTypeParameter(psiType, CommonClassNames.JAVA_UTIL_COLLECTION, 0, eraseTypeParameter);
}
/**
* Substitutes all values for type parameters of the {@code superClass} from the {@code derivedType}.
*
* @return null if the calculation wasn't successful, list of substituted types otherwise.
*/
@Contract("null, _, _-> null")
public static @Nullable List<@NotNull PsiType> substituteTypeParameters(@Nullable PsiType derivedType, @NotNull PsiClass superClass, boolean eraseTypeParameter) {
if (derivedType == null) return null;
PsiClassType.ClassResolveResult resolveResult = getDerivedClassTypeResolveResult(derivedType);
if (resolveResult == null) return null;
PsiClass derivedClass = resolveResult.getElement();
if (derivedClass == null) return null;
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
PsiTypeParameter[] parameters = superClass.getTypeParameters();
PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getClassSubstitutor(superClass, derivedClass, substitutor);
List<@NotNull PsiType> typeParameterList = ContainerUtil.mapNotNull(
parameters, typeParameter -> {
return substituteType(typeParameter, superClassSubstitutor, eraseTypeParameter);
}
);
if (typeParameterList.size() != parameters.length) return null;
return typeParameterList;
}
/**
* Retrieves the resolve result corresponding to the given {@code derivedType}. If the initial resolved
* result is a type parameter with an upper bound, then the upper bound is returned, otherwise the initial resolve result.
*/
private static @Nullable PsiClassType.ClassResolveResult getDerivedClassTypeResolveResult(@Nullable PsiType derivedType) {
final PsiClassType.ClassResolveResult resolveResult = resolveClass(derivedType);
if (resolveResult == PsiClassType.ClassResolveResult.EMPTY) return null;
PsiClass derivedClass = resolveResult.getElement();
if (derivedClass instanceof PsiTypeParameter) {
PsiTypeParameter typeParameter = (PsiTypeParameter)derivedClass;
PsiClassType[] types = typeParameter.getExtendsListTypes();
if (types.length > 1) return PsiClassType.ClassResolveResult.EMPTY;
else if (types.length == 1) {
return types[0].resolveGenerics();
}
}
return resolveResult;
}
@Contract("null, _, _, _ -> null")
public static @Nullable PsiType substituteTypeParameter(@Nullable PsiType psiType, @NotNull String superClass, int typeParamIndex, boolean eraseTypeParameter) {
PsiClassType.ClassResolveResult classResolveResult = resolveClass(psiType);
@@ -1242,9 +1285,16 @@ public final class PsiUtil extends PsiUtilCore {
if (parameters.length <= typeParamIndex) return PsiType.getJavaLangObject(psiClass.getManager(), psiClass.getResolveScope());
PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, psiClass, classResolveResult.getSubstitutor());
PsiType type = substitutor.substitute(parameters[typeParamIndex]);
return substituteType(parameters[typeParamIndex], substitutor, eraseTypeParameter);
}
private static @Nullable PsiType substituteType(@NotNull PsiTypeParameter typeParameter,
@Nullable PsiSubstitutor substitutor,
boolean eraseTypeParameter) {
if (substitutor == null) return null;
PsiType type = substitutor.substitute(typeParameter);
if (type == null && eraseTypeParameter) {
return TypeConversionUtil.typeParameterErasure(parameters[typeParamIndex]);
return TypeConversionUtil.typeParameterErasure(typeParameter);
}
return type;
}
@@ -2,8 +2,9 @@
package com.intellij.util;
import com.intellij.codeInsight.*;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.ApiStatus;
@@ -166,8 +167,10 @@ public final class JavaTypeNullabilityUtil {
private static @NotNull NullabilityConflict getNullabilityConflictInTypeArguments(@NotNull PsiType leftType,
@NotNull PsiType rightType,
boolean checkNotNullToNull) {
List<PsiType> leftParameterTypeList = JavaGenericsUtil.getParentParameterTypeListFromDerivedType(leftType, leftType);
List<PsiType> rightParameterTypeList = JavaGenericsUtil.getParentParameterTypeListFromDerivedType(leftType, rightType);
PsiClass leftClass = PsiTypesUtil.getPsiClass(leftType);
if (leftClass == null) return NullabilityConflict.UNKNOWN;
List<PsiType> leftParameterTypeList = getParentParameterTypeListFromDerivedType(leftType, leftClass);
List<PsiType> rightParameterTypeList = getParentParameterTypeListFromDerivedType(rightType, leftClass);
if (leftParameterTypeList == null ||
rightParameterTypeList == null ||
leftParameterTypeList.size() != rightParameterTypeList.size()) {
@@ -190,6 +193,18 @@ public final class JavaTypeNullabilityUtil {
return NullabilityConflict.UNKNOWN;
}
private static @Nullable List<@NotNull PsiType> getParentParameterTypeListFromDerivedType(@NotNull PsiType derivedType,
@NotNull PsiClass superClass) {
if (derivedType instanceof PsiIntersectionType) {
for (PsiType conjunct : ((PsiIntersectionType)derivedType).getConjuncts()) {
List<@NotNull PsiType> candidates = getParentParameterTypeListFromDerivedType(conjunct, superClass);
if (candidates != null) return candidates;
}
return null;
}
return PsiUtil.substituteTypeParameters(derivedType, superClass, true);
}
private static boolean isAllowedNullabilityConflictType(boolean checkNotNullToNull, @NotNull NullabilityConflict nullabilityConflict) {
return nullabilityConflict != NullabilityConflict.UNKNOWN &&
(checkNotNullToNull || nullabilityConflict != NullabilityConflict.NOT_NULL_TO_NULL);