This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new cafdb3c5fa GROOVY-8411: STC: `instanceof`-like flow typing for class 
literal `case`
cafdb3c5fa is described below

commit cafdb3c5faebceb4f70f5ec1ba55d1340f2ff936
Author: Eric Milles <[email protected]>
AuthorDate: Sat Nov 26 14:12:04 2022 -0600

    GROOVY-8411: STC: `instanceof`-like flow typing for class literal `case`
---
 .../codehaus/groovy/ast/CodeVisitorSupport.java    |  10 ++
 .../transform/stc/StaticTypeCheckingVisitor.java   |  39 ++++-
 .../transform/stc/TypeInferenceSTCTest.groovy      | 174 +++++++++++++++------
 3 files changed, 172 insertions(+), 51 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java 
b/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java
index e8f93808c8..f0e17fd460 100644
--- a/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java
+++ b/src/main/java/org/codehaus/groovy/ast/CodeVisitorSupport.java
@@ -152,13 +152,23 @@ public abstract class CodeVisitorSupport implements 
GroovyCodeVisitor {
         for (Statement caseStatement : statement.getCaseStatements()) {
             caseStatement.visit(this);
         }
+        afterSwitchCaseStatementsVisited(statement);
         statement.getDefaultStatement().visit(this);
     }
 
+    /**
+     * @since 3.0.0
+     */
     protected void afterSwitchConditionExpressionVisited(final SwitchStatement 
statement) {
         // hook for subclass to do something after switch condition, but 
before case(s)
     }
 
+    /**
+     * @since 5.0.0
+     */
+    protected void afterSwitchCaseStatementsVisited(final SwitchStatement 
statement) {
+    }
+
     @Override
     public void visitCaseStatement(final CaseStatement statement) {
         statement.getExpression().visit(this);
diff --git 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 99193c6b16..25498a2d2b 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -87,8 +87,10 @@ import org.codehaus.groovy.ast.expr.UnaryMinusExpression;
 import org.codehaus.groovy.ast.expr.UnaryPlusExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.BreakStatement;
 import org.codehaus.groovy.ast.stmt.CaseStatement;
 import org.codehaus.groovy.ast.stmt.CatchStatement;
+import org.codehaus.groovy.ast.stmt.ContinueStatement;
 import org.codehaus.groovy.ast.stmt.EmptyStatement;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
@@ -4147,24 +4149,34 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
                 popAssignmentTracking(oldTracker);
             }
         } finally {
+            typeCheckingContext.popTemporaryTypeInfo();
             typeCheckingContext.popEnclosingSwitchStatement();
         }
     }
 
     @Override
     protected void afterSwitchConditionExpressionVisited(final SwitchStatement 
statement) {
+        typeCheckingContext.pushTemporaryTypeInfo();
         Expression conditionExpression = statement.getExpression();
         conditionExpression.putNodeMetaData(TYPE, 
getType(conditionExpression));
     }
 
+    @Override
+    protected void afterSwitchCaseStatementsVisited(final SwitchStatement 
statement) {
+        // GROOVY-8411: if any "case Type:" then "default:" contributes 
condition type
+        if (!statement.getDefaultStatement().isEmpty() && 
!typeCheckingContext.temporaryIfBranchTypeInformation.peek().isEmpty())
+            pushInstanceOfTypeInfo(statement.getExpression(), new 
ClassExpression(statement.getExpression().getNodeMetaData(TYPE)));
+    }
+
     @Override
     public void visitCaseStatement(final CaseStatement statement) {
+        Expression selectable = 
typeCheckingContext.getEnclosingSwitchStatement().getExpression();
         Expression expression = statement.getExpression();
-        if (expression instanceof ClosureExpression) { // GROOVY-9854: 
propagate the switch type
-            SwitchStatement switchStatement = 
typeCheckingContext.getEnclosingSwitchStatement();
-            ClassNode inf = 
switchStatement.getExpression().getNodeMetaData(TYPE);
+        if (expression instanceof ClassExpression) { // GROOVY-8411: refine 
the switch type
+            pushInstanceOfTypeInfo(selectable, expression);
+        } else if (expression instanceof ClosureExpression) { // GROOVY-9854: 
propagate the switch type
+            ClassNode inf = selectable.getNodeMetaData(TYPE);
             expression.putNodeMetaData(CLOSURE_ARGUMENTS, new 
ClassNode[]{inf});
-
             Parameter[] params = ((ClosureExpression) 
expression).getParameters();
             if (params != null && params.length == 1) {
                 boolean lambda = (expression instanceof LambdaExpression);
@@ -4175,7 +4187,24 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
             }
         }
         super.visitCaseStatement(statement);
-        restoreTypeBeforeConditional();
+        if (!maybeFallsThrough(statement.getCode())) {
+            typeCheckingContext.temporaryIfBranchTypeInformation
+                .peek().remove(extractTemporaryTypeInfoKey(selectable));
+            restoreTypeBeforeConditional(); // isolate assignment branch
+        }
+    }
+
+    private static boolean maybeFallsThrough(Statement statement) {
+        if (statement.isEmpty()) return true;
+        if (statement instanceof BlockStatement)
+            statement = last(((BlockStatement) statement).getStatements());
+        // end break, continue, return or throw
+        if (statement instanceof BreakStatement
+                || statement instanceof ContinueStatement
+                || !GeneralUtils.maybeFallsThrough(statement)) {
+            return false;
+        }
+        return true;
     }
 
     private void recordAssignment(final VariableExpression lhsExpr, final 
ClassNode rhsType) {
diff --git a/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy 
b/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
index ef48abf961..fa2d6803ef 100644
--- a/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -532,7 +532,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
                     object.toUpperCase()
                 }
             }
