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