diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index 6324456fa1..0bae0f6d54 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -1165,12 +1165,17 @@ static class QualifiedMethodExpr implements Expr { private final String methodName; private final MethodKind kind; private final Class tagClass; + private final StaticFieldExpr fieldOverload; private enum MethodKind { CTOR, INSTANCE, STATIC } - public QualifiedMethodExpr(Class methodClass, Symbol sym){ + public QualifiedMethodExpr(Class methodClass, Symbol sym) { + this(methodClass, sym, null); + } + + public QualifiedMethodExpr(Class methodClass, Symbol sym, StaticFieldExpr fieldOL) { c = methodClass; methodSymbol = sym; tagClass = tagOf(sym) != null ? HostExpr.tagToClass(tagOf(sym)) : AFn.class; @@ -1187,18 +1192,29 @@ else if(sym.name.equals("new")) { kind = MethodKind.STATIC; methodName = sym.name; } + fieldOverload = fieldOL; + } + + private boolean preferOverloadedField() { + return fieldOverload != null && paramTagsOf(methodSymbol) == null; } // Expr impl - invocation, convert to fn expr @Override public Object eval() { - return buildThunk(C.EVAL, this).eval(); + if(preferOverloadedField()) + return fieldOverload.eval(); + else + return buildThunk(C.EVAL, this).eval(); } @Override public void emit(C context, ObjExpr objx, GeneratorAdapter gen) { - buildThunk(context, this).emit(context, objx, gen); + if(preferOverloadedField()) + fieldOverload.emit(context, objx, gen); + else + buildThunk(context, this).emit(context, objx, gen); } // Expr impl - method value, always an AFn @@ -1258,18 +1274,9 @@ private static Set aritySet(Class c, String methodName, MethodKind kind) { return res; } - // Returns a list of methods or ctors matching the name and kind given. - // Otherwise, will throw if the information provided results in no matches - private static List methodsWithName(Class c, String methodName, MethodKind kind) { - if (kind == MethodKind.CTOR) { - List ctors = Arrays.asList(c.getConstructors()); - if(ctors.isEmpty()) - throw noMethodWithNameException(c, methodName, kind); - return ctors; - } - + public static List methodOverloads(Class c, String methodName, MethodKind kind) { final Executable[] methods = c.getMethods(); - List res = Arrays.stream(methods) + return Arrays.stream(methods) .filter(m -> m.getName().equals(methodName)) .filter(m -> { switch(kind) { @@ -1279,6 +1286,19 @@ private static List methodsWithName(Class c, String methodName, Meth } }) .collect(Collectors.toList()); + } + + // Returns a list of methods or ctors matching the name and kind given. + // Otherwise, will throw if the information provided results in no matches + private static List methodsWithName(Class c, String methodName, MethodKind kind) { + if (kind == MethodKind.CTOR) { + List ctors = Arrays.asList(c.getConstructors()); + if(ctors.isEmpty()) + throw noMethodWithNameException(c, methodName, kind); + return ctors; + } + + List res = methodOverloads(c, methodName, kind); if(res.isEmpty()) throw noMethodWithNameException(c, methodName, kind); @@ -4115,7 +4135,7 @@ static Object sigTag(int argcount, Var v){ return tagOf(sig); } return null; - } + } public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) { this.source = source; @@ -4359,18 +4379,26 @@ static public Expr parse(C context, ISeq form) { (KeywordExpr) fexpr, target); } - // Preserving the existing static field bug that replaces a reference in parens with - // the field itself rather than trying to invoke the value in the field. This is - // an exception to the uniform Class/member qualification per CLJ-2806 ticket. - if(fexpr instanceof StaticFieldExpr) - return fexpr; - PersistentVector args = PersistentVector.EMPTY; for(ISeq s = RT.seq(form.next()); s != null; s = s.next()) { args = args.cons(analyze(context, s.first())); } + // Preserving the existing static field syntax that replaces a reference in parens with + // the field itself rather than trying to invoke the value in the field. This is + // an exception to the uniform Class/member qualification per CLJ-2806 ticket. + if(fexpr instanceof StaticFieldExpr) + { + if(RT.count(args) == 0) + return fexpr; + else + throw new IllegalArgumentException("No matching method " + + ((StaticFieldExpr) fexpr).fieldName + + " found taking " + RT.count(args) + " args for " + + ((StaticFieldExpr) fexpr).c); + } + if(fexpr instanceof QualifiedMethodExpr) return toHostExpr((QualifiedMethodExpr)fexpr, (String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), tailPosition, args); @@ -7830,7 +7858,14 @@ private static Expr analyzeSymbol(Symbol sym) { if(c != null) { if(Reflector.getField(c, sym.name, true) != null) - return new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag); + { + List maybeOverloads = QualifiedMethodExpr.methodOverloads(c, sym.name, QualifiedMethodExpr.MethodKind.STATIC); + + if(maybeOverloads.isEmpty()) + return new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag); + else + return new QualifiedMethodExpr(c, sym, new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag)); + } else return new QualifiedMethodExpr(c, sym); } diff --git a/test/clojure/test_clojure/param_tags.clj b/test/clojure/test_clojure/param_tags.clj index fee2d1c53e..bccd3ef127 100644 --- a/test/clojure/test_clojure/param_tags.clj +++ b/test/clojure/test_clojure/param_tags.clj @@ -170,6 +170,30 @@ :instance (let [{:keys [expected actual]} (exercise-instance-method m)] (is (= expected actual)))))) +(deftest field-overloads-method-CLJ-2899-regression + (testing "overloaded in value position" + (is (= "static-field" clojure.test.SwissArmy/doppelganger))) + + (testing "overloaded in value position, w/paramtags" + (is (= "" (apply ^[] clojure.test.SwissArmy/doppelganger [])))) + + (testing "overloaded, invoke no args" + (is (= "" (clojure.test.SwissArmy/doppelganger)))) + + (testing "overloaded, invoke w/args" + (is (= "int-int-long" (clojure.test.SwissArmy/doppelganger (int 1) (int 2) (long 42))))) + + (testing "non-overloaded, field holds IFn, invoke w/args fails" + (is (thrown? Exception (eval '(clojure.test.SwissArmy/idFn 42)))) + (is (= #'clojure.core/identity clojure.test.SwissArmy/idFn))) + + (testing "non-overloaded, field holds IFn, invoke no args" + (is (= #'clojure.core/identity (clojure.test.SwissArmy/idFn)))) + + (testing "instance method overloads" + (is (= "int-int" (clojure.test.SwissArmy/.doppelganger (clojure.test.SwissArmy/new) (int 1) (int 2)))) + (is (= "int-int" (apply clojure.test.SwissArmy/.doppelganger (clojure.test.SwissArmy/new) (int 1) (int 2) []))))) + (defmacro arg-tags-called-in-macro [a-type b-type a b] `(^[~a-type ~b-type] SwissArmy/staticArityOverloadMethod ~a ~b)) diff --git a/test/java/clojure/test/SwissArmy.java b/test/java/clojure/test/SwissArmy.java index 339c3723c4..f38e2d44f5 100644 --- a/test/java/clojure/test/SwissArmy.java +++ b/test/java/clojure/test/SwissArmy.java @@ -1,7 +1,12 @@ package clojure.test; +import clojure.java.api.Clojure; +import clojure.lang.IFn; + public class SwissArmy { + public static String doppelganger = "static-field"; public String ctorId; + public static IFn idFn = Clojure.var("clojure.core", "identity"); public SwissArmy() {this.ctorId = "1";} public SwissArmy(int a, long b) {this.ctorId = "2";} @@ -36,4 +41,5 @@ public class SwissArmy { public static String staticArityOverloadMethod(int a, int b) {return "int-int";} public static String staticArityOverloadMethod(int a, int b, int c) {return "int-int-int";} public static String doppelganger(int a, int b, long c) {return "int-int-long";} -} \ No newline at end of file + public static String doppelganger() {return "";} +}