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

Attachment: pgpfLMYGxbQQb.pgp
Description: PGP signature

Reply via email to