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 35da0221a2 GROOVY-11229: JEP 394: `instanceof` declared variable
35da0221a2 is described below
commit 35da0221a2a9652b36f87812376ad269a8be3ce8
Author: Eric Milles <[email protected]>
AuthorDate: Sat Mar 22 18:38:44 2025 -0500
GROOVY-11229: JEP 394: `instanceof` declared variable
see #1994
---
src/antlr/GroovyParser.g4 | 9 +++-
.../apache/groovy/parser/antlr4/AstBuilder.java | 32 +++++++++--
.../groovy/ast/expr/DeclarationExpression.java | 6 +++
.../classgen/asm/BinaryExpressionHelper.java | 37 ++++++++++---
src/test/groovy/InstanceofTest.groovy | 62 ++++++++++++----------
5 files changed, 107 insertions(+), 39 deletions(-)
diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index af3566872f..a73c13fd4a 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -345,6 +345,10 @@ referenceType
: qualifiedClassName typeArguments?
;
+matchingType // see: instanceof
+ : standardType identifier?
+ ;
+
standardType // see: returnType
options { baseContext = type; }
: annotationsOpt
@@ -803,8 +807,9 @@ expression
right=expression
#shiftExprAlt
// boolean relational expressions (level 7)
- | left=expression nls op=(AS | INSTANCEOF | NOT_INSTANCEOF) nls type
#relationalExprAlt
- | left=expression nls op=(LE | GE | GT | LT | IN | NOT_IN) nls
right=expression #relationalExprAlt
+ | left=expression nls op=INSTANCEOF nls matchingType
#relationalExprAlt
+ | left=expression nls op=(AS | NOT_INSTANCEOF) nls type
#relationalExprAlt
+ | left=expression nls op=(LE | GE | GT | LT | IN | NOT_IN) nls
right=expression #relationalExprAlt
// equality/inequality (==/!=) (level 8)
| left=expression nls
diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index ad2b5d4051..6bba59605b 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -160,6 +160,7 @@ import static
org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.closureX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.listX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
@@ -3024,12 +3025,21 @@ public class AstBuilder extends
GroovyParserBaseVisitor<Object> {
case AS:
Expression expr = (Expression) this.visit(ctx.left);
if (expr instanceof VariableExpression && ((VariableExpression)
expr).isSuperExpression()) {
- this.createParsingFailedException("Cannot cast or coerce
`super`", ctx); // GROOVY-9391
+ throw this.createParsingFailedException("Cannot cast or coerce
`super`", ctx); // GROOVY-9391
}
- CastExpression cast =
CastExpression.asExpression(this.visitType(ctx.type()), expr);
- return configureAST(cast, ctx);
+ return configureAST(
+ CastExpression.asExpression(this.visitType(ctx.type()),
expr),
+ ctx);
case INSTANCEOF:
+ ctx.matchingType().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR,
Boolean.TRUE);
+ return configureAST(
+ new BinaryExpression(
+ (Expression) this.visit(ctx.left),
+ this.createGroovyToken(ctx.op),
+ this.visitMatchingType(ctx.matchingType())),
+ ctx);
+
case NOT_INSTANCEOF:
ctx.type().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR,
Boolean.TRUE);
return configureAST(
@@ -4057,6 +4067,22 @@ public class AstBuilder extends
GroovyParserBaseVisitor<Object> {
// } type
------------------------------------------------------------------
+ @Override
+ public Expression visitMatchingType(final MatchingTypeContext ctx) {
+ if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
+ ctx.type().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR,
Boolean.TRUE);
+ }
+ ClassNode type = this.visitType(ctx.type());
+ Expression ex;
+ if (asBoolean(ctx.identifier())) { // Type name (JEP 394)
+ ex = configureAST(varX(this.visitIdentifier(ctx.identifier()),
type), ctx.identifier());
+ ex = declX(ex, EmptyExpression.INSTANCE);
+ } else { // Type
+ ex = new ClassExpression(type);
+ }
+ return configureAST(ex, ctx);
+ }
+
@Override
public VariableExpression visitVariableDeclaratorId(final
VariableDeclaratorIdContext ctx) {
return configureAST(new
VariableExpression(this.visitIdentifier(ctx.identifier())), ctx);
diff --git
a/src/main/java/org/codehaus/groovy/ast/expr/DeclarationExpression.java
b/src/main/java/org/codehaus/groovy/ast/expr/DeclarationExpression.java
index 48e0cb6b6c..bd74ee8690 100644
--- a/src/main/java/org/codehaus/groovy/ast/expr/DeclarationExpression.java
+++ b/src/main/java/org/codehaus/groovy/ast/expr/DeclarationExpression.java
@@ -19,6 +19,7 @@
package org.codehaus.groovy.ast.expr;
import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GroovyCodeVisitor;
import org.codehaus.groovy.syntax.Token;
@@ -130,6 +131,11 @@ public class DeclarationExpression extends
BinaryExpression {
: null;
}
+ @Override
+ public ClassNode getType() {
+ return (isMultipleAssignmentDeclaration() ? getTupleExpression() :
getVariableExpression()).getType();
+ }
+
@Override
public String getText() {
StringBuilder text = new StringBuilder();
diff --git
a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
index 7f875a8dde..d209baab76 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
@@ -769,15 +769,38 @@ public class BinaryExpressionHelper {
}
private void evaluateInstanceof(final BinaryExpression expression) {
+ CompileStack compileStack = controller.getCompileStack();
+ OperandStack operandStack = controller.getOperandStack();
+
expression.getLeftExpression().visit(controller.getAcg());
- controller.getOperandStack().box();
- Expression rightExp = expression.getRightExpression();
- if (!(rightExp instanceof ClassExpression)) {
- throw new RuntimeException("RHS of the instanceof keyword must be
a class name, not: " + rightExp);
+ operandStack.box(); // TODO: support instanceof primitives
+
+ ClassNode sourceType = operandStack.getTopOperand();
+ ClassNode targetType = expression.getRightExpression().getType();
+
+ int jep394 = 0;
+ if (!(expression.getRightExpression() instanceof ClassExpression)) {
+ operandStack.dup(); // stash value for use by JEP 394 pattern
variable
+ String name =
"instanceof"+Integer.toHexString(expression.hashCode());
+ jep394 = compileStack.defineTemporaryVariable(name, sourceType,
true);
+ }
+
+ String typeName = BytecodeHelper.getClassInternalName(targetType);
+ controller.getMethodVisitor().visitTypeInsn(INSTANCEOF, typeName);
+ operandStack.replace(ClassHelper.boolean_TYPE);
+
+ if (jep394 > 0) {
+ var variable = (Variable) ((BinaryExpression)
expression.getRightExpression()).getLeftExpression();
+ BytecodeVariable v = compileStack.defineVariable(variable,
targetType, false);
+
+ operandStack.dup();
+ Label l0 = operandStack.jump(IFEQ); // skip store if not instanceof
+ BytecodeHelper.load(controller.getMethodVisitor(), sourceType,
jep394);
+ BytecodeHelper.store(controller.getMethodVisitor(), targetType,
v.getIndex());
+
+ controller.getMethodVisitor().visitLabel(l0);
+ compileStack.removeVar(jep394);
}
- String classInternalName =
BytecodeHelper.getClassInternalName(rightExp.getType());
- controller.getMethodVisitor().visitTypeInsn(INSTANCEOF,
classInternalName);
- controller.getOperandStack().replace(ClassHelper.boolean_TYPE);
}
private void evaluateNotInstanceof(final BinaryExpression expression) {
diff --git a/src/test/groovy/InstanceofTest.groovy
b/src/test/groovy/InstanceofTest.groovy
index 55eb25eea6..c1d37f1da9 100644
--- a/src/test/groovy/InstanceofTest.groovy
+++ b/src/test/groovy/InstanceofTest.groovy
@@ -18,57 +18,65 @@
*/
package groovy
-import groovy.test.GroovyTestCase
+import org.junit.jupiter.api.Test
-class InstanceofTest extends GroovyTestCase {
+final class InstanceofTest {
- void testTrue() {
-
- def x = false
+ @Test
+ void testIsInstance() {
def o = 12
- if ( o instanceof Integer ) {
- x = true
- }
-
- assert x == true
+ assert (o instanceof Integer)
}
- void testFalse() {
-
- def x = false
+ @Test
+ void testNotInstance() {
def o = 12
- if ( o instanceof Double ) {
- x = true
- }
-
- assert x == false
+ assert !(o instanceof Double)
}
+ @Test
void testImportedClass() {
def m = ["xyz":2]
- assert m instanceof Map
- assert !(m instanceof Double)
- assertTrue(m instanceof Map)
- assertFalse(m instanceof Double)
+ assert (m instanceof Map)
+ assert !(m !instanceof Map)
+ assert !(m instanceof Double)
+ assert (m !instanceof Double)
}
+ @Test
void testFullyQualifiedClass() {
def l = [1, 2, 3]
- assert l instanceof java.util.List
- assert !(l instanceof Map)
- assertTrue(l instanceof java.util.List)
- assertFalse(l instanceof Map)
+ assert (l instanceof java.util.List)
+ assert !(l instanceof java.util.Map)
+ assert (l !instanceof java.util.Map)
}
- void testBoolean(){
+ @Test
+ void testBoolean() {
assert true instanceof Object
assert true==true instanceof Object
assert true==false instanceof Object
assert true==false instanceof Boolean
assert !new Object() instanceof Boolean
}
+
+ // GROOVY-11229
+ @Test
+ void testVariable() {
+ Number n = 12345;
+ if (n instanceof Integer i) {
+ assert i.intValue() == 12345
+ } else {
+ assert false : 'expected Integer'
+ }
+ if (n instanceof String s) {
+ assert false : 'not String'
+ } else {
+ assert n.intValue() == 12345
+ }
+ }
}