Hi, When using tools.analyzer.jvm you have to remember that its primary use-case is as a front-end for a compiler, in this case :tag and :o-tag serve to inform tools.emitter.jvm when to emit a cast thus might not always reflect the :tag meta of the expression :form.
Regarding the :tag/:o-tag difference, :o-tag (you can read it as "original tag") holds the static type of the node known at that point and might be either inferred by the :tag of some children nodes or from the class of the :form of the node in case it's a literal, :tag on the other hand can be either the same of :o-tag or hold the Class that node needs to be cast to, usually because of an explicit type-hint. For example, ^IPersistentCollection [] will have :o-tag PersistentVector and :tag IPersistentCollection. :class is an attribute of some nodes that deal with host interop forms, like :new, :instance-call and others and holds the Class that node deals with; for example it might hold the Class a :new node is going to instantiate, the Class an :instance-method belongs to etc. BTW, you don't need to roll your own `analyze` function providing those bindings, jvm/analyze already sets up the right bindings for you. I hope this helps, Nicola On Wed, May 21, 2014 at 10:00 AM, guns <s...@sungpae.com> wrote: > 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 > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.