[groovy] dfa: treat null comparison as instanceof (IDEA-201672)

This commit is contained in:
Daniil Ovchinnikov
2019-01-23 12:57:50 +03:00
parent e23ca69828
commit bce0e9a36e
8 changed files with 128 additions and 28 deletions
@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.lang.psi.controlFlow;
import com.intellij.openapi.util.Pair;
@@ -31,6 +17,8 @@ import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.InstructionImpl;
import java.util.Objects;
import static org.jetbrains.plugins.groovy.lang.psi.util.PsiUtilKt.isNullLiteral;
/**
* @author peter
*/
@@ -75,6 +63,16 @@ public class InstanceOfInstruction extends InstructionImpl implements MixinTypeI
return new Pair<>(left, type);
}
}
else if (element instanceof GrBinaryExpression) {
GrExpression left = ((GrBinaryExpression)element).getLeftOperand();
GrExpression right = ((GrBinaryExpression)element).getRightOperand();
if (isNullLiteral(right)) {
return Pair.create(left, PsiType.NULL);
}
else if (right != null && isNullLiteral(left)) {
return Pair.create(right, PsiType.NULL);
}
}
return null;
}
@@ -41,7 +41,8 @@ import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.*;
import static org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes.T_ELVIS_ASSIGN;
import static org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes.*;
import static org.jetbrains.plugins.groovy.lang.psi.util.PsiUtilKt.isNullLiteral;
/**
* @author ven
@@ -559,6 +560,18 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor {
processInstanceOf(expression, ((GrInExpression)expression).getNegationToken() != null);
return;
}
if (opType == T_EQ || opType == T_NEQ) {
if (isNullLiteral(right)) {
left.accept(this);
processInstanceOf(expression, opType == T_NEQ);
return;
}
else if (right != null && isNullLiteral(left)) {
right.accept(this);
processInstanceOf(expression, opType == T_NEQ);
return;
}
}
if (opType != GroovyTokenTypes.mLOR && opType != GroovyTokenTypes.mLAND && opType != GroovyTokenTypes.kIN) {
left.accept(this);
@@ -12,6 +12,7 @@ import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrInstanceOfExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
@@ -160,6 +161,7 @@ class InferenceCache {
return PsiTreeUtil.findFirstParent(
element,
element1 -> !(element1.getParent() instanceof GrExpression)
|| element1 instanceof GrBinaryExpression
|| element1 instanceof GrInstanceOfExpression
|| isExpressionStatement(element1)
);
@@ -7,6 +7,7 @@ import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiParameter
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.kIN
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes.KW_NULL
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration
@@ -14,11 +15,13 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForInClaus
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrOperatorExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl
/**
* @param owner modifier list owner
@@ -63,3 +66,7 @@ fun GrCodeReferenceElement.mayContainTypeArguments(): Boolean {
val (parent, _) = skipParentsOfType<GrCodeReferenceElement>() ?: return true
return parent !is GrImportStatement
}
fun GrExpression?.isNullLiteral(): Boolean {
return this is GrLiteral && GrLiteralImpl.getLiteralType(this) == KW_NULL
}
@@ -106,6 +106,8 @@ class ControlFlowTest extends LightCodeInsightFixtureTestCase {
void testIfInstanceofOr() { doTest() }
void testIfNullOrInstanceof() { doTest() }
void testReturnMapFromClosure() { doTest() }
void testSwitchInTryWithThrows() { doTest() }
@@ -571,6 +571,58 @@ def foo(a) {
''', 'A'
}
void 'test null or instanceof'() {
doTest '''\
def foo(a) {
if (a == null || a instanceof String) {
<caret>a
}
}
''', JAVA_LANG_STRING
}
void 'test null or instanceof 2'() {
doTest '''\
def foo(a) {
if (null == a || a instanceof String) {
<caret>a
}
}
''', JAVA_LANG_STRING
}
void 'test instanceof or null'() {
doTest '''\
def foo(a) {
if (a == null || a instanceof String) {
<caret>a
}
}
''', JAVA_LANG_STRING
}
void 'test instanceof or null 2'() {
doTest '''\
def foo(a) {
if (null == a || a instanceof String) {
<caret>a
}
}
''', JAVA_LANG_STRING
}
void 'test null or instanceof else'() {
doTest '''\
def foo(a) {
if (a != null && a !instanceof String) {
} else {
<caret>a
}
}
''', JAVA_LANG_STRING
}
void 'test enum constant'() {
doTest('''\
import static MyEnum.*
@@ -0,0 +1,22 @@
if (a == null || a instanceof String) {
a
}
-----
0(1) element: null
1(2) element: IF statement
2(3) Condition Logical expression
3(4) READ a
4(5,7) Condition Relational expression, dependent: 2
5(6) instanceof: a == null
6(9) Negating goto instruction, condition=4Relational expression
7(8) instanceof: a == null
8(14) element: Logical expression
9(10) READ a
10(11,13) Condition Instanceof expression
11(12) instanceof: a instanceof String
12(16) Negating goto instruction, condition=10Instanceof expression
13(14) instanceof: a instanceof String
14(15) READ a
15(16) element: Reference expression MAYBE_RETURN
16(17) End element: IF statement
17() element: null
+16 -12
View File
@@ -10,15 +10,19 @@ return url
-----
0(1) element: null
1(2) element: IF statement
2(3,10) READ url
3(4) element: Open block
4(11) WRITE url
5(6) element: Catch clause
6(7) WRITE e
7(8) READ e
8(9) ARGUMENTS element: NEW expression
9(13) THROW. element: THROW statement
10(11) End element: IF statement
11(12) READ url
12(13) element: RETURN statement
13() element: null
2(3) READ url
3(4,6) Condition Relational expression
4(5) instanceof: url == null
5(14) Negating goto instruction, condition=3Relational expression
6(7) instanceof: url == null
7(8) element: Open block
8(15) WRITE url
9(10) element: Catch clause
10(11) WRITE e
11(12) READ e
12(13) ARGUMENTS element: NEW expression
13(17) THROW. element: THROW statement
14(15) End element: IF statement
15(16) READ url
16(17) element: RETURN statement
17() element: null