Skip to content

Commit 6aeb444

Browse files
committed
fixed loop locals
1 parent ba163ac commit 6aeb444

File tree

2 files changed

+53
-57
lines changed

2 files changed

+53
-57
lines changed

src/sci/impl/async_macro.cljc

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,6 @@
5454
(= 'sci.impl.async-await/finally op)
5555
(= 'sci.impl.async-await/resolve op))))))
5656

57-
(defn- ensure-promise-result
58-
"Ensure async function body returns a promise.
59-
Wraps the last expression in js/Promise.resolve if not already a promise."
60-
[body-exprs]
61-
(if (empty? body-exprs)
62-
(list (wrap-promise nil))
63-
(let [exprs (vec body-exprs)
64-
last-idx (dec (count exprs))
65-
last-expr (nth exprs last-idx)]
66-
(if (promise-form? last-expr)
67-
body-exprs
68-
(assoc exprs last-idx (wrap-promise last-expr))))))
69-
7057
(declare transform-async-body)
7158

7259
(defn transform-do
@@ -117,48 +104,6 @@
117104

118105
:else form))
119106

120-
(defn transform-loop*
121-
"Transform loop* with await into a recursive promise-returning function.
122-
(loop* [x 0] (if (< x 3) (do (await p) (recur (inc x))) x))
123-
=>
124-
((fn loop-fn [x] (if (< x 3) (.then p (fn [_] (loop-fn (inc x)))) (js/Promise.resolve x))) 0)"
125-
[ctx locals bindings body]
126-
(let [loop-fn-name (gensym "loop_fn__")
127-
pairs (partition 2 bindings)
128-
param-names (mapv first pairs)
129-
init-values (map second pairs)
130-
;; Transform init values - they may contain await
131-
transformed-inits (doall (map #(transform-async-body ctx locals %) init-values))
132-
;; Add params to locals
133-
body-locals (into locals param-names)
134-
;; Replace recur with loop function call
135-
body-with-replaced-recur (map #(replace-recur % loop-fn-name) body)
136-
;; Transform the body using transform-do to properly chain promises
137-
transformed-body (transform-do ctx body-locals body-with-replaced-recur)
138-
;; Ensure promise result
139-
promised-body (if (promise-form? transformed-body)
140-
transformed-body
141-
(wrap-promise transformed-body))
142-
;; Build the loop function
143-
loop-fn (list 'fn* loop-fn-name param-names promised-body)
144-
;; Build the loop call, chaining any promise init values
145-
make-loop-call (fn [init-vals]
146-
(wrap-promise (apply list loop-fn init-vals)))]
147-
;; If any init value is a promise, chain them
148-
(if (some promise-form? transformed-inits)
149-
(letfn [(chain-inits [vals-before remaining]
150-
(if (seq remaining)
151-
(let [v (first remaining)]
152-
(if (promise-form? v)
153-
(let [await-sym (gensym "init__")]
154-
(promise-then v (list 'fn* [await-sym]
155-
(chain-inits (conj vals-before await-sym)
156-
(rest remaining)))))
157-
(chain-inits (conj vals-before v) (rest remaining))))
158-
(make-loop-call vals-before)))]
159-
(chain-inits [] transformed-inits))
160-
(make-loop-call transformed-inits))))
161-
162107
(defn transform-let*
163108
"Transform let* with await calls into .then chains."
164109
[ctx locals bindings body]
@@ -190,6 +135,38 @@
190135
(list 'let* (vec acc-bindings) transformed-body))
191136
transformed-body)))))
192137

138+
(defn transform-loop*
139+
"Transform loop* with await into a recursive promise-returning function.
140+
Wraps init values in let* to preserve sequential scoping (each init sees previous bindings).
141+
142+
(loop* [x 0] (if (< x 3) (do (await p) (recur (inc x))) x))
143+
=>
144+
(let* [x 0]
145+
((fn loop-fn [x] (if (< x 3) (.then p (fn [_] (loop-fn (inc x)))) (js/Promise.resolve x))) x))"
146+
[ctx locals bindings body]
147+
(let [loop-fn-name (gensym "loop_fn__")
148+
pairs (partition 2 bindings)
149+
param-names (mapv first pairs)
150+
init-values (mapv second pairs)
151+
;; Add params to locals for body transformation
152+
body-locals (into locals param-names)
153+
;; Replace recur with loop function call
154+
body-with-replaced-recur (map #(replace-recur % loop-fn-name) body)
155+
;; Transform the body using transform-do to properly chain promises
156+
transformed-body (transform-do ctx body-locals body-with-replaced-recur)
157+
;; Ensure promise result
158+
promised-body (if (promise-form? transformed-body)
159+
transformed-body
160+
(wrap-promise transformed-body))
161+
;; Build the loop function
162+
loop-fn (list 'fn* loop-fn-name param-names promised-body)
163+
;; Build the loop call - uses param names since they'll be bound by let*
164+
loop-call (wrap-promise (apply list loop-fn param-names))
165+
;; Build let* bindings: [param1 init1 param2 init2 ...]
166+
let-bindings (vec (interleave param-names init-values))]
167+
;; Wrap in let* and transform - this handles sequential scoping and promise chaining
168+
(transform-let* ctx locals let-bindings (list loop-call))))
169+
193170
(defn transform-try
194171
"Transform try/catch/finally with await into Promise .catch/.finally chains.
195172
Only uses promise chains if there's actually an await somewhere."
@@ -420,6 +397,19 @@
420397

421398
:else body)))))
422399

400+
(defn- ensure-promise-result
401+
"Ensure async function body returns a promise.
402+
Wraps the last expression in js/Promise.resolve if not already a promise."
403+
[body-exprs]
404+
(if (empty? body-exprs)
405+
(list (wrap-promise nil))
406+
(let [exprs (vec body-exprs)
407+
last-idx (dec (count exprs))
408+
last-expr (nth exprs last-idx)]
409+
(if (promise-form? last-expr)
410+
body-exprs
411+
(assoc exprs last-idx (wrap-promise last-expr))))))
412+
423413
(defn transform-async-fn-body
424414
"Transform async function body expressions and ensure result is a promise.
425415
This is the main entry point for async function transformation."

test/sci/async_await_test.cljs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,16 @@
252252
(let [a (atom [])]
253253
(doseq [x [1 2 3]]
254254
(swap! a conj (await (js/Promise.resolve x))))
255-
@a)})
255+
@a)
256+
257+
;; REGRESSION: loop bindings should see previous bindings
258+
;; -> is first bound to a fn, so (-> 1) in second binding calls that fn
259+
:binding-sees-previous
260+
(loop [-> (fn [x] (inc x)) x (-> 1)]
261+
(-> x))})
256262
(test-loop)")]
257263
(p/let [result v]
258-
(is (= {:basic-loop [0 1 2] :doseq [1 2 3]} result))))
264+
(is (= {:basic-loop [0 1 2] :doseq [1 2 3] :binding-sees-previous 3} result))))
259265
(p/catch (fn [err]
260266
(is false (str err))))
261267
(p/finally done)))))

0 commit comments

Comments
 (0)