From 463301348b42272fd62c014e183ad334ad489833 Mon Sep 17 00:00:00 2001 From: Alexander Taggart Date: Wed, 27 Apr 2011 12:01:22 -0700 Subject: [PATCH 1/3] Add missing double/long overloads for Util.equiv --- src/jvm/clojure/lang/Util.java | 11 +++++++++++ test/clojure/test_clojure/numbers.clj | 5 +++++ 2 files changed, 16 insertions(+), 0 deletions(-) diff --git a/src/jvm/clojure/lang/Util.java b/src/jvm/clojure/lang/Util.java index 454327e..2d10028 100644 --- a/src/jvm/clojure/lang/Util.java +++ b/src/jvm/clojure/lang/Util.java @@ -20,6 +20,17 @@ import java.lang.ref.SoftReference; import java.lang.ref.ReferenceQueue; public class Util{ + +// Two special overloads allowing us to return false on different +// numeric categories, just like the Object version below. +static public boolean equiv(long k1, double k2){ + return false; +} + +static public boolean equiv(double k1, long k2){ + return false; +} + static public boolean equiv(Object k1, Object k2){ if(k1 == k2) return true; diff --git a/test/clojure/test_clojure/numbers.clj b/test/clojure/test_clojure/numbers.clj index 3bd5405..1115d99 100644 --- a/test/clojure/test_clojure/numbers.clj +++ b/test/clojure/test_clojure/numbers.clj @@ -489,3 +489,8 @@ Math/pow overflows to Infinity." clojure.lang.BigInt (class (-' 0 -9223372036854775808)) java.lang.Long (class (-' 0 -9223372036854775807)))) +(deftest category-equivalence + (are [x y] (= [false true] [(= x y) (== x y)]) + 1N 1M + 1 1.0 + (Long. 1) (Double. 1.0))) -- 1.7.3.5 From 049f7a0b0fba8c67b314965d45805994bac33d08 Mon Sep 17 00:00:00 2001 From: Alexander Taggart Date: Wed, 27 Apr 2011 03:36:51 -0700 Subject: [PATCH 2/3] Refactor code for emitting primitive args into separate methods. --- src/jvm/clojure/lang/Compiler.java | 68 +++++++++++++++++++----------------- 1 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index f8eaab9..6827b06 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -740,6 +740,8 @@ static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{ final static Method fromLongMethod = Method.getMethod("clojure.lang.Num from(long)"); final static Method fromDoubleMethod = Method.getMethod("clojure.lang.Num from(double)"); + final static Method floatFromDoubleMethod = Method.getMethod("float floatCast(double)"); + final static Method intFromLongMethod = Method.getMethod("int intCast(long)"); //* public static void emitBoxReturn(ObjExpr objx, GeneratorAdapter gen, Class returnType){ @@ -856,6 +858,35 @@ static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{ } } + public static boolean canEmitPrimArg(Class from, Class to){ + if (from == null || !from.isPrimitive() || !to.isPrimitive()) + return false; + + if (from == to) + return true; + + // auto-cast + if ((from == double.class && to == float.class) + || (from == long.class && to == int.class)) + return true; + + return false; + } + + public static void emitPrimCast(ObjExpr objx, GeneratorAdapter gen, Class from, Class to){ + if (from != to) + { + if(RT.booleanCast(RT.UNCHECKED_MATH.deref())) + gen.cast(Type.getType(from), Type.getType(to)); + else if (from == double.class && to == float.class) + gen.invokeStatic(RT_TYPE, floatFromDoubleMethod); + else if (from == long.class && to == int.class) + gen.invokeStatic(RT_TYPE, intFromLongMethod); + else + gen.cast(Type.getType(from), Type.getType(to)); + } + } + private static final Class[] EMPTY_TYPES = new Class[0]; static class Parser implements IParser{ @@ -1261,38 +1292,11 @@ static abstract class MethodExpr extends HostExpr{ try { final Class primc = maybePrimitiveType(e); - if(primc == parameterTypes[i]) - { - final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e; - pe.emitUnboxed(C.EXPRESSION, objx, gen); - } - else if(primc == int.class && parameterTypes[i] == long.class) - { - final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e; - pe.emitUnboxed(C.EXPRESSION, objx, gen); - gen.visitInsn(I2L); - } - else if(primc == long.class && parameterTypes[i] == int.class) - { - final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e; - pe.emitUnboxed(C.EXPRESSION, objx, gen); - if(RT.booleanCast(RT.UNCHECKED_MATH.deref())) - gen.invokeStatic(RT_TYPE, Method.getMethod("int uncheckedIntCast(long)")); - else - gen.invokeStatic(RT_TYPE, Method.getMethod("int intCast(long)")); - } - else if(primc == float.class && parameterTypes[i] == double.class) - { - final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e; - pe.emitUnboxed(C.EXPRESSION, objx, gen); - gen.visitInsn(F2D); - } - else if(primc == double.class && parameterTypes[i] == float.class) - { - final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e; - pe.emitUnboxed(C.EXPRESSION, objx, gen); - gen.visitInsn(D2F); - } + if (canEmitPrimArg(primc, parameterTypes[i])) + { + ((MaybePrimitiveExpr)e).emitUnboxed(C.EXPRESSION, objx, gen); + HostExpr.emitPrimCast(objx, gen, primc, parameterTypes[i]); + } else { e.emit(C.EXPRESSION, objx, gen); -- 1.7.3.5 From 8c93bd9db532bea844bf30ab48c997cf836f2b1e Mon Sep 17 00:00:00 2001 From: Alexander Taggart Date: Tue, 10 May 2011 13:09:43 -0700 Subject: [PATCH 3/3] Improved method/constructor resolution by Reflector. --- src/jvm/clojure/lang/Compiler.java | 56 ++-- src/jvm/clojure/lang/Reflector.java | 650 +++++++++++++++++++++++-------- src/script/run_tests.clj | 1 + test/clojure/test_clojure/reflector.clj | 79 ++++ 4 files changed, 589 insertions(+), 197 deletions(-) create mode 100644 test/clojure/test_clojure/reflector.clj diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 6827b06..b16641c 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -745,6 +745,7 @@ static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{ //* public static void emitBoxReturn(ObjExpr objx, GeneratorAdapter gen, Class returnType){ + // NOTICE: Must be consistent with Reflector.prepRet if(returnType.isPrimitive()) { if(returnType == boolean.class) @@ -767,34 +768,29 @@ static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{ { gen.invokeStatic(CHAR_TYPE, charValueOfMethod); } - else - { - if(returnType == int.class) - { - gen.visitInsn(I2L); - gen.invokeStatic(NUMBERS_TYPE, Method.getMethod("Number num(long)")); - } - else if(returnType == float.class) - { - gen.invokeStatic(FLOAT_TYPE, floatValueOfMethod); - -// gen.visitInsn(F2D); -// gen.invokeStatic(DOUBLE_TYPE, doubleValueOfMethod); - } - else if(returnType == double.class) - gen.invokeStatic(DOUBLE_TYPE, doubleValueOfMethod); - else if(returnType == long.class) - gen.invokeStatic(NUMBERS_TYPE, Method.getMethod("Number num(long)")); - else if(returnType == byte.class) - gen.invokeStatic(BYTE_TYPE, byteValueOfMethod); - else if(returnType == short.class) - gen.invokeStatic(SHORT_TYPE, shortValueOfMethod); - } + else + { + if(returnType == int.class) + gen.invokeStatic(INTEGER_TYPE, intValueOfMethod); + else if(returnType == float.class) + gen.invokeStatic(FLOAT_TYPE, floatValueOfMethod); + else if(returnType == double.class) + gen.invokeStatic(DOUBLE_TYPE, doubleValueOfMethod); + else if(returnType == long.class) + gen.invokeStatic(LONG_TYPE, longValueOfMethod); + else if(returnType == byte.class) + gen.invokeStatic(BYTE_TYPE, byteValueOfMethod); + else if(returnType == short.class) + gen.invokeStatic(SHORT_TYPE, shortValueOfMethod); + } } } //*/ public static void emitUnboxArg(ObjExpr objx, GeneratorAdapter gen, Class paramType){ + // NOTICE: Must be consistent with Reflector.boxArg + // and Reflector.isAssignableBy... for non-primitive args + if(paramType.isPrimitive()) { if(paramType == boolean.class) @@ -862,18 +858,14 @@ static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{ if (from == null || !from.isPrimitive() || !to.isPrimitive()) return false; - if (from == to) - return true; - - // auto-cast - if ((from == double.class && to == float.class) - || (from == long.class && to == int.class)) - return true; - - return false; + return (Reflector.isAssignableByType(from, to) + || Reflector.isAssignableByBoxing(from, to) + || Reflector.isAssignableByCasting(from, to)); + // else treat as boxed type } public static void emitPrimCast(ObjExpr objx, GeneratorAdapter gen, Class from, Class to){ + // NOTICE: Must be consistent with Reflector.isAssignableBy... for primitive args if (from != to) { if(RT.booleanCast(RT.UNCHECKED_MATH.deref())) diff --git a/src/jvm/clojure/lang/Reflector.java b/src/jvm/clojure/lang/Reflector.java index 4b074d4..f0861f8 100644 --- a/src/jvm/clojure/lang/Reflector.java +++ b/src/jvm/clojure/lang/Reflector.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; public class Reflector{ @@ -264,118 +265,38 @@ static public Field getField(Class c, String name, boolean getStatics){ return getField(c, name, getStatics ? Statics.T : Statics.F, Invoking.F); } -private static boolean subsumes(Class[] c1, Class[] c2){ - //presumes matching lengths - Boolean better = false; - for(int i = 0; i < c1.length; i++) - { - if(c1[i] != c2[i])// || c2[i].isPrimitive() && c1[i] == Object.class)) - { - if(!c1[i].isPrimitive() && c2[i].isPrimitive() - //|| Number.class.isAssignableFrom(c1[i]) && c2[i].isPrimitive() - || - c2[i].isAssignableFrom(c1[i])) - better = true; - else - return false; - } - } - return better; -} +/** + * Returns the best member (Method or Constructor) to be called with the + * specified argument types. Will return null if no satisfactory member is + * found. If no single best can be determined from multiple options, an + * IllegalArgumentException is thrown during invocation, otherwise returns null. + */ +private static T getMatchingMember(List members, Class[] argTypes, Invoking invoking){ + if (members.size() > 1) + members = filterArity(members, argTypes.length); -private static int getMatchingParams(T member, ArrayList paramlists, Class[] argTypes, - List rets) - { - //presumes matching lengths - int matchIdx = -1; - boolean tied = false; - boolean foundExact = false; - for(int i = 0; i < paramlists.size(); i++) - { - boolean match = true; - int exact = 0; - for(int p = 0; match && p < argTypes.length; ++p) - { - Class aclass = argTypes[p]; - Class pclass = paramlists.get(i)[p]; - if(aclass == pclass) - exact++; - else - match = paramArgTypeMatch(pclass, aclass); - } - if(exact == argTypes.length) - { - if(!foundExact || matchIdx == -1 || rets.get(matchIdx).isAssignableFrom(rets.get(i))) - matchIdx = i; - foundExact = true; - } - else if(match && !foundExact) - { - if(matchIdx == -1) - matchIdx = i; - else - { - if(subsumes(paramlists.get(i), paramlists.get(matchIdx))) - { - matchIdx = i; - tied = false; - } - else if(Arrays.equals(paramlists.get(matchIdx), paramlists.get(i))) - { - if(rets.get(matchIdx).isAssignableFrom(rets.get(i))) - matchIdx = i; - } - else if(!(subsumes(paramlists.get(matchIdx), paramlists.get(i)))) - tied = true; - } - } - } - if(tied) - { - if (member instanceof Method) - { - Method m = ((Method)member); - Class c = m.getDeclaringClass(); - throw new IllegalArgumentException("Found multiple "+m.getName()+" methods in "+c.getName()+" for argtypes: "+toString(argTypes)); - } - else - { - Constructor ctor = ((Constructor)member); - Class c = ctor.getDeclaringClass(); - throw new IllegalArgumentException("Found multiple constructors in "+c.getName()+" for argtypes: "+toString(argTypes)); - } - } + if (members.size() > 1) + members = filterAcceptable(members, argTypes, invoking); - return matchIdx; + if (members.size() == 1) + return members.get(0); + else if (members.size() > 1) + return selectBestMatch(members, argTypes, invoking); + return null; } -private static T getMatchingMember(List members, Class[] argTypes, Invoking invoking){ - ArrayList matches = new ArrayList(); - ArrayList params = new ArrayList(); - ArrayList rets = new ArrayList(); - for(T member : members) - { - if(getParameterTypes(member).length == argTypes.length) - { - matches.add(member); - params.add(getParameterTypes(member)); - rets.add(getReturnType(member)); - } - } - - int matchidx = -1; - if (matches.size() == 1) - { - matchidx = 0; - } - if(matches.size() > 1) +/** + * Returns a list of all members which could be called with arity number of arguments. + */ +private static List filterArity(List members, int arity){ + List filtered = new ArrayList(); + for (T member : members) { - matchidx = getMatchingParams(matches.get(0), params, argTypes, rets); + Class[] paramTypes = getParameterTypes(member); + if (paramTypes.length == arity) + filtered.add(member); } - - T match = matchidx >= 0 ? matches.get(matchidx) : null; - - return match; + return filtered; } private static Constructor getMatchingConstructor(Class c, Class[] argTypes, Invoking invoking){ @@ -453,31 +374,35 @@ public static Method getMatchingStaticMethod(Class c, String methodName, Class[] return getMatchingMethod(c, methodName, argTypes, Statics.T, Invoking.F); } +/** + * Casts arguments to the param type, including box->prim->box. + */ private static Object boxArg(Class paramType, Object arg){ - if(!paramType.isPrimitive()) - return paramType.cast(arg); - else if(paramType == boolean.class) - return Boolean.class.cast(arg); - else if(paramType == char.class) - return Character.class.cast(arg); - else if(arg instanceof Number) - { - Number n = (Number) arg; - if(paramType == int.class) - return n.intValue(); - else if(paramType == float.class) - return n.floatValue(); - else if(paramType == double.class) - return n.doubleValue(); - else if(paramType == long.class) - return n.longValue(); - else if(paramType == short.class) - return n.shortValue(); - else if(paramType == byte.class) - return n.byteValue(); - } - throw new IllegalArgumentException("Unexpected param type, expected: " + paramType + - ", given: " + arg.getClass().getName()); + // NOTICE: Must be consistent with Compiler.emitUnboxArg + if(paramType.isPrimitive()) + { + if(paramType == boolean.class) + return Boolean.class.cast(arg); + else if(paramType == char.class) + return Character.class.cast(arg); + else + { + Number n = (Number) arg; + if(paramType == int.class) + return RT.intCast(n); + else if(paramType == float.class) + return RT.floatCast(n); + else if(paramType == double.class) + return RT.doubleCast(n); + else if(paramType == long.class) + return RT.longCast(n); + else if(paramType == short.class) + return RT.shortCast(n); + else if(paramType == byte.class) + return RT.byteCast(n); + } + } + return paramType.cast(arg); } private static Object[] boxArgs(Class[] params, Object[] args){ @@ -493,46 +418,441 @@ private static Object[] boxArgs(Class[] params, Object[] args){ return ret; } -private static boolean paramArgTypeMatch(Class paramType, Class argType){ - if(argType == null) - return !paramType.isPrimitive(); - if(paramType == argType || paramType.isAssignableFrom(argType)) - return true; - if(paramType == int.class) - return argType == Integer.class - || argType == long.class - || argType == Long.class;// || argType == FixNum.class; - else if(paramType == float.class) - return argType == Float.class - || argType == double.class; - else if(paramType == double.class) - return argType == Double.class - || argType == float.class;// || argType == DoubleNum.class; - else if(paramType == long.class) - return argType == Long.class - || argType == int.class;// || argType == BigNum.class; - else if(paramType == char.class) - return argType == Character.class; - else if(paramType == short.class) - return argType == Short.class; - else if(paramType == byte.class) - return argType == Byte.class; - else if(paramType == boolean.class) - return argType == Boolean.class; - return false; -} - public static Object prepRet(Class c, Object x){ + // NOTICE: Must be consistent with Compiler.emitBoxReturn if (!(c.isPrimitive() || c == Boolean.class)) return x; if(x instanceof Boolean) return ((Boolean) x)?Boolean.TRUE:Boolean.FALSE; - else if(x instanceof Integer) - { - return ((Integer)x).longValue(); - } -// else if(x instanceof Float) -// return Double.valueOf(((Float) x).doubleValue()); return x; } + +/* + * CONVERSION RULES + * Type: + * - primitive to a wider primitive of the same numeric category + * Boxing: + * - boxed number to its primitive + * - boxed number to a wider primitive of the same numeric category + * - primitive to its boxed value + * - primitive to Number or Object + * Casting: + * - long to int + * - double to float + */ + +/** + * Returns true if casting is needed and able to convert argType to paramType. + * Disjoint from other isAssignableBy... methods. + */ +static boolean isAssignableByCasting(Class paramType, Class argType){ + if (argType == null) + return false; + if (argType == double.class) + return paramType == float.class; + if (argType == long.class) + return paramType == int.class; + return false; +} + +/** + * Returns true if boxing is needed and able to convert argType to paramType. + * Disjoint from other isAssignableBy... methods. + */ +static boolean isAssignableByBoxing(Class paramType, Class argType){ + if (argType == null) + return false; + if (paramType.isPrimitive() && !argType.isPrimitive()) + { + if(paramType == double.class) + return argType == Double.class + || argType == Float.class; + if(paramType == float.class) + return argType == Float.class; + if(paramType == long.class) + return argType == Long.class + || argType == Integer.class + || argType == Short.class + || argType == Byte.class; + if(paramType == int.class) + return argType == Integer.class + || argType == Short.class + || argType == Byte.class; + if(paramType == short.class) + return argType == Short.class + || argType == Byte.class; + if(paramType == char.class) + return argType == Character.class; + if(paramType == byte.class) + return argType == Byte.class; + if(paramType == boolean.class) + return argType == Boolean.class; + } + else if (!paramType.isPrimitive() && argType.isPrimitive()) + { + if (paramType == Object.class) + return true; + if(paramType == Double.class) + return argType == double.class; + if(paramType == Float.class) + return argType == float.class; + if(paramType == Long.class) + return argType == long.class; + if(paramType == Integer.class) + return argType == int.class; + if(paramType == Short.class) + return argType == short.class; + if(paramType == Character.class) + return argType == char.class; + if(paramType == Byte.class) + return argType == byte.class; + if(paramType == Boolean.class) + return argType == boolean.class; + if (paramType == Number.class) + return argType == double.class + || argType == float.class + || argType == long.class + || argType == int.class + || argType == byte.class + || argType == short.class; + } + return false; +} + +/** + * Returns true if nothing more than type-conversion is needed to assign argType + * to paramType. Disjoint from other isAssignableBy... methods. + */ +static boolean isAssignableByType(Class paramType, Class argType){ + if(argType == null) + return !paramType.isPrimitive(); + + if(paramType == argType || paramType.isAssignableFrom(argType)) + return true; + + // Widening-conversion of numerics in the same category. + // NOTICE: Must be consistent with Compiler.canEmitPrimArg + if (paramType.isPrimitive()) + { + if(paramType == double.class) + return argType == float.class; + if(paramType == long.class) + return argType == int.class + || argType == short.class + || argType == byte.class; + if(paramType == int.class) + return argType == short.class + || argType == byte.class; + if(paramType == short.class) + return argType == byte.class; + } + return false; +} + +private enum AcceptBy{ + TYPE, BOXING, CASTING, NONE, WILD; +} + +private static AcceptBy isAcceptableBy(Class paramType, Class argType, Invoking invoking){ + if (!invoking.b && argType == Object.class && paramType != Object.class) + return AcceptBy.WILD; + if (isAssignableByType(paramType, argType)) + return AcceptBy.TYPE; + if (isAssignableByBoxing(paramType, argType)) + return AcceptBy.BOXING; + if (isAssignableByCasting(paramType, argType)) + return AcceptBy.CASTING; + return AcceptBy.NONE; +} + +private static AcceptBy areAcceptableBy(Class[] paramTypes, Class[] argTypes, Invoking invoking){ + AcceptBy acceptBy = null; + for (int i = 0; i < argTypes.length; i++) + { + AcceptBy ab = isAcceptableBy(paramTypes[i], argTypes[i], invoking); + if (ab == AcceptBy.NONE) + return ab; + else if (acceptBy == null) + acceptBy = ab; + else if (acceptBy == AcceptBy.WILD) + continue; // remains wild, continue checking other params for NONE + else if (ab == AcceptBy.WILD) + acceptBy = ab; + else if (acceptBy == ab) + continue; + else if (ab == AcceptBy.CASTING) + acceptBy = ab; + else if (ab == AcceptBy.BOXING && acceptBy != AcceptBy.CASTING) + acceptBy = ab; + } + return acceptBy; +} + +/** + * Returns a list of members which, based on their parameter types, could be + * called with the specified argument types. + * + * The returned members will be the first non-empty set of the following: + * - Members matching by type (i.e., pre-1.5 java) + * - Members matching by boxing (java 1.5+) + * - Members matching by casting (clojure-specific behavior) + * - Members matching by wildcard (i.e., argument type is Object). Only used when not invoking. + */ +private static List filterAcceptable(List members, Class[] argTypes, Invoking invoking){ + if (argTypes.length == 0) + return members; + + List type = new ArrayList(); + List box = new ArrayList(); + List cast = new ArrayList(); + List wild = new ArrayList(); + for (T member : members) + { + Class[] paramTypes = getParameterTypes(member); + AcceptBy acceptBy = areAcceptableBy(paramTypes, argTypes, invoking); + if (acceptBy == AcceptBy.NONE) + continue; + else if (acceptBy == AcceptBy.TYPE) + type.add(member); + else if (acceptBy == AcceptBy.BOXING) + box.add(member); + else if (acceptBy == AcceptBy.CASTING) + cast.add(member); + else if (acceptBy == AcceptBy.WILD) + wild.add(member); + } + if (!type.isEmpty()) + return type; + if (!box.isEmpty()) + return box; + if (!cast.isEmpty()) + return cast; + if (!invoking.b && !wild.isEmpty()) + return wild; + return Collections.emptyList(); +} + +private enum Prefer{ + A, B, TIED, DISJOINT, AMBIGUOUS; + public Prefer other(){ + if (this == A) + return B; + if (this == B) + return A; + return this; + } +} + +private static Prefer compare(Class a, Class b){ + boolean ab = isAssignableByType(a, b); + boolean ba = isAssignableByType(b, a); + if (ab && !ba) + return Prefer.B; + if (ba && !ab) + return Prefer.A; + if (ab && ba) + return Prefer.TIED; + + ab = isAssignableByBoxing(a, b); + ba = isAssignableByBoxing(b, a); + if (ab && !ba) + return Prefer.B; + if (ba && !ab) + return Prefer.A; + if (ab && ba) + return a.isPrimitive() ? Prefer.A : Prefer.B; + + ab = isAssignableByCasting(a, b); + ba = isAssignableByCasting(b, a); + if (ab && !ba) + return Prefer.B; + if (ba && !ab) + return Prefer.A; + + return Prefer.DISJOINT; +} + +private static Prefer compare(Class a, Class b, Class argType, Invoking invoking){ + if (argType == null) + { + if (a.isPrimitive() && !b.isPrimitive()) + return Prefer.B; + if (!a.isPrimitive() && b.isPrimitive()) + return Prefer.A; + return Prefer.TIED; + } + else if (invoking.b || argType != Object.class) + { + AcceptBy aAcc = isAcceptableBy(a, argType, invoking); + AcceptBy bAcc = isAcceptableBy(b, argType, invoking); + switch(aAcc){ + case TYPE: + switch(bAcc){ + case TYPE: return compare(a, b); + default: return Prefer.A; + } + case BOXING: + switch(bAcc){ + case TYPE: return Prefer.B; + case BOXING: return compare(a, b); + default: return Prefer.A; + } + default: + switch(bAcc){ + case TYPE: + case BOXING: return Prefer.B; + default: return compare(a, b).other(); // casting to both, prefer wider + } + } + } + else + { + // wild card comparison, prefer wider + return compare(a, b).other(); + } +} + +/** + * Indicated which of two members are preferable within the context of the + * provided argument types and whether this is for invocation or compilation. + */ +private static Prefer compare(T a, T b, Class[] argTypes, Invoking invoking){ + Prefer ret = null; + + if (argTypes.length == 0) + { + ret = compare(getReturnType(a), getReturnType(b)); + } + else + { + Class[] as = getParameterTypes(a); + Class[] bs = getParameterTypes(b); + + for (int i = 0; i < argTypes.length; i++) + { + Prefer p = compare(as[i], bs[i], argTypes[i], invoking); + if (p == Prefer.DISJOINT) + return p; + else if (ret == null) + ret = p; + else if (ret == Prefer.TIED) + ret = p; + else if (ret == Prefer.A && p == Prefer.B) + return Prefer.AMBIGUOUS; + else if (ret == Prefer.B && p == Prefer.A) + return Prefer.AMBIGUOUS; + } + } + + // attempt tie-breaking + if (ret == Prefer.TIED) + { + // for identical parameters, prefer narrower covariant return type + if (Arrays.equals(getParameterTypes(a), getParameterTypes(b))) + { + ret = compare(getReturnType(a), getReturnType(b)); + // for identical methods (e.g., one from interface, one from class) pick arbitrarily + if (ret == Prefer.TIED && a instanceof Method && b instanceof Method) + ret = Prefer.A; + } + } + + return ret; +} + +/** + * Selects the best member for the given arguments. If no single best can be + * determined from multiple options, an IllegalArgumentException is thrown + * during invocation, otherwise null is returned. + */ +private static T selectBestMatch(List members, Class[] argTypes, Invoking invoking){ + T best = null; + boolean tied = false; + for (T member : members) + { + if (best == null) + { + best = member; + } + else + { + Prefer pref = compare(best, member, argTypes, invoking); + if (pref == Prefer.A) + { + continue; + } + else if (pref == Prefer.B) + { + best = member; + tied = false; + } + else + { + tied = true; + } + } + } + + if (tied) + { + if (invoking.b) + { + if (best instanceof Method) + { + Method m = ((Method)best); + Class c = m.getDeclaringClass(); + throw new IllegalArgumentException("Found multiple "+m.getName()+" methods in "+c.getName()+" for argtypes "+toString(argTypes)); + } + else + { + Constructor ctor = ((Constructor)best); + Class c = ctor.getDeclaringClass(); + throw new IllegalArgumentException("Found multiple constructors in "+c.getName()+" for argtypes "+toString(argTypes)); + } + } + else + return null; + } + + return best; +} + +/** + * Static methods for testing overload resolution. See clojure.test-clojure.reflector + */ +public static class Test{ + public static Method getMatchingForInvoke(Class c, String methodName, Class[] argTypes){ + try + { + return getMatchingMethod(c, methodName, argTypes, Statics.T, Invoking.T); + } + catch (IllegalArgumentException e) + { + if (e.getMessage().startsWith("No matching") + || e.getMessage().startsWith("Found multiple")) + return null; + throw e; + } + } + + public static Class NumberOrString(Number x){return Number.class;} + public static Class NumberOrString(String x){return String.class;} + + public static Class ObjectOrNumber(Object x){return Object.class;} + public static Class ObjectOrNumber(Number x){return Number.class;} + + public static Class NumberOrLong(Number x){return Number.class;} + public static Class NumberOrLong(Long x){return Long.class;} + + public static Class LongIntOrObjectInt(long x, int y){return long.class;} + public static Class LongIntOrObjectInt(Object x, int y){return Object.class;} + + public static Class ObjectLongOrNumberNumber(Object x, long y){return Object.class;} + public static Class ObjectLongOrNumberNumber(Number x, Number y){return Number.class;} + + public static Class ByteOrInt(byte x){return byte.class;} + public static Class ByteOrInt(int x){return int.class;} + +} + } diff --git a/src/script/run_tests.clj b/src/script/run_tests.clj index ef29cd4..17033dc 100644 --- a/src/script/run_tests.clj +++ b/src/script/run_tests.clj @@ -39,6 +39,7 @@ clojure.test-clojure.printer clojure.test-clojure.protocols clojure.test-clojure.reader clojure.test-clojure.reflect +clojure.test-clojure.reflector clojure.test-clojure.refs clojure.test-clojure.repl clojure.test-clojure.rt diff --git a/test/clojure/test_clojure/reflector.clj b/test/clojure/test_clojure/reflector.clj new file mode 100644 index 0000000..8e250f3 --- /dev/null +++ b/test/clojure/test_clojure/reflector.clj @@ -0,0 +1,79 @@ +(ns clojure.test-clojure.reflector + (:use clojure.test) + (:import [clojure.lang Reflector Reflector$Test])) + +(defn compiletime [name & arg-types] + (Reflector/getMatchingStaticMethod Reflector$Test name (into-array Class arg-types))) + +(defn runtime [name & arg-types] + (Reflector$Test/getMatchingForInvoke Reflector$Test name (into-array Class arg-types))) + +(defn m? [c m] + (if m + (let [p (.getParameterTypes m)] + (= c (first p))) + (nil? c))) + +(deftest prim-conversion-casting + (are [input result] (m? result (compiletime "ByteOrInt" input)) + Object Integer/TYPE ; by wild card, prefer most general covariant param type + Number nil + Double nil + Float nil + Long nil + Integer Integer/TYPE ; by unboxing + Short Integer/TYPE ; by unboxing to a wider primitive + Byte Byte/TYPE ; by unboxing + Double/TYPE nil + Float/TYPE nil + Long/TYPE Integer/TYPE ; by casting + Integer/TYPE Integer/TYPE + Short/TYPE Integer/TYPE ; by widening type + Byte/TYPE Byte/TYPE ; by type + )) + +(deftest disjoint-params + (is (nil? (compiletime "NumberOrString" Object))) + (is (nil? (runtime "NumberOrString" Object)))) + +(deftest type-specific-params + (is (m? Object (compiletime "ObjectOrNumber" Object))) + (is (m? Number (compiletime "ObjectOrNumber" Number))) + (is (m? Object (compiletime "ObjectOrNumber" String)))) + +(deftest type-hierarchy-params + (are [input result] (m? result (compiletime "NumberOrLong" input)) + Object Number ; by wild card, prefer most general covariant param type + Number Number + Double Number + Float Number + Long Long + Integer Number + Short Number + Byte Number + Double/TYPE Number + Float/TYPE Number + Long/TYPE Long + Integer/TYPE Number + Short/TYPE Number + Byte/TYPE Number + )) + +(deftest prim-obj-nums + (are [x y result] (m? result (compiletime "LongIntOrObjectInt" x y)) + Object Object Object ; by type,wildcard + Number Object Object ; by type,wildcard + Number Long/TYPE Object ; by type,casting + Double/TYPE Integer/TYPE Object ; by type,type + Long/TYPE Long/TYPE Long/TYPE ; by type,casting + )) + +(deftest wierd + (are [x y result] (m? result (compiletime "ObjectLongOrNumberNumber" x y)) + Object Object nil ; ambiguous + Object Number Number ; by wildcard,type + Number Object Number ; by type,wilcard + Number Long/TYPE Object ; by type,type + Number nil Number ; by type,type + Long/TYPE Long/TYPE nil ; ambiguous + )) -- 1.7.3.5