This is an automated email from the ASF dual-hosted git repository.
paulk 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 165bfd12f1 GROOVY-11893: Standardise hooks for Groovy pretty printing
165bfd12f1 is described below
commit 165bfd12f1b2c25250378fe90413d268a692c32b
Author: Paul King <[email protected]>
AuthorDate: Wed Apr 1 00:57:19 2026 +1000
GROOVY-11893: Standardise hooks for Groovy pretty printing
---
.../lang/IncorrectClosureArgumentsException.java | 4 +-
src/main/java/groovy/lang/MetaClassImpl.java | 4 +-
src/main/java/groovy/lang/MetaMethod.java | 6 +-
.../groovy/reflection/CachedConstructor.java | 2 +-
.../groovy/runtime/ArrayGroovyMethods.java | 136 +++++++++++++++++++++
.../groovy/runtime/DefaultGroovyMethods.java | 95 ++++++++++++++
.../org/codehaus/groovy/runtime/FormatHelper.java | 123 +++++++++++--------
.../groovy/runtime/FormatHelperTest.groovy | 71 +++++++++++
.../org/apache/groovy/macrolib/MacroLibTest.groovy | 8 +-
9 files changed, 385 insertions(+), 64 deletions(-)
diff --git a/src/main/java/groovy/lang/IncorrectClosureArgumentsException.java
b/src/main/java/groovy/lang/IncorrectClosureArgumentsException.java
index ad09235ecb..dcb0a9d06f 100644
--- a/src/main/java/groovy/lang/IncorrectClosureArgumentsException.java
+++ b/src/main/java/groovy/lang/IncorrectClosureArgumentsException.java
@@ -38,9 +38,9 @@ public class IncorrectClosureArgumentsException extends
GroovyRuntimeException {
"Incorrect arguments to closure: "
+ closure
+ ". Expected: "
- + FormatHelper.toString(expected)
+ + FormatHelper.toArrayString(expected)
+ ", actual: "
- + FormatHelper.toString(arguments));
+ + FormatHelper.toString(arguments)); // arguments is Object,
not array
this.closure = closure;
this.arguments = arguments;
this.expected = expected;
diff --git a/src/main/java/groovy/lang/MetaClassImpl.java
b/src/main/java/groovy/lang/MetaClassImpl.java
index 0a2853ef97..3afaf40f47 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -3344,11 +3344,11 @@ public class MetaClassImpl implements MetaClass,
MutableMetaClass {
StringBuilder msg = new StringBuilder("Ambiguous method overloading
for method ");
msg.append(theClassName).append("#").append(name)
.append(".\nCannot resolve which method to invoke for ")
- .append(FormatHelper.toString(arguments))
+ .append(FormatHelper.toArrayString(arguments))
.append(" due to overlapping prototypes between:");
for (final Object match : matches) {
CachedClass[] types = ((ParameterTypes) match).getParameterTypes();
- msg.append("\n\t").append(FormatHelper.toString(types));
+ msg.append("\n\t").append(FormatHelper.toArrayString(types));
}
return msg.toString();
}
diff --git a/src/main/java/groovy/lang/MetaMethod.java
b/src/main/java/groovy/lang/MetaMethod.java
index e8d8cb6849..99f92a9da4 100644
--- a/src/main/java/groovy/lang/MetaMethod.java
+++ b/src/main/java/groovy/lang/MetaMethod.java
@@ -94,9 +94,9 @@ public abstract class MetaMethod extends ParameterTypes
implements MetaMember, C
"Parameters to method: "
+ getName()
+ " do not match types: "
- + FormatHelper.toString(getParameterTypes())
+ + FormatHelper.toArrayString(getParameterTypes())
+ " for arguments: "
- + FormatHelper.toString(arguments));
+ + FormatHelper.toArrayString(arguments));
}
}
@@ -143,7 +143,7 @@ public abstract class MetaMethod extends ParameterTypes
implements MetaMember, C
+ "[name: "
+ getName()
+ " params: "
- + FormatHelper.toString(getParameterTypes())
+ + FormatHelper.toArrayString(getParameterTypes())
+ " returns: "
+ getReturnType()
+ " owner: "
diff --git
a/src/main/java/org/codehaus/groovy/reflection/CachedConstructor.java
b/src/main/java/org/codehaus/groovy/reflection/CachedConstructor.java
index 6d23d52394..ff9021e6d4 100644
--- a/src/main/java/org/codehaus/groovy/reflection/CachedConstructor.java
+++ b/src/main/java/org/codehaus/groovy/reflection/CachedConstructor.java
@@ -112,7 +112,7 @@ public class CachedConstructor extends ParameterTypes
implements MetaMember {
init
+ constructor
+ " with arguments: "
- + FormatHelper.toString(argumentArray)
+ + FormatHelper.toArrayString(argumentArray)
+ " reason: "
+ e,
setReason ? e : null);
diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
index 15690cfd2c..b734bff5d7 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
@@ -4429,6 +4429,142 @@ public class ArrayGroovyMethods extends
DefaultGroovyMethodsSupport {
return DefaultGroovyMethods.groupByMany(Arrays.asList(self), valueFn,
keyFn);
}
+
//--------------------------------------------------------------------------
+ // groovyToString
+
+ /**
+ * Returns Groovy's list-like string representation for an Object array.
+ * This is used by Groovy's formatting infrastructure (e.g., GString
interpolation,
+ * {@code println}, assert messages). By default, it delegates to
+ * {@link FormatHelper#toArrayString(Object[])}.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(Object[])},
+ * the JDK's default array {@code toString()} is used instead.
+ * Alternatively, you have the option to provide a replacement extension
method in an extension module
+ * to customize how arrays are displayed throughout Groovy.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(Object[] self) {
+ return FormatHelper.toArrayString(self);
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for a boolean array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(boolean[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(boolean[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for a byte array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(byte[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(byte[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
+ /**
+ * Returns Groovy's string representation for a char array.
+ * By default, a char array is rendered as a String (e.g. {@code
'abc'.chars}
+ * displays as {@code abc}).
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(char[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(char[] self) {
+ return new String(self);
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for a short array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(short[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(short[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for an int array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(int[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(int[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for a long array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(long[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(long[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for a float array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(float[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(float[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
+ /**
+ * Returns Groovy's list-like string representation for a double array.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(double[])},
+ * the JDK's default array {@code toString()} is used instead.
+ *
+ * @param self the array to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(double[] self) {
+ return
FormatHelper.toListString(DefaultTypeTransformation.primitiveArrayToList(self));
+ }
+
//--------------------------------------------------------------------------
// head
diff --git
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 0044eaf6b6..d52bbbb223 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -8139,6 +8139,101 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
return answer;
}
+
//--------------------------------------------------------------------------
+ // groovyToString
+
+ /**
+ * Returns Groovy's default string representation for a Map.
+ * This is used by Groovy's formatting infrastructure (e.g., GString
interpolation,
+ * {@code println}, assert messages). By default, it delegates to
+ * {@link FormatHelper#toMapString(Map)}.
+ * <p>
+ * <pre class="groovyTestCase">
+ * assert [a:1, b:2].groovyToString() == '[a:1, b:2]'
+ * </pre>
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(Map)},
+ * the normal map {@code toString()} is used instead.
+ * Alternatively, you have the option to provide a replacement extension
method in an extension module
+ * to customize how maps are displayed throughout Groovy.
+ *
+ * @param self the Map to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(Map self) {
+ return FormatHelper.toMapString(self);
+ }
+
+ /**
+ * Returns Groovy's default string representation for a Range.
+ * By default, it delegates to {@link Range#toString()},
+ * producing the compact {@code from..to} notation.
+ * <p>
+ * <pre class="groovyTestCase">
+ * assert (1..4).groovyToString() == '1..4'
+ * </pre>
+ * <p>
+ * This method exists to stop the {@code groovyToString(Collection)}
variant
+ * from overriding the built-in {@code Range#toString}.
+ * Since a range is a list, you can use {@code range.toListString()}
+ * to print it using normal list formatting.
+ *
+ * @param self the Range to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(Range self) {
+ return self.toString();
+ }
+
+ /**
+ * Returns Groovy's default string representation for a Collection.
+ * This is used by Groovy's formatting infrastructure (e.g., GString
interpolation,
+ * {@code println}, assert messages). By default, it delegates to
+ * {@link FormatHelper#toListString(Collection)}.
+ * <p>
+ * <pre class="groovyTestCase">
+ * assert [1, 2, 3].groovyToString() == '[1, 2, 3]'
+ * </pre>
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(Collection)},
+ * the normal list {@code toString()} is used instead.
+ * Alternatively, you have the option to provide a replacement extension
method in an extension module
+ * to customize how collections are displayed throughout Groovy.
+ *
+ * @param self the Collection to format
+ * @return the string representation
+ * @since 6.0.0
+ */
+ public static String groovyToString(Collection self) {
+ return FormatHelper.toListString(self);
+ }
+
+ /**
+ * Returns Groovy's default string representation for an XML Element.
+ * This is used by Groovy's formatting infrastructure. By default, it
+ * serializes the element using {@code
groovy.xml.XmlUtil.serialize(Element)}.
+ * <p>
+ * If disabled, e.g. via {@code
-Dgroovy.extension.disable=groovyToString(Element)},
+ * the element's default {@code toString()} is used instead.
+ * Alternatively, you have the option to provide a replacement extension
method in an extension module
+ * to customize how elements are displayed throughout Groovy.
+ *
+ * @param self the Element to format
+ * @return the serialized XML string
+ * @since 6.0.0
+ */
+ public static String groovyToString(org.w3c.dom.Element self) {
+ try {
+ java.lang.reflect.Method serialize =
Class.forName("groovy.xml.XmlUtil")
+ .getMethod("serialize", org.w3c.dom.Element.class);
+ return (String) serialize.invoke(null, self);
+ } catch (Exception e) {
+ return self.toString();
+ }
+ }
+
//--------------------------------------------------------------------------
// hasProperty
diff --git a/src/main/java/org/codehaus/groovy/runtime/FormatHelper.java
b/src/main/java/org/codehaus/groovy/runtime/FormatHelper.java
index 30f8d7d3a4..6ed703c0e0 100644
--- a/src/main/java/org/codehaus/groovy/runtime/FormatHelper.java
+++ b/src/main/java/org/codehaus/groovy/runtime/FormatHelper.java
@@ -21,6 +21,7 @@ package org.codehaus.groovy.runtime;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClassRegistry;
+import groovy.lang.MetaMethod;
import groovy.lang.Range;
import groovy.lang.Writable;
import groovy.transform.NamedParam;
@@ -28,15 +29,12 @@ import groovy.transform.NamedParams;
import org.apache.groovy.io.StringBuilderWriter;
import org.codehaus.groovy.control.ResolveVisitor;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
-import org.w3c.dom.Element;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
@@ -61,8 +59,25 @@ public class FormatHelper {
private static final int ITEM_ALLOCATE_SIZE = 5;
public static final MetaClassRegistry metaRegistry =
GroovySystem.getMetaClassRegistry();
- private static final String XMLUTIL_CLASS_FULL_NAME = "groovy.xml.XmlUtil";
- private static final String SERIALIZE_METHOD_NAME = "serialize";
+ private static final String GROOVY_TO_STRING = "groovyToString";
+ private static final Class[] EMPTY_TYPES = {};
+
+ /**
+ * Attempts to invoke a {@code groovyToString()} extension method on the
given object
+ * via the metaclass. Returns the result if found, or {@code null} if no
such method exists.
+ */
+ static String tryGroovyToString(Object object) {
+ if (object == null) return null;
+ try {
+ MetaMethod method =
InvokerHelper.getMetaClass(object).getMetaMethod(GROOVY_TO_STRING, EMPTY_TYPES);
+ if (method != null) {
+ return (String) method.invoke(object, EMPTY_ARGS);
+ }
+ } catch (ClassCastException | GroovyRuntimeException ignore) {
+ // method found but not applicable to this object's actual type
+ }
+ return null;
+ }
static final Set<String> DEFAULT_IMPORT_PKGS = new HashSet<>();
static final Set<String> DEFAULT_IMPORT_CLASSES = new HashSet<>();
@@ -152,41 +167,43 @@ public class FormatHelper {
}
if (arguments.getClass().isArray()) {
if (arguments instanceof Object[]) {
+ if (!inspect && !escapeBackslashes && maxSize == -1 && !safe) {
+ String result = tryGroovyToString(arguments);
+ return result != null ? result : arguments.toString();
+ }
return toArrayString((Object[]) arguments, inspect,
escapeBackslashes, maxSize, safe);
}
+ if (!inspect && !escapeBackslashes && maxSize == -1 && !safe) {
+ // char[] and primitive arrays — delegate to groovyToString if
available
+ String result = tryGroovyToString(arguments);
+ return result != null ? result : arguments.toString();
+ }
if (arguments instanceof char[]) {
return new String((char[]) arguments);
}
// other primitives
return
formatCollection(DefaultTypeTransformation.arrayAsCollection(arguments),
inspect, escapeBackslashes, maxSize, safe);
}
- if (arguments instanceof Range range) {
- try {
- if (inspect) {
- return range.inspect();
- } else {
- return range.toString();
+ // When inspect/maxSize/safe are active, use the hardcoded formatters
for
+ // Range, Collection, and Map (they support those parameters).
Otherwise, the
+ // groovyToString check below will handle them.
+ if (inspect || escapeBackslashes || maxSize != -1 || safe) {
+ if (arguments instanceof Range range) {
+ try {
+ return inspect ? range.inspect() : range.toString();
+ } catch (RuntimeException ex) {
+ if (!safe) throw ex;
+ return handleFormattingException(arguments, ex);
+ } catch (Exception ex) {
+ if (!safe) throw new GroovyRuntimeException(ex);
+ return handleFormattingException(arguments, ex);
}
- } catch (RuntimeException ex) {
- if (!safe) throw ex;
- return handleFormattingException(arguments, ex);
- } catch (Exception ex) {
- if (!safe) throw new GroovyRuntimeException(ex);
- return handleFormattingException(arguments, ex);
}
- }
- if (arguments instanceof Collection) {
- return formatCollection((Collection) arguments, inspect,
escapeBackslashes, maxSize, safe);
- }
- if (arguments instanceof Map) {
- return formatMap((Map) arguments, inspect, escapeBackslashes,
maxSize, safe);
- }
- if (arguments instanceof Element) {
- try {
- Method serialize =
Class.forName(XMLUTIL_CLASS_FULL_NAME).getMethod(SERIALIZE_METHOD_NAME,
Element.class);
- return (String) serialize.invoke(null, arguments);
- } catch (ClassNotFoundException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException e) {
- throw new RuntimeException(e);
+ if (arguments instanceof Collection) {
+ return formatCollection((Collection) arguments, inspect,
escapeBackslashes, maxSize, safe);
+ }
+ if (arguments instanceof Map) {
+ return formatMap((Map) arguments, inspect, escapeBackslashes,
maxSize, safe);
}
}
if (arguments instanceof CharSequence) {
@@ -198,9 +215,13 @@ public class FormatHelper {
if (!inspect) return arg;
return !escapeBackslashes && multiline(arg) ? "\"\"\"" + arg +
"\"\"\"" : DQ + arg.replace(DQ, "\\\"") + DQ;
}
+ // Check for a groovyToString() extension method. By default, DGM
methods
+ // provide Groovy's custom formatting for Map, Collection, and
Object[].
+ // Users can add groovyToString for any type via extension modules, or
+ // disable it with -Dgroovy.extension.disable=groovyToString.
+ String groovyStr = tryGroovyToString(arguments);
+ if (groovyStr != null) return groovyStr;
try {
- // TODO: For GROOVY-2599 do we need something like below but it
breaks other things
-// return (String) invokeMethod(arguments, "toString", EMPTY_ARGS);
return arguments.toString();
} catch (RuntimeException ex) {
if (!safe) throw ex;
@@ -482,14 +503,6 @@ public class FormatHelper {
public static void write(Writer out, Object object) throws IOException {
if (object instanceof String) {
out.write((String) object);
- } else if (object instanceof Object[]) {
- out.write(toArrayString((Object[]) object));
- } else if (object instanceof Map) {
- out.write(toMapString((Map) object));
- } else if (object instanceof Collection) {
- out.write(toListString((Collection) object));
- } else if (object instanceof Writable writable) {
- writable.writeTo(out);
} else if (object instanceof InputStream || object instanceof Reader) {
// Copy stream to stream
Reader reader;
@@ -506,7 +519,14 @@ public class FormatHelper {
}
}
} else {
- out.write(toString(object));
+ String result = tryGroovyToString(object);
+ if (result != null) {
+ out.write(result);
+ } else if (object instanceof Writable writable) {
+ writable.writeTo(out);
+ } else {
+ out.write(toString(object));
+ }
}
}
@@ -516,16 +536,6 @@ public class FormatHelper {
public static void append(Appendable out, Object object) throws
IOException {
if (object instanceof String) {
out.append((String) object);
- } else if (object instanceof Object[]) {
- out.append(toArrayString((Object[]) object));
- } else if (object instanceof Map) {
- out.append(toMapString((Map) object));
- } else if (object instanceof Collection) {
- out.append(toListString((Collection) object));
- } else if (object instanceof Writable writable) {
- Writer stringWriter = new StringBuilderWriter();
- writable.writeTo(stringWriter);
- out.append(stringWriter.toString());
} else if (object instanceof InputStream || object instanceof Reader) {
// Copy stream to stream
try (Reader reader =
@@ -540,7 +550,16 @@ public class FormatHelper {
}
}
} else {
- out.append(toString(object));
+ String result = tryGroovyToString(object);
+ if (result != null) {
+ out.append(result);
+ } else if (object instanceof Writable writable) {
+ Writer stringWriter = new StringBuilderWriter();
+ writable.writeTo(stringWriter);
+ out.append(stringWriter.toString());
+ } else {
+ out.append(toString(object));
+ }
}
}
}
diff --git
a/src/test/groovy/org/codehaus/groovy/runtime/FormatHelperTest.groovy
b/src/test/groovy/org/codehaus/groovy/runtime/FormatHelperTest.groovy
index ca9e3c1c55..ba3dade169 100644
--- a/src/test/groovy/org/codehaus/groovy/runtime/FormatHelperTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/runtime/FormatHelperTest.groovy
@@ -364,4 +364,75 @@ class FormatHelperTest {
void testMetaRegistryNotNull() {
assertNotNull(FormatHelper.metaRegistry)
}
+
+ // -- groovyToString tests (GROOVY-11893) --
+
+ static class Foo {
+ String toString() { 'some foo' }
+ String groovyToString() { 'some bar' }
+ }
+
+ @Test
+ void groovyToStringUsedInMapFormatting() {
+ assert [foo: new Foo()].toString() == '[foo:some bar]'
+ }
+
+ @Test
+ void groovyToStringUsedInListFormatting() {
+ assert [new Foo()].toString() == '[some bar]'
+ }
+
+ @Test
+ void groovyToStringUsedInInterpolation() {
+ def f = new Foo()
+ assert "$f" == 'some bar'
+ }
+
+ @Test
+ void groovyToStringUsedByFormatHelper() {
+ assert FormatHelper.toString(new Foo()) == 'some bar'
+ }
+
+ static class BadFoo {
+ Date groovyToString() { new Date() }
+ String toString() { 'fallback foo' }
+ }
+
+ @Test
+ void groovyToStringWrongReturnTypeFallsBack() {
+ assert FormatHelper.toString(new BadFoo()) == 'fallback foo'
+ }
+
+ @Test
+ void groovyToStringDisabledViaForkedJvm() {
+ def groovyHome = System.getProperty('groovy.home') ?:
System.env.GROOVY_HOME
+ // Find java executable
+ def javaHome = System.getProperty('java.home')
+ def java = new File(javaHome, 'bin/java').absolutePath
+ // Build classpath from current test classpath
+ def cp = System.getProperty('java.class.path')
+
+ def script = '''
+ int[] arr = [1, 2, 3]
+ // With groovyToString disabled for int[], should fall back to
Java's toString
+ // which produces something like [I@hashcode rather than [1, 2, 3]
+ print(arr.toString().startsWith('[1'))
+ '''
+
+ def scriptFile = File.createTempFile('groovyToStringTest', '.groovy')
+ scriptFile.deleteOnExit()
+ scriptFile.text = script
+
+ def pb = new ProcessBuilder(java, '-cp', cp,
+ '-Dgroovy.extension.disable=groovyToString(int[])',
+ 'groovy.ui.GroovyMain', scriptFile.absolutePath)
+ pb.redirectErrorStream(true)
+ def process = pb.start()
+ def output = process.inputStream.text.trim()
+ def exitCode = process.waitFor()
+
+ // With groovyToString disabled, int[].toString() should NOT produce
[1, 2, 3]
+ assert output == 'false', "Expected groovyToString to be disabled, but
got: $output"
+ assert exitCode == 0
+ }
}
diff --git
a/subprojects/groovy-macro-library/src/test/groovy/org/apache/groovy/macrolib/MacroLibTest.groovy
b/subprojects/groovy-macro-library/src/test/groovy/org/apache/groovy/macrolib/MacroLibTest.groovy
index 95e110ab92..2f67ecc1e5 100644
---
a/subprojects/groovy-macro-library/src/test/groovy/org/apache/groovy/macrolib/MacroLibTest.groovy
+++
b/subprojects/groovy-macro-library/src/test/groovy/org/apache/groovy/macrolib/MacroLibTest.groovy
@@ -34,7 +34,7 @@ final class MacroLibTest {
@Test
void testSV() {
assertScript BASE + '''\
- assert SV(num, list, range, string).toString() == 'num=42,
list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo'
+ assert SV(num, list, range, string).toString() == 'num=42,
list=[1, 2, 3], range=0..5, string=foo'
'''
}
@@ -44,14 +44,14 @@ final class MacroLibTest {
def cl = {
SV(num, list, range, string).toString()
}
- assert cl().toString() == 'num=42, list=[1, 2, 3], range=[0, 1, 2,
3, 4, 5], string=foo'
+ assert cl().toString() == 'num=42, list=[1, 2, 3], range=0..5,
string=foo'
'''
}
@Test
void testList() {
assertScript BASE + '''\
- assert [SV(num, list), SV(range, string)].toString() == '[num=42,
list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo]'
+ assert [SV(num, list), SV(range, string)].toString() == '[num=42,
list=[1, 2, 3], range=0..5, string=foo]'
'''
}
@@ -59,7 +59,7 @@ final class MacroLibTest {
void testSVInclude() {
assertScript BASE + '''\
def numSV = SV(num)
- assert SV(numSV, list, range, string).toString() == 'numSV=num=42,
list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo'
+ assert SV(numSV, list, range, string).toString() == 'numSV=num=42,
list=[1, 2, 3], range=0..5, string=foo'
'''
}