[java-highlighting] Finish moving module access errors

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 0f89a7cd9609aea2780e132118aae892a6b0bafd
This commit is contained in:
Tagir Valeev
2025-02-24 15:25:03 +01:00
committed by intellij-monorepo-bot
parent f976c0b04c
commit 2250bb64ee
25 changed files with 248 additions and 404 deletions

View File

@@ -3,11 +3,11 @@ package com.intellij.java.codeserver.core
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.JavaModuleSystem
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.psi.PsiJavaModule
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.pom.java.JavaFeature
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightJavaModule
import com.intellij.psi.util.PsiUtil
/**
* Represents a JPMS module and the corresponding module in IntelliJ project model
@@ -55,4 +55,49 @@ sealed interface JpmsModuleInfo {
return JpmsModuleAccessInfo(current, this)
}
}
companion object {
/**
* Find module info structures when accessing a given location.
*
* @param targetPackageName package name which about to be accessed
* @param targetFile concrete target file which is about to be accessed; null if not known (in this case,
* multiple results could be returned, as multiple source roots may define a given package)
* @param place source place from where the access is requested
* @return list of TargetModuleInfo structures that describe the possible target; empty list if the target package is empty
* (which is generally an error), null if not applicable (e.g., modules are not supported at place;
* target does not belong to any module; etc.). In this case, no access problem should be reported.
*/
@JvmStatic
fun findTargetModuleInfos(targetPackageName: String, targetFile: PsiFile?, place: PsiFile): List<TargetModuleInfo>? {
val originalTargetFile = targetFile?.originalFile
if (!PsiUtil.isAvailable(JavaFeature.MODULES, place)) return null
val useVFile = place.virtualFile
val project = place.project
val index = ProjectFileIndex.getInstance(project)
if (useVFile != null && index.isInLibrarySource(useVFile)) return null
if (originalTargetFile != null && originalTargetFile.isPhysical) {
return listOf(TargetModuleInfo(originalTargetFile, targetPackageName))
}
if (useVFile == null) return null
val target = JavaPsiFacade.getInstance(project).findPackage(targetPackageName) ?: return null
val module = index.getModuleForFile(useVFile) ?: return null
val test = index.isInTestSourceContent(useVFile)
val moduleScope = module.getModuleWithDependenciesAndLibrariesScope(test)
val dirs = target.getDirectories(moduleScope)
val packageName = target.qualifiedName
if (dirs.isEmpty()) {
return if (target.getFiles(moduleScope).isEmpty()) {
listOf()
}
else {
null
}
}
return dirs.map { dir -> TargetModuleInfo(dir, packageName) }
}
}
}

View File

@@ -303,6 +303,7 @@ reference.class.in.default.package=Class ''{0}'' is in the default package
reference.non.static.from.static.context=Non-static {0} ''{1}'' cannot be referenced from a static context
reference.outer.type.parameter.from.static.context=''{0}'' cannot be referenced from a static context
reference.select.from.type.parameter=Cannot select from a type parameter
reference.package.not.found=Package not found: {0}
statement.case.outside.switch=Case statement outside switch
statement.invalid=Invalid statement

View File

