Skip to content

Commit 8dc2c99

Browse files
committed
Update to Java commit 3296c3c (): CLJ-2914: Throw if QualifiedMethodExpr given no target
1 parent aaa1217 commit 8dc2c99

File tree

3 files changed

+257
-1
lines changed

3 files changed

+257
-1
lines changed

Clojure/Clojure.Tests/clojure/test_clojure/java_interop.clj

Lines changed: 247 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,4 +648,250 @@
648648

649649
(deftest test-boxing-prevention-when-compiling-statements
650650
(is (= 1 (.get (doto (AtomicInteger. 0) inc-atomic-int))))
651-
(is (= 1 (.get (doto (AtomicLong. 0) inc-atomic-long)))))
651+
(is (= 1 (.get (doto (AtomicLong. 0) inc-atomic-long)))))
652+
653+
(deftest array-type-symbols
654+
(is (= long/1 (class (make-array Int64 0)))) ;;; Long/TYPE
655+
(is (= int/1 (class (make-array Int32 0)))) ;;; Integer/TYPE
656+
(is (= double/1 (class (make-array Double 0)))) ;;; Double/TYPE
657+
(is (= short/1 (class (make-array Int16 0)))) ;;; Short/TYPE
658+
(is (= boolean/1 (class (make-array Boolean 0)))) ;;; Boolean/TYPE
659+
(is (= byte/1 (class (make-array Byte 0)))) ;;; Byte/TYPE
660+
(is (= float/1 (class (make-array Single 0)))) ;;; Float/TYPE
661+
(is (= String/1 (class (make-array String 0))))
662+
(is (= System.String/1 (class (make-array String 0)))) ;;; java.lang.String
663+
(is (= System.Guid/1 (class (make-array System.Guid 0)))) ;;; java.util.UUID java.util.UUID
664+
(is (= `byte/1 'byte/1))
665+
(is (= `byte/3 'byte/3))
666+
(is (= `System.Guid/1 'System.Guid/1)) ;;; java.util.UUID java.util.UUID
667+
(is (= `String/1 'System.String/1)) ;;; java.lang.String
668+
(is (= `System.String/1 'System.String/1)) ;;; java.lang.String java.lang.String
669+
(is (= ['long/2] `[~'long/2])))
670+
671+
(comment "Mostly Java FI"
672+
(defn make-test-files []
673+
(let [id (str (UUID/randomUUID))
674+
temp-1 (java.io.File/createTempFile (str "test-1-" id)".edn")
675+
temp-2 (java.io.File/createTempFile "test-2"".xml")
676+
temp-3 (java.io.File/createTempFile (str "test-3-" id)".edn")
677+
dir (File. (.getParent temp-3))]
678+
{:dir dir :file-id id}))
679+
680+
(defn return-long ^long []
681+
(let [^java.util.function.ToLongFunction f (fn ^long [x] 1)]
682+
(Long/highestOneBit (.applyAsLong f :x))))
683+
684+
(deftest clojure-fn-as-java-fn
685+
;; pass Clojure fn as Java Predicate
686+
(let [coll (java.util.ArrayList. [1 2 3 4 5])]
687+
(is (true? (.removeIf coll even?)))
688+
(is (= coll [1 3 5])))
689+
690+
;; binding type hint triggers coercion
691+
(is (instance? FileFilter
692+
(let [^FileFilter ff (fn [f] true)] ff)))
693+
694+
;; coercion in let - reflection has types that should work
695+
(let [{:keys [dir file-id]} (make-test-files)
696+
^FileFilter ff (fn [^File f]
697+
(str/includes? (.getName f) file-id))
698+
filtered (.listFiles dir ff)]
699+
(is (= 2 (count filtered))))
700+
701+
;; coercion in let
702+
(let [{:keys [dir file-id]} (make-test-files)
703+
^FileFilter ff (fn [^File f]
704+
(str/includes? (.getName f) file-id))
705+
filtered (.listFiles ^File dir ff)]
706+
(is (= 2 (count filtered))))
707+
708+
;;; resolve method ambiguity using member symbol and param-tags
709+
(let [{:keys [dir file-id]} (make-test-files)
710+
^FileFilter ff (fn [^File f]
711+
(str/includes? (.getName f) file-id))
712+
filtered (^[FileFilter] File/.listFiles dir ff)]
713+
(is (= 2 (count filtered))))
714+
715+
(defn files-with-ext [^File dir ext]
716+
(vec (.list dir ^FilenameFilter #(str/ends-with? % ext))))
717+
718+
(let [{:keys [dir file-id]} (make-test-files)
719+
^FilenameFilter ff (fn [dir file-name]
720+
(str/includes? file-name file-id))
721+
filtered (.list ^File dir ff)]
722+
(is (= 2 (count filtered))))
723+
724+
(let [^java.util.function.DoubleToLongFunction f (fn [d] (int d))]
725+
(is (instance? java.util.function.DoubleToLongFunction f))
726+
(is (= 10 (.applyAsLong f (double 10.6)))))
727+
728+
(let [^java.util.function.IntConsumer f (fn [i] nil)]
729+
(is (nil? (.accept f 42))))
730+
731+
(let [^java.util.function.IntPredicate f (fn [i] true)]
732+
(is (true? (.test f 42))))
733+
734+
(let [arr (java.util.ArrayList. [1 2 3 4 5])
735+
^java.util.function.ObjDoubleConsumer f (fn [arr i] nil)]
736+
(is (nil? (.accept f arr 42))))
737+
738+
(let [f (constantly 100)
739+
^Runnable g f]
740+
(is (identical? f g) "has been unintentionally adapted"))
741+
742+
(let [^java.util.function.Predicate pred even?
743+
coll1 (java.util.ArrayList. [1 2 3 4 5])
744+
coll2 (java.util.ArrayList. [6 7 8 9 10])]
745+
(is (instance? java.util.function.Predicate pred))
746+
(is (true? (.removeIf coll1 pred)))
747+
(is (= coll1 [1 3 5]))
748+
(is (true? (.removeIf coll2 pred)))
749+
(is (= coll2 [7 9])))
750+
751+
(let [^java.util.function.Predicate pred even?
752+
coll1 (java.util.ArrayList. [1 2 3 4 5])
753+
cup-fn (java.util.ArrayList. [1 2 3 4 5])]
754+
(is (instance? java.util.function.Predicate pred))
755+
(is (true? (.removeIf coll1 pred)))
756+
(is (= coll1 [1 3 5]))
757+
(is (true? (.removeIf cup-fn pred)))
758+
(is (= cup-fn [1 3 5])))
759+
760+
(should-not-reflect #(clojure.test-clojure.java-interop/return-long))
761+
762+
;; FI in class constructor
763+
(let [^java.util.function.Predicate hinted-pred (fn [i] (> i 0))
764+
clj-pred (fn [i] (> i 0))
765+
fi-constructor-1 (FIConstructor. hinted-pred)
766+
fi-constructor-2 (FIConstructor. clj-pred)
767+
fi-constructor-3 (FIConstructor. (fn [i] (> i 0)))]
768+
(is (= [1 2] (.numbers fi-constructor-1)))
769+
(is (= [1 2] (.numbers fi-constructor-2)))
770+
(is (= [1 2] (.numbers fi-constructor-3))))
771+
772+
;; FI as arg to static
773+
(let [^java.util.function.Predicate hinted-pred (fn [i] (> i 0))
774+
res (FIStatic/numbers hinted-pred)]
775+
(is (= [1 2] res))))
776+
777+
(deftest eval-in-place-supplier-instance
778+
(def stream (java.util.stream.Stream/generate ^java.util.function.Supplier (atom 42)))
779+
(is (instance? java.util.stream.Stream stream)))
780+
781+
(deftest eval-in-place-as-java-fn
782+
(def filtered-list (.removeIf (java.util.ArrayList. [1 2 3 4 5]) even?))
783+
(is (true? filtered-list))
784+
785+
(def fi-constructor-numbers (.numbers (FIConstructor. (fn [i] (> i 0)))))
786+
(is (= [1 2] fi-constructor-numbers))
787+
788+
(def fi-static (FIStatic/numbers (fn [i] (< i 0))))
789+
(is (= [-2 -1] fi-static)))
790+
791+
;; newDirectoryStream is overloaded, takes ^[Path String] or ^[Path DirectoryStream$Filter]
792+
;; so this method will reflect
793+
(defn get-dir-stream [^java.nio.file.Path dir-path glob-pattern]
794+
(let [path (.toPath (java.io.File. dir-path))]
795+
(java.nio.file.Files/newDirectoryStream path glob-pattern)))
796+
797+
(deftest test-reflection-to-overloaded-method-taking-FI
798+
;; all of these should resolve at runtime in reflection
799+
(is (not (nil? (get-dir-stream "." "*"))))
800+
(is (not (nil? (get-dir-stream "." (reify java.nio.file.DirectoryStream$Filter (accept [_ path] (.isDirectory (.toFile path))))))))
801+
;; this one gets FI converted from IFn to DirectoryStream$Filter
802+
(is (not (nil? (get-dir-stream "." (fn [^java.nio.file.Path path] (.isDirectory (.toFile path))))))))
803+
804+
;; we only support FI invoke coercion up to 10 args, this has 11
805+
(definterface ^{java.lang.FunctionalInterface true} FIWontWork
806+
(invoke [a b c d e f g h i j k]))
807+
808+
(definterface ReceivesFI
809+
(call [^clojure.test_clojure.java_interop.FIWontWork fi]))
810+
811+
(deftest test-reify-to-FI-allowed
812+
;; throws because there is no 11-arity invoker method and thus it is not possible to coerce
813+
(is (thrown? ClassCastException
814+
(eval '(let [^clojure.test_clojure.java_interop.FIWontWork f
815+
(fn [p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11] p11)]
816+
(.invoke f 1 2 3 4 5 6 7 8 9 10 11)))))
817+
818+
(let [r (reify clojure.test_clojure.java_interop.ReceivesFI
819+
(call [_ fi] (.invoke fi 0 0 0 0 0 0 0 0 0 0 1)))]
820+
821+
;; doesn't throw at compilation time, but throws at runtime
822+
;; because IFn cannot be implicitly converted
823+
(is (thrown? ClassCastException
824+
(.call r (fn [a b c d e f g h i j k] k))))
825+
826+
;; works because the reify implements the FI, no conversion necessary
827+
(is (= 1 (.call r (reify clojure.test_clojure.java_interop.FIWontWork (invoke [_ a b c d e f g h i j k] k)))))))
828+
829+
(definterface ^{java.lang.FunctionalInterface true} FIPrims
830+
(^long invoke [^long a ^long b ^long c ^long d]))
831+
832+
(definterface ReceivesFIPrims
833+
(call [^clojure.test_clojure.java_interop.FIPrims fi]))
834+
835+
(deftest test-match-prim-args-only-to-2
836+
(let [r (reify clojure.test_clojure.java_interop.ReceivesFIPrims
837+
(call [_ fi] (.invoke fi 1 2 3 4)))]
838+
(is (= 4 (.call r (fn [^long a ^long b ^long c ^long d] d))))))
839+
840+
(deftest test-invoke-fiprim-rets
841+
(let [^clojure.test_clojure.java_interop.FIPrims f (fn [a b c d] a)]
842+
(is (instance? clojure.test_clojure.java_interop.FIPrims f))
843+
(is (= 1 (.invoke f 1 2 3 4))))
844+
845+
(is (= "LLL" (AdapterExerciser/.methodLLL (AdapterExerciser.) (fn ^long [^long a ^long b]))))
846+
(is (= "OOOO" (AdapterExerciser/.methodOOOO (AdapterExerciser.) (fn ^long [^long a ^long b ^long c]))))
847+
)
848+
849+
(deftest test-null-reify
850+
(is (= "null" ((fn [x] (FIStatic/allowsNullFI x)) nil))))
851+
852+
(deftest test-FI-subtype
853+
(is (= [1 2 3 4 5] (->> (java.util.stream.Stream/iterate 1 inc) stream-seq! (take 5)))))
854+
855+
(deftest class-methods-with-fi-args
856+
(testing "Constructor accepting FI arg, provided overloaded static class FI"
857+
(let [fi (FunctionalTester. "Constructor" 0 FunctionalTester/getChar)]
858+
(is (= \C (.testVar fi)))))
859+
860+
(testing "Instance method accepting FI arg, provided overloaded static class FI"
861+
(let [fi (FunctionalTester. "asf" 0 FunctionalTester/getChar)]
862+
(.instanceMethodWithFIArg fi "Instance" 0 FunctionalTester/getChar)
863+
(is (= \I (.testVar fi)))))
864+
865+
(testing "Static method accepting FI arg, provided overloaded static class FI"
866+
(is (= \S (FunctionalTester/staticMethodWithFIArg "Static" 0 FunctionalTester/getChar)))))
867+
868+
;; call is reflective and one overload takes an FI (Supplier)
869+
(definterface TakesFIOverloaded
870+
(call [^java.util.function.Supplier s])
871+
(call [^String s]))
872+
873+
(deftest CLJ-2898-reified-objs-both-IFn-and-FI
874+
;; f is both IFn and FI (Supplier)
875+
(let [f (reify
876+
java.util.function.Supplier
877+
(get [_] 100)
878+
879+
clojure.lang.IFn
880+
(applyTo [_ _] 201)
881+
(invoke [_] 200))]
882+
883+
;; should not be adapted. use Supplier.get() impl on tl
884+
(is (= 100 (.get (ThreadLocal/withInitial f))))
885+
886+
(let [tfio (reify TakesFIOverloaded
887+
(call [_ ^java.util.function.Supplier o] (.get o))
888+
(call [_ ^String s] "string"))]
889+
;; reflective call to TakesFIOverloaded.call()
890+
;; as above, should not be adapted and use Supplier.get()
891+
(is (= 100 (.call tfio (identity f)))))))
892+
) ;; and commented out section
893+
894+
(deftest CLJ-2914-Qualified-Method-Expr-NPE
895+
(is (fails-with-cause? ArgumentException ;;; IllegalArgumentException
896+
#"Malformed method expression.*String/\.length"
897+
(eval '(fn [] (System.String/.length)))))) ;;; java.lang.String

Clojure/Clojure/CljCompiler/Ast/InvokeExpr.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,16 @@ fexpr is StaticFieldExpr sfe
241241

242242

243243
if (fexpr is QualifiedMethodExpr qmfexpr)
244+
{
245+
if (qmfexpr.Kind == QualifiedMethodExpr.EMethodKind.INSTANCE && RT.count(args) == 0)
246+
throw QualifiedMethodExpr.InstanceNoTargetException(qmfexpr);
247+
244248
return ToHostExpr(pcon,
245249
qmfexpr,
246250
Compiler.TagOf(form),
247251
tailPosition,
248252
form.next());
253+
}
249254

250255

251256
//if (args.count() > Compiler.MAX_POSITIONAL_ARITY)

Clojure/Clojure/CljCompiler/Ast/QualifiedMethodExpr.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,11 @@ private static ArgumentException ParamTagsDontResolveException(Type t, string me
318318
return new ArgumentException($"Error - param-tags {genericTypeArgs}{paramTags} insufficient to resolve {Compiler.MethodDescription(t, methodName)}");
319319
}
320320

321+
internal static ArgumentException InstanceNoTargetException(QualifiedMethodExpr qme)
322+
{
323+
return new ArgumentException($"Malformed method expression, expecting ({qme.MethodType.Name}/.{qme.MethodName} target...)");
324+
}
325+
321326

322327
#endregion
323328
}

0 commit comments

Comments
 (0)