You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/02/17 06:49:10 UTC

[groovy] 01/01: GROOVY-9946: Add `@Pure` to mark constant result of method

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY-9946
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 917b6c95c36ecf7805d78b7a7c36f688f56826b6
Author: Daniel Sun <su...@apache.org>
AuthorDate: Wed Feb 17 14:48:44 2021 +0800

    GROOVY-9946: Add `@Pure` to mark constant result of method
---
 src/main/java/groovy/transform/Pure.java           | 35 ++++++++++++++++++++++
 .../org/codehaus/groovy/runtime/GStringImpl.java   | 19 ++++++++++--
 src/test/groovy/GStringTest.groovy                 | 31 +++++++++++++++++++
 3 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/src/main/java/groovy/transform/Pure.java b/src/main/java/groovy/transform/Pure.java
new file mode 100644
index 0000000..7827343
--- /dev/null
+++ b/src/main/java/groovy/transform/Pure.java
@@ -0,0 +1,35 @@
+/*
+ *  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 groovy.transform;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark the return value of "pure" method only comes from expressions involving constants or other pure methods
+ *
+ * @since 4.0.0
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Pure {
+}
diff --git a/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java b/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java
index 32930e7..9e90a17 100644
--- a/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java
+++ b/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java
@@ -20,10 +20,12 @@ package org.codehaus.groovy.runtime;
 
 import groovy.lang.GString;
 import groovy.lang.GroovyObject;
+import groovy.transform.Pure;
 import org.apache.groovy.ast.tools.ImmutablePropertyUtils;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.lang.reflect.Method;
 import java.nio.charset.Charset;
 import java.util.Locale;
 
@@ -56,7 +58,7 @@ public class GStringImpl extends GString {
      * @param strings the string parts
      */
     public GStringImpl(Object[] values, String[] strings) {
-        this(values, strings, checkValuesImmutable(values), null, false);
+        this(values, strings, checkValuesStringConstant(values), null, false);
     }
 
     /**
@@ -351,10 +353,11 @@ public class GStringImpl extends GString {
         return str;
     }
 
-    private static boolean checkValuesImmutable(Object[] values) {
+    private static boolean checkValuesStringConstant(Object[] values) {
         for (Object value : values) {
             if (null == value) continue;
-            if (!(ImmutablePropertyUtils.builtinOrMarkedImmutableClass(value.getClass())
+            if (!(toStringMarkedPure(value.getClass())
+                    || ImmutablePropertyUtils.builtinOrMarkedImmutableClass(value.getClass())
                     || (value instanceof GStringImpl && ((GStringImpl) value).cacheable))) {
                 return false;
             }
@@ -362,4 +365,14 @@ public class GStringImpl extends GString {
 
         return true;
     }
+
+    private static boolean toStringMarkedPure(Class<?> clazz) {
+        Method toStringMethod;
+        try {
+            toStringMethod = clazz.getMethod("toString");
+        } catch (NoSuchMethodException | SecurityException e) {
+            return false;
+        }
+        return toStringMethod.isAnnotationPresent(Pure.class);
+    }
 }
diff --git a/src/test/groovy/GStringTest.groovy b/src/test/groovy/GStringTest.groovy
index c6c5c31..701a4b7 100644
--- a/src/test/groovy/GStringTest.groovy
+++ b/src/test/groovy/GStringTest.groovy
@@ -19,6 +19,7 @@
 package groovy
 
 import groovy.test.GroovyTestCase
+import groovy.transform.Pure
 
 class GStringTest extends GroovyTestCase {
 
@@ -646,6 +647,10 @@ class GStringTest extends GroovyTestCase {
         def gstr14 = "a${new JcipImmutableValue()}b"
         assert 'a234b' == gstr14
         assert gstr14.toString() === gstr14.toString()
+
+        def gstr15 = "a${new PureValue()}b"
+        assert 'a345b' == gstr15
+        assert gstr15.toString() === gstr15.toString()
     }
 
     void testGStringLiteral2() {
@@ -669,6 +674,27 @@ class GStringTest extends GroovyTestCase {
         '''
     }
 
+    // GROOVY-9946
+    void testGStringLiteral3() {
+        assertScript '''
+            final class Test {
+                private static final class Item {
+                    private int toStringInvocationCount = 0
+                    @Override
+                    @groovy.transform.Pure
+                    synchronized String toString() {
+                        toStringInvocationCount++
+                        return "item"
+                    }
+                }
+                static void main(String[] args) {
+                    Item item = new Item()
+                    new StringBuilder().append("item: ${item}")
+                    assert 1 == item.toStringInvocationCount
+                }
+            }
+        '''
+    }
 
     @groovy.transform.Immutable
     static class GroovyImmutableValue {
@@ -682,4 +708,9 @@ class GStringTest extends GroovyTestCase {
         String toString() { v }
     }
 
+    static final class PureValue {
+        private final String v = '345'
+        @Pure
+        String toString() { v }
+    }
 }