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()
'''
}