Skip to content

Commit 58d264b

Browse files
Copilotjackfirth
andauthored
Add refactoring rules to suggest define-syntax-parse-rule over verbose macro forms (#740)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: jackfirth <[email protected]> Co-authored-by: Jack Firth <[email protected]>
1 parent 8b3da66 commit 58d264b

File tree

3 files changed

+153
-9
lines changed

3 files changed

+153
-9
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#lang resyntax/test
2+
3+
4+
require: resyntax/default-recommendations syntax-parse-shortcuts
5+
6+
7+
header:
8+
------------------------------
9+
#lang racket/base
10+
(require (for-syntax racket/base syntax/parse)
11+
syntax/parse/define)
12+
------------------------------
13+
14+
15+
test: "define-syntax with syntax-parse and one clause refactorable to define-syntax-parse-rule"
16+
------------------------------
17+
(define-syntax my-or
18+
(lambda (stx)
19+
(syntax-parse stx
20+
[(_ a b)
21+
#'(let ([tmp a]) (if tmp tmp b))])))
22+
==============================
23+
(define-syntax-parse-rule (my-or a b)
24+
(let ([tmp a]) (if tmp tmp b)))
25+
------------------------------
26+
27+
28+
test: "define-syntax-parser with one clause refactorable to define-syntax-parse-rule"
29+
------------------------------
30+
(define-syntax-parser my-or
31+
[(_ a b)
32+
#'(let ([tmp a]) (if tmp tmp b))])
33+
==============================
34+
(define-syntax-parse-rule (my-or a b)
35+
(let ([tmp a]) (if tmp tmp b)))
36+
------------------------------
37+
38+
39+
test: "define-syntax with syntax-parse using stx name refactorable to define-syntax-parse-rule"
40+
------------------------------
41+
(define-syntax my-macro
42+
(lambda (stx)
43+
(syntax-parse stx
44+
[(_ x:id)
45+
#'(quote x)])))
46+
==============================
47+
(define-syntax-parse-rule (my-macro x:id)
48+
(quote x))
49+
------------------------------
50+
51+
52+
test: "define-syntax with syntax-parse using custom name in directives replaced with this-syntax"
53+
------------------------------
54+
(define-syntax my-macro
55+
(lambda (input-stx)
56+
(syntax-parse input-stx
57+
[(_ x:id)
58+
#:with loc input-stx
59+
#'(quote (x loc))])))
60+
==============================
61+
(define-syntax-parse-rule (my-macro x:id)
62+
#:with loc this-syntax
63+
(quote (x loc)))
64+
------------------------------
65+
66+
67+
no-change-test: "define-syntax with syntax-parse and multiple clauses not refactorable"
68+
------------------------------
69+
(define-syntax my-or
70+
(lambda (stx)
71+
(syntax-parse stx
72+
[(_ a b)
73+
#'(let ([tmp a]) (if tmp tmp b))]
74+
[(_ a)
75+
#'a])))
76+
------------------------------
77+
78+
79+
no-change-test: "define-syntax-parser with multiple clauses not refactorable"
80+
------------------------------
81+
(define-syntax-parser my-or
82+
[(_ a b)
83+
#'(let ([tmp a]) (if tmp tmp b))]
84+
[(_ a)
85+
#'a])
86+
------------------------------

default-recommendations/syntax-parse-shortcuts.rkt

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,63 @@
99
[syntax-parse-shortcuts refactoring-suite?]))
1010

1111

12-
(require resyntax/base)
12+
(require (for-syntax racket/base
13+
syntax/parse)
14+
rebellion/private/static-name
15+
resyntax/base
16+
resyntax/private/more-syntax-parse-classes
17+
resyntax/private/syntax-traversal
18+
racket/stream
19+
syntax/parse
20+
syntax/parse/define)
1321

1422

1523
;@----------------------------------------------------------------------------------------------------
1624

1725

26+
(define-refactoring-rule define-syntax-syntax-parse-to-define-syntax-parse-rule
27+
#:description
28+
"This `define-syntax` macro with a single `syntax-parse` clause can be replaced with a simpler,
29+
equivalent `define-syntax-parse-rule` macro."
30+
#:literals (define-syntax lambda [syntax-parse syntax-parse #:phase 1] [syntax-id syntax #:phase 1])
31+
32+
(define-syntax macro:id
33+
(lambda (stx-id:id)
34+
(syntax-parse stx-id2:id
35+
[(_ pattern ...) directive:syntax-parse-pattern-directive ... (syntax-id last-form)])))
36+
37+
#:when (free-identifier=? (attribute stx-id) (attribute stx-id2) 1)
38+
39+
#:with (new-body ...)
40+
(syntax-traverse #'((~@ . directive) ... last-form)
41+
[id-in-body:id
42+
#:when (free-identifier=? (attribute id-in-body) (attribute stx-id) 1)
43+
(syntax-property #'this-syntax 'skip-incorrect-binding-check? #true)])
44+
45+
(define-syntax-parse-rule (macro pattern ...) new-body ...))
46+
47+
48+
(define-refactoring-rule define-syntax-parser-to-define-syntax-parse-rule-simple
49+
#:description
50+
"This `define-syntax-parser` macro with a single clause can be replaced with a simpler, equivalent
51+
`define-syntax-parse-rule` macro."
52+
#:literals (define-syntax-parser)
53+
54+
(define-syntax-parser macro:id
55+
[(_ . pattern) body ...])
56+
57+
#:do [(define (strip-syntax-wrapper stx)
58+
(syntax-parse stx
59+
#:literals (syntax)
60+
[(syntax body) #'body]
61+
[other #'other]))
62+
(define new-body (map strip-syntax-wrapper (attribute body)))]
63+
64+
#:with (new-body-part ...) new-body
65+
66+
(define-syntax-parse-rule (macro . pattern) new-body-part ...))
67+
68+
1869
(define-refactoring-suite syntax-parse-shortcuts
19-
#:rules ())
70+
#:rules (define-syntax-syntax-parse-to-define-syntax-parse-rule
71+
define-syntax-parser-to-define-syntax-parse-rule-simple))

private/syntax-replacement.rkt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,9 @@
360360
;; Without the fix, the formatter would allow the code to exceed the line length limit.
361361
;; With the fix, the formatter either keeps it on one line within the limit, or breaks it
362362
;; across multiple lines if it cannot fit.
363-
(define orig-line " [`(,(and (or '+ '- '* '/ 'and 'or) op) ,as ..2 ,b) `(,op ,(loop `(,op ,@as) env) ,(loop b env))]")
363+
(define orig-line
364+
(string-append " [`(,(and (or '+ '- '* '/ 'and 'or) op) ,as ..2 ,b)"
365+
" `(,op ,(loop `(,op ,@as) env) ,(loop b env))]"))
364366
(check-equal? (string-length orig-line) 102 "Original line should be 102 characters")
365367

366368
;; The quasiquote expression starts at position 57 and ends at position 101
@@ -370,9 +372,10 @@
370372
(define quasiquote-end 101) ; just before the final ]
371373

372374
(define replacement
373-
(string-replacement #:start quasiquote-start
374-
#:end quasiquote-end
375-
#:contents (list (inserted-string "(list op (loop `(,op ,@as) env) (loop b env))"))))
375+
(string-replacement
376+
#:start quasiquote-start
377+
#:end quasiquote-end
378+
#:contents (list (inserted-string "(list op (loop `(,op ,@as) env) (loop b env))"))))
376379

377380
;; Test with Racket's standard line width of 102
378381
(parameterize ([current-width 102])
@@ -383,7 +386,8 @@
383386
;; If it's multi-line, each line should not exceed 102 characters
384387
(for ([line (in-list (string-split result "\n"))])
385388
(check-true (<= (string-length line) 102)
386-
(format "Line exceeds length limit: ~a chars (should be <= 102)" (string-length line)))))))
389+
(format "Line exceeds length limit: ~a chars (should be <= 102)"
390+
(string-length line)))))))
387391

388392

389393
(define (syntax-replacement-introduces-incorrect-bindings? replacement)
@@ -392,7 +396,8 @@
392396
#:introduction-scope intro)
393397
replacement)
394398
(for/and ([new-id (in-syntax-identifiers new)]
395-
#:unless (bound-identifier=? new-id (intro new-id 'remove)))
399+
#:unless (bound-identifier=? new-id (intro new-id 'remove))
400+
#:unless (syntax-property new-id 'skip-incorrect-binding-check?))
396401
(free-identifier=? new-id (datum->syntax orig (syntax->datum new-id)))))
397402

398403

@@ -403,7 +408,8 @@
403408
replacement)
404409
(for/list ([new-id (in-syntax-identifiers new)]
405410
#:unless (bound-identifier=? new-id (intro new-id 'remove))
406-
#:unless (free-identifier=? new-id (datum->syntax orig (syntax->datum new-id))))
411+
#:unless (free-identifier=? new-id (datum->syntax orig (syntax->datum new-id)))
412+
#:unless (syntax-property new-id 'skip-incorrect-binding-check?))
407413
new-id))
408414

409415

0 commit comments

Comments
 (0)