diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java index 3c9b2548d4c3..3aecd6c9829c 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java @@ -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; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java index 2c2da7946ad7..29c96023c583 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java @@ -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); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/InferenceCache.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/InferenceCache.java index a497bbf59f7a..6e9557665b32 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/InferenceCache.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/InferenceCache.java @@ -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) ); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/psiUtil.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/psiUtil.kt index 558b3efd98f8..a86e5f98a4ea 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/psiUtil.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/psiUtil.kt @@ -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() ?: return true return parent !is GrImportStatement } + +fun GrExpression?.isNullLiteral(): Boolean { + return this is GrLiteral && GrLiteralImpl.getLiteralType(this) == KW_NULL +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/controlFlow/ControlFlowTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/controlFlow/ControlFlowTest.groovy index f009ddac046e..078ee1635f0c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/controlFlow/ControlFlowTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/controlFlow/ControlFlowTest.groovy @@ -106,6 +106,8 @@ class ControlFlowTest extends LightCodeInsightFixtureTestCase { void testIfInstanceofOr() { doTest() } + void testIfNullOrInstanceof() { doTest() } + void testReturnMapFromClosure() { doTest() } void testSwitchInTryWithThrows() { doTest() } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy index 493f088a61c7..87398403fc5e 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy @@ -571,6 +571,58 @@ def foo(a) { ''', 'A' } + void 'test null or instanceof'() { + doTest '''\ +def foo(a) { + if (a == null || a instanceof String) { + a + } +} +''', JAVA_LANG_STRING + } + + void 'test null or instanceof 2'() { + doTest '''\ +def foo(a) { + if (null == a || a instanceof String) { + a + } +} +''', JAVA_LANG_STRING + } + + void 'test instanceof or null'() { + doTest '''\ +def foo(a) { + if (a == null || a instanceof String) { + a + } +} +''', JAVA_LANG_STRING + } + + void 'test instanceof or null 2'() { + doTest '''\ +def foo(a) { + if (null == a || a instanceof String) { + a + } +} +''', JAVA_LANG_STRING + } + + void 'test null or instanceof else'() { + doTest '''\ +def foo(a) { + if (a != null && a !instanceof String) { + + } else { + a + } +} +''', JAVA_LANG_STRING + } + void 'test enum constant'() { doTest('''\ import static MyEnum.* diff --git a/plugins/groovy/testdata/groovy/controlFlow/ifNullOrInstanceof.test b/plugins/groovy/testdata/groovy/controlFlow/ifNullOrInstanceof.test new file mode 100644 index 000000000000..5490e5897a74 --- /dev/null +++ b/plugins/groovy/testdata/groovy/controlFlow/ifNullOrInstanceof.test @@ -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 \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/try6.test b/plugins/groovy/testdata/groovy/controlFlow/try6.test index f375c6a90eae..e3783c46d825 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/try6.test +++ b/plugins/groovy/testdata/groovy/controlFlow/try6.test @@ -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 \ No newline at end of file +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 \ No newline at end of file