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
+        }
+    }
 }

Reply via email to