quasisyntax: recognize unsyntax in a box

This is a backward-incompatible change, but I think it's unlikely that
any code intentionally uses `unsyntax` or `unsyntax-splicing` within a
syntax-quoted box and expects it to stay literal.

Meanwhile, as @rocketnia noted, the documentation for `quasiquote` was
unclear about the espacing positions for `unquote` and
`unquote-splicing`, so this commit impoves that documentation. It
adjusts the documentation for `quasisyntax` to note that a hash table
value position is not an escape position, unlike for `quasiquote`.
(The lack of an escape position within hash tables is consistent with
`syntax`. That's arguably inconsistent with `quasiquote`, but it seems
simpler to leave that alone, and changing `syntax` just might matter
for existing code.)

Closes #3656
This commit is contained in:
Matthew Flatt 2021-05-05 16:46:37 -06:00
parent c8d605a8cc
commit fc955b99f3
4 changed files with 39 additions and 18 deletions

View File

@ -449,15 +449,16 @@ variables, then @racket[#'template] is equivalent to
@defform[(quasisyntax template)]{
Like @racket[syntax], but @racket[(#,(racketkeywordfont "unsyntax")
_expr)] and @racket[(#,(racketkeywordfont "unsyntax-splicing") _expr)]
Like @racket[syntax], but @racket[(@#,racket[unsyntax]
_expr)] and @racket[(@#,racket[unsyntax-splicing] _expr)]
escape to an expression within the @racket[template].
The @racket[_expr] must produce a syntax object (or syntax list) to be
substituted in place of the @racket[unsyntax] or
@racket[unsyntax-splicing] form within the quasiquoting template, just
like @racket[unquote] and @racket[unquote-splicing] within
@racket[quasiquote]. (If the escaped expression does not generate a
@racket[quasiquote], except that a hash table value position is not
an escape position for @racket[quasisyntax]. (If the escaped expression does not generate a
syntax object, it is converted to one in the same way as for the
right-hand side of @racket[with-syntax].) Nested
@racket[quasisyntax]es introduce quasiquoting layers in the same way

View File

@ -2861,21 +2861,28 @@ The same as @racket[(quote datum)] if @racket[datum] does not include
and the result of the @racket[_expr] takes the place of the
@racket[(#,unquote-id _expr)] form in the @racket[quasiquote] result. An
@racket[(#,unquote-splicing-id _expr)] similarly escapes, but the
@racket[_expr] must produce a list, and its elements are spliced as
multiple values place of the @racket[(#,unquote-splicing-id _expr)], which
must appear as the @racket[car] of a quoted pair, as an element of a
quoted vector, or as an element of a quoted @tech{prefab} structure;
in the case of a pair, if the @racket[cdr] of the relevant quoted pair
is empty, then @racket[_expr] need not produce a list, and its result
is used directly in place of the quoted pair (in the same way that
@racket[append] accepts a non-list final argument). In a quoted
@tech{hash table}, an @racket[(#,unquote-id _expr)] or
@racket[(#,unquote-splicing-id _expr)] expression escapes only in the
second element of an entry pair (i.e., the value), while entry keys
are always implicitly quoted. If @racket[unquote] or
@racket[unquote-splicing] appears within @racket[quasiquote] in any
other way than as @racket[(#,unquote-id _expr)] or
@racket[(#,unquote-splicing-id _expr)], a syntax error is reported.
@racket[_expr] produces a list whose elements are spliced as
multiple values place of the @racket[(#,unquote-splicing-id _expr)].
An @|unquote-id| or @|unquote-splicing-id| form is recognized in any
of the following escaping positions within @racket[datum]: in a pair,
in a vector, in a box, in a @tech{prefab} structure field after the
name position, and in hash table value position (but not in a hash
table key position). Such escaping positions can be nested to an
arbitrary depth.
An @|unquote-splicing-id| form must appear as the @racket[car] of a
quoted pair, as an element of a quoted vector, or as an element of a
quoted @tech{prefab} structure. In the case of a pair, if the
@racket[cdr] of the relevant quoted pair is empty, then @racket[_expr]
need not produce a list, and its result is used directly in place of
the quoted pair (in the same way that @racket[append] accepts a
non-list final argument).
If @racket[unquote] or @racket[unquote-splicing] appears within
@racket[quasiquote] in an escaping position but in a way other than as
@racket[(#,unquote-id _expr)] or @racket[(#,unquote-splicing-id
_expr)], a syntax error is reported.
@mz-examples[
(eval:alts (#,(racket quasiquote) (0 1 2)) `(0 1 2))

View File

@ -2353,6 +2353,7 @@
(test '((1) (2)) syntax->datum (quasisyntax ((a) ((unsyntax b)))))
(test '#(1 2) syntax->datum (quasisyntax #(a (unsyntax b))))
(test '#(1 2 3 4 5) syntax->datum (quasisyntax #(a (unsyntax b) c ...)))
(test '#&2 syntax->datum (quasisyntax #&(unsyntax b)))
(test '#s(PS 1 2) syntax->datum (quasisyntax #s(PS a (unsyntax b))))
(test '#s(PS 1 2 3 4 5) syntax->datum (quasisyntax #s(PS a (unsyntax b) c ...)))
(test '#(1 2 3 4 5) syntax->datum (quasisyntax #(a (unsyntax b) (unsyntax-splicing ds))))
@ -2368,6 +2369,7 @@
(syntax-test #'(quasisyntax unsyntax-splicing))
(syntax-test #'(quasisyntax (unsyntax-splicing)))
(syntax-test #'(quasisyntax (unsyntax-splicing 1 2)))
(syntax-test #'(quasisyntax #&(unsyntax-splicing 1 2)))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Check preservation of properties by `quasisyntax'

View File

@ -250,6 +250,17 @@
stx
stx)
bindings))))]
[(box? (syntax-e stx))
(loop (unbox (syntax-e stx))
depth
same-k
(lambda (v bindings)
(convert-k (datum->syntax
stx
(box v)
stx
stx)
bindings)))]
[(prefab-struct-key (syntax-e stx))
(let* ([d (syntax-e stx)]
[key (prefab-struct-key d)]