myMethodRefQualifiers = new HashMap<>();
+
+ CFGBuilder(ControlFlowAnalyzer analyzer) {
+ myAnalyzer = analyzer;
+ }
+
+ /**
+ * Generate instructions to push unknown DfaValue on stack.
+ *
+ * Stack before: ...
+ *
+ * Stack after: ... unknown
+ *
+ * @return this builder
+ */
+ public CFGBuilder pushUnknown() {
+ myAnalyzer.pushUnknown();
+ return this;
+ }
+
+ /**
+ * Generate instructions to push null DfaValue on stack.
+ *
+ * Stack before: ...
+ *
+ * Stack after: ... null
+ *
+ * @return this builder
+ */
+ public CFGBuilder pushNull() {
+ myAnalyzer.addInstruction(new PushInstruction(getFactory().getConstFactory().getNull(), null));
+ return this;
+ }
+
+ /**
+ * Generate instructions to evaluate given expression and push its result on stack.
+ *
+ * Stack before: ...
+ *
+ * Stack after: ... expression_result
+ *
+ * @param expression expression to evaluate
+ * @return this builder
+ */
+ public CFGBuilder pushExpression(PsiExpression expression) {
+ expression.accept(myAnalyzer);
+ return this;
+ }
+
+ /**
+ * Generate instructions to push given variable on stack for subsequent write.
+ *
+ * Stack before: ...
+ *
+ * Stack after: ... variable
+ *
+ * @param variable to push
+ * @return this builder
+ */
+ public CFGBuilder pushVariable(PsiVariable variable) {
+ myAnalyzer.addInstruction(
+ new PushInstruction(getFactory().getVarFactory().createVariableValue(variable, false), null, true));
+ return this;
+ }
+
+ /**
+ * Generate instructions to push given DfaValue on stack.
+ *
+ * Stack before: ...
+ *
+ * Stack after: ... value
+ *
+ * @param value value to push
+ * @return this builder
+ */
+ public CFGBuilder push(DfaValue value) {
+ myAnalyzer.addInstruction(new PushInstruction(value, null));
+ return this;
+ }
+
+ /**
+ * Generate instructions to pop single DfaValue from stack
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ...
+ *
+ * @return this builder
+ */
+ public CFGBuilder pop() {
+ myAnalyzer.addInstruction(new PopInstruction());
+ return this;
+ }
+
+ /**
+ * Generate instructions to duplicate top stack value
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ... value value
+ *
+ * @return this builder
+ */
+ public CFGBuilder dup() {
+ myAnalyzer.addInstruction(new DupInstruction());
+ return this;
+ }
+
+ /**
+ * Generate instructions to pop given number of stack values, then push some or all of popped values referred by indices,
+ * possibly duplicating them
+ *
+ * E.g. {@code splice(2, 0, 1, 0)} will change "... val1 val2" stack to "... val2 val1 val2".
+ * Stack depth is increased by {@code replacement.length - count}.
+ *
+ * @param count number of values to pop
+ * @param replacement replacement indices from 0 to {@code count-1}. Index 0 = top stack value, index 1 = next value and so on.
+ * @return this builder
+ */
+ public CFGBuilder splice(int count, int... replacement) {
+ myAnalyzer.addInstruction(new SpliceInstruction(count, replacement));
+ return this;
+ }
+
+ /**
+ * Generate instructions to swap two top stack values
+ *
+ * Stack before: ... val1 val2
+ *
+ * Stack after: ... val2 val1
+ *
+ * @return this builder
+ */
+ public CFGBuilder swap() {
+ myAnalyzer.addInstruction(new SwapInstruction());
+ return this;
+ }
+
+ /**
+ * Generate instructions to invoke the method associated with given method call assuming that method arguments and qualifier
+ * are already on stack. If vararg call is specified, vararg arguments should be placed as is, without packing into array,
+ * so number of arguments may differ from number of method parameters.
+ *
+ * Stack before: ... qualifier arg1 arg2 ... argN
+ *
+ * Stack after: ... return value
+ *
+ * Note that qualifier must be present even if method is static (use {@link #pushUnknown()}). Similarly, return value will be pushed
+ * on stack always, even if method is void.
+ *
+ * @param call a method call to generate invocation upon
+ * @return this builder
+ */
+ public CFGBuilder invoke(PsiMethodCallExpression call) {
+ myAnalyzer.addBareCall(call, call.getMethodExpression());
+ return this;
+ }
+
+ /**
+ * Generate instructions to compare two values on top of stack with given relation operation (e.g. {@link JavaTokenType#GT}).
+ *
+ * Stack before: ... val1 val2
+ *
+ * Stack after: ... result_of_val1_relation_val2
+ *
+ * @param relation relation to use for comparison
+ * @return this builder
+ */
+ private CFGBuilder compare(IElementType relation) {
+ myAnalyzer.addInstruction(new BinopInstruction(relation, null, myAnalyzer.getContext().getProject()));
+ return this;
+ }
+
+ /**
+ * Generate instructions to start a conditional block based on stack top value, consuming this value
+ *
+ * Stack before: ... condition
+ *
+ * Stack after: ...
+ *
+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
+ * Nested conditional blocks are acceptable.
+ *
+ * @param value a value condition must have to visit conditional block
+ * @return this builder
+ */
+ public CFGBuilder ifConditionIs(boolean value) {
+ ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, value, null);
+ myBranches.add(gotoInstruction);
+ myAnalyzer.addInstruction(gotoInstruction);
+ return this;
+ }
+
+ /**
+ * Generate instructions to start a conditional block based on result of comparison of
+ * two stack values with given relation (e.g. {@link JavaTokenType#GT}), consuming these values.
+ *
+ * Stack before: ... val1 val2
+ *
+ * Stack after: ...
+ *
+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
+ * Nested conditional blocks are acceptable.
+ *
+ * @param relation a relation to use to compare two stack values. Conditional block will be executed if "val1 relation val2" is true.
+ * @return this builder
+ */
+ public CFGBuilder ifCondition(IElementType relation) {
+ return compare(relation).ifConditionIs(true);
+ }
+
+ /**
+ * Generate instructions to start a conditional block which is executed if top stack value is not null.
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ...
+ *
+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
+ * Nested conditional blocks are acceptable.
+ *
+ * @return this builder
+ */
+ public CFGBuilder ifNotNull() {
+ return pushNull().ifCondition(JavaTokenType.NE);
+ }
+
+ /**
+ * Generate instructions to start a conditional block which is executed if top stack value is null.
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ...
+ *
+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
+ * Nested conditional blocks are acceptable.
+ *
+ * @return this builder
+ */
+ public CFGBuilder ifNull() {
+ return pushNull().ifCondition(JavaTokenType.EQEQ);
+ }
+
+ /**
+ * Generate instructions to finish a conditional block started with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)},
+ * {@link #ifNull()} or {@link #ifNotNull()}. Stack is unchanged.
+ *
+ * @return this builder
+ */
+ public CFGBuilder endIf() {
+ myBranches.removeLast().setOffset(myAnalyzer.getInstructionCount());
+ return this;
+ }
+
+ /**
+ * Generate instructions to finish a "then-branch" and start an "else-branch" of a conditional block started
+ * with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)}, {@link #ifNull()} or {@link #ifNotNull()}.
+ * Stack is unchanged.
+ *
+ * @return this builder
+ */
+ public CFGBuilder elseBranch() {
+ GotoInstruction gotoInstruction = new GotoInstruction(null);
+ myAnalyzer.addInstruction(gotoInstruction);
+ endIf();
+ myBranches.add(gotoInstruction);
+ return this;
+ }
+
+ /**
+ * Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #endWhileUnknown()}.
+ * Nested loops are acceptable.
+ *
+ * @return this builder
+ */
+ public CFGBuilder doWhile() {
+ ConditionalGotoInstruction jump = new ConditionalGotoInstruction(null, false, null);
+ jump.setOffset(myAnalyzer.getInstructionCount());
+ myBranches.add(jump);
+ return this;
+ }
+
+ /**
+ * Generate instructions to end a loop started via {@link #doWhile()} by unknown condition. Stack is unchanged.
+ *
+ * @return this builder
+ */
+ public CFGBuilder endWhileUnknown() {
+ pushUnknown();
+ myAnalyzer.addInstruction((ConditionalGotoInstruction)myBranches.removeLast());
+ return this;
+ }
+
+ /**
+ * Generate instructions to box or unbox stack top value if necessary to satisfy the specified expected type.
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ... boxed_or_unboxed_value
+ *
+ * @param expression an expression which result is placed on the top of stack
+ * @param expectedType an expected type
+ *
+ * @return this builder
+ */
+ public CFGBuilder boxUnbox(PsiExpression expression, PsiType expectedType) {
+ myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expectedType);
+ return this;
+ }
+
+ /**
+ * Generate instructions to box or unbox stack top value if necessary to satisfy the specified expected type.
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ... boxed_or_unboxed_value
+ *
+ * @param expression an expression which is used to anchor instructions so issued warnings can point to this expression
+ * @param expressionType an actual type of the expression on top of stack
+ * @param expectedType an expected type
+ *
+ * @return this builder
+ */
+ public CFGBuilder boxUnbox(PsiExpression expression, PsiType expressionType, PsiType expectedType) {
+ myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expressionType, expectedType);
+ return this;
+ }
+
+ /**
+ * Generate instructions to flush known values of non-final fields of mutable classes.
+ *
+ * @return this builder
+ */
+ public CFGBuilder flushFields() {
+ myAnalyzer.addInstruction(new FlushVariableInstruction(null));
+ return this;
+ }
+
+ /**
+ * Generate instructions to check that stack top value is not null issuing a warning like "argument is nullable" if
+ * this is not satisfied. Stack is unchanged.
+ *
+ * @param expression an anchor expression to bind a warning to
+ * @param problem a type of nullability problem to report if value is nullable
+ * @return this builder
+ */
+ public CFGBuilder checkNotNull(PsiExpression expression, NullabilityProblem problem) {
+ myAnalyzer.addInstruction(new CheckNotNullInstruction(expression, problem));
+ return this;
+ }
+
+ /**
+ * Generate instructions to assign top stack value to the second stack value
+ * (usually pushed via {@link #pushVariable(PsiVariable)}).
+ *
+ * Stack before: ... variable_for_write value
+ *
+ * Stack after: ... variable
+ *
+ * @return this builder
+ */
+ public CFGBuilder assign() {
+ myAnalyzer.addInstruction(new AssignInstruction(null, null));
+ return this;
+ }
+
+ /**
+ * Generate instructions to assign top stack value to the specified variable
+ *
+ * Stack before: ... value
+ *
+ * Stack after: ... variable
+ *
+ * @return this builder
+ */
+ public CFGBuilder assignTo(PsiVariable var) {
+ return pushVariable(var).swap().assign();
+ }
+
+ /**
+ * Returns a {@link DfaValueFactory} associated with current control flow.
+ *
+ * @return a {@link DfaValueFactory} associated with current control flow.
+ */
+ public DfaValueFactory getFactory() {
+ return myAnalyzer.getFactory();
+ }
+
+ /**
+ * Generate instructions to evaluate functional expression (but not invoke the function itself
+ * -- see {@link #invokeFunction(int, PsiExpression)}). Stack is unchanged.
+ *
+ * @param functionalExpression a functional expression to evaluate
+ * @return this builder
+ */
+ public CFGBuilder evaluateFunction(@Nullable PsiExpression functionalExpression) {
+ PsiExpression stripped = PsiUtil.deparenthesizeExpression(functionalExpression);
+ if (stripped == null || stripped instanceof PsiLambdaExpression) {
+ return this;
+ }
+ if (stripped instanceof PsiMethodReferenceExpression) {
+ PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)stripped;
+ PsiExpression qualifier = methodRef.getQualifierExpression();
+ if (qualifier != null && !PsiMethodReferenceUtil.isStaticallyReferenced(methodRef)) {
+ PsiVariable qualifierBinding = createTempVariable(qualifier.getType());
+ pushVariable(qualifierBinding)
+ .pushExpression(qualifier)
+ .dup();
+ myAnalyzer.addInstruction(new FieldReferenceInstruction(qualifier, ControlFlowAnalyzer.METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD));
+ assign().pop();
+ myMethodRefQualifiers.put(methodRef, qualifierBinding);
+ } else {
+ pushExpression(methodRef).pop();
+ }
+ return this;
+ }
+ return pushExpression(functionalExpression)
+ .checkNotNull(functionalExpression, NullabilityProblem.passingNullableToNotNullParameter)
+ .pop();
+ }
+
+ /**
+ * Generates instructions to invoke functional expression (inlining it if possible) which
+ * consumes given amount of stack arguments, assuming that it was previously evaluated
+ * (see {@link #evaluateFunction(PsiExpression)}).
+ *
+ * @param argCount number of stack arguments to consume
+ * @param functionalExpression a functional expression to invoke
+ * @return this builder
+ */
+ public CFGBuilder invokeFunction(int argCount, @Nullable PsiExpression functionalExpression) {
+ return invokeFunction(argCount, functionalExpression, false);
+ }
+
+ /**
+ * Generates instructions to invoke functional expression (inlining it if possible) which
+ * consumes given amount of stack arguments, assuming that it was previously evaluated
+ * (see {@link #evaluateFunction(PsiExpression)}).
+ *
+ * @param argCount number of stack arguments to consume
+ * @param functionalExpression a functional expression to invoke
+ * @param forceNotNullResult if true, function result will be forced to not-null (possibly issuing a warning)
+ * @return this builder
+ */
+ public CFGBuilder invokeFunction(int argCount, @Nullable PsiExpression functionalExpression, boolean forceNotNullResult) {
+ PsiExpression stripped = PsiUtil.deparenthesizeExpression(functionalExpression);
+ if (stripped instanceof PsiLambdaExpression) {
+ PsiLambdaExpression lambda = (PsiLambdaExpression)stripped;
+ PsiParameter[] parameters = lambda.getParameterList().getParameters();
+ if (parameters.length == argCount && lambda.getBody() != null) {
+ StreamEx.ofReversed(parameters).forEach(p -> assignTo(p).pop());
+ return inlineLambda(lambda, forceNotNullResult);
+ }
+ }
+ if (stripped instanceof PsiMethodReferenceExpression) {
+ PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)stripped;
+ JavaResolveResult resolveResult = methodRef.advancedResolve(false);
+ PsiMethod method = ObjectUtils.tryCast(resolveResult.getElement(), PsiMethod.class);
+ if (method != null && !method.isVarArgs()) {
+ int expectedArgCount = method.getParameterList().getParametersCount();
+ boolean pushQualifier = true;
+ if (!method.hasModifierProperty(PsiModifier.STATIC) && !method.isConstructor()) {
+ pushQualifier = !PsiMethodReferenceUtil.isStaticallyReferenced(methodRef);
+ if (!pushQualifier) {
+ expectedArgCount++; // qualifier is already on stack for statically referenced method ref
+ }
+ }
+ if (argCount == expectedArgCount) {
+ if (pushQualifier) {
+ PsiVariable qualifierVar = myMethodRefQualifiers.remove(methodRef);
+ DfaValue qualifierValue = qualifierVar == null ? DfaUnknownValue.getInstance() :
+ getFactory().getVarFactory().createVariableValue(qualifierVar, false);
+ push(qualifierValue);
+ moveTopValue(argCount);
+ }
+ myAnalyzer.addBareCall(null, methodRef);
+ myAnalyzer.generateBoxingUnboxingInstructionFor(methodRef, resolveResult.getSubstitutor().substitute(method.getReturnType()),
+ LambdaUtil.getFunctionalInterfaceReturnType(methodRef));
+ if (forceNotNullResult) {
+ checkNotNull(methodRef, NullabilityProblem.nullableFunctionReturn);
+ }
+ return this;
+ }
+ }
+ PsiElement qualifier = methodRef.getQualifier();
+ if(qualifier instanceof PsiTypeElement && ((PsiTypeElement)qualifier).getType() instanceof PsiArrayType) {
+ // like String[]::new
+ splice(argCount)
+ .push(getFactory().createTypeValue(((PsiTypeElement)qualifier).getType(), Nullness.NOT_NULL));
+ return this;
+ }
+ }
+ splice(argCount);
+ if (functionalExpression == null) {
+ pushUnknown();
+ return this;
+ }
+ // Unknown function
+ flushFields();
+ PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalExpression.getType());
+ if (returnType != null) {
+ push(getFactory().createTypeValue(returnType, DfaPsiUtil.getTypeNullability(returnType)));
+ }
+ else {
+ pushUnknown();
+ }
+ return this;
+ }
+
+ /**
+ * Generate instructions to move top stack value to the specified depth
+ *
+ * Stack before: ... val#1 val#2 ... val#depth topValue
+ *
+ * Stack after: ... topValue val#1 val#2 ... val#depth
+ *
+ * @param depth a desired depth for the top stack value
+ */
+ private void moveTopValue(int depth) {
+ if (depth > 0) {
+ int[] permutation = new int[depth + 1];
+ for (int i = 1; i < permutation.length; i++) {
+ permutation[i] = depth + 1 - i;
+ }
+ splice(depth + 1, permutation);
+ }
+ }
+
+ public CFGBuilder inlineLambda(PsiLambdaExpression lambda, boolean forceNotNullResult) {
+ myAnalyzer.inlineLambda(lambda, forceNotNullResult);
+ return this;
+ }
+
+ /**
+ * Create a temporary {@link PsiVariable} (not declared in the original code) to be used within this control flow.
+ *
+ * @param type a type of variable to create
+ * @return newly created variable
+ */
+ @NotNull
+ public PsiVariable createTempVariable(@Nullable PsiType type) {
+ if(type == null) {
+ type = PsiType.VOID;
+ }
+ return new LightVariableBuilder<>("tmp$" + myAnalyzer.getInstructionCount(), type, myAnalyzer.getContext());
+ }
+
+ /**
+ * A convenient method to chain specific builder operation
+ *
+ * @param operation to execute on this builder
+ * @return this builder
+ */
+ public CFGBuilder chain(Consumer operation) {
+ operation.accept(this);
+ return this;
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
index 4b03ac9c8202..b45115b90ea2 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExceptionUtil;
+import com.intellij.codeInspection.dataFlow.inliner.*;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
@@ -46,8 +47,10 @@ import static com.intellij.psi.CommonClassNames.*;
public class ControlFlowAnalyzer extends JavaElementVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer");
public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
+ static final String METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD = "Method reference qualifier";
private final PsiElement myCodeFragment;
- private boolean myIgnoreAssertions;
+ private final boolean myIgnoreAssertions;
+ private final boolean myInlining;
private final Project myProject;
private static class CannotAnalyzeException extends RuntimeException { }
@@ -58,8 +61,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private final ExceptionTransfer myRuntimeException;
private final ExceptionTransfer myError;
private final PsiType myAssertionError;
+ private PsiLambdaExpression myLambdaExpression = null;
+ private boolean myForceNotNullLambdaResult = false;
- ControlFlowAnalyzer(final DfaValueFactory valueFactory, @NotNull PsiElement codeFragment, boolean ignoreAssertions) {
+ ControlFlowAnalyzer(final DfaValueFactory valueFactory, @NotNull PsiElement codeFragment, boolean ignoreAssertions, boolean inlining) {
+ myInlining = inlining;
myFactory = valueFactory;
myCodeFragment = codeFragment;
myProject = codeFragment.getProject();
@@ -96,6 +102,13 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
return myCurrentFlow;
}
+ DfaValueFactory getFactory() {
+ return myFactory;
+ }
+
+ PsiElement getContext() {
+ return myCodeFragment;
+ }
private PsiClassType createClassType(GlobalSearchScope scope, String fqn) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(fqn, scope);
@@ -103,11 +116,15 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
return JavaPsiFacade.getElementFactory(myProject).createTypeByFQClassName(fqn, scope);
}
- private T addInstruction(T i) {
+ T addInstruction(T i) {
myCurrentFlow.addInstruction(i);
return i;
}
+ int getInstructionCount() {
+ return myCurrentFlow.getInstructionCount();
+ }
+
private ControlFlow.ControlFlowOffset getEndOffset(PsiElement element) {
return myCurrentFlow.getEndOffset(element);
}
@@ -644,10 +661,22 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
generateBoxingUnboxingInstructionFor(returnValue, LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression));
}
}
- addInstruction(new CheckReturnValueInstruction(returnValue));
}
- addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, myTrapStack), statement));
+ if (myLambdaExpression == null) {
+ if (returnValue != null) {
+ addInstruction(new CheckReturnValueInstruction(returnValue));
+ }
+ addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, myTrapStack), statement));
+ }
+ else {
+ if (returnValue == null) {
+ pushUnknown();
+ } else if (myForceNotNullLambdaResult) {
+ addInstruction(new CheckNotNullInstruction(returnValue, NullabilityProblem.nullableFunctionReturn));
+ }
+ controlTransfer(new InstructionTransfer(getEndOffset(myLambdaExpression), getVariablesInside(myLambdaExpression)), myTrapStack);
+ }
finishElement(statement);
}
@@ -746,7 +775,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiExpression qualifier = expression.getQualifierExpression();
if (qualifier != null) {
qualifier.accept(this);
- addInstruction(new FieldReferenceInstruction(qualifier, "Method reference qualifier"));
+ addInstruction(new FieldReferenceInstruction(qualifier, METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD));
}
addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getFunctionalInterfaceType(), Nullness.NOT_NULL), expression));
@@ -825,6 +854,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
return DfaInstructionState.EMPTY_ARRAY;
}
+
+ @Override
+ public String toString() {
+ return "APPLY NOT NULL";
+ }
}
@Override
@@ -1115,24 +1149,26 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
}
- private void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression expression, PsiType expectedType) {
+ void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression expression, PsiType expectedType) {
+ generateBoxingUnboxingInstructionFor(expression, expression.getType(), expectedType);
+ }
+
+ void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression context, PsiType actualType, PsiType expectedType) {
if (PsiType.VOID.equals(expectedType)) return;
- PsiType exprType = expression.getType();
-
- if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isPrimitiveWrapper(exprType)) {
- addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.UNBOXING, expectedType));
+ if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isPrimitiveWrapper(actualType)) {
+ addInstruction(new MethodCallInstruction(context, MethodCallInstruction.MethodType.UNBOXING, expectedType));
}
- else if (TypeConversionUtil.isPrimitiveAndNotNull(exprType) && TypeConversionUtil.isAssignableFromPrimitiveWrapper(expectedType)) {
+ else if (TypeConversionUtil.isPrimitiveAndNotNull(actualType) && TypeConversionUtil.isAssignableFromPrimitiveWrapper(expectedType)) {
addConditionalRuntimeThrow();
- addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.BOXING, expectedType));
+ addInstruction(new MethodCallInstruction(context, MethodCallInstruction.MethodType.BOXING, expectedType));
}
- else if (exprType != expectedType &&
- TypeConversionUtil.isPrimitiveAndNotNull(exprType) &&
+ else if (actualType != expectedType &&
+ TypeConversionUtil.isPrimitiveAndNotNull(actualType) &&
TypeConversionUtil.isPrimitiveAndNotNull(expectedType) &&
- TypeConversionUtil.isNumericType(exprType) &&
+ TypeConversionUtil.isNumericType(actualType) &&
TypeConversionUtil.isNumericType(expectedType)) {
- addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.CAST, expectedType) {
+ addInstruction(new MethodCallInstruction(context, MethodCallInstruction.MethodType.CAST, expectedType) {
@Override
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
return visitor.visitCast(this, runner, stateBefore);
@@ -1281,7 +1317,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(expression);
}
- private void pushUnknown() {
+ void pushUnknown() {
addInstruction(new PushInstruction(DfaUnknownValue.getInstance(), null));
}
@@ -1333,6 +1369,15 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitMethodCallExpression(PsiMethodCallExpression expression) {
startElement(expression);
+ if (myInlining) {
+ for (CallInliner inliner : INLINERS) {
+ if (inliner.tryInlineCall(new CFGBuilder(this), expression)) {
+ finishElement(expression);
+ return;
+ }
+ }
+ }
+
PsiReferenceExpression methodExpression = expression.getMethodExpression();
PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
@@ -1360,40 +1405,17 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
if (i == 0 && isEqualsCall) {
// stack: .., qualifier, arg1
- addInstruction(new SwapInstruction());
- // stack: .., arg1, qualifier
- addInstruction(new DupInstruction(2, 1));
- // stack: .., arg1, qualifier, arg1, qualifier
- addInstruction(new PopInstruction());
+ addInstruction(new SpliceInstruction(2, 0, 1, 0));
// stack: .., arg1, qualifier, arg1
}
}
- addConditionalRuntimeThrow();
- List extends MethodContract> contracts =
- method instanceof PsiMethod ? getMethodCallContracts((PsiMethod)method, expression) : Collections.emptyList();
- addInstruction(new MethodCallInstruction(expression, myFactory.createValue(expression), contracts));
- if (contracts.stream().anyMatch(c -> c.getReturnValue() == MethodContract.ValueConstraint.THROW_EXCEPTION)) {
- // if a contract resulted in 'fail', handle it
- addInstruction(new DupInstruction());
- addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null));
- addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, myProject));
- ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null);
- addInstruction(ifNotFail);
- addInstruction(new EmptyStackInstruction());
- addInstruction(new ReturnInstruction(myFactory.controlTransfer(new ExceptionTransfer(DfaUnknownValue.getInstance()), myTrapStack), expression));
-
- ifNotFail.setOffset(myCurrentFlow.getInstructionCount());
- }
-
- if (!myTrapStack.isEmpty()) {
- addMethodThrows(expression.resolveMethod(), expression);
- }
+ addBareCall(expression, expression.getMethodExpression());
if (isEqualsCall) {
// assume equals argument must be not-null if the result is true
// don't assume the call result to be false if arg1==null
-
+
// stack: .., arg1, call-result
ConditionalGotoInstruction ifFalse = addInstruction(new ConditionalGotoInstruction(null, true, null));
@@ -1408,6 +1430,41 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(expression);
}
+ void addBareCall(@Nullable PsiMethodCallExpression expression, @NotNull PsiReferenceExpression reference) {
+ addConditionalRuntimeThrow();
+ PsiMethod method = ObjectUtils.tryCast(reference.resolve(), PsiMethod.class);
+ List extends MethodContract> contracts = method == null ? Collections.emptyList() : getMethodCallContracts(method, expression);
+ MethodCallInstruction instruction;
+ PsiExpression anchor;
+ if (expression == null) {
+ assert reference instanceof PsiMethodReferenceExpression;
+ instruction = new MethodCallInstruction((PsiMethodReferenceExpression)reference, contracts);
+ anchor = reference;
+ }
+ else {
+ instruction = new MethodCallInstruction(expression, myFactory.createValue(expression), contracts);
+ anchor = expression;
+ }
+ addInstruction(instruction);
+ if (contracts.stream().anyMatch(c -> c.getReturnValue() == MethodContract.ValueConstraint.THROW_EXCEPTION)) {
+ // if a contract resulted in 'fail', handle it
+ addInstruction(new DupInstruction());
+ addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null));
+ addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, myProject));
+ ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null);
+ addInstruction(ifNotFail);
+ addInstruction(new EmptyStackInstruction());
+ addInstruction(
+ new ReturnInstruction(myFactory.controlTransfer(new ExceptionTransfer(DfaUnknownValue.getInstance()), myTrapStack), anchor));
+
+ ifNotFail.setOffset(myCurrentFlow.getInstructionCount());
+ }
+
+ if (!myTrapStack.isEmpty()) {
+ addMethodThrows(method, anchor);
+ }
+ }
+
public static List extends MethodContract> getMethodCallContracts(@NotNull final PsiMethod method,
@Nullable PsiMethodCallExpression call) {
List contracts = HardcodedContracts.getHardcodedContracts(method, call);
@@ -1650,5 +1707,38 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitClass(PsiClass aClass) {
}
+ void inlineLambda(PsiLambdaExpression lambda, boolean forceNotNullResult) {
+ PsiLambdaExpression oldLambda = myLambdaExpression;
+ boolean oldForceNotNullLambdaResult = myForceNotNullLambdaResult;
+ // Transfer value is pushed to avoid emptying stack beyond this point
+ addInstruction(new PushInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, this.myTrapStack), null));
+ myLambdaExpression = lambda;
+ myForceNotNullLambdaResult = forceNotNullResult;
+ startElement(lambda);
+ try {
+ PsiElement body = lambda.getBody();
+ Objects.requireNonNull(body).accept(this);
+ if (body instanceof PsiCodeBlock) {
+ // return value for void or incomplete lambda
+ pushUnknown();
+ }
+ else if (body instanceof PsiExpression) {
+ generateBoxingUnboxingInstructionFor((PsiExpression)body, LambdaUtil.getFunctionalInterfaceReturnType(lambda));
+ if (myForceNotNullLambdaResult) {
+ addInstruction(new CheckNotNullInstruction((PsiExpression)body, NullabilityProblem.nullableFunctionReturn));
+ }
+ }
+ }
+ finally {
+ finishElement(lambda);
+ myLambdaExpression = oldLambda;
+ myForceNotNullLambdaResult = oldForceNotNullLambdaResult;
+ // Pop transfer value (which is second value in stack now)
+ addInstruction(new SpliceInstruction(2, 0));
+ }
+ }
+
+ static final CallInliner[] INLINERS = {new OptionalChainInliner(), new LambdaInliner(), new CollectionFactoryInliner(),
+ new StreamChainInliner()};
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java
index 57c88dd26b04..de9a9dcf6f7d 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java
@@ -24,10 +24,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFi
import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix;
import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix;
import com.intellij.codeInspection.*;
-import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix;
-import com.intellij.codeInspection.dataFlow.fix.ReplaceWithConstantValueFix;
-import com.intellij.codeInspection.dataFlow.fix.ReplaceWithObjectsEqualsFix;
-import com.intellij.codeInspection.dataFlow.fix.SimplifyToAssignmentFix;
+import com.intellij.codeInspection.dataFlow.fix.*;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue;
@@ -51,6 +48,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.psiutils.ComparisonUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
+import com.siyeh.ig.psiutils.SideEffectChecker;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.StreamEx;
import org.jdom.Element;
@@ -270,7 +268,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
if (isVolatileFieldReference(qualifier)) {
ContainerUtil.addIfNotNull(fixes, createIntroduceVariableFix(qualifier));
}
- else if (!isNullLiteral(qualifier) && !(qualifier instanceof PsiMethodCallExpression)) {
+ else if (!isNullLiteral(qualifier) && !SideEffectChecker.mayHaveSideEffects(qualifier)) {
if (PsiUtil.getLanguageLevel(qualifier).isAtLeast(LanguageLevel.JDK_1_4)) {
final Project project = qualifier.getProject();
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
@@ -286,6 +284,10 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
}
+ if (!isNullLiteral(qualifier) && PsiUtil.isLanguageLevel7OrHigher(qualifier)) {
+ fixes.add(new SurroundWithRequireNonNullFix(qualifier));
+ }
+
ContainerUtil.addIfNotNull(fixes, DfaOptionalSupport.registerReplaceOptionalOfWithOfNullableFix(qualifier));
}
catch (IncorrectOperationException e) {
@@ -333,7 +335,12 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
HashSet reportedAnchors = new HashSet<>();
for (PsiElement element : visitor.getProblems(NullabilityProblem.callNPE)) {
if (reportedAnchors.add(element)) {
- reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element, holder.isOnTheFly());
+ if (element instanceof PsiMethodReferenceExpression) {
+ holder.registerProblem(element, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"));
+ }
+ else {
+ reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element, holder.isOnTheFly());
+ }
}
}
for (PsiElement element : visitor.getProblems(NullabilityProblem.fieldAccessNPE)) {
@@ -358,6 +365,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
reportConstantPushes(runner, holder, visitor, reportedAnchors);
+ reportNullableFunctions(visitor, holder, reportedAnchors);
reportNullableArguments(visitor, holder, reportedAnchors);
reportNullableAssignments(visitor, holder, reportedAnchors);
reportUnboxedNullables(visitor, holder, reportedAnchors);
@@ -502,20 +510,20 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, Set reportedAnchors, Instruction[] instructions) {
for (Instruction instruction : instructions) {
if (instruction instanceof MethodCallInstruction) {
- final PsiExpression[] args = ((MethodCallInstruction)instruction).getArgs();
- if (args.length != 1) continue;
+ MethodCallInstruction methodCall = (MethodCallInstruction)instruction;
+ if (methodCall.getArgCount() != 1) continue;
- final PsiExpression expr = args[0];
+ final PsiElement arg = methodCall.getArgumentAnchor(0);
- if (((MethodCallInstruction)instruction).isOptionalAlwaysNullProblem()) {
- if (!reportedAnchors.add(expr)) continue;
- holder.registerProblem(expr, "Passing null argument to Optional",
- DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(expr));
+ if (methodCall.isOptionalAlwaysNullProblem()) {
+ if (!reportedAnchors.add(arg)) continue;
+ holder.registerProblem(arg, "Passing null argument to Optional",
+ DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(arg));
}
- else if (((MethodCallInstruction)instruction).isOptionalAlwaysNotNullProblem()) {
- if (!reportedAnchors.add(expr)) continue;
- holder.registerProblem(expr, "Passing a non-null argument to Optional",
- DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix());
+ else if (methodCall.isOptionalAlwaysNotNullProblem()) {
+ if (!reportedAnchors.add(arg)) continue;
+ holder.registerProblem(arg, "Passing a non-null argument to Optional",
+ DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(arg));
}
}
@@ -546,6 +554,11 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter)) {
if (reportedAnchors.contains(expr)) continue;
+ if (expr.getParent() instanceof PsiMethodReferenceExpression) {
+ holder.registerProblem(expr.getParent(), "Method reference argument might be null but passed to non annotated parameter");
+ continue;
+ }
+
final String text = isNullLiteralExpression(expr)
? "Passing null argument to non annotated parameter"
: "Argument #ref #loc might be null but passed to non annotated parameter";
@@ -687,15 +700,27 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
visitor.silenceConstantCondition(psiAnchor);
}
+ private static void reportNullableFunctions(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set reportedAnchors) {
+ for (PsiElement expr : visitor.getProblems(NullabilityProblem.nullableFunctionReturn)) {
+ if (!reportedAnchors.add(expr)) continue;
+ holder.registerProblem(expr, InspectionsBundle.message("dataflow.message.return.nullable.from.notnull.function"));
+ }
+ }
+
private void reportNullableArguments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set reportedAnchors) {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableToNotNullParameter)) {
if (!reportedAnchors.add(expr)) continue;
- final String text = isNullLiteralExpression(expr)
- ? InspectionsBundle.message("dataflow.message.passing.null.argument")
- : InspectionsBundle.message("dataflow.message.passing.nullable.argument");
- List fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly());
- holder.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
+ if (expr.getParent() instanceof PsiMethodReferenceExpression) {
+ holder.registerProblem(expr.getParent(), InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref"));
+ }
+ else {
+ final String text = isNullLiteralExpression(expr)
+ ? InspectionsBundle.message("dataflow.message.passing.null.argument")
+ : InspectionsBundle.message("dataflow.message.passing.nullable.argument");
+ List fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly());
+ holder.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
+ }
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java
index 4ce5270446e1..7922667774c2 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java
@@ -33,7 +33,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
-import java.util.function.Predicate;
public class DataFlowRunner {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowRunner");
@@ -41,11 +40,9 @@ public class DataFlowRunner {
private Instruction[] myInstructions;
private final MultiMap myNestedClosures = new MultiMap<>();
- // Closures which were registered for previous instruction and can be queried by visitor
- // to adjust them somehow
- private final Map myStackTopClosures = new HashMap<>();
@NotNull
private final DfaValueFactory myValueFactory;
+ private boolean myInlining = true;
// Maximum allowed attempts to process instruction. Fail as too complex to process if certain instruction
// is executed more than this limit times.
static final int MAX_STATES_PER_BRANCH = 300;
@@ -69,7 +66,14 @@ public class DataFlowRunner {
if (container != null && (!(container instanceof PsiClass) || PsiUtil.isLocalOrAnonymousClass((PsiClass)container))) {
PsiElement block = DfaPsiUtil.getTopmostBlockInSameClass(container.getParent());
if (block != null) {
- final RunnerResult result = analyzeMethod(block, visitor);
+ final RunnerResult result;
+ try {
+ myInlining = false;
+ result = analyzeMethod(block, visitor);
+ }
+ finally {
+ myInlining = true;
+ }
if (result == RunnerResult.OK) {
final Collection closureStates = myNestedClosures.get(DfaPsiUtil.getTopmostBlockInSameClass(psiBlock));
if (!closureStates.isEmpty()) {
@@ -83,14 +87,6 @@ public class DataFlowRunner {
return Collections.singletonList(createMemoryState());
}
- void updateStackTopClosures(Predicate updater) {
- myStackTopClosures.forEach((state, element) -> {
- if(!updater.test(state)) {
- myNestedClosures.remove(element, state);
- }
- });
- }
-
@NotNull
public final RunnerResult analyzeMethod(@NotNull PsiElement psiBlock, @NotNull InstructionVisitor visitor) {
Collection initialStates = createInitialStates(psiBlock, visitor);
@@ -103,15 +99,14 @@ public class DataFlowRunner {
boolean ignoreAssertions,
@NotNull Collection initialStates) {
try {
- final ControlFlow flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, ignoreAssertions).buildControlFlow();
+ final ControlFlow flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, ignoreAssertions, myInlining).buildControlFlow();
if (flow == null) return RunnerResult.NOT_APPLICABLE;
int[] loopNumber = LoopAnalyzer.calcInLoop(flow);
int endOffset = flow.getInstructionCount();
myInstructions = flow.getInstructions();
myNestedClosures.clear();
- myStackTopClosures.clear();
-
+
Set joinInstructions = ContainerUtil.newHashSet();
for (int index = 0; index < myInstructions.length; index++) {
Instruction instruction = myInstructions[index];
@@ -282,7 +277,6 @@ public class DataFlowRunner {
Instruction instruction = instructionState.getInstruction();
DfaInstructionState[] states = instruction.accept(this, instructionState.getMemoryState(), visitor);
- myStackTopClosures.clear();
PsiElement closure = DfaUtil.getClosureInside(instruction);
if (closure instanceof PsiClass) {
registerNestedClosures(instructionState, (PsiClass)closure);
@@ -318,9 +312,7 @@ public class DataFlowRunner {
}
private void createClosureState(PsiElement anchor, DfaMemoryState state) {
- DfaMemoryState closureState = state.createClosureState();
- myStackTopClosures.put(closureState, anchor);
- myNestedClosures.putValue(anchor, closureState);
+ myNestedClosures.putValue(anchor, state.createClosureState());
}
@NotNull
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java
index 7b584c4db565..a074d58d29ed 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java
@@ -627,9 +627,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
- int c1Index = getEqClassIndex(dfaVar);
+ Integer c1Index = getOrCreateEqClassIndex(dfaVar);
int c2Index = getEqClassIndex(dfaNull);
- if (c1Index < 0 || c2Index < 0) {
+ if (c1Index == null || c2Index < 0) {
return false;
}
@@ -1032,7 +1032,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
if (value instanceof DfaVariableValue) {
- DfaVariableState state = myVariableStates.get((DfaVariableValue)value);
+ DfaVariableState state = findVariableState((DfaVariableValue)value);
if (state != null) {
T fact = state.getFact(factType);
if (fact != null) {
@@ -1073,9 +1073,31 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
myCachedHash = null;
}
+
+ private DfaVariableState findVariableState(DfaVariableValue var) {
+ DfaVariableState state = myVariableStates.get(var);
+ if (state != null) {
+ return state;
+ }
+ DfaVariableValue qualifier = var.getQualifier();
+ if (qualifier == null) return null;
+ int qualifierIndex = getEqClassIndex(qualifier);
+ if (qualifierIndex == -1) return null;
+ for (DfaValue eqQualifier : myEqClasses.get(qualifierIndex).getMemberValues()) {
+ if (eqQualifier != qualifier && eqQualifier instanceof DfaVariableValue) {
+ DfaVariableValue eqValue = getFactory().getVarFactory()
+ .createVariableValue(var.getPsiVariable(), var.getVariableType(), var.isNegated(), (DfaVariableValue)eqQualifier);
+ state = myVariableStates.get(eqValue);
+ if (state != null) {
+ return state;
+ }
+ }
+ }
+ return null;
+ }
DfaVariableState getVariableState(DfaVariableValue dfaVar) {
- DfaVariableState state = myVariableStates.get(dfaVar);
+ DfaVariableState state = findVariableState(dfaVar);
if (state == null) {
state = myDefaultVariableStates.get(dfaVar);
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java
index 380673d953f0..b13b1a33a61b 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java
@@ -22,6 +22,8 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
+import com.siyeh.ig.callMatcher.CallMatcher;
+import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,7 +32,11 @@ import org.jetbrains.annotations.Nullable;
*/
public class DfaOptionalSupport {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DfaOptionalSupport");
- private static final String GUAVA_OPTIONAL = "com.google.common.base.Optional";
+ public static final String GUAVA_OPTIONAL = "com.google.common.base.Optional";
+
+ public static final CallMatcher JDK_OPTIONAL_OF_NULLABLE = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "ofNullable").parameterCount(1);
+ public static final CallMatcher GUAVA_OPTIONAL_FROM_NULLABLE = CallMatcher.staticCall(GUAVA_OPTIONAL, "fromNullable").parameterCount(1);
+ public static final CallMatcher OPTIONAL_OF_NULLABLE = CallMatcher.anyOf(JDK_OPTIONAL_OF_NULLABLE, GUAVA_OPTIONAL_FROM_NULLABLE);
@Nullable
static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(@NotNull PsiExpression qualifier) {
@@ -59,34 +65,20 @@ public class DfaOptionalSupport {
}
return null;
}
- private static boolean isJdkOptional(@NotNull PsiElement anchor) {
- final PsiElement parent = findCallExpression(anchor);
- PsiMethod method = parent == null ? null : resolveOfNullable(findCallExpression(anchor));
- return method != null && "ofNullable".equals(method.getName());
- }
- @NotNull
+ @Nullable
static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(@NotNull PsiElement anchor) {
- return new ReplaceOptionalCallFix(isJdkOptional(anchor) ? "empty" : "absent", true);
- }
-
- @NotNull
- static LocalQuickFix createReplaceOptionalOfNullableWithOfFix() {
- return new ReplaceOptionalCallFix("of", false);
+ final PsiMethodCallExpression parent = findCallExpression(anchor);
+ if (parent == null) return null;
+ boolean jdkOptional = JDK_OPTIONAL_OF_NULLABLE.test(parent);
+ return new ReplaceOptionalCallFix(jdkOptional ? "empty" : "absent", true);
}
@Nullable
- public static PsiMethod resolveOfNullable(@NotNull PsiMethodCallExpression expression) {
- String name = expression.getMethodExpression().getReferenceName();
- if ("ofNullable".equals(name) || "fromNullable".equals(name)) {
- PsiMethod method = expression.resolveMethod();
- PsiClass psiClass = method == null ? null : method.getContainingClass();
- String qname = psiClass == null ? null : psiClass.getQualifiedName();
- if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qname) || GUAVA_OPTIONAL.equals(qname)) {
- return method;
- }
- }
- return null;
+ static LocalQuickFix createReplaceOptionalOfNullableWithOfFix(@NotNull PsiElement anchor) {
+ final PsiMethodCallExpression parent = findCallExpression(anchor);
+ if (parent == null) return null;
+ return new ReplaceOptionalCallFix("of", false);
}
static boolean isOptionalGetMethodName(String name) {
@@ -113,15 +105,7 @@ public class DfaOptionalSupport {
final PsiMethodCallExpression
methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class);
if (methodCallExpression != null) {
- final PsiElement ofNullableExprName =
- ((PsiMethodCallExpression)JavaPsiFacade.getElementFactory(project)
- .createExpressionFromText("Optional." + myTargetMethodName + "(null)", null)).getMethodExpression();
- final PsiElement referenceNameElement = methodCallExpression.getMethodExpression().getReferenceNameElement();
- if (referenceNameElement != null) {
- final PsiElement ofNullableNameElement = ((PsiReferenceExpression)ofNullableExprName).getReferenceNameElement();
- LOG.assertTrue(ofNullableNameElement != null);
- referenceNameElement.replace(ofNullableNameElement);
- }
+ ExpressionUtils.bindCallTo(methodCallExpression, myTargetMethodName);
if (myClearArguments) {
PsiExpressionList argList = methodCallExpression.getArgumentList();
PsiExpression[] args = argList.getExpressions();
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java
index 8c1777873ec2..e44381b3120e 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java
@@ -117,8 +117,8 @@ public class DfaPsiUtil {
@NotNull
public static Nullness getTypeNullability(@Nullable PsiType type) {
- if (type == null) return Nullness.UNKNOWN;
-
+ if (type == null || type instanceof PsiPrimitiveType) return Nullness.UNKNOWN;
+
Ref result = Ref.create(Nullness.UNKNOWN);
InheritanceUtil.processSuperTypes(type, true, eachType -> {
result.set(getTypeOwnNullability(result, eachType));
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java
index e02f15c29870..edfb55b59f67 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java
@@ -19,7 +19,6 @@ import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
-import com.intellij.psi.PsiExpression;
import java.util.ArrayList;
@@ -34,6 +33,10 @@ public abstract class InstructionVisitor {
return nextInstruction(instruction, runner, memState);
}
+ public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
+ return nextInstruction(instruction, runner, memState);
+ }
+
protected static DfaInstructionState[] nextInstruction(Instruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
return new DfaInstructionState[]{new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState)};
}
@@ -138,8 +141,7 @@ public abstract class InstructionVisitor {
}
public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
- //noinspection UnusedDeclaration
- for (PsiExpression arg : instruction.getArgs()) {
+ for(int i = instruction.getArgCount(); i > 0; i--) {
memState.pop();
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java
index 9240f5d3383e..b5c62a649954 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java
@@ -9,6 +9,7 @@ public enum NullabilityProblem {
unboxingNullable,
assigningToNotNull,
nullableReturn,
+ nullableFunctionReturn,
passingNullableToNotNullParameter,
passingNullableArgumentToNonAnnotatedParameter
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java
index 1359dd4d8bb1..25c1454415f6 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java
@@ -148,6 +148,13 @@ public class NullnessUtil {
PsiElement target = ((PsiReferenceExpression)expression).resolve();
return DfaPsiUtil.getElementNullability(expression.getType(), (PsiModifierListOwner)target);
}
+ if (expression instanceof PsiAssignmentExpression) {
+ PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression;
+ if(assignment.getOperationTokenType().equals(JavaTokenType.EQ)) {
+ return getExpressionNullness(assignment.getRExpression());
+ }
+ return Nullness.NOT_NULL;
+ }
if (expression instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
return method != null ? DfaPsiUtil.getElementNullability(expression.getType(), method) : Nullness.UNKNOWN;
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java
index bd85b23b2828..f51ec9dcdffc 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java
@@ -29,11 +29,9 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
-import com.siyeh.ig.psiutils.TypeUtils;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -49,9 +47,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.StandardInstructionVisitor");
private static final Object ANY_VALUE = new Object();
- private static final Set OPTIONAL_METHOD_NAMES = ContainerUtil
- .set("of", "ofNullable", "fromNullable", "empty", "absent", "or", "orElse", "orElseGet", "ifPresent", "map", "flatMap", "filter",
- "transform");
private static final CallMapper KNOWN_METHOD_RANGES = new CallMapper()
.register(CallMatcher.instanceCall("java.time.LocalDateTime", "getHour"), LongRangeSet.range(0, 23))
.register(CallMatcher.instanceCall("java.time.LocalDateTime", "getMinute", "getSecond"), LongRangeSet.range(0, 59))
@@ -65,16 +60,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private final MultiMap myPossibleVariableValues = MultiMap.createSet();
private final Set myNotToReportReachability = new THashSet<>();
private final Set myUsefulInstanceofs = new THashSet<>();
- @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
- private final Map myReturnTypeNullability = FactoryMap.createMap(key-> {
- final PsiCall callExpression = key.getCallExpression();
- if (callExpression instanceof PsiNewExpression) {
- return Nullness.NOT_NULL;
- }
-
- return callExpression != null ? DfaPsiUtil.getElementNullability(key.getResultType(), callExpression.resolveMethod()) : null;
- }
- );
@Override
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
@@ -308,7 +293,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@Override
public DfaInstructionState[] visitMethodCall(final MethodCallInstruction instruction, final DataFlowRunner runner, final DfaMemoryState memState) {
Set finalStates = ContainerUtil.newLinkedHashSet();
- finalStates.addAll(handleOptionalMethods(instruction, runner, memState));
finalStates.addAll(handleKnownMethods(instruction, runner, memState));
if (finalStates.isEmpty()) {
@@ -363,65 +347,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return states;
}
- @NotNull
- private List handleOptionalMethods(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
- PsiMethodCallExpression call = ObjectUtils.tryCast(instruction.getCallExpression(), PsiMethodCallExpression.class);
- if (call == null) return Collections.emptyList();
- String methodName = call.getMethodExpression().getReferenceName();
- if (methodName == null || !OPTIONAL_METHOD_NAMES.contains(methodName)) return Collections.emptyList();
- PsiMethod method = call.resolveMethod();
- if (method == null || !TypeUtils.isOptional(method.getContainingClass())) return Collections.emptyList();
- DfaCallArguments arguments = popCall(instruction, runner, memState, false);
- DfaValue[] argValues = arguments.myArguments;
- DfaValue result = null;
- DfaValueFactory factory = runner.getFactory();
- switch (methodName) {
- case "of":
- case "ofNullable":
- case "fromNullable":
- if ("of".equals(methodName) || (argValues != null && argValues.length == 1 && memState.isNotNull(argValues[0]))) {
- result = factory.getOptionalFactory().getOptional(true);
- }
- break;
- case "empty":
- case "absent":
- result = factory.getOptionalFactory().getOptional(false);
- break;
- case "orElse":
- if (argValues != null && argValues.length == 1) {
- DfaMemoryState falseState = memState.createCopy();
- DfaOptionalValue optional = factory.getOptionalFactory().getOptional(true);
- DfaValue relation = factory.createCondition(arguments.myQualifier, RelationType.IS, optional);
- List states = new ArrayList<>(2);
- if (memState.applyCondition(relation)) {
- memState.push(factory.createTypeValue(instruction.getResultType(), Nullness.NOT_NULL));
- states.add(memState);
- }
- if (falseState.applyCondition(relation.createNegated())) {
- falseState.push(argValues[0]);
- states.add(falseState);
- }
- return states;
- }
- break;
- case "filter":
- case "flatMap":
- case "ifPresent":
- case "map":
- case "or":
- case "orElseGet":
- case "transform": {
- DfaOptionalValue optional = factory.getOptionalFactory().getOptional(!methodName.startsWith("or"));
- DfaValue relation = factory.createCondition(arguments.myQualifier, RelationType.IS, optional);
- runner.updateStackTopClosures(state -> state.applyCondition(relation));
- break;
- }
- default:
- }
- memState.push(result == null ? getMethodResultValue(instruction, arguments.myQualifier, factory) : result);
- return Collections.singletonList(memState);
- }
-
@NotNull
private DfaCallArguments popCall(MethodCallInstruction instruction,
DataFlowRunner runner,
@@ -437,7 +362,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DataFlowRunner runner,
DfaMemoryState memState,
boolean contractOnly) {
- final PsiExpression[] args = instruction.getArgs();
+ final int argCount = instruction.getArgCount();
PsiMethod method = instruction.getTargetMethod();
boolean varargCall = instruction.isVarArgCall();
@@ -447,7 +372,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
} else {
PsiParameterList paramList = method.getParameterList();
int paramCount = paramList.getParametersCount();
- if (paramCount == args.length || method.isVarArgs() && args.length >= paramCount - 1) {
+ if (paramCount == argCount || method.isVarArgs() && argCount >= paramCount - 1) {
argValues = new DfaValue[paramCount];
if (varargCall) {
argValues[paramCount - 1] = runner.getFactory().createTypeValue(paramList.getParameters()[paramCount - 1].getType(), Nullness.NOT_NULL);
@@ -457,22 +382,22 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
- for (int i = 0; i < args.length; i++) {
+ for (int i = 0; i < argCount; i++) {
final DfaValue arg = memState.pop();
- int paramIndex = args.length - i - 1;
+ int paramIndex = argCount - i - 1;
if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) {
argValues[paramIndex] = arg;
}
- PsiExpression expr = args[paramIndex];
- Nullness requiredNullability = instruction.getArgRequiredNullability(expr);
+ PsiElement anchor = instruction.getArgumentAnchor(paramIndex);
+ Nullness requiredNullability = instruction.getArgRequiredNullability(paramIndex);
if (requiredNullability == Nullness.NOT_NULL) {
- if (!checkNotNullable(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, expr)) {
+ if (!checkNotNullable(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, anchor)) {
forceNotNull(runner, memState, arg);
}
}
else if (!instruction.updateOfNullable(memState, arg) && requiredNullability == Nullness.UNKNOWN) {
- checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr);
+ checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, anchor);
}
}
return argValues;
@@ -482,7 +407,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@NotNull final DfaValue qualifier = memState.pop();
boolean unboxing = instruction.getMethodType() == MethodCallInstruction.MethodType.UNBOXING;
NullabilityProblem problem = unboxing ? NullabilityProblem.unboxingNullable : NullabilityProblem.callNPE;
- PsiElement anchor = unboxing ? instruction.getContext() : instruction.getCallExpression();
+ PsiElement anchor = instruction.getContext();
if (!checkNotNullable(memState, qualifier, problem, anchor)) {
forceNotNull(runner, memState, qualifier);
}
@@ -545,7 +470,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
@NotNull
- private DfaValue getMethodResultValue(MethodCallInstruction instruction, @Nullable DfaValue qualifierValue, DfaValueFactory factory) {
+ private static DfaValue getMethodResultValue(MethodCallInstruction instruction,
+ @Nullable DfaValue qualifierValue,
+ DfaValueFactory factory) {
DfaValue precalculated = instruction.getPrecalculatedReturnValue();
if (precalculated != null) {
return precalculated;
@@ -573,7 +500,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
if (type != null && !(type instanceof PsiPrimitiveType)) {
- Nullness nullability = myReturnTypeNullability.get(instruction);
+ Nullness nullability = instruction.getReturnNullability();
PsiMethod targetMethod = instruction.getTargetMethod();
if (nullability == Nullness.UNKNOWN && targetMethod != null) {
nullability = factory.suggestNullabilityForNonAnnotatedMember(targetMethod);
@@ -612,6 +539,24 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return notNullable;
}
+ @Override
+ public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
+ if (!checkNotNullable(memState, memState.peek(), instruction.getProblem(), instruction.getExpression())) {
+ DfaValue arg = memState.peek();
+ if (arg instanceof DfaVariableValue) {
+ DfaVariableValue var = (DfaVariableValue)arg;
+ memState.setVarValue(var, runner.getFactory().createTypeValue(var.getVariableType(), Nullness.NOT_NULL));
+ } else if (arg instanceof DfaTypeValue) {
+ memState.pop();
+ memState.push(((DfaTypeValue)arg).withNullness(Nullness.NOT_NULL));
+ } else if (memState.isNull(arg) && instruction.getProblem() == NullabilityProblem.nullableFunctionReturn) {
+ memState.pop();
+ memState.push(runner.getFactory().createTypeValue(PsiType.VOID, Nullness.NOT_NULL));
+ }
+ }
+ return super.visitCheckNotNull(instruction, runner, memState);
+ }
+
@Override
public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
myReachable.add(instruction);
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt
index 243f6832dd17..45320f93beef 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt
@@ -38,7 +38,9 @@ class DfaControlTransferValue(factory: DfaValueFactory,
interface TransferTarget
data class ExceptionTransfer(val throwable: DfaValue) : TransferTarget
data class InstructionTransfer(val offset: ControlFlow.ControlFlowOffset, val toFlush: List) : TransferTarget
-object ReturnTransfer : TransferTarget
+object ReturnTransfer : TransferTarget {
+ override fun toString(): String = "ReturnTransfer"
+}
open class ControlTransferInstruction(val transfer: DfaControlTransferValue?) : Instruction() {
override fun accept(runner: DataFlowRunner, state: DfaMemoryState, visitor: InstructionVisitor): Array {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java
new file mode 100644
index 000000000000..460bf941806e
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.fix;
+
+import com.intellij.codeInspection.InspectionsBundle;
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ProblemDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiExpression;
+import com.intellij.psi.SmartPointerManager;
+import com.intellij.psi.SmartPsiElementPointer;
+import com.intellij.psi.codeStyle.JavaCodeStyleManager;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+
+public class SurroundWithRequireNonNullFix implements LocalQuickFix {
+ private final String myText;
+ private final SmartPsiElementPointer myQualifierPointer;
+
+ public SurroundWithRequireNonNullFix(@NotNull PsiExpression expressionToSurround) {
+ myText = expressionToSurround.getText();
+ myQualifierPointer =
+ SmartPointerManager.getInstance(expressionToSurround.getProject()).createSmartPsiElementPointer(expressionToSurround);
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getName() {
+ return InspectionsBundle.message("inspection.surround.requirenonnull.quickfix", myText);
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return InspectionsBundle.message("inspection.surround.requirenonnull.quickfix", "");
+ }
+
+ @Override
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+ PsiExpression qualifier = myQualifierPointer.getElement();
+ if (qualifier == null) return;
+ PsiExpression replacement = JavaPsiFacade.getElementFactory(project)
+ .createExpressionFromText("java.util.Objects.requireNonNull(" + qualifier.getText() + ")", qualifier);
+ JavaCodeStyleManager.getInstance(project).shortenClassReferences(qualifier.replace(replacement));
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CallInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CallInliner.java
new file mode 100644
index 000000000000..d31ff96315b0
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CallInliner.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.inliner;
+
+import com.intellij.codeInspection.dataFlow.CFGBuilder;
+import com.intellij.psi.PsiMethodCallExpression;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A CallInliner can recognize specific method calls and inline their implementation into current CFG
+ */
+public interface CallInliner {
+ /**
+ * Try to inline the supplied call
+ *
+ * @param builder a builder to use for inlining. Current state is before given method call (call arguments and qualifier are not
+ * handled yet).
+ * @param call a call to inline
+ * @return true if inlining is successful. In this case subsequent inliners are skipped and default processing is omitted.
+ * If false is returned, inliner must not emit any instructions via builder.
+ */
+ boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call);
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java
new file mode 100644
index 000000000000..5644994dd80d
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.inliner;
+
+import com.intellij.codeInspection.dataFlow.CFGBuilder;
+import com.intellij.codeInspection.dataFlow.Nullness;
+import com.intellij.codeInspection.dataFlow.SpecialField;
+import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
+import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
+import com.intellij.psi.PsiExpression;
+import com.intellij.psi.PsiMethodCallExpression;
+import com.intellij.psi.PsiType;
+import com.intellij.psi.PsiVariable;
+import com.siyeh.ig.callMatcher.CallMapper;
+import org.jetbrains.annotations.NotNull;
+
+import static com.intellij.codeInspection.dataFlow.SpecialField.COLLECTION_SIZE;
+import static com.intellij.codeInspection.dataFlow.SpecialField.MAP_SIZE;
+import static com.intellij.psi.CommonClassNames.JAVA_UTIL_COLLECTIONS;
+import static com.siyeh.ig.callMatcher.CallMatcher.staticCall;
+
+public class CollectionFactoryInliner implements CallInliner {
+ static final class FactoryInfo {
+ int mySize;
+ SpecialField mySizeField;
+
+ public FactoryInfo(int size, SpecialField sizeField) {
+ mySize = size;
+ mySizeField = sizeField;
+ }
+ }
+
+ private static final CallMapper STATIC_FACTORIES = new CallMapper()
+ .register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyList", "emptySet").parameterCount(0), new FactoryInfo(0, COLLECTION_SIZE))
+ .register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1), new FactoryInfo(1, COLLECTION_SIZE))
+ .register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyMap").parameterCount(0), new FactoryInfo(0, MAP_SIZE))
+ .register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonMap").parameterCount(2), new FactoryInfo(1, MAP_SIZE));
+
+ @Override
+ public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
+ FactoryInfo factoryInfo = STATIC_FACTORIES.mapFirst(call);
+ if (factoryInfo == null) return false;
+ PsiExpression[] args = call.getArgumentList().getExpressions();
+ for (PsiExpression arg : args) {
+ builder.pushExpression(arg).pop();
+ }
+ PsiVariable variable = builder.createTempVariable(call.getType());
+ DfaValueFactory factory = builder.getFactory();
+ DfaVariableValue variableValue = factory.getVarFactory().createVariableValue(variable, false);
+ builder.pushVariable(variable) // tmpVar =
+ .push(factory.createTypeValue(call.getType(), Nullness.NOT_NULL))
+ .assign() // leave tmpVar on stack: it's result of method call
+ .push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size =
+ .push(factory.getConstFactory().createFromValue(factoryInfo.mySize, PsiType.INT, null))
+ .assign()
+ .pop();
+ return true;
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java
new file mode 100644
index 000000000000..f83563e264ff
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.inliner;
+
+import com.intellij.codeInspection.dataFlow.CFGBuilder;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.ObjectUtils;
+import one.util.streamex.EntryStream;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * An inliner which is capable to inline a call like ((IntSupplier)(() -> 5)).getAsInt() to the lambda body.
+ * Works even if lambda body is complex, has several returns, etc.
+ */
+public class LambdaInliner implements CallInliner {
+ @Override
+ public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
+ PsiMethod method = call.resolveMethod();
+ if (method == null || method != LambdaUtil.getFunctionalInterfaceMethod(method.getContainingClass())) return false;
+ PsiTypeCastExpression typeCastExpression = ObjectUtils
+ .tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiTypeCastExpression.class);
+ if (typeCastExpression == null) return false;
+ PsiLambdaExpression lambda =
+ ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(typeCastExpression.getOperand()), PsiLambdaExpression.class);
+ if (lambda == null || lambda.getBody() == null) return false;
+ if (method.isVarArgs()) return false; // TODO: support varargs
+ PsiExpression[] args = call.getArgumentList().getExpressions();
+ PsiParameter[] parameters = lambda.getParameterList().getParameters();
+ if (args.length != parameters.length) return false;
+ EntryStream.zip(args, parameters).forKeyValue((arg, parameter) ->
+ builder.pushVariable(parameter)
+ .pushExpression(arg)
+ .boxUnbox(arg, parameter.getType())
+ .assign()
+ .pop());
+ builder.inlineLambda(lambda, false);
+ return true;
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java
new file mode 100644
index 000000000000..976219597c18
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.inliner;
+
+import com.intellij.codeInspection.dataFlow.CFGBuilder;
+import com.intellij.codeInspection.dataFlow.NullabilityProblem;
+import com.intellij.codeInspection.dataFlow.Nullness;
+import com.intellij.codeInspection.dataFlow.value.DfaOptionalValue;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.ObjectUtils;
+import com.siyeh.ig.callMatcher.CallMapper;
+import com.siyeh.ig.callMatcher.CallMatcher;
+import one.util.streamex.StreamEx;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.BiConsumer;
+
+import static com.intellij.codeInspection.dataFlow.DfaOptionalSupport.GUAVA_OPTIONAL;
+import static com.intellij.psi.CommonClassNames.JAVA_UTIL_OPTIONAL;
+import static com.siyeh.ig.callMatcher.CallMatcher.*;
+
+/**
+ * An inliner which is capable to inline some Optional chains like
+ * {@code Optional.of(xyz).map(lambda).filter(lambda).flatMap(lambda).orElseGet(lambda)}
+ *
+ * TODO support primitive Optionals
+ */
+public class OptionalChainInliner implements CallInliner {
+ private static final CallMatcher OPTIONAL_OR_ELSE = anyOf(
+ instanceCall(JAVA_UTIL_OPTIONAL, "orElse").parameterCount(1),
+ instanceCall(GUAVA_OPTIONAL, "or").parameterTypes("T"));
+ private static final CallMatcher OPTIONAL_OR_NULL = instanceCall(GUAVA_OPTIONAL, "orNull").parameterCount(0);
+ private static final CallMatcher OPTIONAL_OR_ELSE_GET = anyOf(
+ instanceCall(JAVA_UTIL_OPTIONAL, "orElseGet").parameterCount(1),
+ instanceCall(GUAVA_OPTIONAL, "or").parameterTypes("com.google.common.base.Supplier"));
+ private static final CallMatcher OPTIONAL_OR = instanceCall(JAVA_UTIL_OPTIONAL, "or").parameterCount(1); // Java 9
+ private static final CallMatcher OPTIONAL_IF_PRESENT = instanceCall(JAVA_UTIL_OPTIONAL, "ifPresent").parameterCount(1);
+ private static final CallMatcher OPTIONAL_FILTER = instanceCall(JAVA_UTIL_OPTIONAL, "filter").parameterCount(1);
+ private static final CallMatcher OPTIONAL_MAP = instanceCall(JAVA_UTIL_OPTIONAL, "map").parameterCount(1);
+ // Guava transform() throws if function returns null, so handled separately
+ private static final CallMatcher GUAVA_TRANSFORM = instanceCall(GUAVA_OPTIONAL, "transform").parameterCount(1);
+ private static final CallMatcher OPTIONAL_FLAT_MAP = instanceCall(JAVA_UTIL_OPTIONAL, "flatMap").parameterCount(1);
+ private static final CallMatcher OPTIONAL_OF = anyOf(
+ staticCall(JAVA_UTIL_OPTIONAL, "of", "ofNullable").parameterCount(1),
+ staticCall(GUAVA_OPTIONAL, "of", "fromNullable").parameterCount(1));
+ private static final CallMatcher OPTIONAL_EMPTY = anyOf(
+ staticCall(JAVA_UTIL_OPTIONAL, "empty").parameterCount(0),
+ staticCall(GUAVA_OPTIONAL, "absent").parameterCount(0));
+ private static final CallMatcher GUAVA_TO_JAVA =
+ instanceCall(GUAVA_OPTIONAL, "toJavaUtil").parameterCount(0);
+
+ private static final CallMapper> TERMINAL_MAPPER =
+ new CallMapper>()
+ .register(OPTIONAL_OR_ELSE, (builder, call) -> {
+ PsiExpression argument = call.getArgumentList().getExpressions()[0];
+ builder.pushExpression(argument) // stack: .. optValue, elseValue
+ .boxUnbox(argument, call.getType())
+ .splice(2, 0, 1, 1) // stack: .. elseValue, optValue, optValue
+ .ifNotNull()
+ .swap() // stack: .. optValue, elseValue
+ .endIf()
+ .pop();
+ })
+ .register(OPTIONAL_OR_NULL, (builder, call) -> {
+ // no op!
+ })
+ .register(OPTIONAL_OR_ELSE_GET, (builder, call) -> {
+ PsiExpression fn = call.getArgumentList().getExpressions()[0];
+ builder
+ .evaluateFunction(fn)
+ .dup()
+ .ifNull()
+ .pop()
+ .invokeFunction(0, fn)
+ .endIf();
+ })
+ .register(OPTIONAL_IF_PRESENT, (builder, call) -> {
+ PsiExpression fn = call.getArgumentList().getExpressions()[0];
+ builder
+ .evaluateFunction(fn)
+ .dup()
+ .ifNotNull()
+ .invokeFunction(0, fn)
+ .elseBranch()
+ .pop()
+ .pushUnknown()
+ .endIf();
+ });
+
+ private static final CallMapper> INTERMEDIATE_MAPPER =
+ new CallMapper>()
+ .register(OPTIONAL_MAP, (builder, function) -> inlineMap(builder, function, false))
+ .register(GUAVA_TRANSFORM, (builder, function) -> inlineMap(builder, function, true))
+ .register(OPTIONAL_FILTER, (builder, function) -> builder
+ .evaluateFunction(function)
+ .dup()
+ .ifNotNull()
+ .dup()
+ .invokeFunction(1, function)
+ .ifConditionIs(false)
+ .pop()
+ .pushNull()
+ .endIf()
+ .endIf())
+ .register(OPTIONAL_FLAT_MAP, (builder, function) -> builder
+ .dup()
+ .ifNotNull()
+ .chain(b -> invokeAndUnwrapOptional(b, 1, function))
+ .endIf())
+ .register(OPTIONAL_OR, (builder, function) -> builder
+ .dup()
+ .ifNull()
+ .pop()
+ .chain(b -> invokeAndUnwrapOptional(b, 0, function))
+ .endIf())
+ .register(GUAVA_TO_JAVA, (builder, stub) -> {/* no op */});
+
+ @Override
+ public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
+ BiConsumer terminalInliner = TERMINAL_MAPPER.mapFirst(call);
+ if (terminalInliner != null) {
+ PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
+ if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblem.callNPE)) {
+ return false;
+ }
+ terminalInliner.accept(builder, call);
+ return true;
+ }
+ DfaOptionalValue.Factory optionalFactory = builder.getFactory().getOptionalFactory();
+ if (pushIntermediateOperationValue(builder, call)) {
+ builder.ifNotNull()
+ .push(optionalFactory.getOptional(true))
+ .elseBranch()
+ .push(optionalFactory.getOptional(false))
+ .endIf();
+ return true;
+ }
+ if (OPTIONAL_EMPTY.test(call)) {
+ builder.push(optionalFactory.getOptional(false));
+ return true;
+ }
+ return false;
+ }
+
+ @Contract("null -> null")
+ private static PsiType getOptionalElementType(PsiExpression expression) {
+ if (expression == null) return null;
+ PsiClassType type = ObjectUtils.tryCast(expression.getType(), PsiClassType.class);
+ if (type == null) return null;
+ String rawName = type.rawType().getCanonicalText();
+ if (!rawName.equals(JAVA_UTIL_OPTIONAL) && !rawName.equals(GUAVA_OPTIONAL)) return null;
+ PsiType[] parameters = type.getParameters();
+ if (parameters.length != 1) return null;
+ return parameters[0];
+ }
+
+ private static boolean pushOptionalValue(CFGBuilder builder, PsiExpression expression,
+ PsiExpression dereferenceContext, NullabilityProblem problem) {
+ PsiType optionalElementType = getOptionalElementType(expression);
+ if (optionalElementType == null) return false;
+ if (expression instanceof PsiMethodCallExpression) {
+ PsiMethodCallExpression qualifierCall = (PsiMethodCallExpression)expression;
+ if (OPTIONAL_EMPTY.test(qualifierCall)) {
+ builder.pushNull();
+ return true;
+ }
+ if (pushIntermediateOperationValue(builder, qualifierCall)) {
+ builder.assignTo(builder.createTempVariable(optionalElementType));
+ return true;
+ }
+ }
+ DfaOptionalValue presentOptional = builder.getFactory().getOptionalFactory().getOptional(true);
+ builder
+ .pushExpression(expression)
+ .checkNotNull(dereferenceContext, problem)
+ .push(presentOptional)
+ .ifCondition(JavaTokenType.INSTANCEOF_KEYWORD)
+ .push(builder.getFactory().createTypeValue(optionalElementType, Nullness.NOT_NULL))
+ .elseBranch()
+ .pushNull()
+ .endIf()
+ .assignTo(builder.createTempVariable(optionalElementType));
+ return true;
+ }
+
+ private static boolean pushIntermediateOperationValue(CFGBuilder builder, PsiMethodCallExpression call) {
+ if (OPTIONAL_OF.test(call)) {
+ PsiType optionalElementType = getOptionalElementType(call);
+ inlineOf(builder, optionalElementType, call);
+ return true;
+ }
+ BiConsumer intermediateInliner = INTERMEDIATE_MAPPER.mapFirst(call);
+ if (intermediateInliner == null) return false;
+ PsiExpression argument = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
+ PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
+ if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblem.callNPE)) return false;
+ intermediateInliner.accept(builder, argument);
+ return true;
+ }
+
+ private static void invokeAndUnwrapOptional(CFGBuilder builder,
+ int argCount,
+ PsiExpression function) {
+ PsiLambdaExpression lambda = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(function), PsiLambdaExpression.class);
+ if (lambda != null) {
+ PsiParameter[] parameters = lambda.getParameterList().getParameters();
+ PsiExpression lambdaBody = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
+ if (parameters.length == argCount && lambdaBody != null) {
+ StreamEx.ofReversed(parameters).forEach(p -> builder.assignTo(p).pop());
+ if (pushOptionalValue(builder, lambdaBody, lambdaBody, NullabilityProblem.nullableFunctionReturn)) {
+ return;
+ }
+ // Restore stack for common invokeFunction
+ StreamEx.of(parameters).forEach(p -> builder.push(builder.getFactory().getVarFactory().createVariableValue(p, false)));
+ }
+ }
+ builder
+ .evaluateFunction(function)
+ .invokeFunction(argCount, function, true)
+ .pop()
+ .pushUnknown();
+ }
+
+ private static void inlineMap(CFGBuilder builder, PsiExpression function, boolean forceNotNullResult) {
+ builder
+ .evaluateFunction(function)
+ .dup()
+ .ifNotNull()
+ .invokeFunction(1, function, forceNotNullResult)
+ .endIf();
+ }
+
+ private static void inlineOf(CFGBuilder builder, PsiType optionalElementType, PsiMethodCallExpression qualifierCall) {
+ PsiExpression argument = qualifierCall.getArgumentList().getExpressions()[0];
+ builder.pushExpression(argument)
+ .boxUnbox(argument, optionalElementType)
+ .pushUnknown() // ... arg, ?
+ .splice(2, 1, 0, 1) // ... arg, ?, arg
+ .invoke(qualifierCall) // ... arg, opt -- keep original call in CFG so some warnings like "ofNullable for null" can work
+ .pop(); // ... arg
+ if ("of".equals(qualifierCall.getMethodExpression().getReferenceName())) {
+ builder.checkNotNull(argument, NullabilityProblem.passingNullableToNotNullParameter);
+ }
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java
new file mode 100644
index 000000000000..4f753a861118
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.inliner;
+
+import com.intellij.codeInspection.dataFlow.CFGBuilder;
+import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
+import com.intellij.codeInspection.dataFlow.NullabilityProblem;
+import com.intellij.codeInspection.dataFlow.Nullness;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.ObjectUtils;
+import com.siyeh.ig.callMatcher.CallMapper;
+import com.siyeh.ig.callMatcher.CallMatcher;
+import com.siyeh.ig.psiutils.MethodCallUtils;
+import com.siyeh.ig.psiutils.StreamApiUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.UnaryOperator;
+
+import static com.intellij.psi.CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM;
+import static com.intellij.psi.CommonClassNames.JAVA_UTIL_STREAM_STREAM;
+import static com.siyeh.ig.callMatcher.CallMatcher.anyOf;
+import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall;
+
+public class StreamChainInliner implements CallInliner {
+ private static final String[] TERMINALS =
+ {"count", "sum", "summaryStatistics", "reduce", "collect", "findFirst", "findAny", "anyMatch", "allMatch", "noneMatch", "toArray",
+ "average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet"};
+ private static final CallMatcher TERMINAL_CALL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, TERMINALS);
+
+ private static final CallMatcher LAMBDA_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch",
+ "noneMatch", "forEach", "forEachOrdered").parameterCount(1);
+
+ private static final CallMatcher SKIP_STEP =
+ instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "unordered", "parallel", "sequential", "sorted").parameterCount(0);
+ private static final CallMatcher SORTED = instanceCall(JAVA_UTIL_STREAM_STREAM, "sorted").parameterCount(1);
+ private static final CallMatcher FILTER = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "filter").parameterCount(1);
+ private static final CallMatcher STATE_FILTER = anyOf(instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "distinct").parameterCount(0),
+ instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "skip", "limit").parameterCount(1));
+ private static final CallMatcher BOXED = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "boxed").parameterCount(0);
+ private static final CallMatcher MAP =
+ instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "map", "mapToInt", "mapToLong", "mapToDouble", "mapToObj").parameterCount(1);
+ private static final CallMatcher FLAT_MAP =
+ instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble").parameterCount(1);
+ private static final CallMatcher PEEK = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "peek").parameterCount(1);
+
+ private static final CallMapper> INTERMEDIATE_STEP_MAPPER = new CallMapper>()
+ .register(FILTER, (PsiMethodCallExpression call) -> (Step next) -> new FilterStep(call, next))
+ .register(MAP, (PsiMethodCallExpression call) -> (Step next) -> new MapStep(call, next))
+ .register(FLAT_MAP, (PsiMethodCallExpression call) -> (Step next) -> new FlatMapStep(call, next))
+ .register(PEEK, (PsiMethodCallExpression call) -> (Step next) -> new PeekStep(call, next))
+ .register(SORTED, (PsiMethodCallExpression call) -> (Step next) -> new SortedStep(call, next))
+ .register(BOXED, (PsiMethodCallExpression call) -> (Step next) -> new BoxedStep(call, next))
+ .register(STATE_FILTER, (PsiMethodCallExpression call) -> (Step next) -> new StateFilterStep(call, next));
+
+ static abstract class Step {
+ final Step myNext;
+ final @NotNull PsiMethodCallExpression myCall;
+ final PsiExpression myFunction;
+
+ Step(@NotNull PsiMethodCallExpression call, Step next, PsiExpression function) {
+ myNext = next;
+ myCall = call;
+ myFunction = function;
+ }
+
+ void before(CFGBuilder builder) {
+ if (myFunction != null) {
+ builder.evaluateFunction(myFunction);
+ }
+ if (myNext != null) {
+ myNext.before(builder);
+ }
+ }
+
+ abstract void iteration(CFGBuilder builder);
+ }
+
+ static class UnknownTerminalStep extends Step {
+ UnknownTerminalStep(PsiMethodCallExpression call) {
+ super(call, null, null);
+ }
+
+ @Override
+ void before(CFGBuilder builder) {
+ for (PsiExpression arg : myCall.getArgumentList().getExpressions()) {
+ builder.pushExpression(arg).pop();
+ }
+ super.before(builder);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ // Stream variable is on stack
+ builder.pop().flushFields();
+ }
+ }
+
+ static class LambdaTerminalStep extends Step {
+ LambdaTerminalStep(@NotNull PsiMethodCallExpression call) {
+ super(call, null, call.getArgumentList().getExpressions()[0]);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ builder.invokeFunction(1, myFunction).pop();
+ }
+ }
+
+ static class FilterStep extends Step {
+ FilterStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, call.getArgumentList().getExpressions()[0]);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ builder
+ .dup()
+ .invokeFunction(1, myFunction)
+ .ifConditionIs(true)
+ .chain(myNext::iteration)
+ .elseBranch()
+ .pop()
+ .endIf();
+ }
+ }
+
+ static class MapStep extends Step {
+ MapStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, call.getArgumentList().getExpressions()[0]);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ builder
+ .invokeFunction(1, myFunction)
+ .assignTo(builder.createTempVariable(StreamApiUtil.getStreamElementType(myCall.getType())))
+ .chain(myNext::iteration);
+ }
+ }
+
+ static class FlatMapStep extends Step {
+ private final Step myChain;
+ private final PsiParameter myParameter;
+ private final PsiExpression myStreamSource;
+
+ FlatMapStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, null);
+ // Try to inline smoothly .flatMap(x -> stream().call().chain())
+ PsiLambdaExpression lambda =
+ ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(myCall.getArgumentList().getExpressions()[0]), PsiLambdaExpression.class);
+ Step chain = null;
+ PsiParameter parameter = null;
+ PsiExpression streamSource = null;
+ if (lambda != null) {
+ parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters());
+ if (parameter != null) {
+ PsiExpression body = PsiUtil.skipParenthesizedExprDown(LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()));
+ if (body != null) {
+ streamSource = body;
+ chain = next;
+ if (body instanceof PsiMethodCallExpression) {
+ chain = buildChain((PsiMethodCallExpression)body, next);
+ if (chain != next) {
+ streamSource = chain.myCall.getMethodExpression().getQualifierExpression();
+ }
+ }
+ }
+ }
+ }
+ myStreamSource = streamSource;
+ myChain = chain;
+ myParameter = parameter;
+ }
+
+ @Override
+ void before(CFGBuilder builder) {
+ if (myStreamSource == null) {
+ PsiExpression arg = myCall.getArgumentList().getExpressions()[0];
+ builder.pushExpression(arg).checkNotNull(arg, NullabilityProblem.passingNullableToNotNullParameter).pop();
+ }
+ super.before(builder);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ if (myStreamSource != null) {
+ builder.assignTo(myParameter).pop();
+ buildStreamCFG(builder, myChain, myStreamSource);
+ } else {
+ PsiType outType = StreamApiUtil.getStreamElementType(myCall.getType());
+ builder.pop()
+ .doWhile()
+ .push(builder.getFactory().createTypeValue(outType, Nullness.UNKNOWN))
+ .chain(myNext::iteration)
+ .endWhileUnknown();
+ }
+ }
+ }
+
+ static class PeekStep extends Step {
+ PeekStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, call.getArgumentList().getExpressions()[0]);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ builder
+ .dup()
+ .invokeFunction(1, myFunction)
+ .pop()
+ .chain(myNext::iteration);
+ }
+ }
+
+ static class StateFilterStep extends Step {
+ StateFilterStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, null);
+ }
+
+ @Override
+ void before(CFGBuilder builder) {
+ for (PsiExpression arg : myCall.getArgumentList().getExpressions()) {
+ builder.pushExpression(arg).pop();
+ }
+ super.before(builder);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ builder
+ .pushUnknown()
+ .ifConditionIs(true)
+ .chain(myNext::iteration)
+ .elseBranch()
+ .pop()
+ .endIf();
+ }
+ }
+
+ // Currently sorted is just a no-op as DFA results does not depend on sort order.
+ // In future we could check the comparator implementation
+ // (e.g. warn if stream can contain nulls, but comparator is not null-friendly)
+ static class SortedStep extends Step {
+ SortedStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, null);
+ }
+
+ @Override
+ void before(CFGBuilder builder) {
+ builder.pushExpression(myCall.getArgumentList().getExpressions()[0]).pop();
+ super.before(builder);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ myNext.iteration(builder);
+ }
+ }
+
+ static class BoxedStep extends Step {
+ BoxedStep(@NotNull PsiMethodCallExpression call, Step next) {
+ super(call, next, null);
+ }
+
+ @Override
+ void iteration(CFGBuilder builder) {
+ PsiType outType = StreamApiUtil.getStreamElementType(myCall.getType());
+ PsiPrimitiveType primitiveType = PsiPrimitiveType.getUnboxedType(outType);
+ if (primitiveType != null) {
+ builder.boxUnbox(myCall, primitiveType, outType).assignTo(builder.createTempVariable(outType));
+ }
+ myNext.iteration(builder);
+ }
+ }
+
+ @Override
+ public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
+ if (!TERMINAL_CALL.test(call)) {
+ return false;
+ }
+ PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
+ Step terminalStep = createTerminalStep(call);
+ Step firstStep = buildChain(qualifierCall, terminalStep);
+ if (firstStep == terminalStep) {
+ // Do not handle specially case when only terminal operation is known: at least one intermediate op should be known as well
+ return false;
+ }
+ PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression();
+ if (originalQualifier == null) return false;
+ buildStreamCFG(builder, firstStep, originalQualifier);
+ builder.push(
+ builder.getFactory().createTypeValue(call.getType(), DfaPsiUtil.getElementNullability(call.getType(), call.resolveMethod())));
+ return true;
+ }
+
+ static void buildStreamCFG(CFGBuilder builder, Step firstStep, PsiExpression originalQualifier) {
+ PsiType inType = StreamApiUtil.getStreamElementType(originalQualifier.getType());
+ builder
+ .pushExpression(originalQualifier)
+ .checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
+ .pop()
+ .chain(firstStep::before)
+ .doWhile()
+ .pushVariable(builder.createTempVariable(inType))
+ .push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
+ .assign()
+ .chain(firstStep::iteration)
+ .endWhileUnknown();
+ }
+
+ static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) {
+ Step curStep = terminalStep;
+ while (qualifierCall != null) {
+ if (!SKIP_STEP.test(qualifierCall)) {
+ Step nextStep = createIntermediateStep(curStep, qualifierCall);
+ if (nextStep == null) break;
+ curStep = nextStep;
+ }
+ qualifierCall = MethodCallUtils.getQualifierMethodCall(qualifierCall);
+ }
+ return curStep;
+ }
+
+ private static Step createIntermediateStep(Step nextStep, PsiMethodCallExpression call) {
+ UnaryOperator stepFactory = INTERMEDIATE_STEP_MAPPER.mapFirst(call);
+ if (stepFactory == null) return null;
+ return stepFactory.apply(nextStep);
+ }
+
+ private static Step createTerminalStep(PsiMethodCallExpression call) {
+ if (LAMBDA_TERMINAL.test(call)) {
+ return new LambdaTerminalStep(call);
+ }
+ return new UnknownTerminalStep(call);
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java
new file mode 100644
index 000000000000..50c9ca23bffc
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.instructions;
+
+import com.intellij.codeInspection.dataFlow.*;
+import com.intellij.psi.PsiExpression;
+
+public class CheckNotNullInstruction extends Instruction {
+ private final PsiExpression myExpression;
+ private final NullabilityProblem myProblem;
+
+ public CheckNotNullInstruction(PsiExpression expression, NullabilityProblem problem) {
+ myExpression = expression;
+ myProblem = problem;
+ }
+
+ public PsiExpression getExpression() {
+ return myExpression;
+ }
+
+ public NullabilityProblem getProblem() {
+ return myProblem;
+ }
+
+ @Override
+ public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
+ return visitor.visitCheckNotNull(this, runner, stateBefore);
+ }
+
+ @Override
+ public String toString() {
+ return "CHECK_NOT_NULL ["+myProblem+"] "+myExpression.getText();
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java
index 287b86b89d29..1caa02e3265f 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java
@@ -20,7 +20,7 @@ package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.psi.PsiElement;
-public class ConditionalGotoInstruction extends BranchingInstruction {
+public class ConditionalGotoInstruction extends BranchingInstruction implements JumpInstruction {
private ControlFlow.ControlFlowOffset myOffset;
private final boolean myIsNegated;
@@ -43,10 +43,12 @@ public class ConditionalGotoInstruction extends BranchingInstruction {
return (isNegated() ? "!":"") + "cond?_goto " + getOffset();
}
+ @Override
public int getOffset() {
return myOffset.getInstructionOffset();
}
+ @Override
public void setOffset(final int offset) {
myOffset = new ControlFlow.ControlFlowOffset() {
@Override
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java
index 213a64b512e1..081f4f965442 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java
@@ -19,47 +19,19 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner;
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
-import com.intellij.codeInspection.dataFlow.value.DfaValue;
-
-import java.util.ArrayList;
-import java.util.List;
/**
* @author max
*/
public class DupInstruction extends Instruction {
- private final int myValueCount;
- private final int myDuplicationCount;
-
- public DupInstruction() {
- this(1, 1);
- }
-
- public DupInstruction(int valueCount, int duplicationCount) {
- myValueCount = valueCount;
- myDuplicationCount = duplicationCount;
- }
-
@Override
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState memState, InstructionVisitor visitor) {
- if (myDuplicationCount == 1 && myValueCount == 1) {
- memState.push(memState.peek());
- } else {
- List values = new ArrayList<>(myValueCount);
- for (int i = 0; i < myValueCount; i++) {
- values.add(memState.pop());
- }
- for (int j = 0; j < myDuplicationCount + 1; j++) {
- for (int i = values.size() - 1; i >= 0; i--) {
- memState.push(values.get(i));
- }
- }
- }
+ memState.push(memState.peek());
Instruction nextInstruction = runner.getInstruction(getIndex() + 1);
return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, memState)};
}
public String toString() {
- return "DUP(" + myValueCount + " top stack values, " + myDuplicationCount + " times)";
+ return "DUP";
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java
index b4d2eafd1c61..b09862ef7162 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java
@@ -19,13 +19,14 @@ package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
-public class GotoInstruction extends Instruction {
+public class GotoInstruction extends Instruction implements JumpInstruction {
private ControlFlow.ControlFlowOffset myOffset;
public GotoInstruction(ControlFlow.ControlFlowOffset myOffset) {
this.myOffset = myOffset;
}
+ @Override
public int getOffset() {
return myOffset.getInstructionOffset();
}
@@ -40,6 +41,7 @@ public class GotoInstruction extends Instruction {
return "GOTO: " + getOffset();
}
+ @Override
public void setOffset(final int offset) {
myOffset = new ControlFlow.ControlFlowOffset() {
@Override
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/JumpInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/JumpInstruction.java
new file mode 100644
index 000000000000..dd44eb1c3f37
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/JumpInstruction.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.instructions;
+
+public interface JumpInstruction {
+ int getOffset();
+
+ void setOffset(final int offset);
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java
index 4de5bb65051f..99106fe42836 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java
@@ -19,20 +19,21 @@ package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.*;
-import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
public class MethodCallInstruction extends Instruction {
+ private static final Nullness[] EMPTY_NULLNESS_ARRAY = new Nullness[0];
+
@Nullable private final PsiCall myCall;
@Nullable private final PsiType myType;
- @NotNull private final PsiExpression[] myArgs;
+ private final int myArgCount;
private final boolean myShouldFlushFields;
@NotNull private final PsiElement myContext;
@Nullable private final PsiMethod myTargetMethod;
@@ -41,12 +42,13 @@ public class MethodCallInstruction extends Instruction {
@Nullable private final DfaValue myPrecalculatedReturnValue;
private final boolean myOfNullable;
private final boolean myVarArgCall;
- private final Map myArgRequiredNullability;
+ private final Nullness[] myArgRequiredNullability;
private boolean myOnlyNullArgs = true;
private boolean myOnlyNotNullArgs = true;
+ private final Nullness myReturnNullability;
public enum MethodType {
- BOXING, UNBOXING, REGULAR_METHOD_CALL, CAST
+ BOXING, UNBOXING, REGULAR_METHOD_CALL, METHOD_REFERENCE_CALL, CAST
}
public MethodCallInstruction(@NotNull PsiExpression context, MethodType methodType, @Nullable PsiType resultType) {
@@ -54,14 +56,49 @@ public class MethodCallInstruction extends Instruction {
myContracts = Collections.emptyList();
myMethodType = methodType;
myCall = null;
- myArgs = PsiExpression.EMPTY_ARRAY;
+ myArgCount = 0;
myType = resultType;
myShouldFlushFields = false;
myPrecalculatedReturnValue = null;
myTargetMethod = null;
myVarArgCall = false;
myOfNullable = false;
- myArgRequiredNullability = Collections.emptyMap();
+ myArgRequiredNullability = EMPTY_NULLNESS_ARRAY;
+ myReturnNullability = Nullness.UNKNOWN;
+ }
+
+ public MethodCallInstruction(@NotNull PsiMethodReferenceExpression reference, @NotNull List extends MethodContract> contracts) {
+ myContext = reference;
+ myMethodType = MethodType.METHOD_REFERENCE_CALL;
+ JavaResolveResult resolveResult = reference.advancedResolve(false);
+ myTargetMethod = ObjectUtils.tryCast(resolveResult.getElement(), PsiMethod.class);
+ myCall = null;
+ myContracts = Collections.unmodifiableList(contracts);
+ myArgCount = myTargetMethod == null ? 0 : myTargetMethod.getParameterList().getParametersCount();
+ if (myTargetMethod == null) {
+ myType = null;
+ myReturnNullability = Nullness.UNKNOWN;
+ }
+ else {
+ if (myTargetMethod.isConstructor()) {
+ PsiClass containingClass = myTargetMethod.getContainingClass();
+ myType = containingClass == null ? null : JavaPsiFacade.getElementFactory(myTargetMethod.getProject())
+ .createType(containingClass, resolveResult.getSubstitutor());
+ myReturnNullability = Nullness.NOT_NULL;
+ }
+ else {
+ myType = resolveResult.getSubstitutor().substitute(myTargetMethod.getReturnType());
+ myReturnNullability = DfaPsiUtil.getElementNullability(myType, myTargetMethod);
+ }
+ }
+ myVarArgCall = false; // vararg method reference calls are not supported now
+ myPrecalculatedReturnValue = null;
+ myOfNullable = DfaOptionalSupport.OPTIONAL_OF_NULLABLE.methodReferenceMatches(reference);
+ myArgRequiredNullability = myTargetMethod == null
+ ? EMPTY_NULLNESS_ARRAY
+ : calcArgRequiredNullability(resolveResult.getSubstitutor(),
+ myTargetMethod.getParameterList().getParameters());
+ myShouldFlushFields = !isPureCall();
}
public MethodCallInstruction(@NotNull PsiCall call, @Nullable DfaValue precalculatedReturnValue, List extends MethodContract> contracts) {
@@ -70,7 +107,8 @@ public class MethodCallInstruction extends Instruction {
myMethodType = MethodType.REGULAR_METHOD_CALL;
myCall = call;
final PsiExpressionList argList = call.getArgumentList();
- myArgs = argList != null ? argList.getExpressions() : PsiExpression.EMPTY_ARRAY;
+ PsiExpression[] args = argList != null ? argList.getExpressions() : PsiExpression.EMPTY_ARRAY;
+ myArgCount = args.length;
myType = myCall instanceof PsiCallExpression ? ((PsiCallExpression)myCall).getType() : null;
JavaResolveResult result = call.resolveMethodGenerics();
@@ -79,35 +117,57 @@ public class MethodCallInstruction extends Instruction {
PsiSubstitutor substitutor = result.getSubstitutor();
if (argList != null && myTargetMethod != null) {
PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters();
- myVarArgCall = isVarArgCall(myTargetMethod, substitutor, myArgs, parameters);
+ myVarArgCall = isVarArgCall(myTargetMethod, substitutor, args, parameters);
myArgRequiredNullability = calcArgRequiredNullability(substitutor, parameters);
} else {
myVarArgCall = false;
- myArgRequiredNullability = Collections.emptyMap();
+ myArgRequiredNullability = EMPTY_NULLNESS_ARRAY;
}
myShouldFlushFields = !(call instanceof PsiNewExpression && myType != null && myType.getArrayDimensions() > 0) && !isPureCall();
myPrecalculatedReturnValue = precalculatedReturnValue;
- myOfNullable = call instanceof PsiMethodCallExpression && DfaOptionalSupport.resolveOfNullable((PsiMethodCallExpression)call) != null;
+ myOfNullable = call instanceof PsiMethodCallExpression && DfaOptionalSupport.OPTIONAL_OF_NULLABLE.test((PsiMethodCallExpression)call);
+ myReturnNullability = call instanceof PsiNewExpression ? Nullness.NOT_NULL : DfaPsiUtil.getElementNullability(myType, myTargetMethod);
}
- private Map calcArgRequiredNullability(PsiSubstitutor substitutor, PsiParameter[] parameters) {
- int checkedCount = Math.min(myArgs.length, parameters.length) - (myVarArgCall ? 1 : 0);
+ /**
+ * Returns a PsiElement which at best represents an argument with given index
+ *
+ * @param index an argument index, must be from 0 to {@link #getArgCount()}-1.
+ * @return a PsiElement. Either argument expression or method reference if call is described by method reference
+ */
+ public PsiElement getArgumentAnchor(int index) {
+ if (myCall != null) {
+ PsiExpressionList argumentList = myCall.getArgumentList();
+ if (argumentList != null) {
+ return argumentList.getExpressions()[index];
+ }
+ }
+ if (myContext instanceof PsiMethodReferenceExpression) {
+ return ((PsiMethodReferenceExpression)myContext).getReferenceNameElement();
+ }
+ return myContext;
+ }
- Map map = ContainerUtil.newHashMap();
+ private Nullness[] calcArgRequiredNullability(PsiSubstitutor substitutor, PsiParameter[] parameters) {
+ if (myArgCount == 0) {
+ return EMPTY_NULLNESS_ARRAY;
+ }
+
+ int checkedCount = Math.min(myArgCount, parameters.length) - (myVarArgCall ? 1 : 0);
+
+ Nullness[] nullness = new Nullness[myArgCount];
for (int i = 0; i < checkedCount; i++) {
- map.put(myArgs[i], DfaPsiUtil.getElementNullability(substitutor.substitute(parameters[i].getType()), parameters[i]));
+ nullness[i] = DfaPsiUtil.getElementNullability(substitutor.substitute(parameters[i].getType()), parameters[i]);
}
if (myVarArgCall) {
PsiType lastParamType = substitutor.substitute(parameters[parameters.length - 1].getType());
if (isEllipsisWithNotNullElements(lastParamType)) {
- for (int i = parameters.length - 1; i < myArgs.length; i++) {
- map.put(myArgs[i], Nullness.NOT_NULL);
- }
+ Arrays.fill(nullness, parameters.length - 1, myArgCount, Nullness.NOT_NULL);
}
}
- return map;
+ return nullness;
}
private static boolean isEllipsisWithNotNullElements(PsiType lastParamType) {
@@ -146,9 +206,8 @@ public class MethodCallInstruction extends Instruction {
return myType;
}
- @NotNull
- public PsiExpression[] getArgs() {
- return myArgs;
+ public int getArgCount() {
+ return myArgCount;
}
public MethodType getMethodType() {
@@ -168,9 +227,9 @@ public class MethodCallInstruction extends Instruction {
return myVarArgCall;
}
- @Nullable
- public Nullness getArgRequiredNullability(@NotNull PsiExpression arg) {
- return myArgRequiredNullability.get(arg);
+ @Nullable
+ public Nullness getArgRequiredNullability(int index) {
+ return index >= myArgRequiredNullability.length ? null : myArgRequiredNullability[index];
}
public List getContracts() {
@@ -197,12 +256,26 @@ public class MethodCallInstruction extends Instruction {
return myPrecalculatedReturnValue;
}
+ @NotNull
+ public Nullness getReturnNullability() {
+ return myReturnNullability;
+ }
+
public String toString() {
- return myMethodType == MethodType.UNBOXING
- ? "UNBOX"
- : myMethodType == MethodType.BOXING
- ? "BOX" :
- "CALL_METHOD: " + (myCall == null ? "null" : myCall.getText());
+ switch (myMethodType) {
+ case UNBOXING:
+ return "UNBOX";
+ case BOXING:
+ return "BOX";
+ case CAST:
+ return "CAST TO " + myType;
+ case METHOD_REFERENCE_CALL:
+ return "CALL_METHOD_REFERENCE: " + myContext.getText();
+ case REGULAR_METHOD_CALL:
+ return "CALL_METHOD: " + (myCall == null ? "null" : myCall.getText());
+ default:
+ throw new IllegalStateException("Unexpected method type: " + myMethodType);
+ }
}
public boolean updateOfNullable(DfaMemoryState memState, DfaValue arg) {
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/SpliceInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/SpliceInstruction.java
new file mode 100644
index 000000000000..3f84a41446dc
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/SpliceInstruction.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow.instructions;
+
+import com.intellij.codeInspection.dataFlow.DataFlowRunner;
+import com.intellij.codeInspection.dataFlow.DfaInstructionState;
+import com.intellij.codeInspection.dataFlow.DfaMemoryState;
+import com.intellij.codeInspection.dataFlow.InstructionVisitor;
+import com.intellij.codeInspection.dataFlow.value.DfaValue;
+import one.util.streamex.IntStreamEx;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Pop several elements from the stack and replace them with some of them (possibly duplicating, swapping, removing some, etc.)
+ */
+public class SpliceInstruction extends Instruction {
+ private final int myCount;
+ private final int[] myReplacement;
+
+ public SpliceInstruction(int count, int... replacement) {
+ myCount = count;
+ myReplacement = replacement;
+ }
+
+ @Override
+ public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
+ List removed = IntStreamEx.range(myCount).mapToObj(idx -> stateBefore.pop()).toList();
+ IntStreamEx.of(myReplacement).elements(removed).forEach(stateBefore::push);
+ Instruction nextInstruction = runner.getInstruction(getIndex() + 1);
+ return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, stateBefore)};
+ }
+
+ public String toString() {
+ return "SPLICE [" + myCount + "] -> " + Arrays.toString(myReplacement);
+ }
+}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java
index 135b63ce7c71..1d31756d3656 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java
@@ -19,7 +19,6 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.SpecialField;
-import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
@@ -27,6 +26,7 @@ import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
+import com.intellij.psi.impl.light.LightVariableBuilder;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -202,14 +202,8 @@ public class DfaExpressionFactory {
private PsiVariable getArrayIndexVariable(@Nullable PsiExpression indexExpression) {
Object constant = JavaConstantExpressionEvaluator.computeConstantExpression(indexExpression, false);
if (constant instanceof Integer && ((Integer)constant).intValue() >= 0) {
- PsiVariable mockVar = myMockIndices.get(constant);
- if (mockVar == null) {
- PsiJavaFile file = (PsiJavaFile)PsiFileFactory.getInstance(indexExpression.getProject())
- .createFileFromText("ArrayIndex.java", JavaFileType.INSTANCE, "class _Index_ { int $array$index$" + constant + ";}");
- mockVar = file.getClasses()[0].getFields()[0];
- myMockIndices.put((Integer)constant, mockVar);
- }
- return mockVar;
+ return myMockIndices
+ .computeIfAbsent((Integer)constant, k -> new LightVariableBuilder<>("$array$index$" + k, PsiType.INT, indexExpression));
}
return null;
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java
index 78130a783c0f..32c003c3d5cf 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java
@@ -51,6 +51,11 @@ public class SpecialAnnotationsUtilBase {
return family;
}
+ @Override
+ public boolean startInWriteAction() {
+ return false;
+ }
+
@Override
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
doQuickFixInternal(project, targetList, qualifiedName);
diff --git a/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java b/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java
index b3e77fa63781..a85fdc186367 100644
--- a/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java
+++ b/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2016 JetBrains s.r.o.
+ * Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,10 +30,10 @@ import com.intellij.psi.codeStyle.CodeStyleConfigurable;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.JavaVisibilityPanel;
-import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.SortedListModel;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.util.ui.JBInsets;
+import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -73,7 +73,7 @@ public class CodeStyleGenerationConfigurable implements CodeStyleConfigurable {
public CodeStyleGenerationConfigurable(CodeStyleSettings settings) {
mySettings = settings;
- myPanel.setBorder(IdeBorderFactory.createEmptyBorder(2, 2, 2, 2));
+ myPanel.setBorder(JBUI.Borders.empty(2, 2, 2, 2));
myJavaVisibilityPanel = new JavaVisibilityPanel(false, true, RefactoringBundle.message("default.visibility.border.title"));
}
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JShellCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JShellCompletionContributor.java
new file mode 100644
index 000000000000..48eadf690b1a
--- /dev/null
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/JShellCompletionContributor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.completion;
+
+import com.intellij.psi.PsiJShellSyntheticElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 26-Jul-17
+ */
+public class JShellCompletionContributor extends CompletionContributor {
+ @Override
+ public void fillCompletionVariants(@NotNull final CompletionParameters parameters, @NotNull final CompletionResultSet resultSet) {
+ resultSet.runRemainingContributors(parameters, r -> {
+ if (!(r.getLookupElement().getPsiElement() instanceof PsiJShellSyntheticElement)) {
+ resultSet.passResult(r);
+ }
+ });
+ }
+
+}
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java
index b5f751b33b57..74f10b38b543 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java
@@ -789,6 +789,11 @@ public class JavaCompletionContributor extends CompletionContributor {
}
if (context.getCompletionType() == CompletionType.BASIC) {
+ if (PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset() - 1, PsiReferenceParameterList.class, false) != null) {
+ context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED);
+ return;
+ }
+
if (semicolonNeeded(context.getEditor(), file, context.getStartOffset())) {
context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER.trim() + ";");
return;
@@ -818,7 +823,7 @@ public class JavaCompletionContributor extends CompletionContributor {
}
}
- public static boolean semicolonNeeded(final Editor editor, PsiFile file, final int startOffset) {
+ public static boolean semicolonNeeded(Editor editor, PsiFile file, int startOffset) {
PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiJavaCodeReferenceElement.class, false);
if (ref != null && !(ref instanceof PsiReferenceExpression)) {
if (ref.getParent() instanceof PsiTypeElement) {
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java
index d5bb1ed359bd..889549d66e3d 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java
@@ -38,7 +38,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
-import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NotNull;
@@ -311,8 +310,6 @@ public class JavaKeywordCompletion {
addClassLiteral();
- addUnfinishedMethodTypeParameters();
-
addExtendsImplements();
}
@@ -516,8 +513,12 @@ public class JavaKeywordCompletion {
addKeyword(new OverridableSpace(createKeyword(s), TailType.HUMBLE_SPACE_BEFORE_WORD));
}
- addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), TailType.HUMBLE_SPACE_BEFORE_WORD));
- if (PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) {
+ PsiExpression expression = PsiTreeUtil.getParentOfType(myPosition, PsiExpression.class, true, PsiMember.class);
+ if (expression != null && expression.getParent() instanceof PsiExpressionStatement) {
+ addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), TailType.HUMBLE_SPACE_BEFORE_WORD));
+ }
+ if (expression == null && PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) {
+ addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), TailType.HUMBLE_SPACE_BEFORE_WORD));
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.INTERFACE), TailType.HUMBLE_SPACE_BEFORE_WORD));
if (PsiUtil.isLanguageLevel5OrHigher(myPosition)) {
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ENUM), TailType.INSERT_SPACE));
@@ -614,25 +615,6 @@ public class JavaKeywordCompletion {
return END_OF_BLOCK.getValue().isAcceptable(position, position);
}
- private void addUnfinishedMethodTypeParameters() {
- final ProcessingContext context = new ProcessingContext();
- if (psiElement().inside(
- psiElement(PsiTypeElement.class).afterLeaf(
- psiElement().withText(">").withParent(
- psiElement(PsiTypeParameterList.class).withParent(PsiErrorElement.class).save("typeParameterList")))).accepts(myPosition, context)) {
- final PsiTypeParameterList list = (PsiTypeParameterList)context.get("typeParameterList");
- PsiElement current = list.getParent().getParent();
- if (current instanceof PsiField) {
- current = current.getParent();
- }
- if (current instanceof PsiClass) {
- for (PsiTypeParameter typeParameter : list.getTypeParameters()) {
- addKeyword(new JavaPsiClassReferenceElement(typeParameter));
- }
- }
- }
- }
-
static boolean isAfterPrimitiveOrArrayType(PsiElement element) {
return psiElement().withParent(
psiReferenceExpression().withFirstChild(
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java
index 5f106d2680c5..99b68892d938 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java
@@ -20,9 +20,6 @@ import com.intellij.codeInsight.completion.util.MethodParenthesesHandler;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.hint.ShowParameterInfoContext;
import com.intellij.codeInsight.hint.api.impls.MethodParameterInfoHandler;
-import com.intellij.codeInsight.hints.HintInfo;
-import com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider;
-import com.intellij.codeInsight.hints.MethodInfoBlacklistFilter;
import com.intellij.codeInsight.hints.ParameterHintsPass;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer;
@@ -332,10 +329,10 @@ public class JavaMethodCallElement extends LookupItem implements Type
return;
}
- methodCall.putUserData(COMPLETION_HINTS, Boolean.TRUE);
+ setCompletionMode(methodCall, true);
ParameterInfoController controller = new ParameterInfoController(project, editor, braceOffset, infoContext.getItemsToShow(), null,
- parameterOwner, handler, false, false);
- Disposable hintsDisposal = () -> methodCall.putUserData(COMPLETION_HINTS, null);
+ methodCall.getArgumentList(), handler, false, false);
+ Disposable hintsDisposal = () -> setCompletionMode(methodCall, false);
if (Disposer.isDisposed(controller)) {
Disposer.dispose(hintsDisposal);
}
@@ -345,7 +342,11 @@ public class JavaMethodCallElement extends LookupItem implements Type
}
}
- public static boolean showCompletionHints(@NotNull PsiCallExpression expression) {
+ public static void setCompletionMode(@NotNull PsiCall expression, boolean value) {
+ expression.putUserData(COMPLETION_HINTS, value ? Boolean.TRUE : null);
+ }
+
+ public static boolean isCompletionMode(@NotNull PsiCall expression) {
return expression.getUserData(COMPLETION_HINTS) != null;
}
diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java
index da28d516564d..0f65ab658d7d 100644
--- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java
+++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2016 JetBrains s.r.o.
+ * Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,8 +35,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
-
public class ConvertSwitchToIfIntention implements IntentionAction {
private final PsiSwitchStatement mySwitchExpression;
@@ -87,8 +85,7 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
if (switchExpressionType == null) {
return;
}
- final boolean isSwitchOnString =
- switchExpressionType.equalsToText(JAVA_LANG_STRING);
+ final boolean isSwitchOnString = switchExpressionType.equalsToText(CommonClassNames.JAVA_LANG_STRING);
boolean useEquals = isSwitchOnString;
if (!useEquals) {
final PsiClass aClass = PsiUtil.resolveClassInType(switchExpressionType);
@@ -101,16 +98,13 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
if (RemoveUnusedVariableUtil.checkSideEffects(switchExpression, null, new ArrayList<>())) {
hadSideEffects = true;
- final JavaCodeStyleManager javaCodeStyleManager =
- JavaCodeStyleManager.getInstance(project);
+ final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
final String variableName;
if (isSwitchOnString) {
- variableName = javaCodeStyleManager.suggestUniqueVariableName(
- "s", switchExpression, true);
+ variableName = javaCodeStyleManager.suggestUniqueVariableName("s", switchExpression, true);
}
else {
- variableName = javaCodeStyleManager.suggestUniqueVariableName(
- "i", switchExpression, true);
+ variableName = javaCodeStyleManager.suggestUniqueVariableName("i", switchExpression, true);
}
expressionText = variableName;
declarationString = switchExpressionType.getCanonicalText() + ' ' + variableName + " = " + switchExpression.getText() + ';';
@@ -126,19 +120,15 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
if (body == null) {
return;
}
- final List openBranches =
- new ArrayList<>();
- final Set declaredVariables =
- new HashSet<>();
- final List allBranches =
- new ArrayList<>();
+ final List openBranches = new ArrayList<>();
+ final Set declaredVariables = new HashSet<>();
+ final List allBranches = new ArrayList<>();
SwitchStatementBranch currentBranch = null;
final PsiElement[] children = body.getChildren();
for (int i = 1; i < children.length - 1; i++) {
final PsiElement statement = children[i];
if (statement instanceof PsiSwitchLabelStatement) {
- final PsiSwitchLabelStatement label =
- (PsiSwitchLabelStatement)statement;
+ final PsiSwitchLabelStatement label = (PsiSwitchLabelStatement)statement;
if (currentBranch == null) {
openBranches.clear();
currentBranch = new SwitchStatementBranch();
@@ -163,13 +153,10 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
else {
if (statement instanceof PsiStatement) {
if (statement instanceof PsiDeclarationStatement) {
- final PsiDeclarationStatement declarationStatement =
- (PsiDeclarationStatement)statement;
- final PsiElement[] elements =
- declarationStatement.getDeclaredElements();
+ final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)statement;
+ final PsiElement[] elements = declarationStatement.getDeclaredElements();
for (PsiElement varElement : elements) {
- final PsiLocalVariable variable =
- (PsiLocalVariable)varElement;
+ final PsiLocalVariable variable = (PsiLocalVariable)varElement;
declaredVariables.add(variable);
}
}
@@ -177,8 +164,8 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
branch.addStatement(statement);
}
try {
- ControlFlow controlFlow = ControlFlowFactory
- .getInstance(project).getControlFlow(statement, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
+ ControlFlow controlFlow =
+ ControlFlowFactory.getInstance(project).getControlFlow(statement, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
int startOffset = controlFlow.getStartOffset(statement);
int endOffset = controlFlow.getEndOffset(statement);
if (startOffset != -1 && endOffset != -1 && !ControlFlowUtil.canCompleteNormally(controlFlow, startOffset, endOffset)) {
@@ -209,62 +196,34 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
defaultBranch = branch;
}
else {
- final List caseValues = branch.getCaseValues();
- final List bodyElements = branch.getBodyElements();
- final Set pendingVariableDeclarations =
- branch.getPendingVariableDeclarations();
- dumpBranch(expressionText, caseValues, bodyElements,
- pendingVariableDeclarations, firstBranch,
- useEquals, ifStatementText);
+ dumpBranch(branch, expressionText, firstBranch, useEquals, ifStatementText);
firstBranch = false;
}
}
if (defaultBranch != null) {
- final List bodyElements =
- defaultBranch.getBodyElements();
- final Set pendingVariableDeclarations =
- defaultBranch.getPendingVariableDeclarations();
- dumpDefaultBranch(bodyElements, pendingVariableDeclarations,
- firstBranch, ifStatementText);
+ dumpDefaultBranch(defaultBranch, firstBranch, ifStatementText);
}
- if (ifStatementText.length() == 0) return;
- final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
- final PsiElementFactory factory = psiFacade.getElementFactory();
+ if (ifStatementText.length() == 0) {
+ return;
+ }
+ final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
if (hadSideEffects) {
- final PsiStatement declarationStatement =
- factory.createStatementFromText(declarationString,
- switchStatement);
- final PsiStatement ifStatement =
- factory.createStatementFromText(ifStatementText.toString(),
- switchStatement);
- final PsiElement parent = switchStatement.getParent();
- parent.addBefore(declarationStatement, switchStatement);
- switchStatement.replace(ifStatement);
- }
- else {
- final PsiStatement newStatement =
- factory.createStatementFromText(ifStatementText.toString(),
- switchStatement);
- switchStatement.replace(newStatement);
+ final PsiStatement declarationStatement = factory.createStatementFromText(declarationString, switchStatement);
+ switchStatement.getParent().addBefore(declarationStatement, switchStatement);
}
+ final PsiStatement ifStatement = factory.createStatementFromText(ifStatementText.toString(), switchStatement);
+ switchStatement.replace(ifStatement);
}
private static String getCaseValueText(PsiExpression value) {
+ value = PsiUtil.skipParenthesizedExprDown(value);
if (value == null) {
return "";
}
- if (value instanceof PsiParenthesizedExpression) {
- final PsiParenthesizedExpression parenthesizedExpression =
- (PsiParenthesizedExpression)value;
- final PsiExpression expression =
- parenthesizedExpression.getExpression();
- return getCaseValueText(expression);
- }
if (!(value instanceof PsiReferenceExpression)) {
return value.getText();
}
- final PsiReferenceExpression referenceExpression =
- (PsiReferenceExpression)value;
+ final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)value;
final PsiElement target = referenceExpression.resolve();
final String text = referenceExpression.getText();
if (!(target instanceof PsiEnumConstant)) {
@@ -279,96 +238,74 @@ public class ConvertSwitchToIfIntention implements IntentionAction {
return name + '.' + text;
}
- private static void dumpBranch(
- String expressionText, List caseValues,
- List bodyStatements,
- Set variables, boolean firstBranch,
- boolean useEquals,
- @NonNls StringBuilder ifStatementString) {
+ private static void dumpBranch(SwitchStatementBranch branch,
+ String expressionText,
+ boolean firstBranch,
+ boolean useEquals,
+ @NonNls StringBuilder out) {
if (!firstBranch) {
- ifStatementString.append("else ");
+ out.append("else ");
}
- dumpCaseValues(expressionText, caseValues, useEquals,
- ifStatementString);
- dumpBody(bodyStatements, variables, ifStatementString);
+ dumpCaseValues(expressionText, branch.getCaseValues(), useEquals, out);
+ dumpBody(branch, out);
}
- private static void dumpDefaultBranch(
- List bodyStatements,
- Set variables, boolean firstBranch,
- @NonNls StringBuilder ifStatementString) {
+ private static void dumpDefaultBranch(SwitchStatementBranch defaultBranch, boolean firstBranch, @NonNls StringBuilder out) {
if (!firstBranch) {
- ifStatementString.append("else ");
+ out.append("else ");
}
- dumpBody(bodyStatements, variables, ifStatementString);
+ dumpBody(defaultBranch, out);
}
- private static void dumpCaseValues(
- String expressionText, List caseValues, boolean useEquals,
- @NonNls StringBuilder ifStatementString) {
- ifStatementString.append("if(");
+ private static void dumpCaseValues(String expressionText, List caseValues, boolean useEquals, @NonNls StringBuilder out) {
+ out.append("if(");
boolean firstCaseValue = true;
for (String caseValue : caseValues) {
if (!firstCaseValue) {
- ifStatementString.append("||");
+ out.append("||");
}
firstCaseValue = false;
- ifStatementString.append(expressionText);
if (useEquals) {
- ifStatementString.append(".equals(");
- ifStatementString.append(caseValue);
- ifStatementString.append(')');
+ out.append(caseValue).append(".equals(").append(expressionText).append(')');
}
else {
- ifStatementString.append("==");
- ifStatementString.append(caseValue);
+ out.append(expressionText).append("==").append(caseValue);
}
}
- ifStatementString.append(')');
+ out.append(')');
}
- private static void dumpBody(List bodyStatements,
- Set variables,
- @NonNls StringBuilder ifStatementString) {
- ifStatementString.append('{');
- for (PsiLocalVariable variable : variables) {
- if (ReferencesSearch.search(variable, new LocalSearchScope(bodyStatements.toArray(new PsiElement[bodyStatements.size()]))).findFirst() != null) {
- final PsiType varType = variable.getType();
- ifStatementString.append(varType.getCanonicalText());
- ifStatementString.append(' ');
- ifStatementString.append(variable.getName());
- ifStatementString.append(';');
+ private static void dumpBody(SwitchStatementBranch branch, @NonNls StringBuilder out) {
+ final List bodyStatements = branch.getBodyElements();
+ out.append('{');
+ for (PsiLocalVariable variable : branch.getPendingVariableDeclarations()) {
+ if (ReferencesSearch.search(variable, new LocalSearchScope(bodyStatements.toArray(PsiElement.EMPTY_ARRAY))).findFirst() != null) {
+ out.append(variable.getType().getCanonicalText()).append(' ').append(variable.getName()).append(';');
}
}
for (PsiElement bodyStatement : bodyStatements) {
if (bodyStatement instanceof PsiBlockStatement) {
- final PsiBlockStatement blockStatement =
- (PsiBlockStatement)bodyStatement;
+ final PsiBlockStatement blockStatement = (PsiBlockStatement)bodyStatement;
final PsiCodeBlock codeBlock = blockStatement.getCodeBlock();
- final PsiStatement[] statements = codeBlock.getStatements();
- for (PsiStatement statement : statements) {
- appendElement(statement, ifStatementString);
+ for (PsiStatement statement : codeBlock.getStatements()) {
+ appendElement(statement, out);
}
}
else {
- appendElement(bodyStatement, ifStatementString);
+ appendElement(bodyStatement, out);
}
}
- ifStatementString.append("\n}");
+ out.append("\n}");
}
- private static void appendElement(
- PsiElement element, @NonNls StringBuilder ifStatementString) {
+ private static void appendElement(PsiElement element, @NonNls StringBuilder out) {
if (element instanceof PsiBreakStatement) {
- final PsiBreakStatement breakStatement =
- (PsiBreakStatement)element;
- final PsiIdentifier identifier =
- breakStatement.getLabelIdentifier();
+ final PsiBreakStatement breakStatement = (PsiBreakStatement)element;
+ final PsiIdentifier identifier = breakStatement.getLabelIdentifier();
if (identifier == null) {
return;
}
}
- final String text = element.getText();
- ifStatementString.append(text);
+ out.append(element.getText());
}
}
diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java
index 0fb4d3f8b166..1c640d78f82a 100644
--- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java
+++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java
@@ -30,15 +30,18 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actions.EnterAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -363,19 +366,45 @@ public class CreateLocalVarFromInstanceofAction extends BaseIntentionAction {
anchorAfter = ((PsiBlockStatement)whileStatement.getBody()).getCodeBlock().getLBrace();
}
}
+
if (anchorAfter == null) {
return null;
}
- PsiElement nextSibling = PsiTreeUtil.skipWhitespacesForward(anchorAfter);
- anchorAfter = nextSibling instanceof PsiComment ? PsiTreeUtil.skipSiblingsForward(nextSibling, PsiComment.class) : anchorAfter;
- nextSibling = PsiTreeUtil.getNextSiblingOfType(anchorAfter, PsiStatement.class);
- while (nextSibling instanceof PsiDeclarationStatement) {
+ PsiElement nextSibling = anchorAfter.getNextSibling();
+ while (nextSibling != null) {
+ if (nextSibling instanceof PsiWhiteSpace) {
+ final String text = nextSibling.getText();
+ if (StringUtil.countNewLines(text) > 1) {
+ final PsiElement newWhitespace = PsiParserFacade.SERVICE.getInstance(nextSibling.getProject())
+ .createWhiteSpaceFromText(text.substring(0, text.lastIndexOf('\n')));
+ nextSibling.replace(newWhitespace);
+ break;
+ }
+ nextSibling = nextSibling.getNextSibling();
+ continue;
+ }
+ else if (!isValidDeclarationStatement(nextSibling) && !(nextSibling instanceof PsiComment)) {
+ break;
+ }
anchorAfter = nextSibling;
- nextSibling = PsiTreeUtil.getNextSiblingOfType(anchorAfter, PsiStatement.class);
+ nextSibling = anchorAfter.getNextSibling();
}
return anchorAfter.getParent().addAfter(toInsert, anchorAfter);
}
+ private static boolean isValidDeclarationStatement(PsiElement nextSibling) {
+ if (!(nextSibling instanceof PsiDeclarationStatement)) {
+ return false;
+ }
+ final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)nextSibling;
+ final PsiElement[] elements = declarationStatement.getDeclaredElements();
+ if (elements.length == 0) {
+ return false;
+ }
+ final PsiElement lastElement = elements[elements.length - 1];
+ return !(lastElement instanceof PsiClass) && PsiUtil.isJavaToken(lastElement.getLastChild(), JavaTokenType.SEMICOLON);
+ }
+
private static void reformatNewCodeBlockBraces(final PsiElement start, final PsiBlockStatement end)
throws IncorrectOperationException {
CodeStyleManager.getInstance(end.getProject()).reformatRange(end.getContainingFile(),
diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java
index 660775834b50..0eab0117d989 100644
--- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java
+++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java
@@ -47,7 +47,7 @@ public class InsertNewFix implements IntentionAction {
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
- return myMethodCall.isValid() && myMethodCall.getManager().isInProject(myMethodCall);
+ return myMethodCall.isValid() && myMethodCall.getManager().isInProject(myMethodCall) && !(myMethodCall.getNextSibling() instanceof PsiErrorElement);
}
@NotNull
diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java
index 4f8de02b85a6..eb0147675d99 100644
--- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java
+++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java
@@ -25,6 +25,7 @@ import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
@@ -129,10 +130,14 @@ public class JavaMethodOverloadSwitchHandler extends EditorWriteActionHandler {
}
if (targetCaretPosition == -1) targetCaretPosition = offset;
caret.moveToLogicalPosition(editor.offsetToLogicalPosition(targetCaretPosition).leanForward(true));
- call.putUserData(JavaMethodCallElement.COMPLETION_HINTS, Boolean.TRUE);
+ PsiCall methodCall = (PsiCall)call;
+ if (!JavaMethodCallElement.isCompletionMode(methodCall)) {
+ JavaMethodCallElement.setCompletionMode(methodCall, true);
+ Disposer.register(controller, () -> JavaMethodCallElement.setCompletionMode(methodCall, false));
+ }
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
- CompletionMemory.registerChosenMethod(targetMethod, (PsiCall)call);
+ CompletionMemory.registerChosenMethod(targetMethod, methodCall);
controller.resetHighlighted();
controller.updateComponent(); // update popup immediately (otherwise, it will be updated only after delay)
ParameterHintsPass.syncUpdate(call, editor);
diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java b/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java
index ca474ff2370d..cfe0e7915e53 100644
--- a/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java
+++ b/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java
@@ -20,6 +20,7 @@ import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMember;
+import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -52,8 +53,7 @@ public abstract class GenerationInfoBase implements GenerationInfo {
if (!GenerateMembersUtil.isChildInRange(element, lBrace.getNextSibling(), rBrace)) {
return null;
}
- PsiElement prev = leaf.getPrevSibling();
- if (prev != null && prev.getNode() != null && prev.getNode().getElementType() == JavaTokenType.END_OF_LINE_COMMENT) {
+ if (leaf.getParent() == aClass && PsiUtilCore.getElementType(leaf.getPrevSibling()) == JavaTokenType.END_OF_LINE_COMMENT) {
element = leaf.getNextSibling();
}
return element;
diff --git a/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java b/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java
index 6819a9b4a276..1a9c4ad23e1b 100644
--- a/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java
+++ b/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java
@@ -23,12 +23,17 @@ import com.intellij.codeInsight.completion.CompletionMemory;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.codeInsight.completion.JavaMethodCallElement;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
+import com.intellij.codeInsight.hints.ParameterHintsPass;
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.lang.parameterInfo.*;
import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.resolve.CompletionParameterTypeInferencePolicy;
@@ -59,6 +64,8 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc
PsiMethodCallExpression.class, PsiNewExpression.class, PsiAnonymousClass.class, PsiEnumConstant.class);
private static final Set extends Class> ourStopSearch = Collections.singleton(PsiMethod.class);
+
+ private Inlay myHighlightedHint;
@Override
public Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context) {
@@ -125,38 +132,58 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc
@Override
public PsiExpressionList findElementForUpdatingParameterInfo(@NotNull final UpdateParameterInfoContext context) {
PsiExpressionList expressionList = findArgumentList(context.getFile(), context.getOffset(), context.getParameterListStart());
- if (expressionList == null) return null;
- Object[] candidates = context.getObjectsToView();
- if (candidates == null || candidates.length == 0) return null;
- Object currentMethodInfo = context.getHighlightedParameter();
- if (currentMethodInfo == null) currentMethodInfo = candidates[0];
- if (!(currentMethodInfo instanceof CandidateInfo)) return null;
- PsiElement element = ((CandidateInfo)currentMethodInfo).getElement();
- if (!(element instanceof PsiMethod)) return null;
-
- PsiMethod method = (PsiMethod)element;
- PsiElement parent = expressionList.getParent();
- int currentNumberOfParameters = expressionList.getExpressions().length;
- PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(context.getProject());
- Document document = psiDocumentManager.getCachedDocument(context.getFile());
- if ((context.getHighlightedParameter() != null || candidates.length == 1) && parent != null &&
- document != null && psiDocumentManager.isCommitted(document) &&
- isIncompatibleParameterCount(method, currentNumberOfParameters)) {
- parent.putUserData(JavaMethodCallElement.COMPLETION_HINTS, null);
+ if (expressionList != null) {
+ Object[] candidates = context.getObjectsToView();
+ if (candidates != null && candidates.length != 0) {
+ Object currentMethodInfo = context.getHighlightedParameter();
+ if (currentMethodInfo == null) currentMethodInfo = candidates[0];
+ if ((currentMethodInfo instanceof CandidateInfo)) {
+ PsiElement element = ((CandidateInfo)currentMethodInfo).getElement();
+ if ((element instanceof PsiMethod)) {
+ PsiMethod method = (PsiMethod)element;
+ PsiElement parent = expressionList.getParent();
+
+ String originalMethodName = method.getName();
+ PsiQualifiedReference currentMethodReference = null;
+ if (parent instanceof PsiMethodCallExpression && !method.isConstructor()) {
+ currentMethodReference = ((PsiMethodCallExpression)parent).getMethodExpression();
+ }
+ else if (parent instanceof PsiNewExpression) {
+ currentMethodReference = ((PsiNewExpression)parent).getClassReference();
+ }
+ else if (parent instanceof PsiAnonymousClass) {
+ currentMethodReference = ((PsiAnonymousClass)parent).getBaseClassReference();
+ }
+ if (currentMethodReference == null || originalMethodName.equals(currentMethodReference.getReferenceName())) {
+
+ int currentNumberOfParameters = expressionList.getExpressions().length;
+ PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(context.getProject());
+ Document document = psiDocumentManager.getCachedDocument(context.getFile());
+ if (parent instanceof PsiCallExpression && JavaMethodCallElement.isCompletionMode((PsiCall)parent)) {
+ PsiMethod chosenMethod = CompletionMemory.getChosenMethod((PsiCall)parent);
+ if ((context.getHighlightedParameter() != null || candidates.length == 1) && chosenMethod != null &&
+ document != null && psiDocumentManager.isCommitted(document) &&
+ isIncompatibleParameterCount(chosenMethod, currentNumberOfParameters)) {
+ JavaMethodCallElement.setCompletionMode((PsiCall)parent, false);
+ highlightHints(context.getEditor(), null, -1);
+ }
+ else {
+ int index = ParameterInfoUtils.getCurrentParameterIndex(expressionList.getNode(),
+ context.getOffset(), JavaTokenType.COMMA);
+ TextRange textRange = expressionList.getTextRange();
+ if (context.getOffset() <= textRange.getStartOffset() || context.getOffset() >= textRange.getEndOffset()) index = -1;
+ highlightHints(context.getEditor(), expressionList, index);
+ }
+ }
+
+ return expressionList;
+ }
+ }
+ }
+ }
}
-
- String originalMethodName = method.getName();
- PsiQualifiedReference currentMethodReference = null;
- if (parent instanceof PsiMethodCallExpression && !method.isConstructor()) {
- currentMethodReference = ((PsiMethodCallExpression)parent).getMethodExpression();
- }
- else if (parent instanceof PsiNewExpression) {
- currentMethodReference = ((PsiNewExpression)parent).getClassReference();
- }
- else if (parent instanceof PsiAnonymousClass) {
- currentMethodReference = ((PsiAnonymousClass)parent).getBaseClassReference();
- }
- return (currentMethodReference == null || originalMethodName.equals(currentMethodReference.getReferenceName())) ? expressionList : null;
+ highlightHints(context.getEditor(), null, -1);
+ return null;
}
private static boolean isIncompatibleParameterCount(@NotNull PsiMethod method, int numberOfParameters) {
@@ -269,6 +296,56 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc
}
}
+ private void highlightHints(@NotNull Editor editor, @Nullable PsiExpressionList expressionList, int currentHintIndex) {
+ if (editor.isDisposed()) return;
+ ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
+ Inlay hint = null;
+ if (expressionList != null && expressionList.isValid() &&
+ currentHintIndex >= 0 && (currentHintIndex < expressionList.getExpressions().length ||
+ currentHintIndex == 0 && expressionList.getExpressions().length == 0)) {
+ PsiElement prevDelimiter, nextDelimiter;
+ if (currentHintIndex < expressionList.getExpressions().length) {
+ PsiExpression expression = expressionList.getExpressions()[currentHintIndex];
+ //noinspection StatementWithEmptyBody
+ for (prevDelimiter = expression;
+ prevDelimiter != null && !(prevDelimiter instanceof PsiJavaToken);
+ prevDelimiter = prevDelimiter.getPrevSibling())
+ ;
+ //noinspection StatementWithEmptyBody
+ for (nextDelimiter = expression;
+ nextDelimiter != null && !(nextDelimiter instanceof PsiJavaToken);
+ nextDelimiter = nextDelimiter.getNextSibling())
+ ;
+ }
+ else {
+ prevDelimiter = expressionList.getFirstChild(); // left parenthesis
+ nextDelimiter = expressionList.getLastChild(); // right parenthesis
+ }
+ if (prevDelimiter != null && nextDelimiter != null) {
+ ParameterHintsPass.syncUpdate(expressionList.getParent(), editor);
+ for (Inlay inlay : editor.getInlayModel().getInlineElementsInRange(prevDelimiter.getTextRange().getEndOffset(),
+ nextDelimiter.getTextRange().getStartOffset())) {
+ if (presentationManager.isParameterHint(inlay)) {
+ hint = inlay;
+ break;
+ }
+ }
+ }
+ }
+ if (hint == myHighlightedHint) return;
+ if (myHighlightedHint != null && myHighlightedHint.isValid()) presentationManager.setHighlighted(myHighlightedHint, false);
+ myHighlightedHint = hint;
+ if (myHighlightedHint != null && myHighlightedHint.isValid()) presentationManager.setHighlighted(myHighlightedHint, true);
+ }
+
+ @Override
+ public void dispose() {
+ if (myHighlightedHint != null) {
+ if (myHighlightedHint.isValid()) ParameterHintsPresentationManager.getInstance().setHighlighted(myHighlightedHint, false);
+ myHighlightedHint = null;
+ }
+ }
+
private static PsiSubstitutor getCandidateInfoSubstitutor(CandidateInfo candidate) {
return candidate instanceof MethodCandidateInfo && ((MethodCandidateInfo)candidate).isInferencePossible()
? ((MethodCandidateInfo)candidate).inferTypeArguments(CompletionParameterTypeInferencePolicy.INSTANCE, true)
diff --git a/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt b/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt
index 3ed6ea0dd58c..7fbb2f97859c 100644
--- a/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt
@@ -23,12 +23,13 @@ import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil
import com.intellij.psi.impl.source.tree.java.PsiMethodCallExpressionImpl
import com.intellij.psi.impl.source.tree.java.PsiNewExpressionImpl
import com.intellij.psi.util.TypeConversionUtil
+import com.intellij.util.IncorrectOperationException
object JavaInlayHintsProvider {
fun hints(callExpression: PsiCallExpression): Set {
- if (JavaMethodCallElement.showCompletionHints(callExpression)) {
+ if (JavaMethodCallElement.isCompletionMode(callExpression)) {
val method = CompletionMemory.getChosenMethod(callExpression)?:return emptySet()
val params = method.parameterList.parameters
@@ -46,7 +47,7 @@ object JavaInlayHintsProvider {
}.toSet()
}
- if (!EditorSettingsExternalizable.getInstance().isShowParameterNameHints()) return emptySet()
+ if (!EditorSettingsExternalizable.getInstance().isShowParameterNameHints) return emptySet()
val resolveResult = callExpression.resolveMethodGenerics()
val hints = methodHints(callExpression, resolveResult)
@@ -88,12 +89,55 @@ object JavaInlayHintsProvider {
if (element is PsiMethod && isMethodToShow(element, callExpression)) {
val info = callInfo(callExpression, element)
- return hintSet(info, substitutor)
+ if (isCallInfoToShow(info)) {
+ return hintSet(info, substitutor)
+ }
}
return emptySet()
}
+ private fun isCallInfoToShow(info: CallInfo): Boolean {
+ val hintsProvider = JavaInlayParameterHintsProvider.getInstance()
+ if (hintsProvider.ignoreOneCharOneDigitHints.get() && info.allParamsSequential()) {
+ return false
+ }
+ return true
+ }
+
+ private fun String.decomposeOrderedParams(): Pair? {
+ val firstDigit = indexOfFirst { it.isDigit() }
+ if (firstDigit < 0) return null
+
+ val prefix = substring(0, firstDigit)
+ try {
+ val number = substring(firstDigit, length).toInt()
+ return prefix to number
+ }
+ catch (e: NumberFormatException) {
+ return null
+ }
+ }
+
+ private fun CallInfo.allParamsSequential(): Boolean {
+ val paramNames = regularArgs
+ .map { it.parameter.name?.decomposeOrderedParams() }
+ .filterNotNull()
+
+ if (paramNames.size > 1 && paramNames.size == regularArgs.size) {
+ val prefixes = paramNames.map { it.first }
+ if (prefixes.toSet().size != 1) return false
+
+ val numbers = paramNames.map { it.second }
+ val first = numbers.first()
+ if (first == 0 || first == 1) {
+ return numbers.areSequential()
+ }
+ }
+
+ return false
+ }
+
private fun hintSet(info: CallInfo, substitutor: PsiSubstitutor): Set {
val resultSet = mutableSetOf()
@@ -107,7 +151,7 @@ object JavaInlayHintsProvider {
}
resultSet.addAll(info.unclearInlays(substitutor))
-
+
return resultSet
}
@@ -169,6 +213,15 @@ object JavaInlayHintsProvider {
}
+private fun List.areSequential(): Boolean {
+ if (size == 0) throw IncorrectOperationException("List is empty")
+ val ordered = (first()..first() + size - 1).toList()
+ if (ordered.size == size) {
+ return zip(ordered).all { it.first == it.second }
+ }
+ return false
+}
+
private fun inlayInfo(info: CallArgumentInfo, showOnlyIfExistedBefore: Boolean = false): InlayInfo? {
return inlayInfo(info.argument, info.parameter, showOnlyIfExistedBefore)
diff --git a/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt b/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt
index 1e74940b4623..ee2eb66894b3 100644
--- a/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt
@@ -119,12 +119,18 @@ class JavaInlayParameterHintsProvider : InlayParameterHintsProvider {
val isDoNotShowForBuilderLikeMethods = Option("java.build.like.method",
"Do not show for builder-like methods",
true)
-
+
+
+ val ignoreOneCharOneDigitHints = Option("java.simple.sequentially.numbered",
+ "Do not show for methods with same-named numbered parameters",
+ true)
+
override fun getSupportedOptions(): List