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 f348e50481 GROOVY-6429, GROOVY-8412, GROOVY-9931: STC: `!instanceof`
flow type
f348e50481 is described below
commit f348e50481c24f21413c4d2b52b501b433003051
Author: Eric Milles <[email protected]>
AuthorDate: Sun Nov 27 10:29:39 2022 -0600
GROOVY-6429, GROOVY-8412, GROOVY-9931: STC: `!instanceof` flow type
---
.../transform/stc/StaticTypeCheckingVisitor.java | 340 +++++++--------------
.../groovy/transform/stc/TypeCheckingContext.java | 22 +-
.../transform/stc/TypeInferenceSTCTest.groovy | 221 +++++++-------
3 files changed, 240 insertions(+), 343 deletions(-)
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 25498a2d2b..d0cd9d77df 100644
---
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -795,10 +795,10 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
@Override
public void visitNotExpression(final NotExpression expression) {
- // GROOVY-9455: !(x instanceof T) shouldn't propagate T as inferred
type
typeCheckingContext.pushTemporaryTypeInfo();
super.visitNotExpression(expression);
- typeCheckingContext.popTemporaryTypeInfo();
+ // GROOVY-9455: !(x instanceof T) shouldn't propagate T as inferred
type
+
typeCheckingContext.temporaryIfBranchTypeInformation.pop().forEach(this::putNotInstanceOfTypeInfo);
}
@Override
@@ -938,8 +938,10 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
}
}
}
- } else if (op == KEYWORD_INSTANCEOF /*|| op ==
COMPARE_NOT_INSTANCEOF*/) {
+ } else if (op == KEYWORD_INSTANCEOF) {
pushInstanceOfTypeInfo(leftExpression, rightExpression);
+ } else if (op == COMPARE_NOT_INSTANCEOF) { // GROOVY-6429,
GROOVY-8321, GROOVY-8412, GROOVY-8523, GROOVY-9931
+
putNotInstanceOfTypeInfo(extractTemporaryTypeInfoKey(leftExpression),
Collections.singleton(rightExpression.getType()));
}
if (!isEmptyDeclaration) {
storeType(expression, resultType);
@@ -1186,18 +1188,6 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
target.setGenericsTypes(genericsTypes);
}
- /**
- * Stores information about types when [objectOfInstanceof instanceof
typeExpression] is visited.
- *
- * @param objectOfInstanceOf the expression which must be checked against
instanceof
- * @param typeExpression the expression which represents the target
type
- */
- protected void pushInstanceOfTypeInfo(final Expression objectOfInstanceOf,
final Expression typeExpression) {
- List<ClassNode> potentialTypes =
typeCheckingContext.temporaryIfBranchTypeInformation.peek()
- .computeIfAbsent(extractTemporaryTypeInfoKey(objectOfInstanceOf),
key -> new LinkedList<>());
- potentialTypes.add(typeExpression.getType());
- }
-
private boolean typeCheckMultipleAssignmentAndContinue(final Expression
leftExpression, Expression rightExpression) {
if (rightExpression instanceof VariableExpression || rightExpression
instanceof PropertyExpression || rightExpression instanceof MethodCall) {
ClassNode inferredType =
Optional.ofNullable(getType(rightExpression)).orElseGet(rightExpression::getType);
@@ -1458,37 +1448,6 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
return constructors.get(0);
}
- /**
- * When instanceof checks are found in the code, we store temporary type
- * information data in the {@link
TypeCheckingContext#temporaryIfBranchTypeInformation}
- * table. This method computes the key which must be used to store this
type
- * info.
- *
- * @param expression the expression for which to compute the key
- * @return a key to be used for {@link
TypeCheckingContext#temporaryIfBranchTypeInformation}
- */
- protected Object extractTemporaryTypeInfoKey(final Expression expression) {
- return expression instanceof VariableExpression ?
findTargetVariable((VariableExpression) expression) : expression.getText();
- }
-
- /**
- * A helper method which determines which receiver class should be used in
error messages when a field or attribute
- * is not found. The returned type class depends on whether we have
temporary type information available (due to
- * instanceof checks) and whether there is a single candidate in that case.
- *
- * @param expr the expression for which an unknown field has been found
- * @param type the type of the expression (used as fallback type)
- * @return if temporary information is available and there's only one
type, returns the temporary type class
- * otherwise falls back to the provided type class.
- */
- protected ClassNode findCurrentInstanceOfClass(final Expression expr,
final ClassNode type) {
- if (!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) {
- List<ClassNode> nodes = getTemporaryTypesForExpression(expr);
- if (nodes != null && nodes.size() == 1) return nodes.get(0);
- }
- return type;
- }
-
protected boolean existsProperty(final PropertyExpression pexp, final
boolean checkForReadOnly) {
return existsProperty(pexp, checkForReadOnly, null);
}
@@ -2384,29 +2343,6 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
).toArray(ClassNode[]::new);
}
- private ClassNode getInferredTypeFromTempInfo(final Expression expression,
final ClassNode expressionType) {
- if (expression instanceof VariableExpression &&
!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) {
- List<ClassNode> tempTypes =
getTemporaryTypesForExpression(expression);
- if (tempTypes != null && !tempTypes.isEmpty()) {
- List<ClassNode> types = new ArrayList<>(tempTypes.size() + 1);
- if (expressionType != null && !isObjectType(expressionType) //
GROOVY-7333
- && tempTypes.stream().noneMatch(t ->
implementsInterfaceOrIsSubclassOf(t, expressionType))) { // GROOVY-9769
- types.add(expressionType);
- }
- types.addAll(tempTypes);
-
- if (types.isEmpty()) {
- return OBJECT_TYPE;
- } else if (types.size() == 1) {
- return types.get(0);
- } else {
- return new
UnionTypeClassNode(types.toArray(ClassNode.EMPTY_ARRAY));
- }
- }
- }
- return expressionType;
- }
-
@Override
public void visitClosureExpression(final ClosureExpression expression) {
// collect every variable expression used in the closure body
@@ -3927,19 +3863,6 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
}
}
- protected List<ClassNode> getTemporaryTypesForExpression(final Expression
objectExpression) {
- List<ClassNode> classNodes = null;
- int depth =
typeCheckingContext.temporaryIfBranchTypeInformation.size();
- while (classNodes == null && depth > 0) {
- Map<Object, List<ClassNode>> tempo =
typeCheckingContext.temporaryIfBranchTypeInformation.get(--depth);
- Object key = objectExpression instanceof
ParameterVariableExpression
- ? ((ParameterVariableExpression)
objectExpression).parameter
- : extractTemporaryTypeInfoKey(objectExpression);
- classNodes = tempo.get(key);
- }
- return classNodes;
- }
-
protected void storeTargetMethod(final Expression call, final MethodNode
target) {
if (target == null) {
call.removeNodeMetaData(DIRECT_METHOD_CALL_TARGET); return;
@@ -3996,146 +3919,38 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
public void visitIfElse(final IfStatement ifElse) {
Map<VariableExpression, List<ClassNode>> oldTracker =
pushAssignmentTracking();
try {
- // create a new temporary element in the if-then-else type info
+ Statement thenPath = ifElse.getIfBlock(), elsePath =
ifElse.getElseBlock();
+
+ // create a new scope for instanceof testing
typeCheckingContext.pushTemporaryTypeInfo();
visitStatement(ifElse);
ifElse.getBooleanExpression().visit(this);
- ifElse.getIfBlock().visit(this);
- // pop if-then-else temporary type info
- typeCheckingContext.popTemporaryTypeInfo();
+ thenPath.visit(this);
- // GROOVY-6099: restore assignment info as before the if branch
+ Map<Object, List<ClassNode>> tti =
typeCheckingContext.temporaryIfBranchTypeInformation.pop();
+ // GROOVY-6099: isolate assignment tracking
restoreTypeBeforeConditional();
- ifElse.getElseBlock().visit(this);
+ // GROOVY-6429: reverse instanceof tracking
+ typeCheckingContext.pushTemporaryTypeInfo();
+ tti.forEach(this::putNotInstanceOfTypeInfo);
+
+ elsePath.visit(this);
+ typeCheckingContext.popTemporaryTypeInfo();
+ // GROOVY-8523: propagate tracking to outer scope; keep simple for
now
+ if (elsePath.isEmpty() &&
!GeneralUtils.maybeFallsThrough(thenPath)) {
+ tti.forEach(this::putNotInstanceOfTypeInfo);
+ }
// GROOVY-9786: if chaining: "if (...) x=?; else if (...) x=?;"
- Map<VariableExpression, ClassNode> updates =
- ifElse.getElseBlock().getNodeMetaData("assignments");
+ Map<VariableExpression, ClassNode> updates =
elsePath.getNodeMetaData("assignments");
if (updates != null) {
updates.forEach(this::recordAssignment);
}
} finally {
ifElse.putNodeMetaData("assignments",
popAssignmentTracking(oldTracker));
}
-
- if (!typeCheckingContext.enclosingBlocks.isEmpty()) {
- BinaryExpression instanceOfExpression =
findInstanceOfNotReturnExpression(ifElse);
- if (instanceOfExpression == null) {
- instanceOfExpression =
findNotInstanceOfReturnExpression(ifElse);
- }
- if (instanceOfExpression != null) {
- visitInstanceofNot(instanceOfExpression);
- }
- }
- }
-
- protected void visitInstanceofNot(final BinaryExpression be) {
- BlockStatement currentBlock =
typeCheckingContext.enclosingBlocks.getFirst();
- assert currentBlock != null;
- if
(typeCheckingContext.blockStatements2Types.containsKey(currentBlock)) {
- // another instanceOf_not was before, no need store vars
- } else {
- // saving type of variables to restoring them after returning from
block
- Map<VariableExpression, List<ClassNode>> oldTracker =
pushAssignmentTracking();
- getTypeCheckingContext().pushTemporaryTypeInfo();
- typeCheckingContext.blockStatements2Types.put(currentBlock,
oldTracker);
- }
- pushInstanceOfTypeInfo(be.getLeftExpression(),
be.getRightExpression());
- }
-
- @Override
- public void visitBlockStatement(final BlockStatement block) {
- if (block != null) {
- typeCheckingContext.enclosingBlocks.addFirst(block);
- }
- super.visitBlockStatement(block);
- if (block != null) {
- visitClosingBlock(block);
- }
- }
-
- public void visitClosingBlock(final BlockStatement block) {
- BlockStatement peekBlock =
typeCheckingContext.enclosingBlocks.removeFirst();
- boolean found =
typeCheckingContext.blockStatements2Types.containsKey(peekBlock);
- if (found) {
- Map<VariableExpression, List<ClassNode>> oldTracker =
typeCheckingContext.blockStatements2Types.remove(peekBlock);
- getTypeCheckingContext().popTemporaryTypeInfo();
- popAssignmentTracking(oldTracker);
- }
- }
-
- /**
- * Check IfStatement matched pattern :
- * Object var1;
- * if (!(var1 instanceOf Runnable)) {
- * return
- * }
- * // Here var1 instance of Runnable
- * <p>
- * Return expression , which contains instanceOf (without not)
- * Return null, if not found
- */
- protected BinaryExpression findInstanceOfNotReturnExpression(final
IfStatement ifElse) {
- Statement elseBlock = ifElse.getElseBlock();
- if (!(elseBlock instanceof EmptyStatement)) {
- return null;
- }
- Expression conditionExpression =
ifElse.getBooleanExpression().getExpression();
- if (!(conditionExpression instanceof NotExpression)) {
- return null;
- }
- NotExpression notExpression = (NotExpression) conditionExpression;
- Expression expression = notExpression.getExpression();
- if (!(expression instanceof BinaryExpression)) {
- return null;
- }
- BinaryExpression instanceOfExpression = (BinaryExpression) expression;
- int op = instanceOfExpression.getOperation().getType();
- if (op != KEYWORD_INSTANCEOF) {
- return null;
- }
- if (notReturningBlock(ifElse.getIfBlock())) {
- return null;
- }
- return instanceOfExpression;
- }
-
- /**
- * Check IfStatement matched pattern :
- * Object var1;
- * if (var1 !instanceOf Runnable) {
- * return
- * }
- * // Here var1 instance of Runnable
- * <p>
- * Return expression , which contains instanceOf (without not)
- * Return null, if not found
- */
- protected BinaryExpression findNotInstanceOfReturnExpression(final
IfStatement ifElse) {
- Statement elseBlock = ifElse.getElseBlock();
- if (!(elseBlock instanceof EmptyStatement)) {
- return null;
- }
- Expression conditionExpression =
ifElse.getBooleanExpression().getExpression();
- if (!(conditionExpression instanceof BinaryExpression)) {
- return null;
- }
- BinaryExpression instanceOfExpression = (BinaryExpression)
conditionExpression;
- int op = instanceOfExpression.getOperation().getType();
- if (op != COMPARE_NOT_INSTANCEOF) {
- return null;
- }
- if (notReturningBlock(ifElse.getIfBlock())) {
- return null;
- }
- return instanceOfExpression;
- }
-
- private static boolean notReturningBlock(final Statement statement) {
- return statement.isEmpty() || !(statement instanceof BlockStatement)
- || !(last(((BlockStatement) statement).getStatements()) instanceof
ReturnStatement);
}
@Override
@@ -4222,6 +4037,12 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
});
}
+ protected Map<VariableExpression, List<ClassNode>>
pushAssignmentTracking() {
+ Map<VariableExpression, List<ClassNode>> oldTracker =
typeCheckingContext.ifElseForWhileAssignmentTracker;
+ typeCheckingContext.ifElseForWhileAssignmentTracker = new HashMap<>();
+ return oldTracker;
+ }
+
protected Map<VariableExpression, ClassNode> popAssignmentTracking(final
Map<VariableExpression, List<ClassNode>> oldTracker) {
Map<VariableExpression, ClassNode> assignments = new HashMap<>();
typeCheckingContext.ifElseForWhileAssignmentTracker.forEach((var,
types) -> {
@@ -4235,13 +4056,6 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
return assignments;
}
- protected Map<VariableExpression, List<ClassNode>>
pushAssignmentTracking() {
- // memorize current assignment context
- Map<VariableExpression, List<ClassNode>> oldTracker =
typeCheckingContext.ifElseForWhileAssignmentTracker;
- typeCheckingContext.ifElseForWhileAssignmentTracker = new HashMap<>();
- return oldTracker;
- }
-
@Override
public void visitArrayExpression(final ArrayExpression expression) {
super.visitArrayExpression(expression);
@@ -4313,9 +4127,13 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
Expression trueExpression = expression.getTrueExpression();
ClassNode typeOfTrue = findCurrentInstanceOfClass(trueExpression,
null);
typeOfTrue =
Optional.ofNullable(typeOfTrue).orElse(visitValueExpression(trueExpression));
- typeCheckingContext.popTemporaryTypeInfo(); // instanceof doesn't
apply to false branch
+ Map<Object, List<ClassNode>> tti =
typeCheckingContext.temporaryIfBranchTypeInformation.pop();
+
+ typeCheckingContext.pushTemporaryTypeInfo();
+ tti.forEach(this::putNotInstanceOfTypeInfo); // GROOVY-8412
Expression falseExpression = expression.getFalseExpression();
ClassNode typeOfFalse = visitValueExpression(falseExpression);
+ typeCheckingContext.popTemporaryTypeInfo();
ClassNode resultType;
if (isNullConstant(trueExpression) && isNullConstant(falseExpression))
{ // GROOVY-5523
@@ -4529,13 +4347,11 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
List<ClassNode> assignedTypes =
typeCheckingContext.closureSharedVariablesAssignmentTypes.computeIfAbsent(var,
k -> new LinkedList<>());
assignedTypes.add(cn);
}
- if
(!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) {
- List<ClassNode> temporaryTypesForExpression =
getTemporaryTypesForExpression(var);
- if (temporaryTypesForExpression != null &&
!temporaryTypesForExpression.isEmpty()) {
- // a type inference has been made on a variable whose type
was defined in an instanceof block
- // erase available information with the new type
- temporaryTypesForExpression.clear();
- }
+ List<ClassNode> temporaryTypesForExpression =
getTemporaryTypesForExpression(var);
+ if (asBoolean(temporaryTypesForExpression)) {
+ // a type inference has been made on a variable whose type was
defined in an instanceof block
+ // erase available information with the new type
+ temporaryTypesForExpression.clear();
}
}
}
@@ -6104,6 +5920,86 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
return exp;
}
+
//--------------------------------------------------------------------------
+ // temporaryIfBranchTypeInformation support; migrate to
TypeCheckingContext?
+
+ /**
+ * Stores information about types when [objectOfInstanceof instanceof
typeExpression] is visited.
+ *
+ * @param objectOfInstanceOf the expression to be checked against
instanceof
+ * @param typeExpression the expression which represents the target
type
+ */
+ protected void pushInstanceOfTypeInfo(final Expression objectOfInstanceOf,
final Expression typeExpression) {
+ Object ttiKey = extractTemporaryTypeInfoKey(objectOfInstanceOf);
ClassNode type = typeExpression.getType();
+
typeCheckingContext.temporaryIfBranchTypeInformation.peek().computeIfAbsent(ttiKey,
x -> new LinkedList<>()).add(type);
+ }
+
+ private void putNotInstanceOfTypeInfo(final Object key, final
Collection<ClassNode> types) {
+ Object notKey = key instanceof Object[] ? ((Object[]) key)[1] : new
Object[]{"!instanceof", key}; // stash negative type(s)
+
typeCheckingContext.temporaryIfBranchTypeInformation.peek().computeIfAbsent(notKey,
x -> new LinkedList<>()).addAll(types);
+ }
+
+ /**
+ * Computes the key to use for {@link
TypeCheckingContext#temporaryIfBranchTypeInformation}.
+ */
+ protected Object extractTemporaryTypeInfoKey(final Expression expression) {
+ return expression instanceof VariableExpression ?
findTargetVariable((VariableExpression) expression) : expression.getText();
+ }
+
+ /**
+ * A helper method which determines which receiver class should be used in
error messages when a field or attribute
+ * is not found. The returned type class depends on whether we have
temporary type information available (due to
+ * instanceof checks) and whether there is a single candidate in that case.
+ *
+ * @param expr the expression for which an unknown field has been found
+ * @param type the type of the expression (used as fallback type)
+ * @return if temporary information is available and there's only one
type, returns the temporary type class
+ * otherwise falls back to the provided type class.
+ */
+ protected ClassNode findCurrentInstanceOfClass(final Expression expr,
final ClassNode type) {
+ if (!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) {
+ List<ClassNode> types = getTemporaryTypesForExpression(expr);
+ if (types != null && types.size() == 1) return types.get(0);
+ }
+ return type;
+ }
+
+ protected List<ClassNode> getTemporaryTypesForExpression(final Expression
expression) {
+ List<ClassNode> types = null;
+ int depth =
typeCheckingContext.temporaryIfBranchTypeInformation.size();
+ while (types == null && depth > 0) {
+ Map<Object, List<ClassNode>> tempo =
typeCheckingContext.temporaryIfBranchTypeInformation.get(--depth);
+ Object key = expression instanceof ParameterVariableExpression
+ ? ((ParameterVariableExpression) expression).parameter
+ : extractTemporaryTypeInfoKey(expression);
+ types = tempo.get(key);
+ }
+ return types;
+ }
+
+ private ClassNode getInferredTypeFromTempInfo(final Expression expression,
final ClassNode expressionType) {
+ if (expression instanceof VariableExpression &&
!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) {
+ List<ClassNode> tempTypes =
getTemporaryTypesForExpression(expression);
+ if (tempTypes != null && !tempTypes.isEmpty()) {
+ Set<ClassNode> types = new LinkedHashSet<>(tempTypes.size() +
1);
+ if (expressionType != null && !isObjectType(expressionType) //
GROOVY-7333
+ && tempTypes.stream().noneMatch(t ->
implementsInterfaceOrIsSubclassOf(t, expressionType))) { // GROOVY-9769
+ types.add(expressionType);
+ }
+ types.addAll(tempTypes);
+
+ if (types.isEmpty()) {
+ return OBJECT_TYPE;
+ } else if (types.size() == 1) {
+ return types.iterator().next();
+ } else {
+ return new
UnionTypeClassNode(types.toArray(ClassNode.EMPTY_ARRAY));
+ }
+ }
+ }
+ return expressionType;
+ }
+
//--------------------------------------------------------------------------
public static class SignatureCodecFactory {
diff --git
a/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
b/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
index 8966210ef7..5d6da2eb7f 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
@@ -27,7 +27,6 @@ import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.SwitchStatement;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
@@ -37,7 +36,6 @@ import org.codehaus.groovy.control.SourceUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
@@ -112,14 +110,14 @@ public class TypeCheckingContext {
//--------------------------------------------------------------------------
/**
- * Stores information which is only valid in the "if" branch of an
if-then-else statement. This is used when the if
- * condition expression makes use of an instanceof check
+ * Stores information which is valid in the "then" branch of an
if-then-else
+ * statement. This is used when the if condition expression makes use of
an
+ * {@code instanceof} check.
*/
protected Stack<Map<Object, List<ClassNode>>>
temporaryIfBranchTypeInformation = new Stack<>();
public void pushTemporaryTypeInfo() {
- Map<Object, List<ClassNode>> potentialTypes = new HashMap<>();
- temporaryIfBranchTypeInformation.push(potentialTypes);
+ temporaryIfBranchTypeInformation.push(new HashMap<>());
}
public void popTemporaryTypeInfo() {
@@ -151,17 +149,6 @@ public class TypeCheckingContext {
*/
protected Map<VariableExpression, List<ClassNode>>
ifElseForWhileAssignmentTracker;
- /**
- * This field used for type derivation
- * Check IfStatement matched pattern:
- * Object var1;
- * if (!(var1 instanceOf Runnable)){
- * return
- * }
- * // Here var1 instance of Runnable
- */
- protected final IdentityHashMap<BlockStatement, Map<VariableExpression,
List<ClassNode>>> blockStatements2Types = new IdentityHashMap<>();
-
protected Set<MethodNode> alreadyVisitedMethods = new HashSet<>();
/**
@@ -188,7 +175,6 @@ public class TypeCheckingContext {
protected final LinkedList<ClassNode> enclosingClassNodes = new
LinkedList<>();
protected final LinkedList<MethodNode> enclosingMethods = new
LinkedList<>();
protected final LinkedList<Expression> enclosingMethodCalls = new
LinkedList<>();
- protected final LinkedList<BlockStatement> enclosingBlocks = new
LinkedList<>();
protected final LinkedList<SwitchStatement> switchStatements = new
LinkedList<>();
protected final LinkedList<EnclosingClosure> enclosingClosures = new
LinkedList<>();
// stores the current binary expression. This is used when assignments are
made with a null object, for type inference
diff --git a/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
b/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
index fa2d6803ef..ca81b988b6 100644
--- a/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -412,139 +412,154 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
'''
}
- // GROOVY-8523
+ // GROOVY-6429
void testNotInstanceof1() {
- assertScript '''
- class Test1 {
- static int checkRes = 0
-
- static void f1(Object var1) {
- if (!(var1 instanceof Runnable)){
- checkRes = 3
- return
+ String types = '''
+ class C {
+ }
+ class D extends C {
+ def foo() { }
+ }
+ '''
+ for (test in ['!(x instanceof D)', 'x !instanceof D']) {
+ assertScript types + """
+ void p(x) {
+ if ($test) {
+ // ...
+ } else {
+ x.foo()
}
- f2(var1)
}
-
- static void f2(Runnable var2) {
- checkRes = 4
+ p(new C())
+ p(new D())
+ """
+ assertScript types + """
+ void p(x) {
+ if ($test) {
+ return
+ }
+ assert x instanceof D
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ def ecks = node.rightExpression.objectExpression
+ def type = ecks.getNodeMetaData(INFERRED_TYPE)
+ assert type.text == 'D' // not <UnionType:D+D>
+ })
+ def bar = x.foo()
}
- }
-
- Runnable r = {}
- Test1.f1(r)
- assert Test1.checkRes == 4
- Test1.f1(42)
- assert Test1.checkRes == 3
- '''
+ p(new C())
+ p(new D())
+ """
+ }
}
// GROOVY-8523
- void testNotInstanceOf2() {
- assertScript '''
- class Test1 {
- static int checkRes = 0
+ void testNotInstanceof2() {
+ for (test in ['!(x instanceof Runnable)', 'x !instanceof Runnable']) {
+ assertScript """
+ class Test {
+ public static int result = 0
- static void f1(Object var1) {
- if (var1 !instanceof Runnable){
- checkRes = 3
- return
+ static void p(x) {
+ if ($test) {
+ result = 1
+ return
+ }
+ q(x)
}
- f2(var1)
- }
- static void f2(Runnable var2) {
- checkRes = 4
+ private static void q(Runnable r) {
+ result = 2
+ }
}
- }
-
- Runnable r = {}
- Test1.f1(r)
- assert Test1.checkRes == 4
- Test1.f1(42)
- assert Test1.checkRes == 3
- '''
+ Test.p({->} as Runnable)
+ assert Test.result == 2
+ Test.p([''])
+ assert Test.result == 1
+ """
+ }
}
// GROOVY-8523
void testNotInstanceOf3() {
- assertScript '''
- class Test1 {
- static int checkRes = 0
+ for (test in ['!(x instanceof List)', 'x !instanceof List']) {
+ assertScript """
+ class Test {
+ public static int result = 0
- static void f1(Object var1) {
- if (!(var1 instanceof Runnable)){
- checkRes = 3
- return
- }
- if (!(var1 instanceof List)){
- checkRes = 5
- return
+ static void p(x) {
+ if (x !instanceof Runnable) {
+ result = 1
+ return
+ }
+ if ($test) {
+ result = 2
+ return
+ }
+ q(x)
}
- f2(var1)
- }
- static void f2(Runnable var2) {
- checkRes = 4
+ private static void q(Runnable r) { // and List
+ result = 3
+ }
}
- }
-
- Runnable r = {}
- Test1.f1(r)
- assert Test1.checkRes == 5
- '''
+ // TODO: List and Runnable
+ Test.p({->} as Runnable)
+ assert Test.result == 2
+ Test.p([''])
+ assert Test.result == 1
+ """
+ }
}
- // GROOVY-8523
+ // GROOVY-9455
void testNotInstanceOf4() {
- assertScript '''
- class Test1 {
- static int checkRes = 0
-
- static void f1(Object var1) {
- if (!(var1 instanceof Runnable)){
- checkRes = 3
- return
- }
- if (!(var1 instanceof Thread)){
- checkRes = 5
- return
+ for (test in ['!(x instanceof String)', 'x !instanceof String']) {
+ shouldFailWithMessages """
+ void p(x) {
+ if ($test) {
+ x.toUpperCase()
}
- f2(var1)
- }
-
- static void f2(Runnable var2) {
- checkRes = 4
}
- }
-
- Runnable r = {}
- Test1.f1(r)
- assert Test1.checkRes == 5
- '''
+ """,
+ 'Cannot find matching method java.lang.Object#toUpperCase()'
+ }
}
- // GROOVY-9455
- void testNotInstanceOf5() {
- shouldFailWithMessages '''
- void test(object) {
- if (!(object instanceof String)) {
- object.toUpperCase()
+ // GROOVY-9931
+ void testNotInstanceof5() {
+ for (test in ['!(x instanceof Number)', 'x !instanceof Number']) {
+ assertScript """
+ Number f(x) {
+ if ($test) {
+ return null
+ } else {
+ return x
+ }
}
- }
- ''',
- 'Cannot find matching method java.lang.Object#toUpperCase()'
+ assert f(42) == 42
+ assert f('') == null
+ """
+ }
}
- void testNotInstanceOf6() {
- shouldFailWithMessages '''
- void test(object) {
- if (object !instanceof String) {
- object.toUpperCase()
+ // GROOVY-8412
+ void testNotInstanceof6() {
+ for (test in ['!(x instanceof Number)', 'x !instanceof Number']) {
+ assertScript """
+ Number f(x) {
+ return ($test) ? null : x
}
- }
- ''',
- 'Cannot find matching method java.lang.Object#toUpperCase()'
+ assert f(42) == 42
+ assert f('') == null
+ """
+ assertScript """
+ Number f(x) {
+ return !!($test) ? null : x // multiple negation
+ }
+ assert f(42) == 42
+ assert f('') == null
+ """
+ }
}
// GROOVY-10217