From b7c4e04a61e93e980410a9b0ffd710c850e78368 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 26 Oct 2016 11:06:51 +0200 Subject: [PATCH] make NullityInference traverse light AST --- .../dataFlow/NullityInference.java | 209 +++++++++++------- .../java/JavaFunctionalExpressionIndex.java | 2 +- .../psi/impl/source/JavaLightTreeUtil.java | 7 +- 3 files changed, 133 insertions(+), 85 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java index e27fd76421f1..cb083452997a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java @@ -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 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 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 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 myDelegates; + + FromDelegate(Collection 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; } } diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/java/JavaFunctionalExpressionIndex.java b/java/java-indexing-impl/src/com/intellij/psi/impl/java/JavaFunctionalExpressionIndex.java index 8a6d07971889..968732f4cff5 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/java/JavaFunctionalExpressionIndex.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/java/JavaFunctionalExpressionIndex.java @@ -270,7 +270,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension 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 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);