Skip to content

Commit 5dbf295

Browse files
[java] Recognize Java-style printed member references
1 parent f45746e commit 5dbf295

File tree

4 files changed

+103
-75
lines changed

4 files changed

+103
-75
lines changed

src/orchard/info.clj

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,8 @@
6363
(m/special-sym-meta sym)
6464
;; it's a var
6565
(some-> ns (m/resolve-var sym) (m/var-meta var-meta-allowlist))
66-
;; it's a Java constructor/static member symbol
66+
;; it's a Java class/constructor/member symbol
6767
(some-> ns (java/resolve-symbol sym))
68-
;; it's a Java class/record type symbol
69-
(some-> ns (java/resolve-type unqualified-sym))
7068
;; it's an alias for another ns
7169
(some-> ns (m/resolve-aliases) (get sym) (m/ns-meta))
7270
;; We use :unqualified-sym *exclusively* here because because our :ns is
@@ -147,11 +145,10 @@
147145
only applies to `:clj` since for `:cljs` there's no allowlisting)."
148146
[params]
149147
(let [params (normalize-params params)
150-
dialect (:dialect params)
151-
meta (cond
152-
(= dialect :clj) (clj-meta params)
153-
(= dialect :cljs) (cljs-meta params))]
154-
meta))
148+
dialect (:dialect params)]
149+
(cond
150+
(= dialect :clj) (clj-meta params)
151+
(= dialect :cljs) (cljs-meta params))))
155152

156153
(defn info
157154
"Provide the info map for the input ns and sym.

src/orchard/java.clj

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -334,20 +334,6 @@
334334
;; specific query: type information for a class name, and member information for
335335
;; a class/member combination.
336336

337-
(defn type-info
338-
"For the class or interface symbol, return Java type info. If the type has
339-
defined constructors, the line and column returned will be for the first of
340-
these for more convenient `jump` navigation."
341-
[class]
342-
(let [info (class-info class)
343-
ctor (->> (get-in info [:members class])
344-
(vals)
345-
(sort-by :line)
346-
(filter :line)
347-
(first))]
348-
(merge (dissoc info :members)
349-
(select-keys ctor [:line :column]))))
350-
351337
(defn member-info
352338
"For the class and member symbols, return Java member info. If the member is
353339
overloaded, line number and javadoc signature are that of the first overload.
@@ -413,42 +399,63 @@
413399
(keep #(member-info (-> ^Class % .getName symbol) sym))
414400
(distinct))))
415401

