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