-        ''', 'Cannot find matching method java.lang.Object#toUpperCase()'
+        ''',
+        'Cannot find matching method java.lang.Object#toUpperCase()'
     }
 
     void testNotInstanceOf6() {
@@ -542,7 +543,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
                     object.toUpperCase()
                 }
             }
-        ''', 'Cannot find matching method java.lang.Object#toUpperCase()'
+        ''',
+        'Cannot find matching method java.lang.Object#toUpperCase()'
     }
 
     // GROOVY-10217
@@ -609,7 +611,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
             if (a instanceof A) {
                 a.x = '2'
             }
-        ''', 'Cannot assign value of type java.lang.String to variable of type 
int'
+        ''',
+        'Cannot assign value of type java.lang.String to variable of type int'
     }
 
     void testInstanceOfInferenceWithMissingProperty() {
@@ -621,7 +624,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
             if (a instanceof A) {
                 a.y = 2
             }
-        ''', 'No such property: y for class: A'
+        ''',
+        'No such property: y for class: A'
     }
 
     // GROOVY-9967
@@ -700,7 +704,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
             a.with {
                 x = '2' // should be recognized as a.x at compile time and 
fail because of wrong type
             }
-        ''', 'Cannot assign value of type java.lang.String to variable of type 
int'
+        ''',
+        'Cannot assign value of type java.lang.String to variable of type int'
     }
 
     void testShouldNotFailWithWithTwoClasses() {
@@ -820,7 +825,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
             a.with {
                 method().toUpperCase()
             }
-        ''', 'Cannot find matching method int#toUpperCase()'
+        ''',
+        'Cannot find matching method int#toUpperCase()'
    }
 
     void testDeclarationTypeInference() {
@@ -1042,19 +1048,19 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
         '''
     }
 
-    void testSwitchCaseAnalysis() {
+    void testSwitchCaseAnalysis1() {
         assertScript '''
             import 
org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode as 
LUB
 
             def method(int x) {
-                def returnValue= new Date()
+                def returnValue = new Date()
                 switch (x) {
-                    case 1:
-                        returnValue = 'string'
-                        break;
-                    case 2:
-                        returnValue = 1;
-                        break;
+                  case 1:
+                    returnValue = 'string'
+                    break
+                  case 2:
+                    returnValue = 42
+                    break
                 }
                 @ASTTest(phase=INSTRUCTION_SELECTION, value={
                     def type = node.getNodeMetaData(INFERRED_TYPE)
@@ -1071,24 +1077,20 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
     // GROOVY-6215
     void testSwitchCaseAnalysis2() {
         assertScript '''
-            def processNumber(int x) {
-                def value = getValueForNumber(x)
-                value
-            }
-
             def getValueForNumber(int x) {
                 def valueToReturn
                 switch(x) {
-                    case 1:
-                        valueToReturn = 'One'
-                        break
-                    case 2:
-                        valueToReturn = []
-                        valueToReturn << 'Two'
-                        break
+                  case 1:
+                    valueToReturn = 'One'
+                    break
+                  case 2:
+                    valueToReturn = []
+                    valueToReturn << 'Two'
+                    break
                 }
                 valueToReturn
             }
+
             def v1 = getValueForNumber(1)
             def v2 = getValueForNumber(2)
             def v3 = getValueForNumber(3)
@@ -1098,6 +1100,88 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-8411
+    void testSwitchCaseAnalysis3() {
+        shouldFailWithMessages '''
+            void test(something) {
+                switch (something) {
+                  case Class:
+                    break
+                  case File:
+                    something.canonicalName
+                }
+            }
+        ''',
+        'No such property: canonicalName for class: java.io.File'
+
+        shouldFailWithMessages '''
+            void test(something) {
+                switch (something) {
+                  case Class:
+                    break
+                  default:
+                    something.canonicalName
+                }
+            }
+        ''',
+        'No such property: canonicalName for class: java.lang.Object'
+/*
+        shouldFailWithMessages '''
+            void test(something) {
+                switch (something) {
+                  case Class:
+                  case File:
+                    something.toString()
+                  default:
+                    something.getCanonicalName()
+                }
+            }
+        ''',
+        'No such property: canonicalName for class: java.lang.Object'
+*/
+        shouldFailWithMessages '''
+            void test(something) {
+                switch (something) {
+                  case Class:
+                  case File:
+                    something.toString()
+                }
+                something.canonicalName
+            }
+        ''',
+        'No such property: canonicalName for class: java.lang.Object'
+
+        assertScript '''
+            void test(something) {
+                switch (something) {
+                  case Class.class:
+                    something.canonicalName
+                    break
+                  case File:
+                    something.canonicalPath
+                    break
+                  default:
+                    something?.toString()
+                }
+            }
+        '''
+
+        assertScript '''
+            void test(something) {
+                switch (something) {
+                  case Float:
+                  case Double:
+                  case Integer:
+                    something.doubleValue()
+                }
+            }
+            test(1)
+            test(1.1d)
+            test(1.1f)
+            test('11')
+        '''
+    }
+
     void testNumberPrefixPlusPlusInference() {
         [Byte:'Integer',
          Character: 'Character',
@@ -1110,14 +1194,14 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
          BigInteger: 'BigInteger'
         ].each { orig, dest ->
             assertScript """
-            $orig b = 65 as $orig
-            @ASTTest(phase=INSTRUCTION_SELECTION, value={
-                def type = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
-                assert type == make($dest)
-            })
-            def pp = ++b
-            println '++${orig} -> ' + pp.class + ' ' + pp
-            assert pp.class == ${dest}
+                $orig b = 65 as $orig
+                @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                    def type = 
node.rightExpression.getNodeMetaData(INFERRED_TYPE)
+                    assert type == make($dest)
+                })
+                def pp = ++b
+                println '++${orig} -> ' + pp.class + ' ' + pp
+                assert pp.class == ${dest}
             """
         }
     }
@@ -1134,14 +1218,14 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
          BigInteger: 'BigInteger'
         ].each { orig, dest ->
             assertScript """
-            $orig b = 65 as $orig
-            @ASTTest(phase=INSTRUCTION_SELECTION, value={
-                def type = node.rightExpression.getNodeMetaData(INFERRED_TYPE)
-                assert type == make($dest)
-            })
-            def pp = b++
-            println '${orig}++ -> ' + pp.class + ' ' + pp
-            assert pp.class == ${dest}
+                $orig b = 65 as $orig
+                @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                    def type = 
node.rightExpression.getNodeMetaData(INFERRED_TYPE)
+                    assert type == make($dest)
+                })
+                def pp = b++
+                println '${orig}++ -> ' + pp.class + ' ' + pp
+                assert pp.class == ${dest}
             """
         }
     }
@@ -1366,10 +1450,8 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
     void testInferredTypeForPropertyThatResolvesToMethod() {
         assertScript '''
             import groovy.transform.*
-            import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET
 
-            @CompileStatic
-            void meth() {
+            void test() {
                 def items = [1, 2] as LinkedList
 
                 @ASTTest(phase=INSTRUCTION_SELECTION, value={
@@ -1391,7 +1473,7 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
                 def alsoOne = items.peek()
             }
 
-            meth()
+            test()
         '''
     }
 

Reply via email to