Backported more 2.3.20 compatibility tests
diff --git a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
index b9c6bab..a041ac3 100644
--- a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
@@ -80,6 +80,7 @@
 import freemarker.ext.beans.ResourceBundleModel;
 import freemarker.ext.dom.NodeModel;
 import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
 import freemarker.template.SimpleCollection;
 import freemarker.template.SimpleDate;
 import freemarker.template.SimpleNumber;
@@ -367,6 +368,7 @@
 
         else if (testName.startsWith("overloaded-methods-2-")) {
             dataModel.put("obj", new OverloadedMethods2());
+            dataModel.put("dow", Boolean.valueOf(conf.getObjectWrapper() instanceof DefaultObjectWrapper));
         }
         
         else if (testName.startsWith("overloaded-methods-")) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/AllTemplateModels.java b/src/test/java/freemarker/test/templatesuite/models/AllTemplateModels.java
new file mode 100644
index 0000000..7a7e650
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/AllTemplateModels.java
@@ -0,0 +1,94 @@
+package freemarker.test.templatesuite.models;
+
+import java.util.Date;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateDateModel;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.TemplateScalarModel;
+import freemarker.template.TemplateSequenceModel;
+
+/**
+ * Implements all template models that are interesting when calling overloaded Java methods.
+ */
+public class AllTemplateModels implements
+        TemplateScalarModel, TemplateNumberModel, TemplateDateModel, TemplateBooleanModel,
+        TemplateHashModelEx, TemplateSequenceModel, TemplateCollectionModel {
+
+    public static final AllTemplateModels INSTANCE = new AllTemplateModels();
+    
+    private final TemplateModelIterator EMPTY_ITERATOR = new TemplateModelIterator() {
+
+        public TemplateModel next() throws TemplateModelException {
+            return null;
+        }
+
+        public boolean hasNext() throws TemplateModelException {
+            return false;
+        }
+        
+    };
+    
+    private final TemplateCollectionModel EMPTY_COLLECTION = new TemplateCollectionModel() {
+
+        public TemplateModelIterator iterator() throws TemplateModelException {
+            return EMPTY_ITERATOR;
+        }
+    };
+    
+    public TemplateModel get(String key) throws TemplateModelException {
+        return new SimpleScalar("value for key " + key);
+    }
+
+    public boolean isEmpty() throws TemplateModelException {
+        return true;
+    }
+
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        return EMPTY_ITERATOR;
+    }
+
+    public TemplateModel get(int index) throws TemplateModelException {
+        return null;
+    }
+
+    public int size() throws TemplateModelException {
+        return 0;
+    }
+
+    public TemplateCollectionModel keys() throws TemplateModelException {
+        return EMPTY_COLLECTION;
+    }
+
+    public TemplateCollectionModel values() throws TemplateModelException {
+        return EMPTY_COLLECTION;
+    }
+
+    public boolean getAsBoolean() throws TemplateModelException {
+        return true;
+    }
+
+    public Date getAsDate() throws TemplateModelException {
+        return new Date(0);
+    }
+
+    public int getDateType() {
+        return TemplateDateModel.DATETIME;
+    }
+
+    @SuppressWarnings("boxing")
+    public Number getAsNumber() throws TemplateModelException {
+        return 1;
+    }
+
+    public String getAsString() throws TemplateModelException {
+        return "s";
+    }
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/BooleanAndScalarModel.java b/src/test/java/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
new file mode 100644
index 0000000..37994f9
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
@@ -0,0 +1,19 @@
+package freemarker.test.templatesuite.models;
+
+import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateScalarModel;
+
+public class BooleanAndScalarModel implements TemplateBooleanModel, TemplateScalarModel {
+
+    public static final BooleanAndScalarModel INSTANCE = new BooleanAndScalarModel();
+
+    public String getAsString() throws TemplateModelException {
+        return "s";
+    }
+
+    public boolean getAsBoolean() throws TemplateModelException {
+        return true;
+    }
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/HashAndScalarModel.java b/src/test/java/freemarker/test/templatesuite/models/HashAndScalarModel.java
new file mode 100644
index 0000000..2ab79af
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/HashAndScalarModel.java
@@ -0,0 +1,56 @@
+package freemarker.test.templatesuite.models;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateScalarModel;
+
+public class HashAndScalarModel implements TemplateHashModelEx, TemplateScalarModel {
+    
+    public static final HashAndScalarModel INSTANCE = new HashAndScalarModel();
+    
+    private final TemplateCollectionModel EMPTY_COLLECTION = new TemplateCollectionModel() {
+
+        public TemplateModelIterator iterator() throws TemplateModelException {
+            return new TemplateModelIterator() {
+
+                public TemplateModel next() throws TemplateModelException {
+                    return null;
+                }
+
+                public boolean hasNext() throws TemplateModelException {
+                    return false;
+                }
+                
+            };
+        }
+    };
+
+    public String getAsString() throws TemplateModelException {
+        return "scalarValue";
+    }
+
+    public TemplateModel get(String key) throws TemplateModelException {
+        return new SimpleScalar("mapValue for " + key);
+    }
+
+    public boolean isEmpty() throws TemplateModelException {
+        return true;
+    }
+
+    public int size() throws TemplateModelException {
+        return 0;
+    }
+
+    public TemplateCollectionModel keys() throws TemplateModelException {
+        return EMPTY_COLLECTION;
+    }
+
+    public TemplateCollectionModel values() throws TemplateModelException {
+        return EMPTY_COLLECTION;
+    }
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java b/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
index 995828e..26c99ab 100644
--- a/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
+++ b/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
@@ -4,14 +4,25 @@
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
+import freemarker.core.Environment;
 import freemarker.ext.beans.RationalNumber;
 import freemarker.ext.util.WrapperTemplateModel;
 import freemarker.template.AdapterTemplateModel;
+import freemarker.template.ObjectWrapper;
 import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.utility.StringUtil;
+import freemarker.test.utility.Helpers;
 
 public class OverloadedMethods2 {
 
@@ -293,107 +304,80 @@
     }
 
     public String varargs1(String s, int... xs) {
-        return "varargs1(String s = " + StringUtil.jQuote(s) + ", int... xs = [" + arrayToString(xs) + "])";
+        return "varargs1(String s = " + StringUtil.jQuote(s) + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs1(String s, double... xs) {
-        return "varargs1(String s = " + StringUtil.jQuote(s) + ", double... xs = [" + arrayToString(xs) + "])";
+        return "varargs1(String s = " + StringUtil.jQuote(s) + ", double... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs1(String s, Object... xs) {
-        return "varargs1(String s = " + StringUtil.jQuote(s) + ", Object... xs = [" + arrayToString(xs) + "])";
+        return "varargs1(String s = " + StringUtil.jQuote(s) + ", Object... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs1(Object s, Object... xs) {
-        return "varargs1(Object s = " + s + ", Object... xs = [" + arrayToString(xs) + "])";
+        return "varargs1(Object s = " + s + ", Object... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs2(int... xs) {
-        return "varargs2(int... xs = [" + arrayToString(xs) + "])";
+        return "varargs2(int... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs2(double... xs) {
-        return "varargs2(double... xs = [" + arrayToString(xs) + "])";
+        return "varargs2(double... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs3(String... xs) {
-        return "varargs3(String... xs = [" + arrayToString(xs) + "])";
+        return "varargs3(String... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs3(Comparable... xs) {
-        return "varargs3(Comparable... xs = [" + arrayToString(xs) + "])";
+        return "varargs3(Comparable... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs3(Object... xs) {
-        return "varargs3(Object... xs = [" + arrayToString(xs) + "])";
+        return "varargs3(Object... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs4(Integer... xs) {
-        return "varargs4(Integer... xs = [" + arrayToString(xs) + "])";
+        return "varargs4(Integer... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs4(int... xs) {
-        return "varargs4(int... xs = [" + arrayToString(xs) + "])";
+        return "varargs4(int... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs5(int... xs) {
-        return "varargs5(int... xs = [" + arrayToString(xs) + "])";
+        return "varargs5(int... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs5(int a1, int... xs) {
-        return "varargs5(int a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+        return "varargs5(int a1 = " + a1 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs5(int a1, int a2, int... xs) {
-        return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = [" + arrayToString(xs) + "])";
+        return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs5(int a1, int a2, int a3, int... xs) {
         return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int a3 = " + a3
-                + ", int... xs = [" + arrayToString(xs) + "])";
+                + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
 
     public String varargs6(String a1, int... xs) {
-        return "varargs6(String a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+        return "varargs6(String a1 = " + a1 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs6(Object a1, int a2, int... xs) {
-        return "varargs6(Object a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = [" + arrayToString(xs) + "])";
+        return "varargs6(Object a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs7(int... xs) {
-        return "varargs7(int... xs = [" + arrayToString(xs) + "])";
+        return "varargs7(int... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String varargs7(short a1, int... xs) {
-        return "varargs7(short a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
-    }
-    
-    private String arrayToString(int[] xs) {
-        StringBuilder sb = new StringBuilder();
-        for (int x : xs) {
-            if (sb.length() != 0) sb.append(", ");
-            sb.append(x);
-        }
-        return sb.toString();
-    }
-
-    private String arrayToString(double[] xs) {
-        StringBuilder sb = new StringBuilder();
-        for (double x : xs) {
-            if (sb.length() != 0) sb.append(", ");
-            sb.append(x);
-        }
-        return sb.toString();
-    }
-
-    private String arrayToString(Object[] xs) {
-        StringBuilder sb = new StringBuilder();
-        for (Object x : xs) {
-            if (sb.length() != 0) sb.append(", ");
-            sb.append(x);
-        }
-        return sb.toString();
+        return "varargs7(short a1 = " + a1 + ", int... xs = " + Helpers.arrayToString(xs) + ")";
     }
     
     public String mNullAmbiguous(String s) {
@@ -429,11 +413,11 @@
     }
     
     public String mVarargsIgnoredTail(int i, double... ds) {
-        return "mVarargsIgnoredTail(int i = " + i + ", double... ds = [" + arrayToString(ds) + "])"; 
+        return "mVarargsIgnoredTail(int i = " + i + ", double... ds = " + Helpers.arrayToString(ds) + ")"; 
     }
     
     public String mVarargsIgnoredTail(int... is) {
-        return "mVarargsIgnoredTail(int... is = [" + arrayToString(is) + "])"; 
+        return "mVarargsIgnoredTail(int... is = " + Helpers.arrayToString(is) + ")"; 
     }
     
     public String mLowRankWins(int x, int y, Object o) {
@@ -471,6 +455,604 @@
         return new File("file");
     }
 
+    public String mSeqToArrayNonOverloaded(String[] items, String s) {
+        return "mSeqToArrayNonOverloaded(String[] " + arrayToString(items) + ", String " + s + ")";
+    }
+    
+    public String mSeqToArrayGoodHint(String[] items, String s) {
+        return "mSeqToArrayGoodHint(String[] " + arrayToString(items) + ", String " + s + ")";
+    }
+
+    public String mSeqToArrayGoodHint(String[] items, int i) {
+        return "mSeqToArrayGoodHint(String[] " + arrayToString(items) + ", int " + i + ")";
+    }
+
+    public String mSeqToArrayGoodHint2(String[] items, String s) {
+        return "mSeqToArrayGoodHint2(String[] " + arrayToString(items) + ", String " + s + ")";
+    }
+
+    public String mSeqToArrayGoodHint2(String item) {
+        return "mSeqToArrayGoodHint2(String " + item + ")";
+    }
+    
+    public String mSeqToArrayPoorHint(String[] items, String s) {
+        return "mSeqToArrayPoorHint(String[] " + arrayToString(items) + ", String " + s + ")";
+    }
+
+    public String mSeqToArrayPoorHint(String item, int i) {
+        return "mSeqToArrayPoorHint(String " + item + ", int " + i + ")";
+    }
+
+    public String mSeqToArrayPoorHint2(String[] items) {
+        return "mSeqToArrayPoorHint2(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mSeqToArrayPoorHint2(String item) {
+        return "mSeqToArrayPoorHint2(String " + item + ")";
+    }
+    
+    public String mSeqToArrayPoorHint3(String[] items) {
+        return "mSeqToArrayPoorHint3(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mSeqToArrayPoorHint3(int[] items) {
+        return "mSeqToArrayPoorHint3(int[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVsListPreference(String[] items) {
+        return "mStringArrayVsListPreference(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVsListPreference(List items) {
+        return "mStringArrayVsListPreference(List " + listToString(items) + ")";
+    }
+
+    public String mStringArrayVsObjectArrayPreference(String[] items) {
+        return "mStringArrayVsObjectArrayPreference(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVsObjectArrayPreference(Object[] items) {
+        return "mStringArrayVsObjectArrayPreference(Object[] " + arrayToString(items) + ")";
+    }
+
+    public String mIntArrayVsIntegerArrayPreference(int[] items) {
+        return "mIntArrayVsIntegerArrayPreference(int[] " + arrayToString(items) + ")";
+    }
+
+    public String mIntArrayVsIntegerArrayPreference(Integer[] items) {
+        return "mIntArrayVsIntegerArrayPreference(Integer[] " + arrayToString(items) + ")";
+    }
+    
+    public String mIntArrayNonOverloaded(int[] items) {
+        return "mIntArrayNonOverloaded(int[] " + arrayToString(items) + ")";
+    }
+
+    public String mIntegerArrayNonOverloaded(Integer[] items) {
+        return "mIntegerArrayNonOverloaded(Integer[] " + arrayToString(items) + ")";
+    }
+
+    public String mIntegerListNonOverloaded(List<Integer> items) {
+        return "mIntegerListNonOverloaded(List<Integer> " + items + ")";
+    }
+
+    public String mStringListNonOverloaded(List<String> items) {
+        return "mStringListNonOverloaded(List<String> " + items + ")";
+    }
+
+    public String mStringArrayNonOverloaded(String[] items) {
+        return "mStringArrayNonOverloaded(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mObjectListNonOverloaded(List<Object> items) {
+        return "mObjectListNonOverloaded(List<Object> " + items + ")";
+    }
+
+    public String mObjectArrayNonOverloaded(Object[] items) {
+        return "mObjectArrayNonOverloaded(Object[] " + arrayToString(items) + ")";
+    }
+
+    public String mIntegerArrayOverloaded(Integer[] items, int i) {
+        return "mIntegerArrayOverloaded(Integer[] " + arrayToString(items) + ", int " + i + ")";
+    }
+
+    public String mIntegerArrayOverloaded(Object obj, boolean b) {
+        return "mIntegerArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+    }
+
+    public String mStringArrayOverloaded(String[] items, int i) {
+        return "mStringArrayOverloaded(String[] " + arrayToString(items) + ", int " + i + ")";
+    }
+
+    public String mStringArrayOverloaded(Object obj, boolean b) {
+        return "mStringArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+    }
+
+    public String mCharArrayOverloaded(char[] items, int i) {
+        return "mCharArrayOverloaded(char[] " + arrayToString(items) + ", int " + i + ")";
+    }
+
+    public String mCharArrayOverloaded(Character[] items, String s) {
+        return "mCharArrayOverloaded(Character[] " + arrayToString(items) + ", String " + s + ")";
+    }
+    
+    public String mCharArrayOverloaded(Object obj, boolean b) {
+        return "mCharArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+    }
+
+    public String mStringArrayArrayOverloaded(String[][] arrayArray, int i) {
+        return "mStringArrayArrayOverloaded(String[][] " + arrayToString(arrayArray) + ", int " + i + ")";
+    }
+    
+    public String mStringArrayArrayOverloaded(Object obj, boolean b) {
+        return "mStringArrayArrayOverloaded(Object " + obj + ", boolean " + b + ")";
+    }
+    
+    public String mIntArrayArrayOverloaded(int[][] xss) {
+        return "mIntArrayArrayOverloaded(" + arrayToString(xss) + ")";
+    }
+
+    public String mArrayOfListsOverloaded(List[] xss) {
+        return "mArrayOfListsOverloaded(" + arrayToString(xss) + ")";
+    }
+
+    public String mArrayOfListsOverloaded(String x) {
+        return "mArrayOfListsOverloaded(" + x + ")";
+    }
+    
+    public String mIntArrayArrayOverloaded(String s) {
+        return "mIntArrayArrayOverloaded(" + s + ")";
+    }
+    
+    public String mStringArrayVarargsNonOverloaded(String... items) {
+        return "mStringArrayVarargsNonOverloaded(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded(String... items) {
+        return "mStringArrayVarargsNonOverloaded(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded1(String... items) {
+        return "mStringArrayVarargsOverloaded1(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded1(List<String> items) {
+        return "mStringArrayVarargsOverloaded1(List " + listToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded2(String... items) {
+        return "mStringArrayVarargsOverloaded2(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded2(String item) {
+        return "mStringArrayVarargsOverloaded2(String " + item + ")";
+    }
+    
+    public String mStringArrayVarargsOverloaded3(String... items) {
+        return "mStringArrayVarargsOverloaded3(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded3(String item1, String item2) {
+        return "mStringArrayVarargsOverloaded3(String " + item1 + ", String " + item2 + ")";
+    }
+    
+    public String mStringArrayVarargsOverloaded4(String... items) {
+        return "mStringArrayVarargsOverloaded4(String[] " + arrayToString(items) + ")";
+    }
+
+    public String mStringArrayVarargsOverloaded4(List... items) {
+        return "mStringArrayVarargsOverloaded4(List[] " + arrayToString(items) + ")";
+    }
+    
+    public String mListOrString(List<String> items) {
+        return "mListOrString(List " + listToString(items) + ")";
+    }
+
+    public String mListOrString(String item) {
+        return "mListOrString(String " + item + ")";
+    }
+
+    public String mListListOrString(List<List<Object>> items) {
+        return "mListListOrString(List " + listToString(items) + ")";
+    }
+
+    public String mListListOrString(String item) {
+        return "mListListOrString(String " + item + ")";
+    }
+    
+    public String mMapOrBoolean(Map v) {
+        return "mMapOrBoolean(Map " + v +")";
+    }
+
+    public String mMapOrBoolean(boolean v) {
+        return "mMapOrBoolean(boolean " + v + ")";
+    }
+
+    public String mMapOrBooleanVarargs(Map... v) {
+        return "mMapOrBooleanVarargs(Map... " + arrayToString(v) +")";
+    }
+
+    public String mMapOrBooleanVarargs(boolean... v) {
+        return "mMapOrBooleanVarargs(boolean... " + arrayToString(v) + ")";
+    }
+
+    public String mMapOrBooleanFixedAndVarargs(Map v) {
+        return "mMapOrBooleanFixedAndVarargs(Map " + v +")";
+    }
+
+    public String mMapOrBooleanFixedAndVarargs(boolean v) {
+        return "mMapOrBooleanFixedAndVarargs(boolean " + v + ")";
+    }
+
+    public String mMapOrBooleanFixedAndVarargs(Map... v) {
+        return "mMapOrBooleanFixedAndVarargs(Map... " + arrayToString(v) +")";
+    }
+
+    public String mMapOrBooleanFixedAndVarargs(boolean... v) {
+        return "mMapOrBooleanFixedAndVarargs(boolean... " + arrayToString(v) + ")";
+    }
+    
+    public String mNumberOrArray(Number v) {
+        return "mNumberOrArray(Number " + v + ")";
+    }
+
+    public String mNumberOrArray(Object[] v) {
+        return "mNumberOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mIntOrArray(int v) {
+        return "mIntOrArray(int " + v + ")";
+    }
+
+    public String mIntOrArray(Object[] v) {
+        return "mIntOrArray(Object[] " + arrayToString(v) + ")";
+    }
+
+    public String mDateOrArray(Date v) {
+        return "mDateOrArray(Date " + v.getTime() + ")";
+    }
+
+    public String mDateOrArray(Object[] v) {
+        return "mDateOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mStringOrArray(String v) {
+        return "mStringOrArray(String " + v + ")";
+    }
+
+    public String mStringOrArray(Object[] v) {
+        return "mStringOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mBooleanOrArray(boolean v) {
+        return "mBooleanOrArray(boolean " + v + ")";
+    }
+
+    public String mBooleanOrArray(Object[] v) {
+        return "mBooleanOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mMapOrArray(Map v) {
+        return "mMapOrArray(Map " + v + ")";
+    }
+
+    public String mMapOrArray(Object[] v) {
+        return "mMapOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mListOrArray(List v) {
+        return "mListOrArray(List " + v + ")";
+    }
+
+    public String mListOrArray(Object[] v) {
+        return "mListOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mSetOrArray(Set v) {
+        return "mSetOrArray(Set " + v + ")";
+    }
+
+    public String mSetOrArray(Object[] v) {
+        return "mSetOrArray(Object[] " + arrayToString(v) + ")";
+    }
+    
+    public String mCharNonOverloaded(char c) {
+        return "mCharNonOverloaded(char " + c + ")";
+    }
+
+    public String mCharacterNonOverloaded(Character c) {
+        return "mCharacterNonOverloaded(Character " + c + ")";
+    }
+    
+    public String mCharOrCharacterOverloaded(char c) {
+        return "mCharOrCharacterOverloaded(char " + c + ")";
+    }
+
+    public String mCharOrCharacterOverloaded(Character c) {
+        return "mCharOrCharacterOverloaded(Character " + c + ")";
+    }
+
+    public String mCharOrBooleanOverloaded(char c) {
+        return "mCharOrBooleanOverloaded(char " + c + ")";
+    }
+
+    public String mCharOrBooleanOverloaded(boolean b) {
+        return "mCharOrBooleanOverloaded(boolean " + b + ")";
+    }
+
+    public String mCharOrStringOverloaded(char c, boolean b) {
+        return "mCharOrStringOverloaded(char " + c + ", boolean " + b + ")";
+    }
+
+    public String mCharOrStringOverloaded(String s, int i) {
+        return "mCharOrStringOverloaded(String " + s + ", int " + i + ")";
+    }
+
+    public String mCharacterOrStringOverloaded(Character c, boolean b) {
+        return "mCharacterOrStringOverloaded(Character " + c + ", boolean " + b + ")";
+    }
+
+    public String mCharacterOrStringOverloaded(String s, int i) {
+        return "mCharacterOrStringOverloaded(String " + s + ", int " + i + ")";
+    }
+    
+    public String mCharOrStringOverloaded2(String s) {
+        return "mCharOrStringOverloaded2(String " + s + ")";
+    }
+
+    public String mCharOrStringOverloaded2(char c) {
+        return "mCharOrStringOverloaded2(char " + c + ")";
+    }
+    
+    public String mCharacterOrStringOverloaded2(String s) {
+        return "mCharacterOrStringOverloaded2(String " + s + ")";
+    }
+
+    public String mCharacterOrStringOverloaded2(Character c) {
+        return "mCharacterOrStringOverloaded2(Character " + c + ")";
+    }
+    
+
+    public String getJavaString() {
+        return "s";
+    }
+    
+    public List getJavaStringList() {
+        List list = new ArrayList();
+        list.add("a");
+        list.add("b");
+        return list;
+    }
+
+    public List getJavaString2List() {
+        List list = new ArrayList();
+        list.add("aa");
+        list.add("bb");
+        return list;
+    }
+
+    public List getJavaStringListList() {
+        List listList = new ArrayList();
+        {
+            List list = new ArrayList();
+            list.add("a");
+            list.add("b");
+            
+            listList.add(list);
+        }
+        {
+            List list = new ArrayList();
+            list.add("c");
+            
+            listList.add(list);
+        }
+        return listList;
+    }
+
+    public List getJavaStringSequenceList() throws TemplateModelException {
+        ObjectWrapper ow = Environment.getCurrentEnvironment().getObjectWrapper();
+        
+        List listList = new ArrayList();
+        {
+            List list = new ArrayList();
+            list.add("a");
+            list.add("b");
+            
+            listList.add(ow.wrap(list));
+        }
+        {
+            List list = new ArrayList();
+            list.add("c");
+            
+            listList.add(ow.wrap(list));
+        }
+        return listList;
+    }
+    
+    public List<int[]> getJavaListOfIntArrays() {
+        List list = new ArrayList();
+        list.add(new int[] {1, 2, 3});
+        list.add(new int[] {});
+        list.add(new int[] {4});
+        return list;
+    }
+    
+    @SuppressWarnings("boxing")
+    public List getJavaIntegerListList() {
+        List listList = new ArrayList();
+        {
+            List list = new ArrayList();
+            list.add(1);
+            list.add(2);
+            
+            listList.add(list);
+        }
+        {
+            List list = new ArrayList();
+            list.add(3);
+            
+            listList.add(list);
+        }
+        return listList;
+    }
+    
+    @SuppressWarnings("boxing")
+    public List<Integer> getJavaIntegerList() {
+        List<Integer> list = new ArrayList<Integer>();
+        list.add(1);
+        list.add(2);
+        return list;
+    }
+
+    @SuppressWarnings("boxing")
+    public List<Byte> getJavaByteList() {
+        List<Byte> list = new ArrayList<Byte>();
+        list.add((byte) 1);
+        list.add((byte) 2);
+        return list;
+    }
+
+    @SuppressWarnings("boxing")
+    public List<Character> getJavaCharacterList() {
+        List<Character> list = new ArrayList<Character>();
+        list.add('c');
+        list.add('C');
+        return list;
+    }
+    
+    public String[] getJavaStringArray() {
+        return new String[] { "a", "b" };
+    }
+
+    public int[] getJavaIntArray() {
+        return new int[] { 11, 22 };
+    }
+
+    public Integer[] getJavaIntegerArray() {
+        return new Integer[] { Integer.valueOf(11), Integer.valueOf(22) };
+    }
+    
+    public String[] getJavaEmptyStringArray() {
+        return new String[] { };
+    }
+    
+    public String[][] getJavaStringArrayArray() {
+        return new String[][] { new String[] { "a", "b" }, new String[] { }, new String[] { "c" } };
+    }
+    
+    public Object[] getJavaObjectArray() {
+        return new Object[] { "a", "b" };
+    }
+    
+    public TemplateModel getHashAndScalarModel() {
+        return HashAndScalarModel.INSTANCE;
+    }
+
+    public TemplateModel getBooleanAndScalarModel() {
+        return BooleanAndScalarModel.INSTANCE;
+    }
+    
+    public TemplateModel getAllModels() {
+        return AllTemplateModels.INSTANCE;
+    }
+    
+    private String arrayToString(Object[] array) {
+        return array != null ? listToString(Arrays.asList(array)) : "null";
+    }
+
+    private String arrayToString(Object[][] arrayArray) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        boolean first = true;
+        for (Object[] array : arrayArray) {
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
+            }
+            sb.append(listToString(Arrays.asList(array)));
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+    
+    private String arrayToString(int[] array) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int i = 0; i < array.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(array[i]);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+    
+    private String arrayToString(int[][] xss) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('[');
+        for (int i = 0; i < xss.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(arrayToString(xss[i]));
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+    private String arrayToString(char[] array) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int i = 0; i < array.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(array[i]);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+    
+    private String arrayToString(boolean[] array) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int i = 0; i < array.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(array[i]);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    private String collectionToString(String prefix, Collection<?> list) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(prefix);
+        sb.append("[");
+        boolean first = true;
+        for (Object item : list) {
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
+            }
+            sb.append(item instanceof Object[] ? arrayToString((Object[]) item) : item);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+    
+    private String listToString(List<?> list) {
+        return collectionToString("", list);
+    }
+    
+    
+    private String setToString(Set<?> list) {
+        return collectionToString("Set", list);
+    }
+    
     public TemplateNumberModel getAdaptedNumber() {
         return new MyAdapterNumberModel();
     }
diff --git a/src/test/java/freemarker/test/utility/Helpers.java b/src/test/java/freemarker/test/utility/Helpers.java
new file mode 100644
index 0000000..e516fc6
--- /dev/null
+++ b/src/test/java/freemarker/test/utility/Helpers.java
@@ -0,0 +1,46 @@
+package freemarker.test.utility;
+
+public class Helpers {
+
+    private Helpers() { }
+
+    public static String arrayToString(int[] xs) {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append('[');
+        for (int x : xs) {
+            if (sb.length() != 1) sb.append(", ");
+            sb.append(x);
+        }
+        sb.append(']');
+        
+        return sb.toString();
+    }
+
+    public static String arrayToString(double[] xs) {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append('[');
+        for (double x : xs) {
+            if (sb.length() != 1) sb.append(", ");
+            sb.append(x);
+        }
+        sb.append(']');
+        
+        return sb.toString();
+    }
+
+    public static String arrayToString(Object[] xs) {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append('[');
+        for (Object x : xs) {
+            if (sb.length() != 1) sb.append(", ");
+            sb.append(x);
+        }
+        sb.append(']');
+        
+        return sb.toString();
+    }
+    
+}
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-common.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-common.ftl
new file mode 100644
index 0000000..14ef9dc
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-common.ftl
@@ -0,0 +1,71 @@
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringList) expected="mStringArrayVsListPreference(List [a, b])" />
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringArray) expected=dow?string("mStringArrayVsListPreference(List [a, b])", "mStringArrayVsListPreference(String[] [a, b])") />
+<#if dow>
+  <@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaObjectArray) expected="mStringArrayVsListPreference(List [a, b])" />
+</#if>
+
+<#-- Check if non-overloaded calls still work; they share some code with overloaded methods: -->
+<@assertEquals actual=obj.mIntArrayNonOverloaded([1, 2, 3]) expected="mIntArrayNonOverloaded(int[] [1, 2, 3])" />
+<@assertEquals actual=obj.mIntegerArrayNonOverloaded([1, 2, 3]) expected="mIntegerArrayNonOverloaded(Integer[] [1, 2, 3])" />
+<@assertEquals actual=obj.mIntegerListNonOverloaded([1, 2, 3]) expected="mIntegerListNonOverloaded(List<Integer> [1, 2, 3])" />
+<@assertEquals actual=obj.mStringListNonOverloaded(['a', 'b', 'c']) expected="mStringListNonOverloaded(List<String> [a, b, c])" />
+<@assertEquals actual=obj.mStringListNonOverloaded(obj.javaStringList) expected="mStringListNonOverloaded(List<String> [a, b])" />
+<@assertEquals actual=obj.mStringListNonOverloaded(obj.javaStringArray) expected="mStringListNonOverloaded(List<String> [a, b])" />
+<@assertEquals actual=obj.mStringArrayNonOverloaded(['a', 'b', 'c']) expected="mStringArrayNonOverloaded(String[] [a, b, c])" />
+<@assertEquals actual=obj.mStringArrayNonOverloaded(obj.javaStringList) expected="mStringArrayNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayNonOverloaded(obj.javaStringArray) expected="mStringArrayNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mObjectListNonOverloaded(['a', 'b', 3]) expected="mObjectListNonOverloaded(List<Object> [a, b, 3])" />
+<@assertEquals actual=obj.mObjectListNonOverloaded(obj.javaStringList) expected="mObjectListNonOverloaded(List<Object> [a, b])" />
+<@assertEquals actual=obj.mObjectListNonOverloaded(obj.javaStringArray) expected="mObjectListNonOverloaded(List<Object> [a, b])" />
+<@assertEquals actual=obj.mObjectArrayNonOverloaded(['a', 'b', 3]) expected="mObjectArrayNonOverloaded(Object[] [a, b, 3])" />
+<@assertEquals actual=obj.mObjectArrayNonOverloaded(obj.javaStringList) expected="mObjectArrayNonOverloaded(Object[] [a, b])" />
+<@assertEquals actual=obj.mObjectArrayNonOverloaded(obj.javaStringArray) expected="mObjectArrayNonOverloaded(Object[] [a, b])" />
+
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringArray) expected=dow?string("mStringArrayVsListPreference(List [a, b])", "mStringArrayVsListPreference(String[] [a, b])") />
+
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded('a', 'b') expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded(['a', 'b']) expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded(obj.javaStringList) expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsNonOverloaded(obj.javaStringArray) expected="mStringArrayVarargsNonOverloaded(String[] [a, b])" />
+
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1('a', 'b') expected="mStringArrayVarargsOverloaded1(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1(['a', 'b']) expected="mStringArrayVarargsOverloaded1(List [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1(obj.javaStringList) expected="mStringArrayVarargsOverloaded1(List [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded1(obj.javaStringArray) expected=dow?string("mStringArrayVarargsOverloaded1(List [a, b])", "mStringArrayVarargsOverloaded1(String[] [a, b])") />
+
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded2('a', 'b') expected="mStringArrayVarargsOverloaded2(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded2('a') expected="mStringArrayVarargsOverloaded2(String a)" />
+
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3(['a']) expected="mStringArrayVarargsOverloaded3(String[] [a])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3(['a', 'b']) expected="mStringArrayVarargsOverloaded3(String[] [a, b])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3(['a', 'b', 'c']) expected="mStringArrayVarargsOverloaded3(String[] [a, b, c])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3('a') expected="mStringArrayVarargsOverloaded3(String[] [a])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3('a', 'b') expected="mStringArrayVarargsOverloaded3(String a, String b)" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded3('a', 'b', 'c') expected="mStringArrayVarargsOverloaded3(String[] [a, b, c])" />
+
+<@assertEquals actual=obj.mListOrString(['a', 'b']) expected="mListOrString(List [a, b])" />
+<@assertEquals actual=obj.mListOrString('a') expected="mListOrString(String a)" />
+<@assertEquals actual=obj.mListListOrString([['a'], 'b', 3]) expected="mListListOrString(List [[a], b, 3])" />
+<@assertEquals actual=obj.mListListOrString('s') expected="mListListOrString(String s)" />
+
+<#-- Because the fixed arg interpretations are ambiguous, it only considers the vararg interpretations:  -->
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(['a', 'b', 'c']) expected="mStringArrayVarargsOverloaded4(List[] [[a, b, c]])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4('a', 'b', 'c') expected="mStringArrayVarargsOverloaded4(String[] [a, b, c])" />
+
+<#-- Fixed arg solutions have priority: -->
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringList) expected="mStringArrayVarargsOverloaded4(List[] [[a, b]])" />
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringArray) expected=dow?string("mStringArrayVarargsOverloaded4(List[] [[a, b]])", "mStringArrayVarargsOverloaded4(String[] [a, b])") />
+
+<#-- Choses between the vararg solutions: -->
+<@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringList, obj.javaStringList) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+
+<#-- Until there's no overloading String->Character conversion work: -->
+<@assertEquals actual=obj.mCharNonOverloaded('c') expected="mCharNonOverloaded(char c)" />
+<@assertEquals actual=obj.mCharNonOverloaded(obj.javaString) expected="mCharNonOverloaded(char s)" />
+<@assertEquals actual=obj.mCharacterNonOverloaded('c') expected="mCharacterNonOverloaded(Character c)" />
+<@assertEquals actual=obj.mCharacterNonOverloaded(obj.javaString) expected="mCharacterNonOverloaded(Character s)" />
+
+<@assertEquals actual=obj.mCharOrStringOverloaded('s', 1) expected="mCharOrStringOverloaded(String s, int 1)" />
+<@assertEquals actual=obj.mCharacterOrStringOverloaded('s', 1) expected="mCharacterOrStringOverloaded(String s, int 1)" />
+<@assertEquals actual=obj.mCharOrStringOverloaded2('ss') expected="mCharOrStringOverloaded2(String ss)" />
+<@assertEquals actual=obj.mCharacterOrStringOverloaded2('ss') expected="mCharacterOrStringOverloaded2(String ss)" />
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
index 3200723..06fc12d 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
@@ -1,3 +1,5 @@
+<#-- Note that the point of 2.3.20 tests is to check if bugs fixed in 2.3.21 are still emulated in pre-2.3.21 mode -->
+
 <@assertFails message="no compatible overloaded">${obj.mVarargs('a', obj.getNnS('b'), obj.getNnS('c'))}</@>
 <@assertFails message="no compatible overloaded">${obj.mChar('a')}</@>
 <@assertFails message="no compatible overloaded">${obj.mIntPrimVSBoxed(123?long)}</@>
@@ -17,4 +19,7 @@
 <@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.adaptedNumber, 0, 0, 0, !obj.booleanWrappedAsAnotherBoolean)}</@>
 <@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, !obj.stringAdaptedToBoolean)}</@>
 
+<@assertFails message="no compatible overloaded">${obj.mCharOrCharacterOverloaded('c')}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharOrCharacterOverloaded(obj.javaString)}</@>
+
 <#include 'overloaded-methods-2-ici-2.3.20.ftl'>
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
index 2a1c34e..cb59514 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
@@ -1,4 +1,7 @@
 <#-- The parts of the IcI 2.3.20 tests that give the same result regardless of method introspection order -->
+<#-- Note that the point of 2.3.20 tests is to check if bugs fixed in 2.3.21 are still emulated in pre-2.3.21 mode -->
+
+<#include "overloaded-methods-2-common.ftl">
 
 <@assertFails message="no compatible overloaded">${obj.mNull1(null)}</@>
 <@assertEquals actual=obj.mNull1(123) expected="mNull1(int a1 = 123)" />
@@ -151,3 +154,109 @@
                expected='mRareWrappings(String s = yes, double d1 = 123.0001, Double d2 = 123.0001, double d3 = 123.0001, b = true)' />
                
 <@assertFails message="no compatible overloaded">${obj.mRareWrappings2(obj.adaptedNumber)}</@>
+
+<#-- Test for List VS array problems due to too vague hinting: -->
+
+<@assertEquals actual=obj.mSeqToArrayNonOverloaded(['a', 'b'], 'c') expected='mSeqToArrayNonOverloaded(String[] [a, b], String c)' />
+
+<@assertEquals actual=obj.mSeqToArrayGoodHint(['a', 'b'], 'c') expected='mSeqToArrayGoodHint(String[] [a, b], String c)' />
+<@assertEquals actual=obj.mSeqToArrayGoodHint(['a', 'b'], 3) expected='mSeqToArrayGoodHint(String[] [a, b], int 3)' />
+
+<@assertEquals actual=obj.mSeqToArrayGoodHint2(['a', 'b'], 'c') expected='mSeqToArrayGoodHint2(String[] [a, b], String c)' />
+<@assertEquals actual=obj.mSeqToArrayGoodHint2('a') expected='mSeqToArrayGoodHint2(String a)' />
+
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint(['a', 'b'], 'c') expected='mSeqToArrayPoorHint(String[] [a, b], String c)' /></@>
+<@assertEquals actual=obj.mSeqToArrayPoorHint('a', 2) expected='mSeqToArrayPoorHint(String a, int 2)' />
+
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint2(['a', 'b']) expected='mSeqToArrayPoorHint2(String[] [a, b])' /></@>
+<@assertEquals actual=obj.mSeqToArrayPoorHint2('a') expected='mSeqToArrayPoorHint2(String a)' />
+
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint3(['a', 'b']) expected='mSeqToArrayPoorHint3(String[] [a, b])' /></@>
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mSeqToArrayPoorHint3([1, 2]) expected='mSeqToArrayPoorHint3(int[] [a, b])' /></@>
+
+<@assertEquals actual=obj.mStringArrayVsListPreference(['a', 'b']) expected="mStringArrayVsListPreference(List [a, b])" />
+<@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(['a', 'b'])}</@>
+<#if !dow>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVsListPreference(obj.javaObjectArray)}</@>
+</#if>
+<@assertFails message="no compatible overloaded">${obj.mIntArrayVsIntegerArrayPreference([1, 2])}</@>
+
+<#if dow>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaStringArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaIntArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaIntegerArray)}</@>
+<#else>
+  <@assertEquals actual=obj.mStringArrayVsObjectArrayPreference(obj.javaStringArray) expected="mStringArrayVsObjectArrayPreference(String[] [a, b])" />
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVsObjectArrayPreference(obj.javaIntArray)}</@>
+  <@assertEquals actual=obj.mStringArrayVsObjectArrayPreference(obj.javaIntegerArray) expected="mStringArrayVsObjectArrayPreference(Object[] [11, 22])" />
+</#if>
+
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded([1, 2], 3)}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded([1?byte, 2?byte], 3)}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded(obj.javaIntegerList, 3)}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntegerArrayOverloaded(obj.javaByteList, 3)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(['a', 'b'])}</@>
+<#if dow>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(obj.javaStringList)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(obj.javaStringArray)}</@>
+<#else>
+  <@assertEquals actual=obj.mStringArrayVarargsOverloaded2(obj.javaStringList) expected="mStringArrayVarargsOverloaded2(String[] [[a, b]])" /> <#-- toString() accident... -->
+  <@assertEquals actual=obj.mStringArrayVarargsOverloaded2(obj.javaStringArray) expected="mStringArrayVarargsOverloaded2(String[] [a, b])" />
+</#if>
+<@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded2(['a'])}</@>
+
+<#if dow>
+  <#-- As with DOW we never end up with array-s after unwrapping, they just work like Lists: -->
+  <@assertEquals actual=obj.mListOrString(obj.javaStringArray) expected="mListOrString(List [a, b])" />
+  <@assertEquals actual=obj.mListListOrString(obj.javaStringArrayArray) expected="mListListOrString(List [[a, b], [], [c]])" />
+  <@assertEquals actual=obj.mListOrString(obj.javaIntArray) expected="mListOrString(List [11, 22])" />
+  <@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringArray) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+  <@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringList, obj.javaStringArray) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+  <@assertEquals actual=obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringList) expected="mStringArrayVarargsOverloaded4(List[] [[a, b], [a, b]])" />
+<#else>
+  <#-- Pure BeansWrapper unwraps to array-s, but before IcI 2.3.21 it couldn't treat them as Lists: -->
+  <@assertFails message="no compatible overloaded">${obj.mListOrString(obj.javaStringArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mListListOrString(obj.javaStringArrayArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mListOrString(obj.javaIntArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded4(obj.javaStringList, obj.javaStringArray)}</@>
+  <@assertFails message="no compatible overloaded">${obj.mStringArrayVarargsOverloaded4(obj.javaStringArray, obj.javaStringList)}</@>
+</#if>
+
+<@assertFails message="no compatible overloaded">${obj.mIntArrayArrayOverloaded(obj.javaListOfIntArrays)}</@>
+<@assertFails message="no compatible overloaded">${obj.mArrayOfListsOverloaded(obj.javaListOfIntArrays)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mMapOrBoolean(obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBoolean(obj.booleanAndScalarModel)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.hashAndScalarModel, obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanVarargs(obj.allModels, obj.allModels)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.hashAndScalarModel, obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.hashAndScalarModel, obj.hashAndScalarModel, obj.hashAndScalarModel)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.allModels, obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrBooleanFixedAndVarargs(obj.allModels, obj.allModels, obj.allModels)}</@>
+
+<@assertEquals actual=obj.mNumberOrArray(obj.allModels) expected="mNumberOrArray(Number 1)" />
+<@assertFails message="no compatible overloaded"><@assertEquals actual=obj.mNumberOrArray([obj.allModels]) expected="mNumberOrArray(Object[] [1])" /></@>
+<@assertEquals actual=obj.mIntOrArray(obj.allModels) expected="mIntOrArray(int 1)" />
+<@assertFails message="no compatible overloaded">${obj.mDateOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mStringOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mBooleanOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mMapOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mListOrArray(obj.allModels)}</@>
+<@assertFails message="no compatible overloaded">${obj.mSetOrArray(obj.allModels)}</@>
+
+<@assertFails message="no compatible overloaded">${obj.mCharOrCharacterOverloaded(null)}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharOrBooleanOverloaded('c')}</@>
+<@assertEquals actual=obj.mCharOrBooleanOverloaded(true) expected="mCharOrBooleanOverloaded(boolean true)" />
+
+<@assertFails message="no compatible overloaded">${obj.mCharOrStringOverloaded('c', true)}</@>
+<@assertFails message="no compatible overloaded">${obj.mCharacterOrStringOverloaded('c', true)}</@>
+<@assertEquals actual=obj.mCharOrStringOverloaded2('c') expected="mCharOrStringOverloaded2(String c)" />
+<@assertEquals actual=obj.mCharacterOrStringOverloaded2('c') expected="mCharacterOrStringOverloaded2(String c)" />
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
index f91d7d8..fa8e413 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
@@ -1,3 +1,5 @@
+<#-- Note that the point of 2.3.20 tests is to check if bugs fixed in 2.3.21 are still emulated in pre-2.3.21 mode -->
+
 <@assertEquals actual=obj.mVarargs('a', obj.getNnS('b'), obj.getNnS('c')) expected='mVarargs(String... a1 = abc)' />
 <@assertFails message="multiple compatible overload">${obj.mChar('a')}</@>
 <@assertFails message="multiple compatible overload">${obj.mIntPrimVSBoxed(123?long)}</@>
@@ -21,4 +23,7 @@
 <@assertEquals actual=obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, !obj.stringAdaptedToBoolean)
                expected='mRareWrappings(Object o = true, double d1 = 0.0, Double d2 = 0.0, double d3 = 0.0, b = true)' />
 
+<@assertFails message="multiple compatible overloaded">${obj.mCharOrCharacterOverloaded('c')}</@>
+<@assertFails message="multiple compatible overloaded">${obj.mCharOrCharacterOverloaded(obj.javaString)}</@>
+
 <#include 'overloaded-methods-2-ici-2.3.20.ftl'>