This is a classic program in the uncomposability of unhygenic macros. (This is discussed some in the syntax-parameters paper.) Basically, the interface of auto-hash-ref/:
has as part of its interface the syntax of the whole form, which is where you get the syntactic context for all the variable bindings you insert. Then your lambda/:
macro creates that syntax in its body, so that the bindings of :a and :b have the syntactic contexts of the expansion of lambda/:
instead of its input.
The best solution is to (a) make it clear in the description of auto-hash-ref/:
where it takes syntactic context from (b) choose something other than the whole form (such as the name of the hash table) and (c) ensure that the syntactic context of that comes from the input (which it does for that choice in your lambda/:
macro). Basically you would change stx
to #'id
in the first argument to datum->syntax
here.
Unfortunately, even if you follow those rules, you will often need to write unhygienic macros to abstract over auto-hash-ref/:
. For example, if you want to write this macro:
(define-syntax-rule (auto-lambda body) (lambda (i) (auto-hash-ref/: i body)))
there is no way to write it as a hygienic macro and have it do what you want. Instead, even with the change I suggest, you would write:
(define-syntax (auto-lambda stx)
(syntax-parse stx
[(_ b:expr)
#:with i (format-id #'b "i")
(lambda (i) (auto-hash-ref/: i body))]))