Hello, This question is primarily addressed to Nicola Mometto, but I am sending it to the list so it may be helpful to other Clojure developers. Help is appreciated, regardless of the source.
I am using tools.analyzer(.jvm) to build a Closeable resource linter, but I have run into the following problem: a function call's type hint is lost when an instance method is called on its output. For example, given the following setup: (alias 'ana 'clojure.tools.analyzer) (alias 'ast 'clojure.tools.analyzer.ast) (alias 'jvm 'clojure.tools.analyzer.jvm) (defn analyze [form] (binding [ana/macroexpand-1 jvm/macroexpand-1 ana/create-var jvm/create-var ana/parse jvm/parse ana/var? var?] (jvm/analyze form (jvm/empty-env)))) (defn analyze-debug [form] (for [ast (ast/nodes (analyze form)) :let [{:keys [op form tag o-tag class]} ast]] (array-map :op op :form form :tag tag :o-tag o-tag :class class))) (defn ^java.io.FileInputStream fis [^String x] (java.io.FileInputStream. x)) I would like to detect that (fis x) returns a java.io.FileInputStream. If I call: (analyze-debug '(str (fis "x"))) I receive: ({:op :invoke, :form (str (fis "x")), :tag java.lang.String, :o-tag java.lang.Object, :class nil} {:op :var, :form str, :tag clojure.lang.AFunction, :o-tag java.lang.Object, :class nil} {:op :invoke, :form (fis "x"), :tag java.io.FileInputStream, ; ◀──── The desired metadata :o-tag java.lang.Object, :class nil} {:op :var, :form fis, :tag clojure.lang.AFunction, :o-tag java.lang.Object, :class nil} {:op :const, :form "x", :tag java.lang.String, :o-tag java.lang.String, :class nil}) However, if I call: (analyze-debug '(.toString (fis "x"))) -> ({:op :instance-call, :form (. (fis "x") toString), :tag java.lang.String, :o-tag java.lang.String, :class java.lang.Object} {:op :invoke, :form (fis "x"), :tag java.lang.Object, ; ◀──── The type hint is missing! :o-tag java.lang.Object, :class nil} {:op :var, :form fis, :tag clojure.lang.AFunction, :o-tag java.lang.Object, :class nil} {:op :const, :form "x", :tag java.lang.String, :o-tag java.lang.String, :class nil}) The :tag of (fis "x") is now java.lang.Object, and java.io.FileInputStream is not present in the node. Calling java.io.InputStream#markSupported sheds more light on the matter: (analyze-debug '(.markSupported (fis "x"))) -> ({:op :instance-call, :form (. (fis "x") markSupported), :tag boolean, :o-tag boolean, :class java.io.InputStream} {:op :invoke, :form (fis "x"), :tag java.io.InputStream, ; ◀──── The instance method's class :o-tag java.lang.Object, :class nil} {:op :var, :form fis, :tag clojure.lang.AFunction, :o-tag java.lang.Object, :class nil} {:op :const, :form "x", :tag java.lang.String, :o-tag java.lang.String, :class nil}) Finally, calling java.io.FileInputStream#getFD on (fis "x") returns the expected :tag entry: (analyze-debug '(.getFD (fis "x"))) -> (… {:op :invoke, :form (fis "x"), :tag java.io.FileInputStream, ; ◀──── Also the instance method's class :o-tag java.lang.Object, :class nil} …) Is there a reliable way to retrieve the original type hint of (.method (fis "x"))? If not, does it make sense to ensure that the metadata of the (fis "x") node is constant regardless of its context? And finally, while we're on the topic, what is the difference between the :tag, :o-tag, and :class entries? I have a vague idea based on a quick perusal of the library, but I would be delighted if you could provide a quick summary. Thank you for your hard work on these analyzers! You are laying the groundwork for a whole new generation of Clojure tools. guns
pgpfLMYGxbQQb.pgp
Description: PGP signature