diff --git a/default-recommendations/define-let-to-multi-define-test.rkt b/default-recommendations/define-let-to-multi-define-test.rkt new file mode 100644 index 0000000..2e2f2ad --- /dev/null +++ b/default-recommendations/define-let-to-multi-define-test.rkt @@ -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) +------------------------------ diff --git a/default-recommendations/formatting-preservation-test.rkt b/default-recommendations/formatting-preservation-test.rkt index 607bea6..09f1008 100644 --- a/default-recommendations/formatting-preservation-test.rkt +++ b/default-recommendations/formatting-preservation-test.rkt @@ -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" ) diff --git a/default-recommendations/let-binding-suggestions.rkt b/default-recommendations/let-binding-suggestions.rkt index 75c5950..6dd0e8c 100644 --- a/default-recommendations/let-binding-suggestions.rkt +++ b/default-recommendations/let-binding-suggestions.rkt @@ -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 ...)) @@ -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))