larcenists / larceny

Larceny Scheme implementation
Other
204 stars 33 forks source link

set! is insufficiently hygienic #653

Open larceny-trac-import opened 11 years ago

larceny-trac-import commented 11 years ago

Reported by: jld on Wed Sep 16 00:20:00 2009 Consider the following top-level form:

(begin
 (define v 1) 
 (let-syntax ((m (syntax-rules () ((_) (set! v 2)))))
   (let ((v 3)) (m))) 
 v)

I would expect the v in the macro to refer to the outer v, and thus for the form to evaluate to 1. Indeed, this is the result under both Larceny's ERR5RS REPL and PLT MzScheme (and PLT R5RS). Larceny, however, yields 2.

To make it clearer what's going on here, I inserted some display calls:

(begin
 (define v 1)
 (let-syntax 
     ((m (syntax-rules () ((_) (begin (display v) (set! v 2) (display v))))))
   (let ((v 3)) (display v) (m) (display v))) 
 v)

Larceny in its default mode outputs 31121, which shows that the inner binding is being changed from 3 to 2, and that the (display v) in the macro body is getting a different v from the (set! v 2) right next to it. In ERR5RS and under PLT, this outputs 31232, as expected.

Interestingly, if the begin is replaced by let (), then the expansion is as expected.

larceny-trac-import commented 11 years ago

Author: pnkfelix Mzscheme v209 yields 2 for me on that first form. So does MzScheme 4.1.3. So does Larceny's ERR5RS REPL.

Why do you expect the form to yield 1 again? The v in the macro's use of set! is the v in the define, and thus the v provided as a final result.

larceny-trac-import commented 11 years ago

Author: pnkfelix (Actually, now I'm seeing 1 from native Larceny. When I was using Larceny at first, I was on a Petit Larceny host, which seems to yield 2.)

So, Jed, were you just mixed up in your first paragraph?

larceny-trac-import commented 11 years ago

Author: pnkfelix Now THIS is fun:

alleyrally:~> ~/Dev/larcenydev/trunk.linux/larceny_src/larceny
Larceny v0.97 "Funny in the Head" (Sep 14 2009 15:41:33, precise:Linux:unified)
larceny.heap, built on Mon Sep 14 15:49:48 EDT 2009

> (define exp 
'(begin
 (define v 1) 
 (let-syntax ((m (syntax-rules () ((_) (set! v 2)))))
   (let ((v 3)) (m))) 
 v))

> (eval (macro-expand exp))
2

> (eval exp)
1

> 
larceny-trac-import commented 11 years ago

Author: pnkfelix From what I can determine, this is actually a bug in pass2, not a bug in pass1. (Unfortunately pass2 operates in part by mutating the sexp input, so you need to be careful how you go about analyzing things like this.)

(define expa 
  '(begin (define v 1) 
          (let-syntax ((m (syntax-rules () ((_) (set! v 2)))))
            (let ((v 3)) (m))) v))

(display 
 (list "p0p1:   " (eval        (pass1 (pass0 expa) (the-usual-syntactic-environment)))

       "p0p1p2: " (eval (pass2 (pass1 (pass0 expa) (the-usual-syntactic-environment))))))
(newline)

It could well be that pass2 is equating identifiers that it should not; I cannot tell. I just want to save others the effort of tracking this down to the right pass.

larceny-trac-import commented 11 years ago

Author: pnkfelix (I am wrong, the bug is in pass1, I just did not notice it because I am not familiar with the invariants of pass1's output and I was passing its output to make-readable which throws away the important meta-data stashed in the lambda expressions...)

WillClinger commented 7 years ago

Just to clarify: The correct result is 2, not 1.

Larceny's R5RS macro expander produces 1, and that's the bug.

In R7RS mode, Larceny produces the correct result (2).

At this point, we are not worrying about corner cases in the R5RS macro expander, so I'm downgrading the priority.