Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by
When there is a instrumentation failure for a function, the explain-data includes "caller" information. However, this information is missing if the instrumentation failure is for a macro.

This comment has led me to believe that the intended behavior is for explain-data to contain this info, so third-party error printers can display it.

In the repro below, I'm setting up a custom printer just to capture the raw explain-data (it's not a useful printer, just a means to show what is happenening)

Repro:


  (require '[clojure.spec.alpha :as s])
  (require '[clojure.spec.test.alpha :as st])
  (require '[clojure.specs.alpha :as s])


  (s/fdef my-fn
          :args (s/cat :x int?))
  (defn my-fn [x]
    x)

  (s/fdef my-macro
          :args (s/cat :x int?))
  (defmacro my-macro [x]
    x)

  (st/instrument)
  (def !ed (atom nil))
  (set! s/*explain-out* (fn [ed]
                          (reset! !ed ed)))
  (my-fn "")
  @!ed
  ;; {:clojure.spec.alpha/problems [{:path [:args :x], :pred clojure.core/int?, :val "", :via [], :in [0]}], :clojure.spec.alpha/spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x72029b0e "clojure.spec.alpha$regex_spec_impl$reify__2436@72029b0e"], :clojure.spec.alpha/value (""), :clojure.spec.alpha/args (""), :clojure.spec.alpha/failure :instrument, :clojure.spec.test.alpha/caller {:file "form-init8333540581183382896.clj", :line 548, :var-scope expound.alpha/eval27394}}

  ;; ^--- Note there is an entry for :clojure.spec.test.alpha/caller

  (my-macro "")
  @!ed

  ;; #:clojure.spec.alpha{:problems [{:path [:args :x], :pred clojure.core/int?, :val "", :via [], :in [0]}], :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x479a6a73 "clojure.spec.alpha$regex_spec_impl$reify__2436@479a6a73"], :value (""), :args ("")}

  ;; ^--- No caller information

10 Answers

0 votes
by

Comment made by: alexmiller

You can't instrument a macro, so that part of the ticket doesn't make sense as written. But I expect you mean the spec check during macro expansion.

In the macro check case, the caller info is known by the compiler and included in the wrapper CompilerException. I suppose that info could be passed into s/macroexpand-check from the Compiler and possibly produce similar results as with instrumented function calls.

0 votes
by

Comment made by: bbrinck

Ah, you're correct, thanks for clarifying.

If caller information was added, it would also be very useful to add a specific :clojure.spec.alpha/failure value for specs that fail during macro expansion. That would allow 3rd party tools to show specific types of error messages when macro expansion failed.

0 votes
by

Comment made by: bbrinck

Additionally, having access to the symbol for the macro name would assist in error reporting.

0 votes
by

Comment made by: sohta

IMHO {{explain-data}} for instrumentation failures and macro spec errors should contain the fspec of the function (or macro), as I suggested before in CLJ-2218.

An {{fspec}} has some useful info (such as the ns-qualified name symbol and the line number etc.) of the spec'ed function in its metadata, so spec error reporters can use them for richer and more precise error messages in a uniform way for both instrumentation failures and macro spec errors.

0 votes
by

Comment made by: alexmiller

After CLJ-2373, the caller information for spec macro failures will be conveyed in a wrapper CompilerException (as with other compiler and macroexpansion errors). Should consider whether this issue is effectively resolved as a result.

0 votes
by

Comment made by: bbrinck

I'm glad to hear this will be included in the CompilerException, but I think it still is useful to include in the spec explain-data.

When printing a spec error, it would be clearer if macro expansion spec errors and instrumentation errors could be printed in a similar format (if the user chooses a custom spec printer). It may be disruptive to look for the caller information in one place for macro expansion errors and a different place for instrumentation errors.

Furthermore, spec printers may choose to do novel things with this caller information like adding color to output or printing the actual code instead of the line numbers. It'd be nice to do have all this information available for the spec printer to customize.

0 votes
by

Comment made by: alexmiller

The issue here is that at the point where the macro spec error is created, we don't have any way to know the proper caller information. The stack at that point is the compiler macroexpansion, not the invoking code. macroexpansion does know that context and that's what it adds when wrapping with a CompilerException (that's the primary purpose of CompilerException).

I think to change that we would need to convey that info out of band into the spec check. Well, maybe that's possible. I'll look at it again.

0 votes
by

Comment made by: bbrinck

With the recent error message work (great work!) the symbol appears to be available, but it not currently passed to the "explain-out" function: https://github.com/clojure/clojure/blob/b182982007df934394f0bc68b3a238ca9f200dd1/src/clj/clojure/main.clj#L268-L279

Would it be possible to "assoc" this into the "spec" passed to "spec/explain-out"? This would allow custom printers to refer to the name of the spec, which would be very useful.

An alternative approach would be for users to set up custom error handlers in the REPL, but this is much more involved than setting up a third-party implementation of "explain-out"

0 votes
by
_Comment made by: alexmiller_

I’d hate to make this something only useable by the clojure main repl tgough - would be better to happen always if using some other repl, so would be better to do at construction. I have a better handle on how to do so now, but don’t know if it’s too late for 1.10.
0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJ-2271 (reported by bbrinck)
...