From 9132a6ebfdd4ce5b95818e134b08c188a7ffee34 Mon Sep 17 00:00:00 2001 From: Jack Firth Date: Tue, 7 Jul 2026 02:07:32 -0700 Subject: [PATCH] Fix quasiquote-to-list changing behavior on quoted datums The quasiquote-to-list rule matched non-unquoted elements with the literal-constant syntax class, which accepts (quote x) forms. Inside a quasiquoted list those are plain data, so rewriting `(1 ,y 'x) to (list 1 y 'x) changed the last element from the list (quote x) to the symbol x. Non-unquoted elements must now be self-quoting literals. The self-quoting-literal syntax class moves from hash-shortcuts, which added it for the same reason in #794, to a shared private module. Also fixes an "equialent" typo in two rule descriptions. Co-Authored-By: Claude Fable 5 --- default-recommendations/hash-shortcuts.rkt | 5 +---- .../list-shortcuts-test.rkt | 21 +++++++++++++++++++ default-recommendations/list-shortcuts.rkt | 11 ++++++---- .../private/self-quoting-literal.rkt | 18 ++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 default-recommendations/private/self-quoting-literal.rkt diff --git a/default-recommendations/hash-shortcuts.rkt b/default-recommendations/hash-shortcuts.rkt index 6b5a7017..0f3a97c5 100644 --- a/default-recommendations/hash-shortcuts.rkt +++ b/default-recommendations/hash-shortcuts.rkt @@ -17,6 +17,7 @@ resyntax/default-recommendations/private/lambda-by-any-name resyntax/default-recommendations/private/literal-constant resyntax/default-recommendations/private/pure-expression + resyntax/default-recommendations/private/self-quoting-literal resyntax/default-recommendations/private/syntax-equivalence resyntax/default-recommendations/private/syntax-identifier-sets resyntax/private/syntax-neighbors @@ -143,10 +144,6 @@ #:original-splice (orig-definition orig-hash-set!))))) -(define-syntax-class self-quoting-literal - (pattern (~or _:boolean _:character _:number _:regexp _:byte-regexp _:string _:bytes))) - - ;; A datum within a quasiquoted expression that can be lifted directly into expression position, ;; either because it's self-quoting or because it's a symbol that can be wrapped in a quote. Quoted ;; datums like (quote x) are excluded because quasiquote treats them as plain data: the quasiquoted diff --git a/default-recommendations/list-shortcuts-test.rkt b/default-recommendations/list-shortcuts-test.rkt index 1582d7bf..54685f17 100644 --- a/default-recommendations/list-shortcuts-test.rkt +++ b/default-recommendations/list-shortcuts-test.rkt @@ -135,6 +135,27 @@ no-change-test: "quasiquotation with only constants not refactorable to list" - `(1 2 3) +no-change-test: "quasiquotation with quoted datums not refactorable to list" +------------------------------ +(define (f y) + `(1 ,y 'x)) +------------------------------ + + +no-change-test: "quasiquotation with symbols not refactorable to list" +------------------------------ +(define (f y) + `(1 ,y x)) +------------------------------ + + +no-change-test: "quasiquotation with nested lists not refactorable to list" +------------------------------ +(define (f y) + `(1 ,y (2 3))) +------------------------------ + + test: "unnecessary splicing quasiquotation refactorable to append" ------------------------------ (define (f xs ys zs) diff --git a/default-recommendations/list-shortcuts.rkt b/default-recommendations/list-shortcuts.rkt index b1c0aeee..374a6511 100644 --- a/default-recommendations/list-shortcuts.rkt +++ b/default-recommendations/list-shortcuts.rkt @@ -18,7 +18,7 @@ resyntax/base resyntax/default-recommendations/analyzers/ignored-result-values resyntax/default-recommendations/private/lambda-by-any-name - resyntax/default-recommendations/private/literal-constant + resyntax/default-recommendations/private/self-quoting-literal resyntax/default-recommendations/private/syntax-identifier-sets syntax/parse) @@ -106,15 +106,18 @@ (sort lst less-than #:key f1)) +;; Elements that aren't unquoted must be self-quoting to lift them out of the quasiquote unchanged. +;; Quoted datums like (quote x) are quasiquoted as plain data, so `(1 'x) is the list (1 (quote x)), +;; not the list (1 x) that a lifted 'x expression would produce. (define-syntax-class unquoted #:attributes (expr literal?) #:literals (unquote) - (pattern expr:literal-constant #:attr literal? #true) + (pattern expr:self-quoting-literal #:attr literal? #true) (pattern (unquote expr) #:attr literal? #false)) (define-refactoring-rule quasiquote-to-list - #:description "This quasiquotation is equialent to a simple `list` call." + #:description "This quasiquotation is equivalent to a simple `list` call." #:literals (quasiquote) (quasiquote (arg:unquoted ...)) #:when (for/or ([literal? (in-list (attribute arg.literal?))]) @@ -123,7 +126,7 @@ (define-refactoring-rule quasiquote-to-append - #:description "This quasiquotation is equialent to calling `append`." + #:description "This quasiquotation is equivalent to calling `append`." #:literals (quasiquote unquote-splicing) (quasiquote ((unquote-splicing arg) ...)) (append arg ...)) diff --git a/default-recommendations/private/self-quoting-literal.rkt b/default-recommendations/private/self-quoting-literal.rkt new file mode 100644 index 00000000..485f6be8 --- /dev/null +++ b/default-recommendations/private/self-quoting-literal.rkt @@ -0,0 +1,18 @@ +#lang racket/base + + +(provide self-quoting-literal) + + +(require syntax/parse) + + +;@---------------------------------------------------------------------------------------------------- + + +;; A datum that evaluates to itself, so it means the same thing in expression position as it does +;; within quoted or quasiquoted data. Symbols and quoted datums like (quote x) are notably not +;; self-quoting: a symbol in expression position is a variable reference, and within quasiquoted +;; data (quote x) is a two-element list rather than the value x. +(define-syntax-class self-quoting-literal + (pattern (~or _:boolean _:character _:number _:regexp _:byte-regexp _:string _:bytes)))