@@ -923,6 +923,9 @@ final class ExpressionChecker {
if (!result.isStaticsScopeCorrect()) {
myVisitor.report(JavaErrorKinds.REFERENCE_NON_STATIC_FROM_STATIC_CONTEXT.create(ref, resolved));
}
if (resolved instanceof PsiModifierListOwner owner) {
myVisitor.myModuleChecker.checkModuleAccess(owner, ref);
}
}
private void checkUnresolvedReference(@NotNull PsiJavaCodeReferenceElement ref, @NotNull JavaResolveResult result) {

View File

@@ -8,6 +8,7 @@ import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErro
import com.intellij.lang.jvm.JvmModifier;
import com.intellij.psi.*;
import com.intellij.psi.impl.IncompleteModelUtil;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
@@ -389,4 +390,51 @@ final class FunctionChecker {
}
}
}
// 15.13 | 15.27
// It is a compile-time error if any class or interface mentioned by either U or the function type of U
// is not accessible from the class or interface in which the method reference expression appears.
void checkFunctionalInterfaceTypeAccessible(@NotNull PsiFunctionalExpression expression,
@NotNull PsiType functionalInterfaceType) {
checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType, true);
}
private void checkFunctionalInterfaceTypeAccessible(@NotNull PsiFunctionalExpression expression,
@NotNull PsiType functionalInterfaceType,
boolean checkFunctionalTypeSignature) {
PsiClassType.ClassResolveResult resolveResult =
PsiUtil.resolveGenericsClassInType(PsiClassImplUtil.correctType(functionalInterfaceType, expression.getResolveScope()));
PsiClass psiClass = resolveResult.getElement();
if (psiClass == null) return;
if (!PsiUtil.isAccessible(myVisitor.project(), psiClass, expression, null)) {
myVisitor.myModifierChecker.reportAccessProblem(expression, psiClass, resolveResult);
return;
}
for (PsiType type : resolveResult.getSubstitutor().getSubstitutionMap().values()) {
if (type != null) {
checkFunctionalInterfaceTypeAccessible(expression, type, false);
if (myVisitor.hasErrorResults()) return;
}
}
PsiMethod psiMethod = checkFunctionalTypeSignature ? LambdaUtil.getFunctionalInterfaceMethod(resolveResult) : null;
if (psiMethod != null) {
PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(psiMethod, resolveResult);
for (PsiParameter parameter : psiMethod.getParameterList().getParameters()) {
PsiType substitute = substitutor.substitute(parameter.getType());
if (substitute != null) {
checkFunctionalInterfaceTypeAccessible(expression, substitute, false);
if (myVisitor.hasErrorResults()) return;
}
}
PsiType substitute = substitutor.substitute(psiMethod.getReturnType());
if (substitute != null) {
checkFunctionalInterfaceTypeAccessible(expression, substitute, false);
}
return;
}
myVisitor.myModuleChecker.checkModuleAccess(psiClass, expression);
}
}

View File

