Using syntax/loc
There’s a nuance to syntax/loc. The documentation says, emphasis mine:
Like
syntax, except that the immediate resulting syntax object takes its source-location information from the result of stx-expr (which must produce a syntax object), unless the template is just a pattern variable, or both the source and position of stx-expr are#f.
What does “immediate” mean here?
Let’s back up. Say that we1 are writing a macro to both define and provide a function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#lang racket (require (for-syntax syntax/parse)) (define-syntax (defp stx) (syntax-parse stx [(_ (id:id arg:expr ...) body:expr ...+) #'(begin (define (id arg ...) body ...) (provide id))])) (defp (f x) (/ 1 x)) (f 1) ;; => 1 |
A macro must return one syntax object, but we want to return both a define and a provide. So we do the usual thing: We wrap them up in a begin.
Everything fine so far.
Now let’s say we call f and it causes a runtime error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#lang racket (require (for-syntax syntax/parse)) (define-syntax (defp stx) (syntax-parse stx [(_ (id:id arg:expr ...) body:expr ...+) #'(begin (define (id arg ...) body ...) (provide id))])) (defp (f x) (/ 1 x)) (f 1) ; 1 (f 0) ; /: division by zero ; Context: ; /tmp/derp.rkt:9:9 f ; derp [running body] |
See the problem? The error message points to line 9 — inside the defp macro. That’s not helpful. We want it to say line 13 — where we use defp to define the f function.
Fortunately, we’ve heard of syntax/loc. Instead of specifying the macro template using #' a.k.a. syntax, we can use syntax/loc. Easy. So we update our macro:
1 2 3 4 5 6 7 8 |
(define-syntax (defp stx) (syntax-parse stx [(_ (id:id arg:expr ...) body:expr ...+) (syntax/loc stx ;; <-- new (begin (define (id arg ...) body ...) (provide id)))])) |
But that still doesn’t work. The error message still points to the macro.
Huh. So we try other values, like (syntax/loc id . . .). But still no joy.
It turns out this is where that word “immediate” matters: syntax/loc is setting the source location for the entire (begin . . . ) syntax object — but not necessarily the pieces inside it. That’s why the define isn’t getting the source location we’re supplying.
The solution? Use syntax/loc directly on the define piece:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#lang racket (require (for-syntax syntax/parse)) (define-syntax (defp stx) (syntax-parse stx [(_ (id:id arg:expr ...) body:expr ...+) #`(begin #,(syntax/loc stx ;; <-- new (define (id arg ...) body ...)) (provide id))])) (defp (f x) (/ 1 x)) (f 1) ; 1 (f 0) ; /: division by zero ; Context: ; /tmp/derp.rkt:14:0 f ; derp [running body] |
And now the error message points to f on line 14. \o/
There’s also a quasisyntax/loc.
Cheat sheet:
| Default | Source location | |
| Plain | #' a.k.a. syntax |
syntax/loc |
| Quasi | #` a.k.a. quasisyntax |
quasisyntax/loc |
To anticipate a comment: Yes, I know. I ought to add this to Fear of Macros, too.
-
The “we” in this blog post was “me” yesterday. Big thanks to Eric Dobson for pointing this out on #racket IRC. ↩