From fc955b99f365be438907ba57bc58f428076ba2e0 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Wed, 5 May 2021 16:46:37 -0600 Subject: [PATCH] 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 --- .../scribblings/reference/stx-patterns.scrbl | 7 ++-- .../scribblings/reference/syntax.scrbl | 37 +++++++++++-------- pkgs/racket-test-core/tests/racket/stx.rktl | 2 + racket/collects/racket/private/qqstx.rkt | 11 ++++++ 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/pkgs/racket-doc/scribblings/reference/stx-patterns.scrbl b/pkgs/racket-doc/scribblings/reference/stx-patterns.scrbl index ebee707c59..8d182e9efe 100644 --- a/pkgs/racket-doc/scribblings/reference/stx-patterns.scrbl +++ b/pkgs/racket-doc/scribblings/reference/stx-patterns.scrbl @@ -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 diff --git a/pkgs/racket-doc/scribblings/reference/syntax.scrbl b/pkgs/racket-doc/scribblings/reference/syntax.scrbl index db9405cd90..63b73523e1 100644 --- a/pkgs/racket-doc/scribblings/reference/syntax.scrbl +++ b/pkgs/racket-doc/scribblings/reference/syntax.scrbl @@ -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)) diff --git a/pkgs/racket-test-core/tests/racket/stx.rktl b/pkgs/racket-test-core/tests/racket/stx.rktl index 1fb34358a3..e1604cfd29 100644 --- a/pkgs/racket-test-core/tests/racket/stx.rktl +++ b/pkgs/racket-test-core/tests/racket/stx.rktl @@ -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' diff --git a/racket/collects/racket/private/qqstx.rkt b/racket/collects/racket/private/qqstx.rkt index a5d21d4613..d09116d2ea 100644 --- a/racket/collects/racket/private/qqstx.rkt +++ b/racket/collects/racket/private/qqstx.rkt @@ -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)]