@@ -60,7 +60,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this);
private final @NotNull FunctionChecker myFunctionChecker = new FunctionChecker(this);
final @NotNull PatternChecker myPatternChecker = new PatternChecker(this);
private final @NotNull ModuleChecker myModuleChecker = new ModuleChecker(this);
final @NotNull ModuleChecker myModuleChecker = new ModuleChecker(this);
final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this);
final @NotNull ExpressionChecker myExpressionChecker = new ExpressionChecker(this);
private final @NotNull SwitchChecker mySwitchChecker = new SwitchChecker(this);
@@ -426,6 +426,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
report(JavaErrorKinds.LAMBDA_NOT_FUNCTIONAL_INTERFACE.create(expression, functionalInterfaceType));
}
if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceContext(expression, functionalInterfaceType);
if (!hasErrorResults()) myFunctionChecker.checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType);
}
if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceResolve(expression, results, functionalInterfaceType);
if (!hasErrorResults()) myFunctionChecker.checkMethodReferenceReturnType(expression, result, functionalInterfaceType);
@@ -472,7 +473,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
else if (parent instanceof PsiClass aClass) {
if (!hasErrorResults()) myClassChecker.checkDuplicateNestedClass(aClass);
if (!hasErrorResults() && !(aClass instanceof PsiAnonymousClass)) {
/* anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/
/* an anonymous class is highlighted in HighlightClassUtil.checkAbstractInstantiation()*/
myClassChecker.checkClassMustBeAbstract(aClass);
}
if (!hasErrorResults()) {
@@ -933,6 +934,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (functionalInterfaceType != null) {
myFunctionChecker.checkExtendsSealedClass(expression, functionalInterfaceType);
if (!hasErrorResults()) myFunctionChecker.checkInterfaceFunctional(expression, functionalInterfaceType);
if (!hasErrorResults()) myFunctionChecker.checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType);
}
else if (LambdaUtil.getFunctionalInterfaceType(expression, true) != null) {
report(JavaErrorKinds.LAMBDA_TYPE_INFERENCE_FAILURE.create(expression));

View File

@@ -178,9 +178,7 @@ final class ModifierChecker {
}
}
void reportAccessProblem(@NotNull PsiJavaCodeReferenceElement ref,
@NotNull PsiModifierListOwner resolved,
@NotNull JavaResolveResult result) {
void reportAccessProblem(@NotNull PsiElement ref, @NotNull PsiModifierListOwner resolved, @NotNull JavaResolveResult result) {
result = withElement(result, resolved);
if (resolved.hasModifierProperty(PsiModifier.PRIVATE)) {
myVisitor.report(JavaErrorKinds.ACCESS_PRIVATE.create(ref, result));
@@ -202,15 +200,11 @@ final class ModifierChecker {
return;
}
checkModuleAccess(resolved, ref);
myVisitor.myModuleChecker.checkModuleAccess(resolved, ref);
if (myVisitor.hasErrorResults()) return;
myVisitor.report(JavaErrorKinds.ACCESS_GENERIC_PROBLEM.create(ref, result));
}
private void checkModuleAccess(@NotNull PsiModifierListOwner resolved, @NotNull PsiElement ref) {
// TODO: JPMS
}
private static @NotNull JavaResolveResult withElement(@NotNull JavaResolveResult original, @NotNull PsiElement newElement) {
if (newElement == original.getElement()) return original;
return new JavaResolveResult() {

View File

@@ -4,7 +4,9 @@ package com.intellij.java.codeserver.highlighting;
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
import com.intellij.java.codeserver.core.JavaServiceProviderUtil;
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo;
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo.JpmsModuleAccessProblem;
import com.intellij.java.codeserver.core.JpmsModuleInfo;
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.openapi.module.Module;
@@ -24,6 +26,7 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -299,7 +302,7 @@ final class ModuleChecker {
myVisitor.report(kind.create(statement));
}
JavaErrorKind.Parameterized<PsiElement, JpmsModuleAccessInfo> accessError(@NotNull JpmsModuleAccessInfo.JpmsModuleAccessProblem problem) {
JavaErrorKind.Parameterized<PsiElement, JpmsModuleAccessInfo> accessError(@NotNull JpmsModuleAccessProblem problem) {
return switch (problem) {
case FROM_NAMED -> JavaErrorKinds.MODULE_ACCESS_FROM_NAMED;
case FROM_UNNAMED -> JavaErrorKinds.MODULE_ACCESS_FROM_UNNAMED;
@@ -325,10 +328,51 @@ final class ModuleChecker {
return;
}
JpmsModuleAccessInfo moduleAccess = new JpmsModuleInfo.TargetModuleInfo(target, "").accessAt(myVisitor.file().getOriginalFile());
JpmsModuleAccessInfo.JpmsModuleAccessProblem problem = moduleAccess.checkModuleAccess(statement);
JpmsModuleAccessProblem problem = moduleAccess.checkModuleAccess(statement);
if (problem != null) {
myVisitor.report(accessError(problem).create(statement, moduleAccess));
}
}
private static @NotNull PsiElement findPackagePrefix(@NotNull PsiJavaCodeReferenceElement ref) {
PsiElement candidate = ref;
while (candidate instanceof PsiJavaCodeReferenceElement element) {
if (element.resolve() instanceof PsiPackage) return candidate;
candidate = element.getQualifier();
}
return ref;
}
void checkModuleAccess(@NotNull PsiModifierListOwner target, @NotNull PsiElement ref) {
if (target instanceof PsiClass targetClass && !(target instanceof PsiTypeParameter)) {
String packageName = PsiUtil.getPackageName(targetClass);
if (packageName != null) {
checkAccess(packageName, target.getContainingFile(), ref);
}
}
else if (target instanceof PsiPackage targetPackage) {
checkAccess(targetPackage.getQualifiedName(), null, ref);
}
}
private void checkAccess(@NotNull String targetPackageName, @Nullable PsiFile targetFile, @NotNull PsiElement place) {
PsiFile file = myVisitor.file().getOriginalFile();
List<JpmsModuleInfo.@NotNull TargetModuleInfo> infos = JpmsModuleInfo.findTargetModuleInfos(targetPackageName, targetFile, file);
if (infos == null) return;
if (infos.isEmpty()) {
myVisitor.report(JavaErrorKinds.REFERENCE_PACKAGE_NOT_FOUND.create(place, targetPackageName));
return;
}
JavaCompilationError<PsiElement, JpmsModuleAccessInfo> error = null;
for (JpmsModuleInfo.TargetModuleInfo info : infos) {
JpmsModuleAccessInfo moduleAccessInfo = info.accessAt(file);
JpmsModuleAccessProblem problem = moduleAccessInfo.checkAccess(file, JpmsModuleAccessInfo.JpmsModuleAccessMode.READ);
if (problem == null) return;
if (error == null) {
PsiElement anchor = place instanceof PsiJavaCodeReferenceElement ref ? findPackagePrefix(ref) : place;
error = accessError(problem).create(anchor, moduleAccessInfo);
}
}
myVisitor.report(error);
}
}

View File

@@ -117,8 +117,8 @@ final class JavaErrorFormatUtil {
return nameElement.getTextRangeInParent();
}
}
if (element instanceof PsiReferenceExpression refExpression) {
PsiElement nameElement = refExpression.getReferenceNameElement();
if (element instanceof PsiJavaCodeReferenceElement ref) {
PsiElement nameElement = ref.getReferenceNameElement();
if (nameElement != null) {
return nameElement.getTextRangeInParent();
}

View File

@@ -1157,6 +1157,9 @@ public final class JavaErrorKinds {
.withRawDescription((ref, refElement) -> message("reference.outer.type.parameter.from.static.context", refElement.getName()));
public static final Simple<PsiJavaCodeReferenceElement> REFERENCE_SELECT_FROM_TYPE_PARAMETER =
error(PsiJavaCodeReferenceElement.class, "reference.select.from.type.parameter");
public static final Parameterized<PsiElement, String> REFERENCE_PACKAGE_NOT_FOUND =
parameterized(PsiElement.class, String.class, "reference.package.not.found")
.withRawDescription((psi, packageName) -> message("reference.package.not.found", packageName));
public static final Simple<PsiSwitchLabelStatementBase> STATEMENT_CASE_OUTSIDE_SWITCH = error("statement.case.outside.switch");
public static final Simple<PsiStatement> STATEMENT_INVALID = error("statement.invalid");
@@ -1307,23 +1310,23 @@ public final class JavaErrorKinds {
parameterized(PsiExpression.class, PsiType.class, "string.template.raw.processor")
.withRawDescription((psi, type) -> message("string.template.raw.processor", type.getPresentableText()));
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_PRIVATE =
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.private")
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_PRIVATE =
parameterized(PsiElement.class, JavaResolveResult.class, "access.private")
.withRange((psi, cls) -> getRange(psi))
.withRawDescription((psi, result) -> message("access.private", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_PROTECTED =
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.protected")
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_PROTECTED =
parameterized(PsiElement.class, JavaResolveResult.class, "access.protected")
.withRange((psi, cls) -> getRange(psi))
.withRawDescription(
(psi, result) -> message("access.protected", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_PACKAGE_LOCAL =
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.package.local")
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_PACKAGE_LOCAL =
parameterized(PsiElement.class, JavaResolveResult.class, "access.package.local")
.withRange((psi, cls) -> getRange(psi))
.withRawDescription(
(psi, result) -> message("access.package.local", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));
public static final Parameterized<PsiJavaCodeReferenceElement, JavaResolveResult> ACCESS_GENERIC_PROBLEM =
parameterized(PsiJavaCodeReferenceElement.class, JavaResolveResult.class, "access.generic.problem")
.withAnchor(psi -> requireNonNullElse(psi.getReferenceNameElement(), psi))
public static final Parameterized<PsiElement, JavaResolveResult> ACCESS_GENERIC_PROBLEM =
parameterized(PsiElement.class, JavaResolveResult.class, "access.generic.problem")
.withRange((psi, cls) -> getRange(psi))
.withRawDescription(
(psi, result) -> message("access.generic.problem", formatResolvedSymbol(result), formatResolvedSymbolContainer(result)));