416-
(defn trim-one-dot
417-
[s]
418-
(string/replace s #"^\.|\.$" ""))
402+
(defn resolve-constructor
403+
"Given namespace and classname symbols, search the first constructor for the
404+
given class and return its info."
405+
[ns class-sym]
406+
(when-let [info (resolve-class ns class-sym)]
407+
(when-let [ctors (->> (get-in info [:members (:class info)])
408+
vals
409+
(sort-by :line)
410+
seq)]
411+
(merge (dissoc info :members)
412+
(select-keys (some #(when (:line %) %) ctors)
413+
[:line :column])))))
419414

420415
(defn resolve-symbol
421-
"Return the info map for a Java member symbol.
422-
423-
Constructors and static calls are resolved to the class
424-
unambiguously. Instance members are resolved unambiguously if defined
425-
by only one imported class. If multiple imported classes have a member
426-
by that name, a map of class names to member info is returned as
427-
`:candidates`."
416+
"Return the info map for a Java member symbol. The following Java symbols are
417+
supported:
418+
- Java classes (`Thread` and `java.lang.Thread`)
419+
- Java classes with module prefix (`java.base/java.lang.Thread`)
420+
- constructors (`Thread.` and `java.lang.Thread.`)
421+
- static members (`Thread/currentThread`)
422+
- instance members for classes imported into `ns` (`.start`)
423+
- qualified instance members (`Thread/.start`)
424+
- Java-style printed member references (`clojure.lang.AFn.run`)
425+
426+
If multiple imported classes have a non-qualified instance member by that
427+
name, a map of class names to member info is returned as `:candidates`."
428428
[ns sym]
429429
{:pre [(every? symbol? [ns sym])]}
430-
(let [sym (-> sym str trim-one-dot)
431-
sym* (symbol sym)
432-
[class static-member] (->> (string/split sym #"/" 2)
433-
(map #(when % (symbol %))))]
434-
(if-let [c (resolve-class ns class)]
435-
(when static-member
436-
(member-info (:class c) static-member)) ; SomeClass/methodCall
437-
(when-let [ms (seq (resolve-member ns sym*))] ; methodCall
438-
(if (= 1 (count ms))
439-
(first ms)
440-
{:candidates (zipmap (map :class ms) ms)})))))
441-
442-
(defn resolve-type
443-
"Return type info, for a Java class, interface or record."
444-
[ns sym]
445-
(let [sym (-> sym str trim-one-dot)
446-
sym-split (->> (string/split sym #"/" 2)
447-
(map #(when % (symbol %))))]
448-
(some->> (first sym-split)
449-
(resolve-class ns)
450-
:class
451-
type-info)))
430+
(let [s (str sym)]
431+
(or (when-let [[_ klass] (re-matches #"(.+)\." s)]
432+
(resolve-constructor ns (symbol klass)))
433+
434+
(resolve-class ns (symbol s)) ;; When s is a class symbol
435+
436+
(when-let [[_ instance-member] (re-matches #"\.(.+)" s)]
437+
(let [ms (->> (resolve-member ns (symbol instance-member))
438+
(remove #(:static (:modifiers %))))]
439+
(condp = (count ms)
440+
0 nil
441+
1 (first ms)
442+
{:candidates (zipmap (map :class ms) ms)})))
443+
444+
(when-let [[_ klass member] (re-matches #"(.+)/\.?([^/]+)" s)]
445+
(when-let [c (resolve-class ns (symbol klass))]
446+
(member-info (:class c) (symbol member))))
447+
448+
;; Special case: java classes with module prefix.
449+
(when-let [[_ klass] (re-matches #"[^/]+/([^/]+)" s)]
450+
(resolve-class ns (symbol klass)))
451+
452+
;; Special case: java methods that are printed in stacktraces and look
453+
;; like this: clojure.lang.AFn.run or java.base/java.lang.Thread.run
454+
(when-let [[_ klass member] (re-matches #"(?:[^/]+/)?(.+)\.([^\.]+)" s)]
455+
(when-let [c (resolve-class ns (symbol klass))]
456+
(member-info (:class c) (symbol member)))))))
457+
458+
;;;; Online Javadoc
452459

453460
(defn javadoc-base-url
454461
"Re-implementation of `clojure.java.javadoc/*core-java-api*` because it doesn't

src/orchard/java/parser_next.clj

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,7 @@
405405
(mapv #(parse-info % env))
406406
;; Index by name, argtypes. Args for fields are nil.
407407
(group-by :name)
408-
(reduce (fn [ret [n ms]]
409-
(assoc ret n (zipmap (mapv :non-generic-argtypes ms) ms)))
410-
{}))})
408+
(misc/update-vals #(zipmap (mapv :non-generic-argtypes %) %)))})
411409

412410
ExecutableElement ;; => method, constructor
413411
(parse-info* [o env]

test/orchard/java_test.clj

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[clojure.set :as set]
55
[clojure.string :as string]
66
[clojure.test :refer [are deftest is testing]]
7-
[orchard.java :as sut :refer [cache class-info class-info* javadoc-url member-info resolve-class resolve-javadoc-path resolve-member resolve-symbol resolve-type source-info]]
7+
[orchard.java :as sut :refer [cache class-info class-info* javadoc-url member-info resolve-class resolve-javadoc-path resolve-member resolve-symbol source-info]]
88
[orchard.misc :as misc]
99
[orchard.test.util :as util])
1010
(:import
@@ -406,16 +406,42 @@
406406
(deftest symbol-resolution-test
407407
(let [ns (ns-name *ns*)]
408408
(testing "Symbol resolution"
409+
(testing "of classes"
410+
(is (= 'java.lang.String (:class (resolve-symbol ns 'String)))))
411+
(testing "of deftype in clojure.core"
412+
(is (= 'clojure.core.Eduction (:class (resolve-symbol 'clojure.core 'Eduction)))))
413+
(testing "of constructors"
414+
(is (= 'java.lang.String (:class (resolve-symbol ns 'String.)))))
409415
(testing "of unambiguous instance members"
410416
(is (= 'java.lang.SecurityManager
411-
(:class (resolve-symbol ns 'checkPackageDefinition)))))
417+
(:class (resolve-symbol ns '.checkPackageDefinition))))
418+
(is (nil? (:class (resolve-symbol ns '.currentThread)))
419+
"Shouldn't resolve since Thread/currentThread is a static method"))
420+
(testing "of qualified instance members"
421+
(is (= 'java.lang.Thread
422+
(:class (resolve-symbol ns 'Thread/.start)))))
412423
(testing "of candidate instance members"
413424
(is (every? #(= 'toString (:member %))
414425
(vals (:candidates (resolve-symbol ns 'toString))))))
415426
(testing "of static methods"
416427
(is (= 'forName (:member (resolve-symbol ns 'Class/forName)))))
417428
(testing "of static fields"
418429
(is (= 'TYPE (:member (resolve-symbol ns 'Void/TYPE)))))
430+
(testing "of java-style printed members"
431+
(is (= (resolve-symbol ns 'Thread/.start)
432+
(resolve-symbol ns 'Thread.start)))
433+
(is (= (resolve-symbol ns 'Thread/currentThread)
434+
(resolve-symbol ns 'Thread.currentThread)))
435+
(is (= (resolve-symbol ns 'clojure.lang.Compiler$DefExpr/.eval)
436+
(resolve-symbol ns 'clojure.lang.Compiler$DefExpr.eval)))
437+
(is (= 'clojure.lang.Compiler$DefExpr
438+
(:class (resolve-symbol ns 'clojure.lang.Compiler$DefExpr.eval)))))
439+
(testing "of module-prefixed classes"
440+
(is (= (resolve-symbol ns 'java.lang.Thread)
441+
(resolve-symbol ns 'java.base/java.lang.Thread))))
442+
(testing "of java-style printed members with module prefix"
443+
(is (= (resolve-symbol ns 'java.lang.Thread/.run)
444+
(resolve-symbol ns 'java.base/java.lang.Thread.run))))
419445

420446
(testing "equality of qualified vs unqualified"
421447
(testing "classes"
@@ -429,15 +455,22 @@
429455
(resolve-symbol ns 'Class/forName))))
430456
(testing "static fields"
431457
(is (= (resolve-symbol ns 'java.lang.Void/TYPE)
432-
(resolve-symbol ns 'Void/TYPE)))))
433-
434-
(testing "equality of dotted"
435-
(testing "constructor syntax"
436-
(is (= (resolve-symbol ns 'Exception)
437-
(resolve-symbol ns 'Exception.))))
438-
(testing "method syntax"
439-
(is (= (resolve-symbol ns 'toString)
440-
(resolve-symbol ns '.toString)))))
458+
(resolve-symbol ns 'Void/TYPE))))
459+
(testing "qualified members"
460+
(is (= (resolve-symbol ns 'Thread/.start)
461+
(resolve-symbol ns 'java.lang.Thread/.start))))
462+
(testing "java-style printed members"
463+
(is (= (resolve-symbol ns 'Thread.start)
464+
(resolve-symbol ns 'java.lang.Thread.start)))
465+
(is (= (resolve-symbol ns 'Thread.currentThread)
466+
(resolve-symbol ns 'java.lang.Thread.currentThread)))))
467+
468+
(when util/jdk-sources-present?
469+
(testing "class and constructor resolve to different lines"
470+
(is (not= (:line (resolve-symbol ns 'java.lang.String))
471+
(:line (resolve-symbol ns 'java.lang.String.))))
472+
(is (not= (:line (resolve-symbol ns 'Thread))
473+
(:line (resolve-symbol ns 'Thread.))))))
441474

442475
(testing "of things that shouldn't resolve"
443476
(is (nil? (resolve-symbol ns 'MissingUnqualifiedClass)))
@@ -450,13 +483,6 @@
450483
(is (nil? (resolve-symbol ns '.missingDottedMethod)))
451484
(is (nil? (resolve-symbol ns '.random.bunch/of$junk)))))))
452485

453-
(deftest type-resolution-test
454-
(testing "Type resolution"
455-
(testing "of Java classes/constructors in any namespace"
456-
(is (= 'java.lang.String (:class (resolve-type (ns-name *ns*) 'String)))))
457-
(testing "of deftype in clojure.core"
458-
(is (= 'clojure.core.Eduction (:class (resolve-type 'clojure.core 'Eduction)))))))
459-
460486
(defn- replace-last-dot [^String s]
461487
(if (re-find #"(.*\.)" s)
462488
(str (second (re-matches #"(.*)(\..*)" s))

0 commit comments

Comments
 (0)