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. ↩