make NullityInference traverse light AST

This commit is contained in:
peter
2016-10-26 12:45:39 +02:00
parent ac9cac008a
commit b7c4e04a61
3 changed files with 133 additions and 85 deletions
@@ -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;
}
}
@@ -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);