Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions default-recommendations/define-let-to-multi-define-test.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#lang resyntax/test


require: resyntax/default-recommendations let-binding-suggestions


header:
- #lang racket/base


test: "define-let-to-multi-define with single binding"
------------------------------
(define (f)
(define a (let ([b 1]) (+ b 10)))
a)
==============================
(define (f)
(define b 1)
(define a (+ b 10))
a)
------------------------------


test: "define-let-to-multi-define with two bindings"
------------------------------
(define (f)
(define a (let ([b 1] [c 2]) (+ b c 10)))
a)
==============================
(define (f)
(define b 1)
(define c 2)
(define a (+ b c 10))
a)
------------------------------


test: "define-let-to-multi-define with three bindings"
------------------------------
(define (f)
(define result (let ([x 1] [y 2] [z 3]) (* x y z)))
result)
==============================
(define (f)
(define x 1)
(define y 2)
(define z 3)
(define result (* x y z))
result)
------------------------------


test: "define-let-to-multi-define with body-before"
------------------------------
(define (f)
(displayln "foo")
(define a (let ([b 1] [c 2]) (+ b c)))
a)
==============================
(define (f)
(displayln "foo")
(define b 1)
(define c 2)
(define a (+ b c))
a)
------------------------------


test: "define-let-to-multi-define with body-after"
------------------------------
(define (f)
(define a (let ([b 1] [c 2]) (+ b c)))
(displayln "bar")
a)
==============================
(define (f)
(define b 1)
(define c 2)
(define a (+ b c))
(displayln "bar")
a)
------------------------------


test: "define-let-to-multi-define preserves complex expressions"
------------------------------
(define (f x)
(define result
(let ([sum (+ x 1)]
[product (* x 2)])
(+ sum product)))
result)
==============================
(define (f x)
(define sum (+ x 1))
(define product (* x 2))
(define result (+ sum product))
result)
------------------------------


no-change-test: "define-let-to-multi-define doesn't apply when bindings shadow outer scope"
------------------------------
(define (f b)
(define a (let ([b 1]) (+ b 10)))
a)
------------------------------


no-change-test: "define-let-to-multi-define doesn't apply when later binding depends on earlier"
------------------------------
(define (f)
(define a (let ([b 1] [c b]) (+ b c)))
a)
------------------------------
2 changes: 1 addition & 1 deletion default-recommendations/formatting-preservation-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test: "refactoring an expression doesn't affect formatting of unrefactored code"
----------------------------------------


test: "define-let-to-double-define doesn't reformat the entire definition context"
test: "define-let-to-multi-define doesn't reformat the entire definition context"
----------------------------------------
(define (f)
( displayln "foo" )
Expand Down
34 changes: 24 additions & 10 deletions default-recommendations/let-binding-suggestions.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,34 @@
(call-with-values (λ () expr) receiver))


(define-definition-context-refactoring-rule define-let-to-double-define
#:description "This `let` expression can be pulled up into a `define` expression."
(define-definition-context-refactoring-rule define-let-to-multi-define
#:description "This `let` expression can be pulled up into multiple `define` expressions."
#:literals (define let)
(~seq body-before ...
(~and original-definition (define id:id (let ([nested-id:id nested-expr:expr]) expr:expr)))
(~and original-definition
(define id:id (let ([nested-id:id nested-expr:expr] ...+) expr:expr)))
body-after ...)
#:when (identifier-binding-unchanged-in-context? (attribute id) (attribute nested-expr))
#:when (for/and ([body-free-id
(in-free-id-set
(syntax-free-identifiers #'(body-before ... nested-expr body-after ...)))])
(identifier-binding-unchanged-in-context? body-free-id (attribute nested-id)))
#:when (identifier-binding-unchanged-in-context? (attribute id) #'(nested-expr ...))
#:when (for/and ([nested-id (in-list (attribute nested-id))])
(not (identifier-has-exact-binding-in-context? nested-id (attribute original-definition))))
#:when (for*/and ([body-free-id
(in-free-id-set
(syntax-free-identifiers #'(body-before ... nested-expr ... body-after ...)))]
[nested-id (in-list (attribute nested-id))])
(identifier-binding-unchanged-in-context? body-free-id nested-id))
;; Ensure later bindings don't depend on earlier ones (sequential let* semantics)
#:when (for/and ([rhs (in-list (attribute nested-expr))]
[i (in-naturals)]
#:when #true ; ensures proper sequencing in the nested loop
[nested-id (in-list (attribute nested-id))]
[j (in-naturals)]
#:when (< i j)
#:when #true ; ensures proper sequencing in the nested loop
[rhs-free-id (in-free-id-set (syntax-free-identifiers rhs))])
(identifier-binding-unchanged-in-context? rhs-free-id nested-id))
(body-before ...
(~@ . (~focus-replacement-on
(~splicing-replacement ((define nested-id nested-expr) (define id expr))
(~splicing-replacement ((define nested-id nested-expr) ... (define id expr))
#:original original-definition)))
body-after ...))

Expand Down Expand Up @@ -117,7 +131,7 @@
(define-refactoring-suite let-binding-suggestions
#:rules (let-to-define
begin0-let-to-define-begin0
define-let-to-double-define
define-let-to-multi-define
delete-redundant-let
let-values-then-call-to-call-with-values
named-let-to-plain-let))