From b02a597e065d22e8c5f70063eaeb95d187b814e5 Mon Sep 17 00:00:00 2001 From: vemv Date: Sat, 16 Sep 2023 21:44:44 +0200 Subject: [PATCH 1/5] Infer the class string and coll literals Fixes https://github.com/alexander-yakushev/compliment/issues/106 --- CHANGELOG.md | 2 ++ src/compliment/sources/class_members.clj | 8 ++++--- src/compliment/sources/local_bindings.clj | 3 ++- src/compliment/utils.clj | 8 +++++++ test/compliment/sources/t_class_members.clj | 25 ++++++++++++++++++++ test/compliment/sources/t_local_bindings.clj | 14 ++++++++++- 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdc8089..ace9b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### master (unreleased) +- Infer the class string and coll literals, giving more accurate completions for those. + ### 0.4.1 (2023-08-23) - [#33](https://github.com/alexander-yakushev/compliment/issues/33): Demunge diff --git a/src/compliment/sources/class_members.clj b/src/compliment/sources/class_members.clj index 08149c7..3ed0adf 100644 --- a/src/compliment/sources/class_members.clj +++ b/src/compliment/sources/class_members.clj @@ -105,9 +105,11 @@ (:tag (meta (get (set (bindings-from-context context ns)) form))))] ;; We have a tag - try to resolve the class from it. (resolve-class ns tag) - ;; Otherwise, try to resolve symbol to a Var. + ;; Otherwise, try to resolve symbol to a Var, + ;; or literal to class. (or (utils/var->class ns form) - (utils/invocation-form->class ns form)))))) + (utils/invocation-form->class ns form) + (utils/literal->class form)))))) (defn members-candidates "Returns a list of Java non-static fields and methods candidates." @@ -222,7 +224,7 @@ (if (static? c) (let [full-name (.getName c)] (if (cache (.getName c)) - (recur (update-in cache [full-name] conj c) r) + (recur (update cache full-name conj c) r) (recur (assoc cache full-name [c]) r))) (recur cache r)) (swap! static-members-cache assoc class cache)))) diff --git a/src/compliment/sources/local_bindings.clj b/src/compliment/sources/local_bindings.clj index cb8a02c..deb7e6b 100644 --- a/src/compliment/sources/local_bindings.clj +++ b/src/compliment/sources/local_bindings.clj @@ -41,7 +41,8 @@ (when bound-to (when-let [found (or (-> bound-to meta :tag) (utils/var->class ns bound-to) - (utils/invocation-form->class ns bound-to))] + (utils/invocation-form->class ns bound-to) + (utils/literal->class bound-to))] (if (class? found) (-> ^Class found .getName symbol) found)))) diff --git a/src/compliment/utils.clj b/src/compliment/utils.clj index 6583242..4dfb96f 100644 --- a/src/compliment/utils.clj +++ b/src/compliment/utils.clj @@ -267,3 +267,11 @@ Note that should always have the same value, regardless of OS." (ns-resolve ns (first form)))] (when (var? var-from-invocation) (-> var-from-invocation meta :tag)))) + +(defn literal->class + "Extracts the class from a literal. + This is meant to support interop on strings and Clojure collections." + [form] + (when (or (string? form) + (coll? form)) + (class form))) diff --git a/test/compliment/sources/t_class_members.clj b/test/compliment/sources/t_class_members.clj index 9e35ada..74faa58 100644 --- a/test/compliment/sources/t_class_members.clj +++ b/test/compliment/sources/t_class_members.clj @@ -207,3 +207,28 @@ (fact "static class members have docs" (src/static-member-doc "Integer/parseInt" (-ns)) => (checker string?))) + +(deftest literals-inference-test + (fact "Has around 19 candidates (give or take, varies per JDK), +which indicates that the members are an exact match against the class of `[]`" + (let [c (count (src/members-candidates "." (-ns) (ctx/cache-context + "(__prefix__ [])")))] + (< 17 c 21)) + => + truthy) + + + (fact "A docstring is offered for the previous query" + (src/members-doc ".assocN" (-ns)) => (checker string?)) + + (fact "Has around 50 candidates (give or take, varies per JDK), +which indicates that the members are an exact match against the class of `\"\"`" + (let [c (count (src/members-candidates "." (-ns) (ctx/cache-context + "(__prefix__ \"\")")))] + (< 34 c 52)) + => + truthy) + + (fact "A docstring is offered for the previous query" + (src/members-doc ".codePointBefore" (-ns)) + => (checker string?))) diff --git a/test/compliment/sources/t_local_bindings.clj b/test/compliment/sources/t_local_bindings.clj index 2df08cf..80691b5 100644 --- a/test/compliment/sources/t_local_bindings.clj +++ b/test/compliment/sources/t_local_bindings.clj @@ -33,7 +33,19 @@ (map (comp :tag meta) (src/bindings-from-context (ctx/parse-context '(let [a (string/trim "a")] __prefix__)) (-ns))) - => (just [`String]))) + => (just [`String])) + + (fact "The class of a given binding can be identified by the class of a string literal" + (map (comp :tag meta) + (src/bindings-from-context (ctx/parse-context '(let [a ""] __prefix__)) + (-ns))) + => (just [`String])) + + (fact "The class of a given binding can be identified by the class of a vector literal" + (map (comp :tag meta) + (src/bindings-from-context (ctx/parse-context '(let [a []] __prefix__)) + (-ns))) + => (just ['clojure.lang.PersistentVector]))) (deftest local-bindings (defmacro ^{:completion/locals :let} like-let [& _]) From ceb611966bbdc4bfb497a40ec3a8f86ef29df24a Mon Sep 17 00:00:00 2001 From: vemv Date: Sat, 16 Sep 2023 22:27:09 +0200 Subject: [PATCH 2/5] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ace9b9d..41b08e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### master (unreleased) -- Infer the class string and coll literals, giving more accurate completions for those. +- Infer the class of string and coll literals, giving more accurate completions for those. ### 0.4.1 (2023-08-23) From 27bdd161870e87f7d5eab165c9b6da1550e32121 Mon Sep 17 00:00:00 2001 From: vemv Date: Sun, 17 Sep 2023 13:04:50 +0200 Subject: [PATCH 3/5] Favor exact matches --- test/compliment/sources/t_class_members.clj | 57 ++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/test/compliment/sources/t_class_members.clj b/test/compliment/sources/t_class_members.clj index 74faa58..2e1cb52 100644 --- a/test/compliment/sources/t_class_members.clj +++ b/test/compliment/sources/t_class_members.clj @@ -1,5 +1,6 @@ (ns compliment.sources.t-class-members - (:require [clojure.test :refer :all] + (:require [clojure.string :as str] + [clojure.test :refer :all] [compliment.context :as ctx] [compliment.sources.class-members :as src] [compliment.t-helpers :refer :all] @@ -106,7 +107,6 @@ => (contains #{{:candidate ".put", :type :method} {:candidate ".putAll", :type :method}} :gaps-ok)) - (fact "if context is provided and the first arg is a symbol with type tag (either immediate or anywhere in the local scope)" (strip-tags (src/members-candidates ".sta" (-ns) nil)) @@ -208,27 +208,38 @@ (fact "static class members have docs" (src/static-member-doc "Integer/parseInt" (-ns)) => (checker string?))) +(def java-version (-> (System/getProperty "java.version") + (str/split #"\.") + first + read-string)) + (deftest literals-inference-test - (fact "Has around 19 candidates (give or take, varies per JDK), + (testing "Vector literals" + (let [candidates-count (count (src/members-candidates "." (-ns) (ctx/cache-context + "(__prefix__ [])"))) + {:keys [major minor]} *clojure-version* + expected (if (and (= 1 major) + (< minor 12)) + 18 + 19)] + (fact "Has around 19 candidates (give or take, varies per *clojure-version*), which indicates that the members are an exact match against the class of `[]`" - (let [c (count (src/members-candidates "." (-ns) (ctx/cache-context - "(__prefix__ [])")))] - (< 17 c 21)) - => - truthy) - - - (fact "A docstring is offered for the previous query" - (src/members-doc ".assocN" (-ns)) => (checker string?)) - - (fact "Has around 50 candidates (give or take, varies per JDK), + candidates-count => expected)) + + (fact "A docstring is offered for the previous query" + (src/members-doc ".assocN" (-ns)) => (checker string?))) + + (testing "String literals" + (let [candidates-count (count (src/members-candidates "." (-ns) (ctx/cache-context + "(__prefix__ \"\")"))) + expected (case java-version + 1 35 + 11 43 + 50)] + (fact "Has around 50 candidates (give or take, varies per JDK), which indicates that the members are an exact match against the class of `\"\"`" - (let [c (count (src/members-candidates "." (-ns) (ctx/cache-context - "(__prefix__ \"\")")))] - (< 34 c 52)) - => - truthy) - - (fact "A docstring is offered for the previous query" - (src/members-doc ".codePointBefore" (-ns)) - => (checker string?))) + candidates-count => expected)) + + (fact "A docstring is offered for the previous query" + (src/members-doc ".codePointBefore" (-ns)) + => (checker string?)))) From cdf5cea48608da00618e0f9c721b6ac0cc98e506 Mon Sep 17 00:00:00 2001 From: vemv Date: Sun, 17 Sep 2023 13:10:37 +0200 Subject: [PATCH 4/5] Add specific examples --- test/compliment/sources/t_class_members.clj | 22 +++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/compliment/sources/t_class_members.clj b/test/compliment/sources/t_class_members.clj index 2e1cb52..57b91b7 100644 --- a/test/compliment/sources/t_class_members.clj +++ b/test/compliment/sources/t_class_members.clj @@ -227,7 +227,16 @@ which indicates that the members are an exact match against the class of `[]`" candidates-count => expected)) (fact "A docstring is offered for the previous query" - (src/members-doc ".assocN" (-ns)) => (checker string?))) + (src/members-doc ".assocN" (-ns)) => (checker string?)) + + (fact "Only returns members of clojure.lang.PersistentVector for the very short \".a\" query" + (src/members-candidates ".a" (-ns) (ctx/cache-context + "(__prefix__ [])")) + => + (just [{:candidate ".arrayFor", :type :method} + {:candidate ".assocN", :type :method} + {:candidate ".asTransient", :type :method}] + :in-any-order))) (testing "String literals" (let [candidates-count (count (src/members-candidates "." (-ns) (ctx/cache-context @@ -236,10 +245,19 @@ which indicates that the members are an exact match against the class of `[]`" 1 35 11 43 50)] + (fact "Has around 50 candidates (give or take, varies per JDK), which indicates that the members are an exact match against the class of `\"\"`" candidates-count => expected)) (fact "A docstring is offered for the previous query" (src/members-doc ".codePointBefore" (-ns)) - => (checker string?)))) + => (checker string?)) + + (fact "Only returns members of String for the very short \".g\" query" + (src/members-candidates ".g" (-ns) (ctx/cache-context + "(__prefix__ \"\")")) + => + (just [{:candidate ".getChars", :type :method} + {:candidate ".getBytes", :type :method}] + :in-any-order)))) From e277d3d2d11d03596436f154e7f49fe98e6f5ff8 Mon Sep 17 00:00:00 2001 From: vemv Date: Sun, 17 Sep 2023 17:14:41 +0200 Subject: [PATCH 5/5] Remove extra tests --- test/compliment/sources/t_class_members.clj | 43 ++++----------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/test/compliment/sources/t_class_members.clj b/test/compliment/sources/t_class_members.clj index 57b91b7..4772418 100644 --- a/test/compliment/sources/t_class_members.clj +++ b/test/compliment/sources/t_class_members.clj @@ -208,27 +208,8 @@ (fact "static class members have docs" (src/static-member-doc "Integer/parseInt" (-ns)) => (checker string?))) -(def java-version (-> (System/getProperty "java.version") - (str/split #"\.") - first - read-string)) - (deftest literals-inference-test (testing "Vector literals" - (let [candidates-count (count (src/members-candidates "." (-ns) (ctx/cache-context - "(__prefix__ [])"))) - {:keys [major minor]} *clojure-version* - expected (if (and (= 1 major) - (< minor 12)) - 18 - 19)] - (fact "Has around 19 candidates (give or take, varies per *clojure-version*), -which indicates that the members are an exact match against the class of `[]`" - candidates-count => expected)) - - (fact "A docstring is offered for the previous query" - (src/members-doc ".assocN" (-ns)) => (checker string?)) - (fact "Only returns members of clojure.lang.PersistentVector for the very short \".a\" query" (src/members-candidates ".a" (-ns) (ctx/cache-context "(__prefix__ [])")) @@ -236,28 +217,20 @@ which indicates that the members are an exact match against the class of `[]`" (just [{:candidate ".arrayFor", :type :method} {:candidate ".assocN", :type :method} {:candidate ".asTransient", :type :method}] - :in-any-order))) - - (testing "String literals" - (let [candidates-count (count (src/members-candidates "." (-ns) (ctx/cache-context - "(__prefix__ \"\")"))) - expected (case java-version - 1 35 - 11 43 - 50)] - - (fact "Has around 50 candidates (give or take, varies per JDK), -which indicates that the members are an exact match against the class of `\"\"`" - candidates-count => expected)) + :in-any-order)) (fact "A docstring is offered for the previous query" - (src/members-doc ".codePointBefore" (-ns)) - => (checker string?)) + (src/members-doc ".assocN" (-ns)) => (checker string?))) + (testing "String literals" (fact "Only returns members of String for the very short \".g\" query" (src/members-candidates ".g" (-ns) (ctx/cache-context "(__prefix__ \"\")")) => (just [{:candidate ".getChars", :type :method} {:candidate ".getBytes", :type :method}] - :in-any-order)))) + :in-any-order)) + + (fact "A docstring is offered for the previous query" + (src/members-doc ".codePointBefore" (-ns)) + => (checker string?))))