This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY_3_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push: new a01446dbe2 GROOVY-10618, GROOVY-10711: SC: `BooleanExpression` and `NotExpression` a01446dbe2 is described below commit a01446dbe28824390d1bb6df1a6e54267644d719 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Fri Jul 29 16:05:01 2022 -0500 GROOVY-10618, GROOVY-10711: SC: `BooleanExpression` and `NotExpression` 3_0_X backport --- .../groovy/ast/expr/BooleanExpression.java | 7 +- .../codehaus/groovy/ast/expr/NotExpression.java | 2 + .../transformers/BooleanExpressionTransformer.java | 175 +++++++------ .../sc/transformers/CompareToNullExpression.java | 52 ++-- .../transformers/StaticCompilationTransformer.java | 32 +-- ...StaticCompileNullCompareOptimizationTest.groovy | 291 ++++++++++++--------- 6 files changed, 306 insertions(+), 253 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/ast/expr/BooleanExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/BooleanExpression.java index bde832082c..744b485f2b 100644 --- a/src/main/java/org/codehaus/groovy/ast/expr/BooleanExpression.java +++ b/src/main/java/org/codehaus/groovy/ast/expr/BooleanExpression.java @@ -31,22 +31,25 @@ public class BooleanExpression extends Expression { this.expression = expression; setType(ClassHelper.boolean_TYPE); // for consistency with AsmClassGenerator. see AsmClassGenerator.visitBooleanExpression. } - + public Expression getExpression() { return expression; } + @Override public void visit(GroovyCodeVisitor visitor) { visitor.visitBooleanExpression(this); } + @Override public Expression transformExpression(ExpressionTransformer transformer) { Expression ret = new BooleanExpression(transformer.transform(expression)); ret.setSourcePosition(this); ret.copyNodeMetaData(this); return ret; } - + + @Override public String getText() { return expression.getText(); } diff --git a/src/main/java/org/codehaus/groovy/ast/expr/NotExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/NotExpression.java index f404765862..6f990e59da 100644 --- a/src/main/java/org/codehaus/groovy/ast/expr/NotExpression.java +++ b/src/main/java/org/codehaus/groovy/ast/expr/NotExpression.java @@ -26,6 +26,7 @@ public class NotExpression extends BooleanExpression { super(expression); } + @Override public void visit(GroovyCodeVisitor visitor) { visitor.visitNotExpression(this); } @@ -34,6 +35,7 @@ public class NotExpression extends BooleanExpression { return false; } + @Override public Expression transformExpression(ExpressionTransformer transformer) { Expression ret = new NotExpression(transformer.transform(getExpression())); ret.setSourcePosition(this); diff --git a/src/main/java/org/codehaus/groovy/transform/sc/transformers/BooleanExpressionTransformer.java b/src/main/java/org/codehaus/groovy/transform/sc/transformers/BooleanExpressionTransformer.java index f15b75caf6..882a2dc86d 100644 --- a/src/main/java/org/codehaus/groovy/transform/sc/transformers/BooleanExpressionTransformer.java +++ b/src/main/java/org/codehaus/groovy/transform/sc/transformers/BooleanExpressionTransformer.java @@ -21,8 +21,8 @@ package org.codehaus.groovy.transform.sc.transformers; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.GroovyCodeVisitor; -import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.BooleanExpression; import org.codehaus.groovy.ast.expr.Expression; @@ -32,7 +32,6 @@ import org.codehaus.groovy.classgen.AsmClassGenerator; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.classgen.asm.OperandStack; import org.codehaus.groovy.classgen.asm.WriterController; -import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser; import org.codehaus.groovy.transform.stc.ExtensionMethodNode; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; @@ -49,73 +48,63 @@ import static org.objectweb.asm.Opcodes.IFNONNULL; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.POP; -public class BooleanExpressionTransformer { +class BooleanExpressionTransformer { + private final StaticCompilationTransformer transformer; - public BooleanExpressionTransformer(StaticCompilationTransformer staticCompilationTransformer) { - transformer = staticCompilationTransformer; + BooleanExpressionTransformer(final StaticCompilationTransformer transformer) { + this.transformer = transformer; } - Expression transformBooleanExpression(final BooleanExpression booleanExpression) { - if (booleanExpression instanceof NotExpression) { - return transformer.superTransform(booleanExpression); - } - final Expression expression = booleanExpression.getExpression(); - if (!(expression instanceof BinaryExpression)) { - StaticTypesTypeChooser typeChooser = transformer.getTypeChooser(); - final ClassNode type = typeChooser.resolveType(expression, transformer.getClassNode()); - BooleanExpression transformed = new OptimizingBooleanExpression(transformer.transform(expression),type); - transformed.setSourcePosition(booleanExpression); - transformed.copyNodeMetaData(booleanExpression); - return transformed; - } - return transformer.superTransform(booleanExpression); - } + Expression transformBooleanExpression(final BooleanExpression boolX) { + Expression expr = boolX; + boolean reverse = false; + do { // undo arbitrary nesting of (Boolean|Not)Expressions + if (expr instanceof NotExpression) reverse = !reverse; + expr = ((BooleanExpression) expr).getExpression(); + } while (expr instanceof BooleanExpression); - private static boolean isExtended(ClassNode owner, Iterator<InnerClassNode> classes) { - while (classes.hasNext()) { - InnerClassNode next = classes.next(); - if (next!=owner && next.isDerivedFrom(owner)) return true; - if (isExtended(owner,next.getInnerClasses())) return true; + if (!(expr instanceof BinaryExpression)) { + expr = transformer.transform(expr); + ClassNode type = transformer.getTypeChooser().resolveType(expr, transformer.getClassNode()); + Expression opt = new OptimizingBooleanExpression(expr, type); + if (reverse) opt = new NotExpression(opt); + opt.setSourcePosition(boolX); + return opt; } - return false; + + return transformer.superTransform(boolX); } + //-------------------------------------------------------------------------- + private static class OptimizingBooleanExpression extends BooleanExpression { - private final Expression expression; private final ClassNode type; - public OptimizingBooleanExpression(final Expression expression, final ClassNode type) { + OptimizingBooleanExpression(final Expression expression, final ClassNode type) { super(expression); - this.expression = expression; // we must use the redirect node, otherwise InnerClassNode would not have the "correct" type this.type = type.redirect(); } @Override public Expression transformExpression(final ExpressionTransformer transformer) { - Expression ret = new OptimizingBooleanExpression(transformer.transform(expression), type); - ret.setSourcePosition(this); - ret.copyNodeMetaData(this); - return ret; + Expression opt = new OptimizingBooleanExpression(transformer.transform(getExpression()), type); + opt.setSourcePosition(this); + opt.copyNodeMetaData(this); + return opt; } @Override public void visit(final GroovyCodeVisitor visitor) { if (visitor instanceof AsmClassGenerator) { - AsmClassGenerator acg = (AsmClassGenerator) visitor; - WriterController controller = acg.getController(); + WriterController controller = ((AsmClassGenerator) visitor).getController(); + MethodVisitor mv = controller.getMethodVisitor(); OperandStack os = controller.getOperandStack(); - if (type.equals(ClassHelper.boolean_TYPE)) { - expression.visit(visitor); - os.doGroovyCast(ClassHelper.boolean_TYPE); - return; - } - if (type.equals(ClassHelper.Boolean_TYPE)) { - MethodVisitor mv = controller.getMethodVisitor(); - expression.visit(visitor); + if (ClassHelper.Boolean_TYPE.equals(type)) { + getExpression().visit(visitor); Label unbox = new Label(); Label exit = new Label(); // check for null @@ -125,57 +114,81 @@ public class BooleanExpressionTransformer { mv.visitInsn(ICONST_0); mv.visitJumpInsn(GOTO, exit); mv.visitLabel(unbox); - // unbox - // GROOVY-6270 - if (!os.getTopOperand().equals(type)) BytecodeHelper.doCast(mv, type); + if (!os.getTopOperand().equals(type)) BytecodeHelper.doCast(mv, type); // GROOVY-6270 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); mv.visitLabel(exit); os.replace(ClassHelper.boolean_TYPE); return; } - ClassNode top = type; - if (ClassHelper.isPrimitiveType(top)) { - expression.visit(visitor); - // in case of null safe invocation, it is possible that what was supposed to be a primitive type - // becomes the "null" constant, so we need to recheck - top = controller.getOperandStack().getTopOperand(); - if (ClassHelper.isPrimitiveType(top)) { - BytecodeHelper.convertPrimitiveToBoolean(controller.getMethodVisitor(), top); - controller.getOperandStack().replace(ClassHelper.boolean_TYPE); + + if (ClassHelper.isPrimitiveType(type)) { + getExpression().visit(visitor); + if (ClassHelper.boolean_TYPE.equals(type)) { + os.doGroovyCast(ClassHelper.boolean_TYPE); return; + } else { + // in case of null safe invocation, it is possible that what was supposed to be a primitive type + // becomes the "null" constant, so we need to recheck + ClassNode top = controller.getOperandStack().getTopOperand(); + if (ClassHelper.isPrimitiveType(top)) { + BytecodeHelper.convertPrimitiveToBoolean(mv, top); + os.replace(ClassHelper.boolean_TYPE); + return; + } } } - List<MethodNode> asBoolean = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), top, "asBoolean", ClassNode.EMPTY_ARRAY); + + if (replaceAsBooleanWithCompareToNull(type, controller.getSourceUnit().getClassLoader())) { + new CompareToNullExpression(getExpression(), false).visit(visitor); + return; + } + } + + super.visit(visitor); + } + + /** + * Inline an "expr != null" check instead of boolean conversion iff: + * (1) the class doesn't define an {@code asBoolean()} method + * (2) no subclass defines an {@code asBoolean()} method + * For (2), check that we are in one of these cases: + * (a) a final class + * (b) an effectively-final inner class + */ + private static boolean replaceAsBooleanWithCompareToNull(final ClassNode type, final ClassLoader dgmProvider) { + if (type.getMethod("asBoolean", Parameter.EMPTY_ARRAY) != null) { + // GROOVY-10711 + } else if (Modifier.isFinal(type.getModifiers()) || isEffectivelyFinal(type)) { + List<MethodNode> asBoolean = findDGMMethodsByNameAndArguments(dgmProvider, type, "asBoolean", ClassNode.EMPTY_ARRAY); if (asBoolean.size() == 1) { - MethodNode node = asBoolean.get(0); - if (node instanceof ExtensionMethodNode) { - MethodNode dgmNode = ((ExtensionMethodNode) node).getExtensionMethodNode(); - ClassNode owner = dgmNode.getParameters()[0].getType(); - if (ClassHelper.OBJECT_TYPE.equals(owner)) { - // we may inline a var!=null check instead of calling a helper method iff - // (1) the class doesn't define an asBoolean method (already tested) - // (2) no subclass defines an asBoolean method - // For (2), we check that we are in one of those cases - // (a) a final class - // (b) a private inner class without subclass - if (Modifier.isFinal(top.getModifiers()) - || (top instanceof InnerClassNode - && Modifier.isPrivate(top.getModifiers()) - && !isExtended(top, top.getOuterClass().getInnerClasses())) - ) { - CompareToNullExpression expr = new CompareToNullExpression( - expression, false - ); - expr.visit(acg); - return; - } + MethodNode theAsBoolean = asBoolean.get(0); + if (theAsBoolean instanceof ExtensionMethodNode) { + ClassNode selfType = (((ExtensionMethodNode) theAsBoolean).getExtensionMethodNode()).getParameters()[0].getType(); + if (ClassHelper.OBJECT_TYPE.equals(selfType)) { + return true; } } } - super.visit(visitor); - } else { - super.visit(visitor); } + return false; + } + + private static boolean isEffectivelyFinal(final ClassNode type) { + if (!Modifier.isPrivate(type.getModifiers())) return false; + + List<ClassNode> outers = type.getOuterClasses(); + ClassNode outer = outers.get(outers.size() - 1); + return !isExtended(type, outer.getInnerClasses()); + } + + private static boolean isExtended(final ClassNode type, final Iterator<? extends ClassNode> inners) { + while (inners.hasNext()) { ClassNode next = inners.next(); + if (next != type && next.isDerivedFrom(type)) + return true; + if (isExtended(type, next.getInnerClasses())) + return true; + } + return false; } } } diff --git a/src/main/java/org/codehaus/groovy/transform/sc/transformers/CompareToNullExpression.java b/src/main/java/org/codehaus/groovy/transform/sc/transformers/CompareToNullExpression.java index 8651154dfa..36e6436752 100644 --- a/src/main/java/org/codehaus/groovy/transform/sc/transformers/CompareToNullExpression.java +++ b/src/main/java/org/codehaus/groovy/transform/sc/transformers/CompareToNullExpression.java @@ -29,9 +29,14 @@ import org.codehaus.groovy.classgen.asm.WriterController; import org.codehaus.groovy.syntax.Token; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -public class CompareToNullExpression extends BinaryExpression implements Opcodes { +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_1; +import static org.objectweb.asm.Opcodes.IFNONNULL; +import static org.objectweb.asm.Opcodes.IFNULL; + +public class CompareToNullExpression extends BinaryExpression { private final boolean equalsNull; public CompareToNullExpression(final Expression expression, final boolean equalsNull) { @@ -69,32 +74,31 @@ public class CompareToNullExpression extends BinaryExpression implements Opcodes @Override public void visit(final GroovyCodeVisitor visitor) { - if (visitor instanceof AsmClassGenerator) { - AsmClassGenerator acg = (AsmClassGenerator) visitor; - WriterController controller = acg.getController(); - MethodVisitor mv = controller.getMethodVisitor(); - - getObjectExpression().visit(acg); + if (!(visitor instanceof AsmClassGenerator)) { + super.visit(visitor); + return; + } - if (ClassHelper.isPrimitiveType(controller.getOperandStack().getTopOperand())) { - controller.getOperandStack().pop(); - mv.visitInsn(equalsNull ? ICONST_0 : ICONST_1); + WriterController controller = ((AsmClassGenerator) visitor).getController(); + MethodVisitor mv = controller.getMethodVisitor(); - controller.getOperandStack().push(ClassHelper.boolean_TYPE); - } else { - Label zero = new Label(); - mv.visitJumpInsn(equalsNull ? IFNONNULL : IFNULL, zero); - mv.visitInsn(ICONST_1); - Label end = new Label(); - mv.visitJumpInsn(GOTO, end); - mv.visitLabel(zero); - mv.visitInsn(ICONST_0); - mv.visitLabel(end); + getObjectExpression().visit(visitor); - controller.getOperandStack().replace(ClassHelper.boolean_TYPE); - } + if (ClassHelper.isPrimitiveType(controller.getOperandStack().getTopOperand())) { + controller.getOperandStack().pop(); + mv.visitInsn(equalsNull ? ICONST_0 : ICONST_1); + controller.getOperandStack().push(ClassHelper.boolean_TYPE); } else { - super.visit(visitor); + Label no = new Label(), yes = new Label(); + + mv.visitJumpInsn(equalsNull ? IFNONNULL : IFNULL, no); + mv.visitInsn(ICONST_1); + mv.visitJumpInsn(GOTO, yes); + mv.visitLabel(no); + mv.visitInsn(ICONST_0); + mv.visitLabel(yes); + + controller.getOperandStack().replace(ClassHelper.boolean_TYPE); } } } diff --git a/src/main/java/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java b/src/main/java/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java index e2a5a7902e..b51ca7cd7b 100644 --- a/src/main/java/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java +++ b/src/main/java/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java @@ -71,12 +71,12 @@ public class StaticCompilationTransformer extends ClassCodeExpressionTransformer // various helpers in order to avoid a potential very big class private final StaticMethodCallExpressionTransformer staticMethodCallExpressionTransformer = new StaticMethodCallExpressionTransformer(this); - private final ConstructorCallTransformer constructorCallTransformer = new ConstructorCallTransformer(this); private final MethodCallExpressionTransformer methodCallExpressionTransformer = new MethodCallExpressionTransformer(this); - private final BinaryExpressionTransformer binaryExpressionTransformer = new BinaryExpressionTransformer(this); + private final ConstructorCallTransformer constructorCallTransformer = new ConstructorCallTransformer(this); + private final VariableExpressionTransformer variableExpressionTransformer = new VariableExpressionTransformer(); private final ClosureExpressionTransformer closureExpressionTransformer = new ClosureExpressionTransformer(this); private final BooleanExpressionTransformer booleanExpressionTransformer = new BooleanExpressionTransformer(this); - private final VariableExpressionTransformer variableExpressionTransformer = new VariableExpressionTransformer(); + private final BinaryExpressionTransformer binaryExpressionTransformer = new BinaryExpressionTransformer(this); private final RangeExpressionTransformer rangeExpressionTransformer = new RangeExpressionTransformer(this); private final ListExpressionTransformer listExpressionTransformer = new ListExpressionTransformer(this); private final CastExpressionOptimizer castExpressionTransformer = new CastExpressionOptimizer(this); @@ -105,36 +105,36 @@ public class StaticCompilationTransformer extends ClassCodeExpressionTransformer } @Override - public Expression transform(Expression expr) { + public Expression transform(final Expression expr) { if (expr instanceof StaticMethodCallExpression) { return staticMethodCallExpressionTransformer.transformStaticMethodCallExpression((StaticMethodCallExpression) expr); } - if (expr instanceof BinaryExpression) { - return binaryExpressionTransformer.transformBinaryExpression((BinaryExpression)expr); - } if (expr instanceof MethodCallExpression) { return methodCallExpressionTransformer.transformMethodCallExpression((MethodCallExpression) expr); } - if (expr instanceof ClosureExpression) { - return closureExpressionTransformer.transformClosureExpression((ClosureExpression) expr); - } if (expr instanceof ConstructorCallExpression) { return constructorCallTransformer.transformConstructorCall((ConstructorCallExpression) expr); } + if (expr instanceof VariableExpression) { + return variableExpressionTransformer.transformVariableExpression((VariableExpression) expr); + } + if (expr instanceof ClosureExpression) { + return closureExpressionTransformer.transformClosureExpression((ClosureExpression) expr); + } if (expr instanceof BooleanExpression) { - return booleanExpressionTransformer.transformBooleanExpression((BooleanExpression)expr); + return booleanExpressionTransformer.transformBooleanExpression((BooleanExpression) expr); } - if (expr instanceof VariableExpression) { - return variableExpressionTransformer.transformVariableExpression((VariableExpression)expr); + if (expr instanceof BinaryExpression) { + return binaryExpressionTransformer.transformBinaryExpression((BinaryExpression) expr); } if (expr instanceof RangeExpression) { - return rangeExpressionTransformer.transformRangeExpression(((RangeExpression)expr)); + return rangeExpressionTransformer.transformRangeExpression((RangeExpression) expr); } if (expr instanceof ListExpression) { return listExpressionTransformer.transformListExpression((ListExpression) expr); } if (expr instanceof CastExpression) { - return castExpressionTransformer.transformCastExpression(((CastExpression)expr)); + return castExpressionTransformer.transformCastExpression((CastExpression) expr); } return super.transform(expr); } @@ -142,7 +142,7 @@ public class StaticCompilationTransformer extends ClassCodeExpressionTransformer /** * Called by helpers when super.transform() is needed. */ - final Expression superTransform(Expression expr) { + final Expression superTransform(final Expression expr) { return super.transform(expr); } diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy index 3adf4760ea..3e2e54c758 100644 --- a/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy +++ b/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy @@ -19,71 +19,62 @@ package org.codehaus.groovy.classgen.asm.sc import org.codehaus.groovy.classgen.asm.AbstractBytecodeTestCase + import static org.codehaus.groovy.control.CompilerConfiguration.DEFAULT as config /** * Unit tests for static compilation: null test optimizations. */ -class StaticCompileNullCompareOptimizationTest extends AbstractBytecodeTestCase { - void testShouldUseIfNonNull() { +final class StaticCompileNullCompareOptimizationTest extends AbstractBytecodeTestCase { + + void testShouldUseIfNull1() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(Object o) { - o == null + o != null } ''') - assert bytecode.hasStrictSequence([ - 'IFNONNULL' - ]) + assert bytecode.hasStrictSequence(['IFNULL']) } - void testShouldUseIfNull() { + + void testShouldUseIfNull2() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(Object o) { - o != null + null != o } ''') - assert bytecode.hasStrictSequence([ - 'IFNULL' - ]) + assert bytecode.hasStrictSequence(['IFNULL']) } - void testShouldUseIfNonNull2() { + void testShouldUseIfNonNull1() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(Object o) { - null == o + o == null } ''') - assert bytecode.hasStrictSequence([ - 'IFNONNULL' - ]) + assert bytecode.hasStrictSequence(['IFNONNULL']) } - void testShouldUseIfNull2() { + void testShouldUseIfNonNull2() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(Object o) { - null != o + null == o } ''') - assert bytecode.hasStrictSequence([ - 'IFNULL' - ]) + assert bytecode.hasStrictSequence(['IFNONNULL']) } - void testPrimitiveWithNullShouldBeOptimized() { + void testPrimitiveWithNullShouldBeOptimized1() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(int x) { null == x } ''') - assert bytecode.hasStrictSequence([ - 'ICONST_0', - 'POP' - ]) - + assert bytecode.hasStrictSequence(['ICONST_0', 'POP']) } void testPrimitiveWithNullShouldBeOptimized2() { @@ -93,185 +84,225 @@ class StaticCompileNullCompareOptimizationTest extends AbstractBytecodeTestCase x == null } ''') - assert bytecode.hasStrictSequence([ - 'ICONST_0', - 'POP' + assert bytecode.hasStrictSequence(['ICONST_0', 'POP']) + } + + void testOptimizeGroovyTruthForPrimitiveBoolean1() { + def bytecode = compile(method:'m', ''' + @groovy.transform.CompileStatic + void m(boolean x) { + if (x) { + } + } + ''') + assert bytecode.hasSequence([ + 'ILOAD 1', + 'IFEQ L1', + 'L1', + 'RETURN' ]) } + void testOptimizeGroovyTruthForPrimitiveBoolean2() { + def bytecode = compile(method:'m', ''' + @groovy.transform.CompileStatic + void m(boolean x) { + if (!x) { + } + } + ''') + assert bytecode.hasSequence([ + 'ILOAD 1', + 'IFNE L1', + 'ICONST_1', + 'GOTO L2', + 'L1', + 'ICONST_0', + 'L2', + 'IFEQ L3', + 'L3', + 'RETURN' + ]) + } - void testOptimizeGroovyTruthForPrimitiveBoolean() { + void testOptimizeGroovyTruthForPrimitiveBoolean3() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(boolean x) { - if (x) { - println 'ok' + if (!!x) { } } ''') - assert bytecode.hasStrictSequence(['ILOAD 1', 'IFEQ']) + assert bytecode.hasSequence([ + 'ILOAD 1', + 'IFEQ L1', + 'L1', + 'RETURN' + ]) } - void testOptimizeGroovyTruthForBoxedBoolean() { + void testOptimizeGroovyTruthForNonPrimitiveBoolean() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(Boolean x) { if (x) { - println 'ok' } } ''') - if (config.indyEnabled) { - return - } - assert bytecode.hasStrictSequence(['ALOAD 1', 'DUP', 'IFNONNULL', 'POP', 'ICONST_0', 'GOTO', 'L1', 'INVOKEVIRTUAL', 'L2', 'IFEQ']) || - bytecode.hasStrictSequence(['ALOAD 1', 'DUP', 'IFNONNULL', 'POP', 'ICONST_0', 'GOTO', 'L1', 'FRAME SAME1', 'INVOKEVIRTUAL', 'L2', 'FRAME SAME1', 'IFEQ']) // bytecode with stack map frame + assert bytecode.hasSequence([ + 'ALOAD 1', + 'DUP', + 'IFNONNULL', + 'POP', + 'ICONST_0', + 'GOTO', + 'L1', + 'INVOKEVIRTUAL', + 'L2', + 'IFEQ' + ]) } - void testOptimizeGroovyTruthWithStringShouldNotBeTriggered() { + void testOptimizeGroovyTruthForPrimitiveNumberType() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic - void m(String x) { + void m(int x) { if (x) { - println 'ok' } } ''') - if (config.indyEnabled) { - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'INVOKEDYNAMIC cast(Ljava/lang/String;)Z', - '', - '', - '', - '', - '', - ']', - 'IFEQ' - ]) - } else { - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z', - 'IFEQ' - ]) - } + assert bytecode.hasSequence([ + 'ILOAD 1', + 'IFEQ L1', + 'ICONST_1', + 'GOTO L2', + 'L1', + 'ICONST_0', + 'L2', + 'IFEQ L3', + 'L3', + 'RETURN' + ]) } - void testGroovyTruthOptimizationWithObjectShouldNotBeTriggered() { + void testNoGroovyTruthOptimizationForObject() { def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic void m(Object x) { if (x) { - println 'ok' } } ''') - if (config.indyEnabled) { - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'INVOKEDYNAMIC cast(Ljava/lang/Object;)Z', - '', - '', - '', - '', - '', - ']', - 'IFEQ' - ]) - } else { - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z', - 'IFEQ' - ]) - } + assert bytecode.hasSequence([ + 'ALOAD 1', + config.indyEnabled ? 'INVOKEDYNAMIC cast(Ljava/lang/Object;)Z' : 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z' + ]) } - void testGroovyTruthOptimizationWithFinalClass() { + void testGroovyTruthOptimizationForFinalClass() { def bytecode = compile(method:'m', ''' - final class A {} + final class A { + } @groovy.transform.CompileStatic void m(A x) { if (x) { - println 'ok' } } ''') - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'IFNULL', + assert bytecode.hasSequence([ + 'ALOAD 1', + 'IFNULL L1', + 'ICONST_1', + 'GOTO L2', + 'L1', + 'ICONST_0', + 'L2', + 'IFEQ L3' ]) + if (config.indyEnabled) + assert !bytecode.hasSequence(['INVOKEDYNAMIC cast(LA$B;)Z']) } - void testGroovyTruthOptimizationWithPrivateInnerClass() { + void testGroovyTruthOptimizationForPrivateInnerClass() { def bytecode = compile(method:'m', ''' class A { - private static class B {} + private static class B { + } @groovy.transform.CompileStatic void m(B x) { if (x) { - println 'ok' } } } ''') - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'IFNULL', + assert bytecode.hasSequence([ + 'ALOAD 1', + 'IFNULL L1', + 'ICONST_1', + 'GOTO L2', + 'L1', + 'ICONST_0', + 'L2', + 'IFEQ L3' ]) + if (config.indyEnabled) + assert !bytecode.hasSequence(['INVOKEDYNAMIC cast(LA$B;)Z']) } - void testGroovyTruthOptimizationWithPublicInnerClass() { + void testNoGroovyTruthOptimizationForPublicInnerClass() { def bytecode = compile(method:'m', ''' class A { - public static class B {} + public static class B { + } @groovy.transform.CompileStatic void m(B x) { if (x) { - println 'ok' } } } ''') - if (config.indyEnabled) { - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'INVOKEDYNAMIC cast(LA$B;)Z', - '', - '', - '', - '', - '', - ']', - 'IFEQ' - ]) - } else { - assert bytecode.hasStrictSequence([ - 'ALOAD 1', - 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z', - 'IFEQ' - ]) - } + assert bytecode.hasSequence([ + 'ALOAD 1', + config.indyEnabled ? 'INVOKEDYNAMIC cast(LA$B;)Z' : 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z' + ]) } - void testCompare() { - def bytecode=compile(method:'stat', ''' - class Doc {} - + // GROOVY-10711 + void testNoGroovyTruthOptimizationIfProvidesAsBoolean() { + def bytecode = compile(method:'m', ''' @groovy.transform.CompileStatic - class A { - static void foo() { - Doc doc = null - def cl = { if (doc) { 1 } else { 0 } } - assert cl() == 0 + @groovy.transform.Immutable + class C { + boolean asBoolean() { } } - A.foo() - + @groovy.transform.CompileStatic + void m(C x) { + if (!x) { + } + } ''') - clazz.main() + assert bytecode.hasSequence([ + 'ALOAD 1', + config.indyEnabled ? 'INVOKEDYNAMIC cast(LC;)Z' : 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z' + ]) } + void testCompare() { + assertScript ''' + class Pogo { + } + @groovy.transform.CompileStatic + class C { + static test() { + Pogo pogo = null + def check = { -> if (pogo) { 1 } else { 0 } } + assert check() == 0 + } + } + + C.test() + ''' + } }