I realized I probably also ought to post my workaround, which I use in
Hackett: I just don’t use preserved syntax properties at all (only
unpreserved ones). I found that sticking syntax objects in preserved
syntax properties was unreliable, probably for the reasons Matthew
mentioned in the aforementioned email thread.
Instead of using preserved syntax properties, I just read the property
earlier, then embed its value (or an expression that produces its value)
in the expanded syntax instead of reading/transferring it “just in
time”. This avoids the need for properties to preserved in bytecode.
What do I mean by this? Well, instead of expanding to something like
this:
#'(define-syntax x
(make-variable-like-transformer
(syntax-property #'x* 'prop (syntax-property #'y 'prop)))
...where 'prop is preserved on #'y, I instead expand to something like
this:
#`(define-syntax x
(make-variable-like-transformer
(syntax-property
#'x* 'prop
(quote-syntax #,(syntax-property #'y 'prop))))
This way, the syntax property’s value is actually embedded in the fully
expanded syntax as an expression, and the invocation of
make-variable-like-transformer receives a “hardcoded” value. This means
'prop no longer needs to be preserved on #'y.
I managed to fix your example program with this technique. This changes
the definitions of define-stuff and define-thing to the following:
(define-syntax-parser define-stuff
[(_ name:id)
#:with name* ((make-syntax-introducer) #'name)
#'(begin
(define name* 'stuff)
(define-syntax name
(make-variable-like-transformer
(syntax-property #'name* 'prop #'name))))])
(define-syntax-parser define-thing
[(_ name:id stuff:id)
#:with stuff* (local-expand #'stuff 'expression '())
#`(define-syntax name
(make-variable-like-transformer
(syntax-property
#''thing
'prop
(quote-syntax #,(syntax-property #'stuff* 'prop)))))])
Now, you might argue that this technique doesn’t apply in general, since
it explicitly uses quote-syntax, which wouldn’t work for other things
that can be put into preserved syntax properties. That’s true! In fact,
I use prefab structures as my types in Hackett, so I needed to provide
a replacement for preserved properties containing prefab structures.
Fortunately, it is trivial to produce an expression that evaluates to
and value that is a valid preservable syntax property. The function
Hackett uses looks like this:
(define preservable-property->expression
(match-lambda
[(and (app prefab-struct-key (? values prefab-key))
(app struct->vector (vector _ fields ...)))
#`(make-prefab-struct
'#,prefab-key
#,@(map preservable-property->expression fields))]
[(? list? lst)
#`(list #,@(map preservable-property->expression lst))]
[(cons a b)
#`(cons #,(preservable-property->expression a)
#,(preservable-property->expression b))]
[(? syntax? stx)
#`(quote-syntax #,stx)]
[(and (or (? boolean?) (? symbol?) (? number?) (? char?)
(? string?) (? bytes?) (? regexp?))
datum)
#`(quote #,datum)]
[other
(error 'preservable-property->expression
"non-preservable syntax property\n value: ~e"
other)]))
To use this function, just replace the hardcoded use of quote-syntax to
an evaluated use of preservable-property->expression. Using this
function, define-thing looks like this:
(define-syntax-parser define-thing
[(_ name:id stuff:id)
#:with stuff* (local-expand #'stuff 'expression '())
#`(define-syntax name
(make-variable-like-transformer
(syntax-property
#''thing
'prop
#,(preservable-property->expression
(syntax-property #'stuff* 'prop)))))])
This wouldn’t work for any use of preservable syntax properties, since
it relies on the particular interplay of make-variable-like-transformer
and local-expand that Turnstile uses. However, I think it will work for
your case.
Alexis