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 ccb5e48319 GROOVY-7971, GROOVY-8337, GROOVY-8965: logical-or
`instanceof` guards
ccb5e48319 is described below
commit ccb5e48319553bc6bb747063156c15e3139298a6
Author: Eric Milles <[email protected]>
AuthorDate: Fri Dec 26 12:33:48 2025 -0600
GROOVY-7971, GROOVY-8337, GROOVY-8965: logical-or `instanceof` guards
---
.../classgen/asm/sc/StaticTypesTypeChooser.java | 2 +-
.../transform/stc/StaticTypeCheckingSupport.java | 6 +-
.../transform/stc/StaticTypeCheckingVisitor.java | 166 +++++++++++++++------
.../groovy/transform/stc/TypeCheckingContext.java | 4 +
.../groovy/transform/stc/UnionTypeClassNode.java | 16 +-
src/test/groovy/bugs/Groovy8337Bug.groovy | 52 -------
.../groovy/transform/stc/MethodCallsSTCTest.groovy | 59 ++++----
.../transform/stc/TypeInferenceSTCTest.groovy | 144 ++++++++++++------
.../asm/sc/TypeInferenceStaticCompileTest.groovy | 9 +-
9 files changed, 267 insertions(+), 191 deletions(-)
diff --git
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java
index 4918ca9012..a50fa6c333 100644
---
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java
+++
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesTypeChooser.java
@@ -43,7 +43,7 @@ public class StaticTypesTypeChooser extends
StatementMetaTypeChooser {
var ast = getTarget(exp); // GROOVY-9344, GROOVY-9607, GROOVY-11375
inferredType =
ast.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
}
- if (inferredType != null && !isPrimitiveVoid(inferredType)) {
+ if (inferredType != null && !isPrimitiveVoid(inferredType) &&
!inferredType.getName().startsWith("<")) {
return inferredType;
}
diff --git
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index ea9de4082b..9eb04af018 100644
---
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -883,12 +883,12 @@ public abstract class StaticTypeCheckingSupport {
return true;
}
if (superOrInterface instanceof
WideningCategories.LowestUpperBoundClassNode) {
- if (implementsInterfaceOrIsSubclassOf(type,
superOrInterface.getSuperClass())
+ if (implementsInterfaceOrIsSubclassOf(type,
superOrInterface.getUnresolvedSuperClass())
&&
Arrays.stream(superOrInterface.getInterfaces()).allMatch(type::implementsInterface))
{
return true;
}
- } else if (superOrInterface instanceof UnionTypeClassNode) {
- for (ClassNode delegate : ((UnionTypeClassNode)
superOrInterface).getDelegates()) {
+ } else if (superOrInterface instanceof UnionTypeClassNode union) {
+ for (ClassNode delegate : union.getDelegates()) {
if (implementsInterfaceOrIsSubclassOf(type, delegate)) {
return true;
}
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 a1ef0d5107..5b5e709a82 100644
---
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -266,6 +266,7 @@ import static org.codehaus.groovy.syntax.Types.INTDIV;
import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL;
import static org.codehaus.groovy.syntax.Types.KEYWORD_IN;
import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF;
+import static org.codehaus.groovy.syntax.Types.LOGICAL_OR;
import static org.codehaus.groovy.syntax.Types.MINUS_MINUS;
import static org.codehaus.groovy.syntax.Types.MOD;
import static org.codehaus.groovy.syntax.Types.MOD_EQUAL;
@@ -848,10 +849,13 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
return;
}
+ // GROOVY-7971, GROOVY-8965, GROOVY-10096, GROOVY-10702, et al.
+ if (op == LOGICAL_OR) typeCheckingContext.pushTemporaryTypeInfo();
+
leftExpression.visit(this);
ClassNode lType = getType(leftExpression);
var setterInfo = removeSetterInfo(leftExpression);
- if (setterInfo != null) {
+ if (setterInfo != null) { assert op != LOGICAL_OR ;
if (ensureValidSetter(expression, leftExpression,
rightExpression, setterInfo)) {
return;
}
@@ -860,7 +864,16 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
lType = getOriginalDeclarationType(leftExpression);
applyTargetType(lType, rightExpression);
}
- rightExpression.visit(this);
+ if (op != LOGICAL_OR) {
+ rightExpression.visit(this);
+ } else {
+ var lhs =
typeCheckingContext.temporaryIfBranchTypeInformation.pop();
+ typeCheckingContext.pushTemporaryTypeInfo();
+ rightExpression.visit(this);
+
+ var rhs =
typeCheckingContext.temporaryIfBranchTypeInformation.pop();
+ propagateTemporaryTypeInfo(lhs, rhs); // `instanceof` on
either side?
+ }
}
ClassNode rType = isNullConstant(rightExpression)
@@ -973,7 +986,7 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
} 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()));
+
putNotInstanceOfTypeInfo(extractTemporaryTypeInfoKey(leftExpression),
Set.of(rightExpression.getType()));
}
if (!isEmptyDeclaration) {
storeType(expression, resultType);
@@ -989,6 +1002,36 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
}
}
+ private void propagateTemporaryTypeInfo(final Map<Object, List<ClassNode>>
lhs,
+ final Map<Object, List<ClassNode>>
rhs) {
+ // TODO: deal with (x !instanceof T)
+ lhs.keySet().removeIf(k -> k instanceof Object[]);
+ rhs.keySet().removeIf(k -> k instanceof Object[]);
+
+ for (var entry : lhs.entrySet()) {
+ if (rhs.containsKey(entry.getKey())) {
+ // main case: (x instanceof A || x instanceof B) produces A|B
type
+ ClassNode t =
newUnionTypeClassNode(DefaultGroovyMethods.plus(entry.getValue(),
rhs.get(entry.getKey())));
+
typeCheckingContext.peekTemporaryTypeInfo(entry.getKey()).add(t);
+ } else if (entry.getKey() instanceof Variable v) {
+ // edge case: (x instanceof A || ...) produces A|typeof(x) type
+ ClassNode t =
newUnionTypeClassNode(DefaultGroovyMethods.plus(entry.getValue(), v instanceof
ASTNode n ? getType(n) : v.getType()));
+ typeCheckingContext.peekTemporaryTypeInfo(v).add(t);
+ }
+ }
+
+ rhs.keySet().removeAll(lhs.keySet());
+
+ for (var entry : rhs.entrySet()) {
+ if (entry.getKey() instanceof Variable v) {
+ // edge case: (... || x instanceof B) produces typeof(x)|B type
+ var types = new LinkedList<>(entry.getValue());
+ types.addFirst(v instanceof ASTNode n ? getType(n) :
v.getType());
+
typeCheckingContext.peekTemporaryTypeInfo(v).add(newUnionTypeClassNode(types));
+ }
+ }
+ }
+
private void validateResourceInARM(final BinaryExpression expression,
final ClassNode lType) {
if (expression instanceof DeclarationExpression
&& TryCatchStatement.isResource(expression)
@@ -1170,8 +1213,8 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
}
addStaticTypeError(message, leftExpression);
} else {
- ClassNode[] tergetTypes =
visibleSetters.stream().map(setterType).toArray(ClassNode[]::new);
- addAssignmentError(tergetTypes.length == 1 ? tergetTypes[0] :
new UnionTypeClassNode(tergetTypes), getType(valueExpression), expression);
+ List<ClassNode> targetTypes =
visibleSetters.stream().map(setterType).toList();
+ addAssignmentError(newUnionTypeClassNode(targetTypes),
getType(valueExpression), expression);
}
return true;
}
@@ -1492,9 +1535,8 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
ClassNode valueType = getType(valueExpression);
BinaryExpression kv =
typeCheckingContext.popEnclosingBinaryExpression();
if (propertyTypes.stream().noneMatch(targetType ->
checkCompatibleAssignmentTypes(targetType, getResultType(targetType, ASSIGN,
valueType, kv), valueExpression))) {
- ClassNode targetType = propertyTypes.size() == 1 ?
propertyTypes.iterator().next() : new
UnionTypeClassNode(propertyTypes.toArray(ClassNode.EMPTY_ARRAY));
- if
(!extension.handleIncompatibleAssignment(targetType, valueType,
entryExpression)) {
- addAssignmentError(targetType, valueType,
entryExpression);
+ if
(!extension.handleIncompatibleAssignment(newUnionTypeClassNode(propertyTypes),
valueType, entryExpression)) {
+
addAssignmentError(newUnionTypeClassNode(propertyTypes), valueType,
entryExpression);
}
}
}
@@ -2381,10 +2423,9 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
super.visitExpressionStatement(statement);
Map<?,List<ClassNode>> tti =
typeCheckingContext.temporaryIfBranchTypeInformation.pop();
if (!tti.isEmpty() &&
!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) {
- tti.forEach((k, tempTypes) -> {
+ tti.forEach((key, tempTypes) -> {
if (tempTypes.contains(VOID_TYPE))
- typeCheckingContext.temporaryIfBranchTypeInformation.peek()
- .computeIfAbsent(k, x -> new
LinkedList<>()).add(VOID_TYPE);
+
typeCheckingContext.peekTemporaryTypeInfo(key).add(VOID_TYPE);
});
}
}
@@ -2969,7 +3010,7 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
return;
}
ClassNode type = call.getOwnerType();
- if (type.isEnum() && "$INIT".equals(name)) { // GROOVY-10845:
$INIT(Object[]) delegates to constructor
+ if (type.isEnum() && name.equals("$INIT")) { // GROOVY-10845:
$INIT(Object[]) delegates to constructor
Expression target =
typeCheckingContext.getEnclosingBinaryExpression().getLeftExpression();
ConstructorCallExpression cce = new
ConstructorCallExpression(type, call.getArguments());
cce.setSourcePosition(((FieldExpression) target).getField());
@@ -3700,7 +3741,7 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
ClassNode[] args = getArgumentTypes(argumentList);
boolean functorsVisited = false;
if (!isThisObjectExpression && receiver.equals(CLOSURE_TYPE)
- && ("call".equals(name) || "doCall".equals(name))) {
+ && (name.equals("call") || name.equals("doCall"))) {
if (objectExpression instanceof VariableExpression) {
Variable variable =
findTargetVariable((VariableExpression) objectExpression);
if (variable instanceof ASTNode) {
@@ -3744,7 +3785,7 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
// GROOVY-11386:
if (isThisObjectExpression && call.isImplicitThis()
&& typeCheckingContext.getEnclosingClosure() != null
- && !"call".equals(name) && !"doCall".equals(name)) { //
GROOVY-9662
+ && !name.equals("call") && !name.equals("doCall")) { //
GROOVY-9662
mn = CLOSURE_TYPE.getMethods(name);
if (!mn.isEmpty()) {
receiver = CLOSURE_TYPE.getPlainNodeReference();
@@ -3774,7 +3815,7 @@ out: if ((samParameterTypes.length == 1 &&
isOrImplements(samParameterTypes[0
}
}
}
- if (mn.isEmpty() && !"call".equals(name)) {
+ if (mn.isEmpty() && !name.equals("call")) {
// GROOVY-5705, GROOVY-5881, GROOVY-6324,
GROOVY-11366: closure property
var property = propX(objectExpression,
call.getMethod(), call.isSafe());
property.setImplicitThis(call.isImplicitThis());
@@ -4005,18 +4046,17 @@ out: if (mn.size() != 1) {
ClassNode receiver = getType(objectExpression);
List<Receiver<String>> owners = new ArrayList<>();
if (typeCheckingContext.delegationMetadata != null
- && objectExpression instanceof VariableExpression
- && "owner".equals(((Variable) objectExpression).getName())
- &&
/*isNested:*/typeCheckingContext.delegationMetadata.getParent() != null) {
+ &&
/*isNested:*/typeCheckingContext.delegationMetadata.getParent() != null
+ && objectExpression instanceof VariableExpression v &&
v.getName().equals("owner")) {
owners.add(new Receiver<>(receiver, "owner")); // if nested,
Closure is the first owner
List<Receiver<String>> thisType = new ArrayList<>(2); // and the
enclosing class is the
addDelegateReceiver(thisType, makeThis(), null); // end of
the closure owner chain
addReceivers(owners, thisType,
typeCheckingContext.delegationMetadata.getParent(), "owner.");
} else {
List<ClassNode> temporaryTypes =
getTemporaryTypesForExpression(objectExpression);
- int temporaryTypesCount = (temporaryTypes != null ?
temporaryTypes.size() : 0);
- if (temporaryTypesCount > 0) { // GROOVY-8965, GROOVY-10180,
GROOVY-10668
- owners.add(Receiver.make(lowestUpperBound(temporaryTypes)));
+ temporaryTypes.removeIf(t -> receiver.isDerivedFrom(t) ||
receiver.implementsInterface(t));
+ if (!temporaryTypes.isEmpty()) { // GROOVY-8965, GROOVY-10180,
GROOVY-10668, et al.
+
owners.add(Receiver.make(newIntersectionTypeClassNode(temporaryTypes)));
}
if (isClassClassNodeWrappingConcreteType(receiver)) {
ClassNode staticType =
receiver.getGenericsTypes()[0].getType();
@@ -4033,9 +4073,6 @@ out: if (mn.size() != 1) {
}
}
}
- if (temporaryTypesCount > 1 && !(objectExpression instanceof
VariableExpression)) {
- owners.add(Receiver.make(new
UnionTypeClassNode(temporaryTypes.toArray(ClassNode.EMPTY_ARRAY))));
- }
}
return owners;
}
@@ -4248,9 +4285,11 @@ trying: for (ClassNode[] signature : signatures) {
@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)));
+ // GROOVY-8411: if any "case Type:" then "default:" contributes
alternate type
+ if (!statement.getDefaultStatement().isEmpty()) {
+ Expression selectable = statement.getExpression();
+ optInstanceOfTypeInfo(selectable,
selectable.getNodeMetaData(TYPE));
+ }
}
@Override
@@ -4258,7 +4297,9 @@ trying: for (ClassNode[] signature : signatures) {
Expression selectable =
typeCheckingContext.getEnclosingSwitchStatement().getExpression();
Expression expression = statement.getExpression();
if (expression instanceof ClassExpression) { // GROOVY-8411: refine
the switch type
- pushInstanceOfTypeInfo(selectable, expression);
+ if (!optInstanceOfTypeInfo(selectable, expression.getType())) {
+ 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});
@@ -5004,7 +5045,7 @@ trying: for (ClassNode[] signature : signatures) {
*/
protected List<MethodNode> findMethodsWithGenerated(final ClassNode
receiver, final String name) {
if (receiver.isArray()) {
- if ("clone".equals(name)) { // GROOVY-10319: array clone --
<https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.7>
+ if (name.equals("clone")) { // GROOVY-10319: array clone --
<https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.7>
MethodNode clone = new MethodNode("clone", Opcodes.ACC_PUBLIC,
OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
clone.setDeclaringClass(OBJECT_TYPE); // retain Object for
declaringClass and returnType
clone.setNodeMetaData(INFERRED_RETURN_TYPE, receiver);
@@ -5322,7 +5363,7 @@ trying: for (ClassNode[] signature : signatures) {
if (parameters != null) {
final int n = parameters.length;
String parameterName = parameter.getName();
- if (n == 0 && "it".equals(parameterName)) {
+ if (n == 0 && parameterName.equals("it")) {
return closureParamTypes.length > 0 ?
closureParamTypes[0] : null;
}
for (int i = 0; i < n; i += 1) {
@@ -5338,10 +5379,9 @@ trying: for (ClassNode[] signature : signatures) {
private static ClassNode makeSelf(final ClassNode trait) {
Set<ClassNode> selfTypes = Traits.collectSelfTypes(trait, new
LinkedHashSet<>());
- if (!selfTypes.isEmpty()) { // TODO: reduce to the most-specific
type(s)
- ClassNode superclass = selfTypes.stream().filter(t ->
!t.isInterface()).findFirst().orElse(OBJECT_TYPE);
- selfTypes.remove(superclass); selfTypes.add(trait);
- return new WideningCategories.LowestUpperBoundClassNode("TypesOf$"
+ trait.getNameWithoutPackage(), superclass,
selfTypes.toArray(ClassNode.EMPTY_ARRAY));
+ if (!selfTypes.isEmpty()) {
+ selfTypes.add(trait);
+ return newIntersectionTypeClassNode(selfTypes);
}
return trait;
}
@@ -6158,19 +6198,34 @@ out: for (ClassNode type : todo) {
// temporaryIfBranchTypeInformation support; migrate to
TypeCheckingContext?
/**
- * Stores information about types when [objectOfInstanceof instanceof
typeExpression] is visited.
+ * Stores information about types when <i>[objectExpression instanceof
typeExpression]</i> is visited.
*
- * @param objectOfInstanceOf the expression to be checked against
instanceof
- * @param typeExpression the expression which represents the target
type
+ * @param objectExpression 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);
+ protected void pushInstanceOfTypeInfo(final Expression objectExpression,
final Expression typeExpression) {
+ var tti =
typeCheckingContext.peekTemporaryTypeInfo(extractTemporaryTypeInfoKey(objectExpression));
+ ClassNode type = typeExpression.getType();
+ tti.removeIf(type::isDerivedFrom); // x instanceof Number && x
instanceof Integer; Number is redundant
+ tti.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);
+ Object notKey = key instanceof Object[] arr ? arr[1] : new
Object[]{"!instanceof", key};
+ var tti = typeCheckingContext.peekTemporaryTypeInfo(notKey);
+ tti.addAll(types); // stash negative type(s)
+ }
+
+ private boolean optInstanceOfTypeInfo(final Expression expression, final
ClassNode type) {
+ var tti =
typeCheckingContext.temporaryIfBranchTypeInformation.peek().get(extractTemporaryTypeInfoKey(expression));
+ if (tti != null) { assert !tti.isEmpty();
+ tti.add(type);
+ ClassNode ut = newUnionTypeClassNode(tti);
+ tti.clear();
+ tti.add(ut);
+ return true;
+ }
+ return false;
}
/**
@@ -6209,7 +6264,7 @@ out: for (ClassNode type : todo) {
}
private ClassNode getInferredTypeFromTempInfo(final Expression expression,
final ClassNode expressionType) {
- if (expression instanceof VariableExpression) {
+ if (expression instanceof VariableExpression &&
!isPrimitiveType(expressionType)) {
List<ClassNode> tempTypes =
getTemporaryTypesForExpression(expression);
if (!tempTypes.isEmpty()) {
ClassNode superclass;
@@ -6252,13 +6307,36 @@ out: for (ClassNode type : todo) {
if (typesCount == 1) {
return types.get(0);
} else if (typesCount > 1) {
- return new
UnionTypeClassNode(types.toArray(ClassNode.EMPTY_ARRAY));
+ return newIntersectionTypeClassNode(types);
}
}
}
return expressionType;
}
+ private static ClassNode newIntersectionTypeClassNode(final
Collection<ClassNode> types) {
+ Map<Boolean, List<ClassNode>> spec =
types.stream().collect(Collectors.partitioningBy(ClassNode::isInterface));
+ ClassNode[] interfaces = spec.get(Boolean.TRUE
).toArray(ClassNode[]::new);
+ List<ClassNode> supers = spec.get(Boolean.FALSE);
+ if (interfaces.length == 0) return supers.get(0);
+ supers.add(OBJECT_TYPE); // ensure get(0) nothrow
+ return new
WideningCategories.LowestUpperBoundClassNode("IntersectionTypeClassNode",
supers.get(0), interfaces);
+ }
+
+ private static ClassNode newUnionTypeClassNode(final Collection<ClassNode>
types) {
+ if (types.size() == 1) return types.iterator().next();
+ var set = new LinkedHashSet<ClassNode>();
+ for (ClassNode t : types) {
+ if (t instanceof UnionTypeClassNode u) {
+ Collections.addAll(set, u.getDelegates());
+ } else {
+ set.add(t);
+ }
+ }
+ assert set.size() > 1;
+ return new UnionTypeClassNode(set.toArray(ClassNode[]::new));
+ }
+
//--------------------------------------------------------------------------
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 5d6da2eb7f..e061ecef25 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/TypeCheckingContext.java
@@ -116,6 +116,10 @@ public class TypeCheckingContext {
*/
protected Stack<Map<Object, List<ClassNode>>>
temporaryIfBranchTypeInformation = new Stack<>();
+ List<ClassNode> peekTemporaryTypeInfo(final Object o) {
+ return temporaryIfBranchTypeInformation.peek().computeIfAbsent(o, x ->
new LinkedList<>());
+ }
+
public void pushTemporaryTypeInfo() {
temporaryIfBranchTypeInformation.push(new HashMap<>());
}
diff --git
a/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java
b/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java
index cb1377b9b5..108c12447a 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java
@@ -51,14 +51,15 @@ import static
org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundC
/**
* This class node type is very special and should only be used by the static
type checker
* to represent types which are the union of other types. This is useful when,
for example,
- * we enter a section like :
+ * we enter a section like:
* <pre>if (x instanceof A || x instanceof B)</pre>
* where the type of <i>x</i> can be represented as one of <i>A</i> or
<i>B</i>.
- *
+ * <p>
* This class node type should never leak outside of the type checker. More
precisely, it should
* only be used to check method call arguments, and nothing more.
*/
class UnionTypeClassNode extends ClassNode {
+
private final ClassNode[] delegates;
UnionTypeClassNode(final ClassNode... classNodes) {
@@ -68,7 +69,7 @@ class UnionTypeClassNode extends ClassNode {
}
private static String makeName(final ClassNode[] nodes) {
- StringJoiner sj = new StringJoiner("+", "<UnionType:", ">");
+ var sj = new StringJoiner("+", "<UnionType:", ">");
for (ClassNode node : nodes) {
sj.add(node.getText());
}
@@ -78,7 +79,7 @@ class UnionTypeClassNode extends ClassNode {
private static ClassNode makeSuper(final ClassNode[] nodes) {
ClassNode upper = lowestUpperBound(Arrays.asList(nodes));
if (upper instanceof LowestUpperBoundClassNode) {
- upper = upper.getUnresolvedSuperClass();
+ upper = upper.getUnresolvedSuperClass(false);
} else if (upper.isInterface()) {
upper = OBJECT_TYPE;
}
@@ -87,7 +88,7 @@ class UnionTypeClassNode extends ClassNode {
//--------------------------------------------------------------------------
- public ClassNode[] getDelegates() {
+ ClassNode[] getDelegates() {
return delegates;
}
@@ -384,10 +385,7 @@ class UnionTypeClassNode extends ClassNode {
@Override
public boolean isDerivedFrom(final ClassNode type) {
- for (ClassNode delegate : delegates) {
- if (delegate.isDerivedFrom(type)) return true;
- }
- return false;
+ return getUnresolvedSuperClass(false).isDerivedFrom(type);
}
@Override
diff --git a/src/test/groovy/bugs/Groovy8337Bug.groovy
b/src/test/groovy/bugs/Groovy8337Bug.groovy
deleted file mode 100644
index 365fa7d0aa..0000000000
--- a/src/test/groovy/bugs/Groovy8337Bug.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- */
-package bugs
-
-import gls.CompilableTestSupport
-import groovy.test.NotYetImplemented
-
-class Groovy8337Bug extends CompilableTestSupport {
- void testGroovy8337() {
- assertScript '''
- import groovy.transform.CompileStatic
- @CompileStatic
- class Static {
- private Number n
- BigDecimal meth() {
- return n == null || n instanceof BigDecimal ? n : new
BigDecimal(n.toString())
- }
- }
- assert null == new Static().meth()
- '''
- }
-
- void testGroovy8337WithFieldValue() {
- assertScript '''
- import groovy.transform.CompileStatic
- @CompileStatic
- class Static {
- private Number n = new BigDecimal('1.23')
- BigDecimal meth() {
- return n == null || n instanceof BigDecimal ? n : new
BigDecimal(n.toString())
- }
- }
- assert new BigDecimal('1.23') == new Static().meth()
- '''
- }
-}
diff --git a/src/test/groovy/groovy/transform/stc/MethodCallsSTCTest.groovy
b/src/test/groovy/groovy/transform/stc/MethodCallsSTCTest.groovy
index 2b430400eb..15a06889ed 100644
--- a/src/test/groovy/groovy/transform/stc/MethodCallsSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/MethodCallsSTCTest.groovy
@@ -608,9 +608,10 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-5226
void testMethodCallArgumentUsingInstanceOf() {
assertScript '''
- void foo(String str) { 'String' }
+ void foo(String str) { }
def o
if (o instanceof String) {
foo(o)
@@ -643,7 +644,8 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase
{
'Cannot find matching method'
}
- void testShouldNotFailThanksToInstanceOfChecks() {
+ // GROOVY-5226
+ void testShouldNotFailWithExplicitCasts() {
assertScript '''
static String foo(String s) {
'String'
@@ -656,17 +658,18 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
}
['foo',123,true].each {
if (it instanceof String) {
- foo((String)it)
- } else if (it instanceof Boolean) {
- foo((Boolean)it)
+ foo((String) it)
} else if (it instanceof Integer) {
- foo((Integer)it)
+ foo((Integer) it)
+ } else if (it instanceof Boolean) {
+ foo((Boolean) it)
}
}
'''
}
- void testShouldNotFailThanksToInstanceOfChecksAndWithoutExplicitCasts() {
+ // GROOVY-5226
+ void testShouldNotFailWithInstanceOfChecks() {
assertScript '''
static String foo(String s) {
'String'
@@ -680,16 +683,17 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
['foo',123,true].each {
if (it instanceof String) {
foo(it)
- } else if (it instanceof Boolean) {
- foo(it)
} else if (it instanceof Integer) {
foo(it)
+ } else if (it instanceof Boolean) {
+ foo(it)
}
}
'''
}
- void testShouldNotFailThanksToInstanceOfChecksAndWithoutExplicitCasts2() {
+ // GROOVY-5226
+ void testShouldNotFailWithInstanceOfChecks2() {
assertScript '''
static String foo(String s) {
'String'
@@ -703,15 +707,16 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
['foo',123,true].each { argument ->
if (argument instanceof String) {
foo(argument)
- } else if (argument instanceof Boolean) {
- foo(argument)
} else if (argument instanceof Integer) {
foo(argument)
+ } else if (argument instanceof Boolean) {
+ foo(argument)
}
}
'''
}
+ // GROOVY-5226
void testShouldFailWithMultiplePossibleMethods() {
shouldFailWithMessages '''
static String foo(String s) {
@@ -724,14 +729,15 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
'Boolean'
}
['foo',123,true].each {
- if (it instanceof String || it instanceof Boolean || it
instanceof Integer) {
+ if (it instanceof String || it instanceof Integer || it
instanceof Boolean) {
foo(it)
}
}
''',
- 'Reference to method is ambiguous'
+ 'foo'
}
+ // GROOVY-5226
void testShouldFailWithMultiplePossibleMethods2() {
shouldFailWithMessages '''
static String foo(String s) {
@@ -744,12 +750,12 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
'Boolean'
}
['foo',123,true].each { argument ->
- if (argument instanceof String || argument instanceof Boolean
|| argument instanceof Integer) {
+ if (argument instanceof String || argument instanceof Integer
|| argument instanceof Boolean) {
foo(argument)
}
}
''',
- 'Reference to method is ambiguous'
+ 'foo'
}
// GROOVY-5703
@@ -875,6 +881,17 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
''',
'Cannot find matching method','#foo(java.util.Date)',
'Cannot find matching method','#bar(java.util.Date)'
+
+ shouldFailWithMessages foo + '''
+ def it = ~/regexp/
+ if (it !instanceof String) {
+ it = 123
+ Integer.toHextString(it)
+ } else { // cannot be String
+ foo(it)
+ }
+ ''',
+ 'Cannot find matching method','#foo(java.util.regex.Pattern)'
}
// GROOVY-5226, GROOVY-11290
@@ -903,16 +920,6 @@ class MethodCallsSTCTest extends
StaticTypeCheckingTestCase {
}
}
'''
-
- assertScript foobar + '''
- def it = ~/regexp/
- if (it !instanceof String) {
- it = 123
- foo(it)
- } else {
- bar(it)
- }
- '''
}
// GROOVY-5226, GROOVY-11290
diff --git a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
index dbf1cf374e..54f870a331 100644
--- a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -18,7 +18,6 @@
*/
package groovy.transform.stc
-import groovy.test.NotYetImplemented
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
@@ -145,8 +144,7 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
void bar() {
}
}
-
- void test(o) {
+ void test(Object o) {
if (o instanceof A) {
o.bar()
}
@@ -164,10 +162,10 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
assertScript '''
class A {
}
- A test(Object x) {
- if (x instanceof A) {
- def y = x
- return y
+ A test(Object o) {
+ if (o instanceof A) {
+ def v = o
+ return v
} else {
new A()
}
@@ -176,8 +174,26 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
'''
}
- // GROOVY-9454
+ // GROOVY-8828
void testInstanceOf7() {
+ assertScript '''
+ interface Foo {
+ }
+ interface Bar {
+ String name()
+ }
+ Map<String, Foo> map = [:]
+ map.values().each { foo ->
+ if (foo instanceof Bar) {
+ String name = foo.name() // method available through Bar
+ map.put(name, foo) // second parameter expects Foo
+ }
+ }
+ '''
+ }
+
+ // GROOVY-9454
+ void testInstanceOf8() {
assertScript '''
interface Face {
}
@@ -186,7 +202,6 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
}
class Task<R extends Face> implements
java.util.concurrent.Callable<String> {
R request
-
@Override
String call() {
if (request instanceof Impl) {
@@ -204,29 +219,27 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
}
// GROOVY-10667
- void testInstanceOf8() {
+ void testInstanceOf9() {
assertScript '''
trait Tagged {
String tag
}
class TaggedException extends Exception implements Tagged {
}
-
static void doSomething1(Exception e) {
if (e instanceof Tagged) {
- //println e.tag
+ assert e.tag == 'Test'
doSomething2(e) // Cannot find matching method
#doSomething2(Tagged)
}
}
static void doSomething2(Exception e) {
}
-
doSomething1(new TaggedException(tag:'Test'))
'''
}
// GROOVY-7971
- void testInstanceOf9() {
+ void testInstanceOf10() {
assertScript '''
import groovy.json.JsonOutput
def test(value) {
@@ -242,9 +255,33 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-8337
+ void testInstanceOf11() {
+ assertScript '''
+ class C {
+ private Number n
+ BigDecimal m() {
+ (n == null || n instanceof BigDecimal)
+ ? n : new BigDecimal(n.toString())
+ }
+ }
+ assert new C().m() == null
+ '''
+
+ assertScript '''
+ class C {
+ private Number n = new BigDecimal('1.23')
+ BigDecimal m() {
+ (n == null || n instanceof BigDecimal)
+ ? n : new BigDecimal(n.toString())
+ }
+ }
+ assert new C().m() == 1.23G
+ '''
+ }
+
// GROOVY-10096
- @NotYetImplemented
- void testInstanceOf10() {
+ void testInstanceOf12() {
shouldFailWithMessages '''
class Foo {
void foo() {
@@ -262,14 +299,13 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
}
// GROOVY-11007
- void testInstanceOf11() {
+ void testInstanceOf13() {
assertScript '''
interface I {
CharSequence getCharSequence()
}
-
- void accept(CharSequence cs) { }
-
+ void accept(CharSequence cs) {
+ }
void test(I i) {
i.with {
if (charSequence instanceof String) {
@@ -278,27 +314,25 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
}
}
}
-
test({ -> 'works' } as I)
'''
}
// GROOVY-11290
- void testInstanceOf12() {
+ void testInstanceOf14() {
assertScript '''
def test(List<String> list) {
if (list instanceof List) {
(list*.toLowerCase()).join()
}
}
-
String result = test(['foo', 'bar'])
assert result == 'foobar'
'''
}
// GROOVY-11815
- void testInstanceOf13() {
+ void testInstanceOf15() {
assertScript '''
def c = true ? new ArrayDeque() : new Stack()
if (c instanceof Deque) {
@@ -313,7 +347,7 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
// ^ (AbstractCollection<String> & Serializable & ... & Deque)
}
''',
- 'Cannot call
<UnionType:java.util.AbstractCollection','#add(java.lang.String) with arguments
[int]'
+ 'Cannot call (java.util.AbstractCollection','#add(java.lang.String)
with arguments [int]'
}
// GROOVY-5226
@@ -476,9 +510,39 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
'''
}
- // GROOVY-10668
void testMultipleInstanceOf7() {
- for (string in ['(value as String)', 'value.toString()']) {
+ assertScript '''
+ class C extends AbstractCollection {
+ Iterator<?> iterator() { }
+ void clear() { }
+ int peek() { 3 }
+ int size() { 1 }
+ }
+ void test(which) {
+ def thing = switch (which) {
+ case 01 -> new ArrayDeque()
+ case 02 -> new Stack()
+ default -> new C()
+ }
+ thing.clear()
+ if (thing instanceof Deque) {
+ thing.addFirst(1)
+ } else if (thing instanceof Stack) {
+ thing.addElement(2)
+ }
+ if (thing instanceof C || thing instanceof Queue || thing
instanceof Stack) {
+ assert thing.peek() in 1..3
+ }
+ }
+ test(1)
+ test(2)
+ test(3)
+ '''
+ }
+
+ // GROOVY-10668
+ void testMultipleInstanceOf8() {
+ for (string in ['(value as String)', 'value.toString()', 'value']) {
assertScript """
def toArray(Object value) {
def array
@@ -497,22 +561,6 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
}
}
- // GROOVY-8828
- void testMultipleInstanceOf8() {
- assertScript '''
- interface Foo { }
- interface Bar { String name() }
-
- Map<String, Foo> map = [:]
- map.values().each { foo ->
- if (foo instanceof Bar) {
- String name = foo.name() // method available through Bar
- map.put(name, foo) // second parameter expects Foo
- }
- }
- '''
- }
-
// GROOVY-6429
void testNotInstanceof1() {
String types = '''
@@ -679,11 +727,11 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
void testInstanceOfInferenceWithImplicitIt() {
assertScript '''
- ['a', 'b', 'c'].each {
- if (it instanceof String) {
- println it.toUpperCase()
+ ['a', 'b', 'c'].each {
+ if (it instanceof String) {
+ println it.toUpperCase()
+ }
}
- }
'''
}
@@ -1249,7 +1297,7 @@ class TypeInferenceSTCTest extends
StaticTypeCheckingTestCase {
case File:
something.toString()
default:
- something.getCanonicalName()
+ something.canonicalName // TODO: GROOVY-10702
}
}
''',
diff --git
a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
index b7ae38f82b..ee3a9b19d8 100644
---
a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
+++
b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/TypeInferenceStaticCompileTest.groovy
@@ -18,17 +18,10 @@
*/
package org.codehaus.groovy.classgen.asm.sc
-import groovy.test.NotYetImplemented
import groovy.transform.stc.TypeInferenceSTCTest
/**
* Unit tests for static compilation : type inference.
*/
-class TypeInferenceStaticCompileTest extends TypeInferenceSTCTest implements
StaticCompilationTestSupport {
-
- @Override
- @NotYetImplemented
- void testInstanceOf9() {
- super.testInstanceOf9() // GROOVY-7971
- }
+final class TypeInferenceStaticCompileTest extends TypeInferenceSTCTest
implements StaticCompilationTestSupport {
}