From 9ae8d3dc73112e888da5353c09a3004fe84e1479 Mon Sep 17 00:00:00 2001 From: Dave Ray Date: Fri, 30 Aug 2013 22:12:44 -0700 Subject: [PATCH] Preserve metadata on fn and action macros Because they're macros, rx/fn and rx/action would lose metadata attached to them, in particular type hints which are slightly important to disambiguate overloaded Observable methods. Fixed. --- language-adaptors/rxjava-clojure/build.gradle | 1 + .../main/clojure/rx/lang/clojure/interop.clj | 10 ++- .../clojure/rx/lang/clojure/interop_test.clj | 61 +++++++++++++++++-- .../lang/clojure/interop/DummyObservable.java | 42 +++++++++++++ 4 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 language-adaptors/rxjava-clojure/src/test/java/rx/lang/clojure/interop/DummyObservable.java diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index ef1b2cf8e52..47472611c58 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -13,6 +13,7 @@ dependencies { tasks.compileExamplesClojure.classpath = files(tasks.compileClojure.destinationDir) + tasks.compileClojure.classpath +clojureTest.dependsOn compileTestJava /* * Clojure */ diff --git a/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj b/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj index de87e384dc9..d0d05a87147 100644 --- a/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj +++ b/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj @@ -66,11 +66,13 @@ " [& fn-form] + ; preserve metadata so type hints work ; have to qualify fn*. Otherwise bad things happen with the fn* special form in clojure - `(rx.lang.clojure.interop/fn* (clojure.core/fn ~@fn-form))) + (with-meta `(rx.lang.clojure.interop/fn* (clojure.core/fn ~@fn-form)) + (meta &form))) (defn action* - "Given function f, returns an object that implements rx.util.functions.Action0-9 + "Given function f, returns an object that implements rx.util.functions.Action0-3 by delegating to the given function. Example: @@ -93,6 +95,8 @@ " [& fn-form] - `(action* (clojure.core/fn ~@fn-form))) + ; preserve metadata so type hints work + (with-meta `(action* (clojure.core/fn ~@fn-form)) + (meta &form))) ;################################################################################ diff --git a/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj b/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj index 72dcadaa6ed..051044a17f3 100644 --- a/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj +++ b/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj @@ -3,7 +3,7 @@ [clojure.test :refer [deftest testing is]]) (:import [rx Observable] [rx.observables BlockingObservable] - )) + [rx.lang.clojure.interop DummyObservable])) (deftest test-fn* (testing "implements Func0-9" @@ -27,12 +27,38 @@ (is (= [1 2 3 4 5 6] (.call f 1 2 3 4 5 6))) (is (= [1 2 3 4 5 6 7] (.call f 1 2 3 4 5 6 7))) (is (= [1 2 3 4 5 6 7 8] (.call f 1 2 3 4 5 6 7 8))) - (is (= [1 2 3 4 5 6 7 8 9] (.call f 1 2 3 4 5 6 7 8 9)))))) + (is (= [1 2 3 4 5 6 7 8 9] (.call f 1 2 3 4 5 6 7 8 9))))) + + (let [dummy (DummyObservable.)] + (testing "preserves metadata applied to form" + ; No type hint, picks Object overload + (is (= "Object" + (.call dummy (rx/fn* +)))) + (is (= "rx.util.functions.Func1" + (.call dummy + ^rx.util.functions.Func1 (rx/fn* +)))) + (is (= "rx.util.functions.Func2" + (.call dummy + ^rx.util.functions.Func2 (rx/fn* *))))))) (deftest test-fn (testing "makes appropriate Func*" (let [f (rx/fn [a b c] (println "test-fn") (+ a b c))] - (is (= 6 (.call f 1 2 3)))))) + (is (= 6 (.call f 1 2 3))))) + + (let [dummy (DummyObservable.)] + (testing "preserves metadata applied to form" + ; No type hint, picks Object overload + (is (= "Object" + (.call dummy + (rx/fn [a] a)))) + (is (= "rx.util.functions.Func1" + (.call dummy + ^rx.util.functions.Func1 (rx/fn [a] a)))) + (is (= "rx.util.functions.Func2" + (.call dummy + ^rx.util.functions.Func2 (rx/fn [a b] (* a b)))))))) + (deftest test-fnN* (testing "implements FuncN" @@ -51,14 +77,39 @@ (.call a 1) (.call a 1 2) (.call a 1 2 3) - (is (= [[] [1] [1 2] [1 2 3]]))))) + (is (= [[] [1] [1 2] [1 2 3]])))) + (let [dummy (DummyObservable.)] + (testing "preserves metadata applied to form" + ; no meta, picks Object overload + (is (= "Object" + (.call dummy + (rx/action* println)))) + (is (= "rx.util.functions.Action1" + (.call dummy + ^rx.util.functions.Action1 (rx/action* println)))) + (is (= "rx.util.functions.Action2" + (.call dummy + ^rx.util.functions.Action2 (rx/action* prn))))))) (deftest test-action (testing "makes appropriate Action*" (let [called (atom nil) a (rx/action [a b] (reset! called [a b]))] (.call a 9 10) - (is (= [9 10] @called))))) + (is (= [9 10] @called)))) + + (let [dummy (DummyObservable.)] + (testing "preserves metadata applied to form" + ; no meta, picks Object overload + (is (= "Object" + (.call dummy + (rx/action [a] a)))) + (is (= "rx.util.functions.Action1" + (.call dummy + ^rx.util.functions.Action1 (rx/action [a] a)))) + (is (= "rx.util.functions.Action2" + (.call dummy + ^rx.util.functions.Action2 (rx/action [a b] (* a b)))))))) (deftest test-basic-usage diff --git a/language-adaptors/rxjava-clojure/src/test/java/rx/lang/clojure/interop/DummyObservable.java b/language-adaptors/rxjava-clojure/src/test/java/rx/lang/clojure/interop/DummyObservable.java new file mode 100644 index 00000000000..107c0a90536 --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/test/java/rx/lang/clojure/interop/DummyObservable.java @@ -0,0 +1,42 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.clojure.interop; + +// Dummy class with some overloads to make sure that type hinting works +// correctly with the fn and action macros. Used only for testing. +public class DummyObservable { + + public String call(Object f) { + return "Object"; + } + + public String call(rx.util.functions.Func1 f) { + return "rx.util.functions.Func1"; + } + + public String call(rx.util.functions.Func2 f) { + return "rx.util.functions.Func2"; + } + + public String call(rx.util.functions.Action1 f) { + return "rx.util.functions.Action1"; + } + + public String call(rx.util.functions.Action2 f) { + return "rx.util.functions.Action2"; + } + +}