mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
make NullityInference traverse light AST
This commit is contained in:
+126
-83
@@ -16,17 +16,28 @@
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.lang.LighterAST;
|
||||
import com.intellij.lang.LighterASTNode;
|
||||
import com.intellij.lang.TreeBackedLighterAST;
|
||||
import com.intellij.openapi.util.RecursionManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.JavaLightTreeUtil;
|
||||
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiModificationTracker;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@@ -43,101 +54,133 @@ public class NullityInference {
|
||||
}
|
||||
|
||||
return CachedValuesManager.getCachedValue(method, () -> {
|
||||
Nullness result = RecursionManager.doPreventingRecursion(method, true, () -> doInferNullity(method));
|
||||
if (result == null) result = Nullness.UNKNOWN;
|
||||
return CachedValueProvider.Result.create(result, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
|
||||
TreeBackedLighterAST tree = new TreeBackedLighterAST(method.getContainingFile().getNode());
|
||||
PsiCodeBlock body = ObjectUtils.assertNotNull(method.getBody());
|
||||
NullityInferenceResult result = doInferNullity(tree, TreeBackedLighterAST.wrap(body.getNode()));
|
||||
Nullness nullness = result == null ? null : RecursionManager.doPreventingRecursion(method, true, () -> result.getNullness(method, body));
|
||||
if (nullness == null) nullness = Nullness.UNKNOWN;
|
||||
return CachedValueProvider.Result.create(nullness, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Nullness doInferNullity(PsiMethod method) {
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body != null) {
|
||||
final AtomicBoolean hasErrors = new AtomicBoolean();
|
||||
final AtomicBoolean hasNotNulls = new AtomicBoolean();
|
||||
final AtomicBoolean hasNulls = new AtomicBoolean();
|
||||
final AtomicBoolean hasUnknowns = new AtomicBoolean();
|
||||
final Set<PsiMethod> delegates = ContainerUtil.newLinkedHashSet();
|
||||
body.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitReturnStatement(PsiReturnStatement statement) {
|
||||
PsiExpression value = statement.getReturnValue();
|
||||
@Nullable
|
||||
private static NullityInferenceResult doInferNullity(LighterAST tree, LighterASTNode body) {
|
||||
AtomicBoolean hasErrors = new AtomicBoolean();
|
||||
AtomicBoolean hasNotNulls = new AtomicBoolean();
|
||||
AtomicBoolean hasNulls = new AtomicBoolean();
|
||||
AtomicBoolean hasUnknowns = new AtomicBoolean();
|
||||
MultiMap<String, ExpressionRange> delegates = MultiMap.create();
|
||||
|
||||
new RecursiveLighterASTNodeWalkingVisitor(tree) {
|
||||
@Override
|
||||
public void visitNode(@NotNull LighterASTNode element) {
|
||||
IElementType type = element.getTokenType();
|
||||
if (type == CLASS || type == ANONYMOUS_CLASS || type == LAMBDA_EXPRESSION) return;
|
||||
|
||||
if (type == TokenType.ERROR_ELEMENT) {
|
||||
hasErrors.set(true);
|
||||
}
|
||||
else if (type == RETURN_STATEMENT) {
|
||||
LighterASTNode value = JavaLightTreeUtil.findExpressionChild(tree, element);
|
||||
if (value == null) {
|
||||
hasErrors.set(true);
|
||||
} else if (value instanceof PsiLiteralExpression) {
|
||||
if (value.textMatches(PsiKeyword.NULL)) {
|
||||
hasNulls.set(true);
|
||||
}
|
||||
else {
|
||||
hasNotNulls.set(true);
|
||||
}
|
||||
} else {
|
||||
visitReturnedValue(value);
|
||||
}
|
||||
else if (value instanceof PsiLambdaExpression || value.getType() instanceof PsiPrimitiveType) {
|
||||
hasNotNulls.set(true);
|
||||
}
|
||||
else if (containsNulls(value)) {
|
||||
hasNulls.set(true);
|
||||
}
|
||||
else if (value instanceof PsiMethodCallExpression) {
|
||||
PsiMethod target = ((PsiMethodCallExpression)value).resolveMethod();
|
||||
if (target == null) {
|
||||
hasUnknowns.set(true);
|
||||
}
|
||||
else {
|
||||
delegates.add(target);
|
||||
}
|
||||
}
|
||||
else {
|
||||
hasUnknowns.set(true);
|
||||
}
|
||||
super.visitReturnStatement(statement);
|
||||
}
|
||||
|
||||
private boolean containsNulls(PsiExpression value) {
|
||||
if (value instanceof PsiConditionalExpression) {
|
||||
return containsNulls(((PsiConditionalExpression)value).getElseExpression()) || containsNulls(((PsiConditionalExpression)value).getThenExpression());
|
||||
}
|
||||
if (value instanceof PsiParenthesizedExpression) {
|
||||
return containsNulls(((PsiParenthesizedExpression)value).getExpression());
|
||||
}
|
||||
return value instanceof PsiLiteralExpression && value.textMatches(PsiKeyword.NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(PsiClass aClass) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLambdaExpression(PsiLambdaExpression expression) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitErrorElement(PsiErrorElement element) {
|
||||
hasErrors.set(true);
|
||||
super.visitErrorElement(element);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNulls.get()) {
|
||||
return InferenceFromSourceUtil.suppressNullable(method) ? Nullness.UNKNOWN : Nullness.NULLABLE;
|
||||
}
|
||||
|
||||
if (hasErrors.get() || hasUnknowns.get() || delegates.size() > 1) {
|
||||
return Nullness.UNKNOWN;
|
||||
super.visitNode(element);
|
||||
}
|
||||
|
||||
if (delegates.size() == 1) {
|
||||
if (NullableNotNullManager.isNotNull(delegates.iterator().next())) {
|
||||
return Nullness.NOT_NULL;
|
||||
private void visitReturnedValue(LighterASTNode expr) {
|
||||
IElementType type = expr.getTokenType();
|
||||
if (containsNulls(expr)) {
|
||||
hasNulls.set(true);
|
||||
}
|
||||
else if (type == LAMBDA_EXPRESSION || type == NEW_EXPRESSION ||
|
||||
type == LITERAL_EXPRESSION || type == BINARY_EXPRESSION || type == POLYADIC_EXPRESSION) {
|
||||
hasNotNulls.set(true);
|
||||
}
|
||||
else if (type == METHOD_CALL_EXPRESSION) {
|
||||
String calledMethod = JavaLightTreeUtil.getNameIdentifierText(tree, tree.getChildren(expr).get(0));
|
||||
if (calledMethod != null) {
|
||||
delegates.putValue(calledMethod, new ExpressionRange(expr, body.getStartOffset()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
hasUnknowns.set(true);
|
||||
}
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
if (hasNotNulls.get()) {
|
||||
return Nullness.NOT_NULL;
|
||||
private boolean containsNulls(@NotNull LighterASTNode value) {
|
||||
if (value.getTokenType() == CONDITIONAL_EXPRESSION) {
|
||||
List<LighterASTNode> exprChildren = JavaLightTreeUtil.getExpressionChildren(tree, value);
|
||||
return exprChildren.subList(1, exprChildren.size()).stream().anyMatch(e -> containsNulls(e));
|
||||
}
|
||||
if (value.getTokenType() == PARENTH_EXPRESSION) {
|
||||
LighterASTNode wrapped = JavaLightTreeUtil.findExpressionChild(tree, value);
|
||||
return wrapped != null && containsNulls(wrapped);
|
||||
}
|
||||
return value.getTokenType() == LITERAL_EXPRESSION && tree.getChildren(value).get(0).getTokenType() == JavaTokenType.NULL_KEYWORD;
|
||||
}
|
||||
|
||||
|
||||
}.visitNode(body);
|
||||
|
||||
|
||||
if (hasNulls.get()) {
|
||||
return new NullityInferenceResult.Predefined(Nullness.NULLABLE);
|
||||
}
|
||||
if (hasErrors.get() || hasUnknowns.get() || delegates.size() > 1) {
|
||||
return null;
|
||||
}
|
||||
if (delegates.size() == 1) {
|
||||
return new NullityInferenceResult.FromDelegate(delegates.get(delegates.keySet().iterator().next()));
|
||||
}
|
||||
|
||||
if (hasNotNulls.get()) {
|
||||
return new NullityInferenceResult.Predefined(Nullness.NOT_NULL);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface NullityInferenceResult {
|
||||
@NotNull
|
||||
Nullness getNullness(@NotNull PsiMethod method, @NotNull PsiCodeBlock body);
|
||||
|
||||
class Predefined implements NullityInferenceResult {
|
||||
private final Nullness myNullness;
|
||||
|
||||
Predefined(Nullness nullness) {
|
||||
myNullness = nullness;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Nullness getNullness(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) {
|
||||
return myNullness == Nullness.NULLABLE && InferenceFromSourceUtil.suppressNullable(method) ? Nullness.UNKNOWN : myNullness;
|
||||
}
|
||||
}
|
||||
|
||||
class FromDelegate implements NullityInferenceResult {
|
||||
private final Collection<ExpressionRange> myDelegates;
|
||||
|
||||
FromDelegate(Collection<ExpressionRange> delegates) {
|
||||
myDelegates = delegates;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Nullness getNullness(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) {
|
||||
return myDelegates.stream().allMatch(range -> isNotNullCall(range, body)) ? Nullness.NOT_NULL : Nullness.UNKNOWN;
|
||||
}
|
||||
|
||||
private static boolean isNotNullCall(ExpressionRange delegate, @NotNull PsiCodeBlock body) {
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)delegate.restoreExpression(body);
|
||||
if (call.getType() instanceof PsiPrimitiveType) return true;
|
||||
|
||||
PsiMethod target = call.resolveMethod();
|
||||
return target != null && NullableNotNullManager.isNotNull(target);
|
||||
}
|
||||
return Nullness.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getCalledMethodName(LighterAST tree, LighterASTNode call) {
|
||||
public static String getCalledMethodName(LighterAST tree, LighterASTNode call) {
|
||||
if (call.getTokenType() == NEW_EXPRESSION) {
|
||||
LighterASTNode anonClass = LightTreeUtil.firstChildOfType(tree, call, ANONYMOUS_CLASS);
|
||||
LighterASTNode ref = LightTreeUtil.firstChildOfType(tree, anonClass != null ? anonClass : call, JAVA_CODE_REFERENCE);
|
||||
|
||||
@@ -39,7 +39,7 @@ public class JavaLightTreeUtil {
|
||||
public static List<LighterASTNode> getArgList(@NotNull LighterAST tree, @Nullable LighterASTNode call) {
|
||||
LighterASTNode anonClass = LightTreeUtil.firstChildOfType(tree, call, ANONYMOUS_CLASS);
|
||||
LighterASTNode exprList = LightTreeUtil.firstChildOfType(tree, anonClass != null ? anonClass : call, EXPRESSION_LIST);
|
||||
return exprList == null ? null : LightTreeUtil.getChildrenOfType(tree, exprList, ElementType.EXPRESSION_BIT_SET);
|
||||
return exprList == null ? null : getExpressionChildren(tree, exprList);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -49,6 +49,11 @@ public class JavaLightTreeUtil {
|
||||
return id != null ? RecordUtil.intern(tree.getCharTable(), id) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<LighterASTNode> getExpressionChildren(@NotNull LighterAST tree, @NotNull LighterASTNode node) {
|
||||
return LightTreeUtil.getChildrenOfType(tree, node, ElementType.EXPRESSION_BIT_SET);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static LighterASTNode findExpressionChild(@NotNull LighterAST tree, @Nullable LighterASTNode node) {
|
||||
return LightTreeUtil.firstChildOfType(tree, node, ElementType.EXPRESSION_BIT_SET);
|
||||
|
||||
Reference in New Issue
Block a user