From e89edae2997ecdb587bfb51a8b6e7cf77483fb59 Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Mon, 28 May 2018 18:12:51 +0300 Subject: [PATCH] [groovy] control flow for negated `in` and `instanceof` (IDEA-188424) --- .../controlFlow/ControlFlowBuilderUtil.java | 20 +----- .../impl/ConditionInstruction.java | 25 +++---- .../controlFlow/impl/ControlFlowBuilder.java | 21 +++--- .../groovy/lang/psi/dataFlow/DFAType.java | 20 +----- .../lang/controlFlow/ControlFlowTest.groovy | 18 +---- .../lang/resolve/TypeInferenceTest.groovy | 69 +++++++++++++++++++ .../controlFlow/ifNegatedInstanceofElse.test | 28 ++++++++ 7 files changed, 125 insertions(+), 76 deletions(-) create mode 100644 plugins/groovy/testdata/groovy/controlFlow/ifNegatedInstanceofElse.test diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java index 7f4a58c39dac..d55a1080180f 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.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-2018 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.diagnostic.Logger; @@ -25,9 +11,9 @@ import com.intellij.util.ArrayUtilRt; import com.intellij.util.containers.ObjectIntHashMap; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; +import org.jetbrains.plugins.groovy.lang.psi.api.GrInExpression; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.formatter.GrControlStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; @@ -94,7 +80,7 @@ public class ControlFlowBuilderUtil { } public static boolean isInstanceOfBinary(GrBinaryExpression binary) { - if (binary.getOperationTokenType() == GroovyTokenTypes.kIN) { + if (binary instanceof GrInExpression) { GrExpression left = binary.getLeftOperand(); GrExpression right = binary.getRightOperand(); if (left instanceof GrReferenceExpression && ((GrReferenceExpression)left).getQualifier() == null && diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.java index ad24d98541ce..25d00b8fe563 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.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-2018 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.impl; import com.intellij.openapi.util.text.StringUtil; @@ -30,19 +16,26 @@ import java.util.Set; */ public class ConditionInstruction extends InstructionImpl implements Instruction { + private final boolean myNegated; private final Set myDependent; - public ConditionInstruction(@NotNull PsiElement element, @NotNull Collection dependent) { + public ConditionInstruction(@NotNull PsiElement element, boolean negated, @NotNull Collection dependent) { super(element); + myNegated = negated; myDependent = new LinkedHashSet<>(dependent); myDependent.add(this); } + public boolean isNegated() { + return myNegated; + } + @NotNull @Override protected String getElementPresentation() { StringBuilder builder = new StringBuilder(); builder.append("Condition ").append(getElement()); + if (myNegated) builder.append(", negated"); if (myDependent.size() > 1) { builder.append(", dependent: "); builder.append(StringUtil.join(ContainerUtil.filter(myDependent, d -> d != this), i -> String.valueOf(i.num()), ", ")); 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 76df1981b2fe..190625583c0d 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 @@ -1,6 +1,4 @@ -/* - * Copyright 2000-2017 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. - */ +// Copyright 2000-2018 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.impl; import com.intellij.openapi.diagnostic.Attachment; @@ -22,6 +20,7 @@ import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; +import org.jetbrains.plugins.groovy.lang.psi.api.GrInExpression; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrCondition; import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; @@ -442,7 +441,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } FList conditionsBefore = myConditions; - ConditionInstruction cond = registerCondition(expression); + ConditionInstruction cond = registerCondition(expression, false); addNodeAndCheckPending(cond); operand.accept(this); @@ -500,7 +499,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { @Override public void visitInstanceofExpression(@NotNull GrInstanceOfExpression expression) { expression.getOperand().accept(this); - processInstanceOf(expression); + processInstanceOf(expression, expression.getNegationToken() != null); } @Override @@ -562,7 +561,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { if (ControlFlowBuilderUtil.isInstanceOfBinary(expression)) { expression.getLeftOperand().accept(this); - processInstanceOf(expression); + processInstanceOf(expression, ((GrInExpression)expression).getNegationToken() != null); return; } @@ -576,7 +575,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } FList conditionsBefore = myConditions; - ConditionInstruction condition = registerCondition(expression); + ConditionInstruction condition = registerCondition(expression, false); addNodeAndCheckPending(condition); left.accept(this); @@ -619,9 +618,9 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { right.accept(this); } - private void processInstanceOf(GrExpression expression) { + private void processInstanceOf(GrExpression expression, boolean negated) { FList conditionsBefore = myConditions; - ConditionInstruction cond = registerCondition(expression); + ConditionInstruction cond = registerCondition(expression, negated); addNodeAndCheckPending(cond); addNode(new InstanceOfInstruction(expression, cond)); @@ -718,8 +717,8 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } @NotNull - private ConditionInstruction registerCondition(@NotNull PsiElement element) { - ConditionInstruction condition = new ConditionInstruction(element, myConditions); + private ConditionInstruction registerCondition(@NotNull PsiElement element, boolean negated) { + ConditionInstruction condition = new ConditionInstruction(element, negated, myConditions); myConditions = myConditions.prepend(condition); return condition; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java index bec2a760d6fd..85e4db43bc0f 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2014 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-2018 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.dataFlow; import com.intellij.openapi.util.Comparing; @@ -78,12 +64,12 @@ public class DFAType { this.primary = primary; } - public void addMixin(@Nullable PsiType mixin, ConditionInstruction instruction) { + public void addMixin(@Nullable PsiType mixin, @Nullable ConditionInstruction instruction) { if (mixin == null) { return; } - mixins.add(new Mixin(mixin, instruction, false)); + mixins.add(new Mixin(mixin, instruction, instruction != null && instruction.isNegated())); } @Override 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 60d95884620c..0961c848fc9d 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 @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 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-2018 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.controlFlow import com.intellij.openapi.editor.SelectionModel @@ -108,6 +94,8 @@ class ControlFlowTest extends LightCodeInsightFixtureTestCase { void testIfInstanceofElse() { doTest() } + void testIfNegatedInstanceofElse() { 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 88b2a706acfc..4a4eae72cd78 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 @@ -466,6 +466,15 @@ def bar(oo) { ''', null) } + void testNegatedInstanceOfInferring1() { + doTest('''\ +def bar(oo) { + boolean b = oo !instanceof String || oo != null + oo +} +''', null) + } + void testInstanceOfInferring2() { doTest('''\ def bar(oo) { @@ -475,6 +484,15 @@ def bar(oo) { ''', null) } + void testNegatedInstanceOfInferring2() { + doTest('''\ +def bar(oo) { + boolean b = oo !instanceof String || oo != null + oo +} +''', JAVA_LANG_STRING) + } + void testInstanceOfInferring3() { doTest('''\ def bar(oo) { @@ -484,6 +502,15 @@ def bar(oo) { ''', String.canonicalName) } + void testNegatedInstanceOfInferring3() { + doTest('''\ +def bar(oo) { + boolean b = oo !instanceof String && oo != null + oo +} +''', null) + } + void testInstanceOfInferring4() { doTest('''\ def bar(oo) { @@ -493,6 +520,15 @@ def bar(oo) { ''', null) } + void testNegatedInstanceOfInferring4() { + doTest('''\ +def bar(oo) { + boolean b = oo !instanceof String && oo != null + oo +} +''', null) + } + void testInstanceOfInferring5() { doTest('''\ def foo(def oo) { @@ -507,6 +543,20 @@ def foo(def oo) { ''', null) } + void testNegatedInstanceOfInferring5() { + doTest('''\ +def foo(def oo) { + if (oo !instanceof String || oo !instanceof CharSequence) { + oo + } + else { + oo + } + +} +''', JAVA_LANG_STRING) + } + void testInstanceOfInferring6() { doTest('''\ def foo(bar) { @@ -516,6 +566,17 @@ def foo(bar) { }''', 'java.lang.Runnable') } + void testNegatedInstanceOfInferring6() { + doTest('''\ +def foo(bar) { + if (!(bar !instanceof String) || bar !instanceof Runnable) { + + } else { + bar + } +}''', 'java.lang.Runnable') + } + void testInString() { doTest '''\ def foo(ii) { @@ -524,6 +585,14 @@ def foo(ii) { }''', 'java.lang.String' } + void testNegatedInString() { + doTest '''\ +def foo(ii) { + if (ii !in String) {} + else print ii +}''', 'java.lang.String' + } + void testIndexProperty() { doTest('''\ private void getCommonAncestor() { diff --git a/plugins/groovy/testdata/groovy/controlFlow/ifNegatedInstanceofElse.test b/plugins/groovy/testdata/groovy/controlFlow/ifNegatedInstanceofElse.test new file mode 100644 index 000000000000..fa03f043677a --- /dev/null +++ b/plugins/groovy/testdata/groovy/controlFlow/ifNegatedInstanceofElse.test @@ -0,0 +1,28 @@ +if (o !instanceof String) b = 1 +else if (!(o !instanceof Integer)) b = 2 +else b = 3 +----- +0(1) element: null +1(2) element: IF statement +2(3) READ o +3(4,6) Condition Instanceof expression, negated +4(5) instanceof: o !instanceof String +5(9) Negating goto instruction, condition=3Instanceof expression +6(7) instanceof: o !instanceof String +7(8) WRITE b +8(22) element: Assignment expression MAYBE_RETURN +9(10) element: IF statement +10(11) Condition Unary expression +11(12) READ o +12(13,15) Condition Instanceof expression, negated, dependent: 10 +13(14) instanceof: o !instanceof Integer +14(17) Negating goto instruction, condition=12Instanceof expression +15(16) instanceof: o !instanceof Integer +16(19) Positive goto instruction, condition=10Unary expression +17(18) WRITE b +18(21) element: Assignment expression MAYBE_RETURN +19(20) WRITE b +20(21) element: Assignment expression MAYBE_RETURN +21(22) End element: IF statement +22(23) End element: IF statement +23() element: null \ No newline at end of file