diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 09ed37ded8..77d7b3b69a 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -5166,4 +5166,37 @@ public void testCompileStatic9771() { runConformTest(sources, "[key:true]"); } + + @Test + public void testCompileStatic9786() { + //@formatter:off + String[] sources = { + "Main.groovy", + "interface I {\n" + + " void m()\n" + + "}\n" + + "class A implements I {\n" + + " void m() { print 'A' }\n" + + "}\n" + + "class B implements I {\n" + + " void m() { print 'B' }\n" + + "}\n" + + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " I x\n" + + " def y = false\n" + + " def z = true \n" + + " if (y) {\n" + + " x = new A()\n" + + " } else if (z) {\n" + + " x = new B()\n" + + " }\n" + + " x.m()\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "B"); + } } diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 420464bf02..d380f4b56b 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -985,6 +985,7 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) { accessedVariable = new ParameterVariableExpression((Parameter) accessedVariable); } if (accessedVariable instanceof VariableExpression) { + /* GRECLIPSE edit -- GROOVY-9786 VariableExpression var = (VariableExpression) accessedVariable; List types = typeCheckingContext.ifElseForWhileAssignmentTracker.get(var); if (types == null) { @@ -994,6 +995,9 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) { typeCheckingContext.ifElseForWhileAssignmentTracker.put(var, types); } types.add(resultType); + */ + recordAssignment((VariableExpression) accessedVariable, resultType); + // GRECLIPSE end } } storeType(leftExpression, resultType); @@ -4105,7 +4109,6 @@ protected void typeCheckClosureCall(final Expression callArguments, final ClassN @Override public void visitIfElse(final IfStatement ifElse) { Map> oldTracker = pushAssignmentTracking(); - try { // create a new temporary element in the if-then-else type info typeCheckingContext.pushTemporaryTypeInfo(); @@ -4126,20 +4129,26 @@ public void visitIfElse(final IfStatement ifElse) { visitEmptyStatement((EmptyStatement) elseBlock); } else { elseBlock.visit(this); + // GRECLIPSE add -- GROOVY-9786 + Map updates = + elseBlock.getNodeMetaData("assignments"); + if (updates != null) { + updates.forEach(this::recordAssignment); + } + // GRECLIPSE end } } finally { - popAssignmentTracking(oldTracker); + // GRECLIPSE add -- GROOVY-9786 + ifElse.putNodeMetaData("assignments", + // GRECLIPSE end + popAssignmentTracking(oldTracker)); } - BinaryExpression instanceOfExpression = findInstanceOfNotReturnExpression(ifElse); - if (instanceOfExpression == null) { - } else { - if (typeCheckingContext.enclosingBlocks.size() > 0) { - visitInstanceofNot(instanceOfExpression); - } + + if (!typeCheckingContext.enclosingBlocks.isEmpty()) { + Optional.ofNullable(findInstanceOfNotReturnExpression(ifElse)).ifPresent(this::visitInstanceofNot); } } - public void visitInstanceofNot(BinaryExpression be) { final BlockStatement currentBlock = typeCheckingContext.enclosingBlocks.getFirst(); assert currentBlock != null; @@ -4237,6 +4246,17 @@ public void visitCaseStatement(final CaseStatement statement) { restoreTypeBeforeConditional(); } + // GRECLIPSE add -- GROOVY-9786 + private void recordAssignment(final VariableExpression lhsExpr, final ClassNode rhsType) { + typeCheckingContext.ifElseForWhileAssignmentTracker.computeIfAbsent(lhsExpr, lhs -> { + ClassNode lhsType = lhs.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + List types = new ArrayList<>(2); + types.add(lhsType); + return types; + }).add(rhsType); + } + // GRECLIPSE end + private void restoreTypeBeforeConditional() { Set>> entries = typeCheckingContext.ifElseForWhileAssignmentTracker.entrySet(); for (Map.Entry> entry : entries) { diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index db05205cf1..19614f80fd 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -869,13 +869,15 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) { } // if we are in an if/else branch, keep track of assignment - if (typeCheckingContext.ifElseForWhileAssignmentTracker != null && leftExpression instanceof VariableExpression - && !isNullConstant(rightExpression)) { + if (!isNullConstant(rightExpression) + && leftExpression instanceof VariableExpression + && typeCheckingContext.ifElseForWhileAssignmentTracker != null) { Variable accessedVariable = ((VariableExpression) leftExpression).getAccessedVariable(); if (accessedVariable instanceof Parameter) { accessedVariable = new ParameterVariableExpression((Parameter) accessedVariable); } if (accessedVariable instanceof VariableExpression) { + /* GRECLIPSE edit -- GROOVY-9786 VariableExpression var = (VariableExpression) accessedVariable; List types = typeCheckingContext.ifElseForWhileAssignmentTracker.get(var); if (types == null) { @@ -885,6 +887,9 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) { typeCheckingContext.ifElseForWhileAssignmentTracker.put(var, types); } types.add(resultType); + */ + recordAssignment((VariableExpression) accessedVariable, resultType); + // GRECLIPSE end } } storeType(leftExpression, resultType); @@ -3889,7 +3894,6 @@ protected void typeCheckClosureCall(final Expression callArguments, final ClassN @Override public void visitIfElse(final IfStatement ifElse) { Map> oldTracker = pushAssignmentTracking(); - try { // create a new temporary element in the if-then-else type info typeCheckingContext.pushTemporaryTypeInfo(); @@ -3904,15 +3908,26 @@ public void visitIfElse(final IfStatement ifElse) { restoreTypeBeforeConditional(); ifElse.getElseBlock().visit(this); + // GRECLIPSE add -- GROOVY-9786 + Map updates = + ifElse.getElseBlock().getNodeMetaData("assignments"); + if (updates != null) { + updates.forEach(this::recordAssignment); + } + // GRECLIPSE end } finally { - popAssignmentTracking(oldTracker); - } - BinaryExpression instanceOfExpression = findInstanceOfNotReturnExpression(ifElse); - if (instanceOfExpression == null) { - instanceOfExpression = findNotInstanceOfReturnExpression(ifElse); + // GRECLIPSE add + ifElse.putNodeMetaData("assignments", + // GRECLIPSE end + popAssignmentTracking(oldTracker)); } - if (instanceOfExpression != null) { - if (!typeCheckingContext.enclosingBlocks.isEmpty()) { + + if (!typeCheckingContext.enclosingBlocks.isEmpty()) { + BinaryExpression instanceOfExpression = findInstanceOfNotReturnExpression(ifElse); + if (instanceOfExpression == null) { + instanceOfExpression = findNotInstanceOfReturnExpression(ifElse); + } + if (instanceOfExpression != null) { visitInstanceofNot(instanceOfExpression); } } @@ -4062,6 +4077,17 @@ public void visitCaseStatement(final CaseStatement statement) { restoreTypeBeforeConditional(); } + // GRECLIPSE add -- GROOVY-9786 + private void recordAssignment(final VariableExpression lhsExpr, final ClassNode rhsType) { + typeCheckingContext.ifElseForWhileAssignmentTracker.computeIfAbsent(lhsExpr, lhs -> { + ClassNode lhsType = lhs.getNodeMetaData(INFERRED_TYPE); + List types = new ArrayList<>(2); + types.add(lhsType); + return types; + }).add(rhsType); + } + // GRECLIPSE end + private void restoreTypeBeforeConditional() { Set>> entries = typeCheckingContext.ifElseForWhileAssignmentTracker.entrySet(); for (Map.Entry> entry : entries) {