I've aleady done a first-draft implementation of a different variation of this idea, with a single new special form I called compile-time-let. It is demonstrated briefly here:
http://dev.clojure.org/display/design/Overriding+compile-time+var+values+at+expression-level+granularity
That implementation seems pretty straightforward to me -- the introduction of one new special form, and nothing else in the compiler is affected. compile-time-let can be used inside of macros without problems. Anywhere you have a subexpression in your code that you want to modify some compile-time settings for, you just wrap it in a compile-time-let with the desired bindings and you are done. It just works. (Please point out any problems you see with the idea that I could be missing.)
Aaron, were you proposing that the metadata would only work when followed by a (do ...) subexpression? If so, that would help limit the number of changes required for an implementation I've been considering so far, and you can skip reading the long-winded stuff that follows.
If you meant to be able to put such metadata before an arbitrary Clojure subexpression, the only way I know of to do that involves changes sprinkled throughout Compiler.java. One way is to modify the implementation of every single parse() method in Compiler.java (there are 28 of them) in nearly identical ways. Each modified parse() method would check whether there is metadata on the form being parsed, and if so, and if the metdata map has a key :warn-on-reflection, :unchecked-math, or some other name that affects compile-time behavior, then parse() would pushThreadBindings() to change the values of those vars in the scope of that expression, and pop those bindings just before returning from parse().
Can anyone think of a different way to implement this with the metadata syntax? I haven't thought of one, but I'm not very familiar with the compiler internals.
Assuming the "modify all parse() methods" is reasonable, I've put some debug print statements into a local copy of the compiler to see if the metadata is attached to the code objects being parsed. It turns out that it is, in many cases. For example InvokeExpr.parse() is called for the (conj x 2) subexpression of the following function definition, with a form object that has the desired metadata {:warn-on-reflection false} attached to it:
(defn foo [x] ^{:warn-on-reflection false} (conj x 2))
However, there are many other subexpressions that do not have metadata on them, and it seems to correspond to the cases when that subexpression is a macro. Examples:
;; let expands into let* with no metadata
(defn foo1 [x] ^{:warn-on-reflection false} (let [y 2] [x y]))
;; case expands into let* with no metadata
(defn foo1 [x]
^{:warn-on-reflection false}
(case x
0 (printf "zero")
1 (printf "one")))
Metadata is also not preserved for (loop ...) subexpressions, nor even a subexpression like (+ x 2), because the compiler transforms it into (. clojure.lang.Numbers (add x 2)) without the metadata that was on the (+ x 2) expression.
I also don't see metadata when I define my own macro, and then annotate a call to the macro with metadata.
Is it reasonable to consider making all macro expansions preserve metadata that was attached to the original, pre-expansion, expression? Is that possible?
Thanks,
Andy