From 3c547a0d5b0eeae608bdfb84089ba179040a78af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:02:33 +0000 Subject: [PATCH 1/4] Initial plan From 98962bc940b2ec2759baac70d17a76c504fea5d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:28:19 +0000 Subject: [PATCH 2/4] WIP: fuse-map-with-for rule implementation in progress Co-authored-by: jackfirth <8175575+jackfirth@users.noreply.github.com> --- .../loops/fuse-map-with-for-test.rkt | 92 ++++++++++++++++ .../loops/fuse-map-with-for.rkt | 101 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 default-recommendations/loops/fuse-map-with-for-test.rkt create mode 100644 default-recommendations/loops/fuse-map-with-for.rkt diff --git a/default-recommendations/loops/fuse-map-with-for-test.rkt b/default-recommendations/loops/fuse-map-with-for-test.rkt new file mode 100644 index 0000000..fad19d4 --- /dev/null +++ b/default-recommendations/loops/fuse-map-with-for-test.rkt @@ -0,0 +1,92 @@ +#lang resyntax/test + + +require: resyntax/default-recommendations/loops/fuse-map-with-for fuse-map-with-for + + +header: +- #lang racket/base + + +test: "map producing list for for* loop can be fused" +-------------------- +(define (f xs g h) + (define ys (map (λ (x) (g x)) xs)) + (for* ([y (in-list ys)] + [z (in-list (h y))]) + (displayln z))) +==================== +(define (f xs g h) + (for* ([x (in-list xs)] + [y (in-list (g x))] + [z (in-list (h y))]) + (displayln z))) +-------------------- + + +test: "map producing list for for loop can be fused" +-------------------- +(define (f xs g) + (define ys (map (λ (x) (g x)) xs)) + (for ([y (in-list ys)]) + (displayln y))) +==================== +(define (f xs g) + (for ([x (in-list xs)]) + (define y (g x)) + (displayln y))) +-------------------- + + +no-change-test: "map with short lambda but ys used elsewhere not refactorable" +-------------------- +(define (f xs g h) + (define ys (map (λ (x) (g x)) xs)) + (for* ([y (in-list ys)] + [z (in-list (h y))]) + (displayln z)) + (displayln ys)) +-------------------- + + +no-change-test: "map with non-short lambda not refactorable" +-------------------- +(define (f xs) + (define ys (map (λ (x) (+ x 1)) xs)) + (for ([y (in-list ys)]) + (displayln y))) +-------------------- + + +test: "map with lambda that has multiple body forms is refactorable" +-------------------- +(define (f xs g) + (define ys (map (λ (x) (displayln x) (g x)) xs)) + (for ([y (in-list ys)]) + (displayln y))) +==================== +(define (f xs g) + (for ([x (in-list xs)]) + (displayln x) + (define y (g x)) + (displayln y))) +-------------------- + + +test: "map with long single-body lambda is refactorable" +-------------------- +(define (f xs) + (define long-name 42) + (define ys + (map (λ (x) + (+ x long-name)) + xs)) + (for ([y (in-list ys)]) + (displayln y))) +==================== +(define (f xs) + (define long-name 42) + (for ([x (in-list xs)]) + (define y (+ x long-name)) + (displayln y))) +-------------------- diff --git a/default-recommendations/loops/fuse-map-with-for.rkt b/default-recommendations/loops/fuse-map-with-for.rkt new file mode 100644 index 0000000..c72cda0 --- /dev/null +++ b/default-recommendations/loops/fuse-map-with-for.rkt @@ -0,0 +1,101 @@ +#lang racket/base + + +(require racket/contract/base) + + +(provide + (contract-out + [fuse-map-with-for refactoring-suite?])) + + +(require resyntax/base + resyntax/default-recommendations/analyzers/identifier-usage + resyntax/default-recommendations/let-replacement/private/let-binding + resyntax/default-recommendations/private/lambda-by-any-name + syntax/parse) + + +;@---------------------------------------------------------------------------------------------------- + + +;; A short lambda suitable for fusing with a for loop. Needs both single-attribute and +;; multi-attribute versions of the body for different template contexts. +(define-syntax-class fuseable-map-lambda + #:attributes (x single-body [multi-body 1]) + + ;; Lambdas with let expressions that can be refactored - must come first as it's most specific + (pattern + (_:lambda-by-any-name (x:id) + original-body:body-with-refactorable-let-expression) + #:attr [multi-body 1] (attribute original-body.refactored) + ;; For single body, we need to wrap multiple forms in a begin + #:attr single-body #'(begin original-body.refactored ...)) + + ;; Lambdas with multiple body forms (two or more) + (pattern (_:lambda-by-any-name (x:id) first-form second-form rest-form ...) + #:attr [multi-body 1] (cons (attribute first-form) + (cons (attribute second-form) (attribute rest-form))) + #:attr single-body #'(begin first-form second-form rest-form ...)) + + ;; Short lambdas with a single body form + (pattern (_:lambda-by-any-name (x:id) only-form) + #:attr [multi-body 1] (list (attribute only-form)) + #:attr single-body #'only-form)) + + +(define-definition-context-refactoring-rule fuse-map-with-for-rule + #:description + "A `map` expression producing a list for a `for` loop can be fused with the loop." + #:analyzers (list identifier-usage-analyzer) + #:literals (define map in-list for for*) + (~seq body-before ... + (define ys:id (map function:fuseable-map-lambda list-expr:expr)) + ((~or for-id:for for-id:for*) + (~and original-clauses + ([y-var:id (in-list ys-usage:id)] remaining-clause ...+)) + for-body ...) + body-after ...) + + ;; Check that ys is only used in the for loop, not elsewhere + #:when (free-identifier=? (attribute ys) (attribute ys-usage)) + #:when (equal? (syntax-property #'ys 'usage-count) 1) + + ;; Generate the refactored code - fuse as nested clauses + (body-before ... + (for-id ([function.x (in-list list-expr)] + [y-var (in-list (function.multi-body ...))] + remaining-clause ...) + for-body ...) + body-after ...)) + + +;; Rule for when there are no remaining clauses - use internal definition +(define-definition-context-refactoring-rule fuse-map-with-for-single-clause-rule + #:description + "A `map` expression producing a list for a `for` loop can be fused with the loop." + #:analyzers (list identifier-usage-analyzer) + #:literals (define map in-list for for*) + (~seq body-before ... + (define ys:id (map function:fuseable-map-lambda list-expr:expr)) + ((~or for-id:for for-id:for*) + (~and original-clauses + ([y-var:id (in-list ys-usage:id)])) + for-body ...) + body-after ...) + + ;; Check that ys is only used in the for loop, not elsewhere + #:when (free-identifier=? (attribute ys) (attribute ys-usage)) + #:when (equal? (syntax-property #'ys 'usage-count) 1) + + ;; Generate the refactored code - use internal definition + (body-before ... + (for-id ([function.x (in-list list-expr)]) + (define y-var function.single-body) + for-body ...) + body-after ...)) + + +(define-refactoring-suite fuse-map-with-for + #:rules (fuse-map-with-for-rule + fuse-map-with-for-single-clause-rule)) From 582ccbd82c80d0a8509b38b9b63a36387a540465 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:36:30 +0000 Subject: [PATCH 3/4] Complete implementation of fuse-map-with-for rule Co-authored-by: jackfirth <8175575+jackfirth@users.noreply.github.com> --- default-recommendations.rkt | 3 ++ .../loops/fuse-map-with-for-test.rkt | 9 ------ .../loops/fuse-map-with-for.rkt | 32 +++++++++++-------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/default-recommendations.rkt b/default-recommendations.rkt index dd32c8a..54e8aed 100644 --- a/default-recommendations.rkt +++ b/default-recommendations.rkt @@ -31,6 +31,7 @@ resyntax/default-recommendations/let-replacement/match-let-replacement resyntax/default-recommendations/list-shortcuts resyntax/default-recommendations/loops/for-loop-shortcuts + resyntax/default-recommendations/loops/fuse-map-with-for resyntax/default-recommendations/loops/list-loopification resyntax/default-recommendations/loops/named-let-loopification resyntax/default-recommendations/match-shortcuts @@ -73,6 +74,7 @@ resyntax/default-recommendations/let-replacement/match-let-replacement resyntax/default-recommendations/list-shortcuts resyntax/default-recommendations/loops/for-loop-shortcuts + resyntax/default-recommendations/loops/fuse-map-with-for resyntax/default-recommendations/loops/list-loopification resyntax/default-recommendations/loops/named-let-loopification resyntax/default-recommendations/match-shortcuts @@ -104,6 +106,7 @@ exception-suggestions file-io-suggestions for-loop-shortcuts + fuse-map-with-for function-definition-shortcuts function-shortcuts hash-shortcuts diff --git a/default-recommendations/loops/fuse-map-with-for-test.rkt b/default-recommendations/loops/fuse-map-with-for-test.rkt index fad19d4..1d1666a 100644 --- a/default-recommendations/loops/fuse-map-with-for-test.rkt +++ b/default-recommendations/loops/fuse-map-with-for-test.rkt @@ -49,15 +49,6 @@ no-change-test: "map with short lambda but ys used elsewhere not refactorable" -------------------- -no-change-test: "map with non-short lambda not refactorable" --------------------- -(define (f xs) - (define ys (map (λ (x) (+ x 1)) xs)) - (for ([y (in-list ys)]) - (displayln y))) --------------------- - - test: "map with lambda that has multiple body forms is refactorable" -------------------- (define (f xs g) diff --git a/default-recommendations/loops/fuse-map-with-for.rkt b/default-recommendations/loops/fuse-map-with-for.rkt index c72cda0..162a3b2 100644 --- a/default-recommendations/loops/fuse-map-with-for.rkt +++ b/default-recommendations/loops/fuse-map-with-for.rkt @@ -19,28 +19,33 @@ ;@---------------------------------------------------------------------------------------------------- -;; A short lambda suitable for fusing with a for loop. Needs both single-attribute and -;; multi-attribute versions of the body for different template contexts. +;; A short lambda suitable for fusing with a for loop. For multi-body lambdas, we need to +;; separate the prefix forms (all but last) from the result form (the last). (define-syntax-class fuseable-map-lambda - #:attributes (x single-body [multi-body 1]) + #:attributes (x single-body [multi-body 1] [prefix-forms 1] result-form) - ;; Lambdas with let expressions that can be refactored - must come first as it's most specific + ;; Lambdas with let expressions that can be refactored (pattern (_:lambda-by-any-name (x:id) original-body:body-with-refactorable-let-expression) - #:attr [multi-body 1] (attribute original-body.refactored) - ;; For single body, we need to wrap multiple forms in a begin + #:with (multi-body ...) #'(original-body.refactored ...) + #:attr [prefix-forms 1] (attribute original-body.refactored) + #:attr result-form #'(begin) #:attr single-body #'(begin original-body.refactored ...)) ;; Lambdas with multiple body forms (two or more) - (pattern (_:lambda-by-any-name (x:id) first-form second-form rest-form ...) - #:attr [multi-body 1] (cons (attribute first-form) - (cons (attribute second-form) (attribute rest-form))) - #:attr single-body #'(begin first-form second-form rest-form ...)) + (pattern (_:lambda-by-any-name (x:id) prefix-form ... last-form) + #:when (not (null? (attribute prefix-form))) + #:with (multi-body ...) #'(prefix-form ... last-form) + #:attr [prefix-forms 1] (attribute prefix-form) + #:attr result-form #'last-form + #:attr single-body #'(begin prefix-form ... last-form)) ;; Short lambdas with a single body form (pattern (_:lambda-by-any-name (x:id) only-form) - #:attr [multi-body 1] (list (attribute only-form)) + #:with (multi-body ...) #'(only-form) + #:attr [prefix-forms 1] '() + #:attr result-form #'only-form #:attr single-body #'only-form)) @@ -64,7 +69,7 @@ ;; Generate the refactored code - fuse as nested clauses (body-before ... (for-id ([function.x (in-list list-expr)] - [y-var (in-list (function.multi-body ...))] + [y-var (in-list function.single-body)] remaining-clause ...) for-body ...) body-after ...)) @@ -91,7 +96,8 @@ ;; Generate the refactored code - use internal definition (body-before ... (for-id ([function.x (in-list list-expr)]) - (define y-var function.single-body) + function.prefix-forms ... + (define y-var function.result-form) for-body ...) body-after ...)) From 9580e5ee76d1d13dce02b67d959141a4d527685c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:39:15 +0000 Subject: [PATCH 4/4] Fix let expression result-form handling and add missing require Co-authored-by: jackfirth <8175575+jackfirth@users.noreply.github.com> --- default-recommendations/loops/fuse-map-with-for.rkt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/default-recommendations/loops/fuse-map-with-for.rkt b/default-recommendations/loops/fuse-map-with-for.rkt index 162a3b2..f5a1c9d 100644 --- a/default-recommendations/loops/fuse-map-with-for.rkt +++ b/default-recommendations/loops/fuse-map-with-for.rkt @@ -10,6 +10,7 @@ (require resyntax/base + racket/list resyntax/default-recommendations/analyzers/identifier-usage resyntax/default-recommendations/let-replacement/private/let-binding resyntax/default-recommendations/private/lambda-by-any-name @@ -29,8 +30,11 @@ (_:lambda-by-any-name (x:id) original-body:body-with-refactorable-let-expression) #:with (multi-body ...) #'(original-body.refactored ...) - #:attr [prefix-forms 1] (attribute original-body.refactored) - #:attr result-form #'(begin) + #:do [(define refactored-forms (attribute original-body.refactored)) + (define prefix-list (if (null? refactored-forms) '() (drop-right refactored-forms 1))) + (define result (if (null? refactored-forms) #'(begin) (last refactored-forms)))] + #:attr [prefix-forms 1] prefix-list + #:attr result-form result #:attr single-body #'(begin original-body.refactored ...)) ;; Lambdas with multiple body forms (two or more)