I was looking over changes to Rust recently, and I saw with some alarm that at 
least one macro had added an ad-hoc local expansion mechanism.  I sat down with 
MTWT to see if I could figure out a situation where this might cause a problem. 
I came up with something, and I'm hoping that one of you can tell me whether it 
makes sense, and whether there are other situations where this comes up.

For what it's worth, the macro in question is one that tries to glue together 
strings at expansion time; something like this:

(concat-strings (file-name) ":" (line-number) " there problem is that ~a is not 
equal to ~a")

... where (file-name) and (line-number) are macros, and the concat-strings 
macro wants to join them all together into a format string that can be 
analyzed. In order to perform this analysis, the two sub-macros must be 
expanded into strings. It appears to me that syntax-case + compile-time 
bindings solves this, but Rust has neither of these.



Looking over MTWT, it appears to me that the principal complexity of 
local-expand arises from 
- stop-lists, and
- un- /re-marking

Looking at Rust, it appears to me that stop-lists are not an issue, at least 
for this particular macro. It may be that the marking behavior is also a 
non-problem, but I thought I'd try to come up with an example of macros where 
it *would* make a difference. The best I got was this:

#lang racket

;; this macro just does a local-expand
(define-syntax (does-local-expand stx)
  (syntax-case stx ()
    [(_ exp) (local-expand #'exp 'expression #f)]))

(define z 13)

(does-local-expand 
 (let ()
   (define-syntax m
     (syntax-rules ()
       [(m) z]))
   (m)))

So, in Racket, if I understand correctly, the mark from the expansion of 
'does-local-expand' is removed (or cancelled) before the syntax-local-expand, 
and restored again afterward. The let, on the other hand, generates an instance 
of the identifier 'z' that was introduced by the macro 'm', and therefore has 
'm's mark on it.

Looking only at the 'z' in the body of the let, then: first it gets a mark m1 
upon entry to the expansion of does-local-expand. Then, when the body of 
does-local-expand calls local-expand, 'z' gets another m1 mark. Then, in the 
expansion of the use of 'm', it gets the mark (call it m2) on the way *out* of 
m only (because it wasn't marked on the way into m). Then, the local-expand is 
finished, and it gets another m1. Finally, expansion of the use of 
does-local-expand is finished, and it gets another m1. So, the end list of 
marks is

m1 m1 m2 m1 m1  

which is equivalent to just 

m2

Now, suppose you *didn't* have the unmarking/re-marking behavior (as currently 
Rust does not).  In this case, IIUC, you'd wind up with just

m1 m2 m1

which is *not* equivalent to m2. This would then make the 'z' in the definition 
of 'z' not bound-identifier=? to the 'z' that's the result of expanding the 
'does-local-expand'. 

So: did what I said make sense?  Is there a more compelling example? Am I right 
in suggesting that for this particular macro, it may not make a difference?

Thanks in advance for any corrections / advice,

John

cc: Alex Crichton, Huon Wilson


____________________
  Racket Users list:
  http://lists.racket-lang.org/users

Reply via email to