in-value: fix inlined binding

The inlined version of `in-value` in a `for` form did not bind the
left-hand identifier at the right layer relative to other bindings in
`for/fold`, which caused the inlined `in-value` to behave differently
than a non-inlined `in-value`. Confusing about this `in-value`, in
turn, had led to incorrect documentation on `for/fold`.

It would be nice to clean up a little more of the evaluator and
availability of bindings in `for/fold`, but doing so runs a
significant risk of breaking existing code (unlike fixing `in-value`,
which behaved even worse and where the repair seems less likely to
break existing programs).

Closes #1659 (again)
This commit is contained in:
Matthew Flatt 2021-04-28 14:19:50 -06:00
parent 09480c86e8
commit 94725ffb4e
3 changed files with 34 additions and 15 deletions

View File

@ -374,19 +374,19 @@ terminates, if a @racket[result-expr] is provided then the result of the
]
The binding and evaluation order of @racket[accum-id]s and
@racket[init-expr]s does not follow the textual, left-to-right order
relative to the @racket[for-clause]s . Instead, the sequence
expressions in @racket[for-clause]s that determine the outermost
iteration are evaluated first, the associated identifiers are bound,
and then the @racket[init-expr]s are evaluated and the
@racket[accum-id]s are bound. One consequence is that the
@racket[accum-id]s are not bound in @racket[for-clause]s for the
outermost initialization. Another consequence is that when a
@racket[accum-id] is used as a @racket[for-clause] binding for the
outermost iteration, the @racket[for-clause] binding is shadowed in
the loop body (even though, syntactically, a @racket[for-clause] is
closer to the body). A fresh variable for each @racket[accum-id] (at a
fresh location) is bound to in each nested iteration created by a
@racket[init-expr]s do not completely follow the textual,
left-to-right order relative to the @racket[for-clause]s. Instead, the
sequence expressions in @racket[for-clause]s that determine the
outermost iteration are evaluated first, then the @racket[init-expr]s
are evaluated and the @racket[accum-id]s are bound, and finally the
outermost iteration's identifiers are bound. One consequence is that
the @racket[accum-id]s are not bound in @racket[for-clause]s for the
outermost initialization. At the same time, when a @racket[accum-id]
is used as a @racket[for-clause] binding for the outermost iteration,
the @racket[for-clause] binding shadows the @racket[accum-id] binding
in the loop body (which is what you would expect syntactically).
A fresh variable for each @racket[accum-id] (at a
fresh location) is bound in each nested iteration that is created by a
later group for @racket[for-clause]s (after a @racket[#:when] or
@racket[#:unless], for example).

View File

@ -461,6 +461,25 @@
(test 13 next)
(test #f more?))
(test 1 'in-value-bind-correctly (for/fold ([x #f])
([x (in-value 1)])
x))
(test 2 'in-value-bind-correctly (for/fold ([x #f])
([x (values (in-value 2))])
x))
(let ([x 'out]
[prints '()])
(for/fold ([x (begin
(set! prints (cons (list 'top x) prints))
'top)])
([x (in-list (begin
(set! prints (cons (list 'rhs x) prints))
(list 1 2 3)))])
(set! prints (cons x prints))
x)
(test '(3 2 1 (top out) (rhs out)) values prints))
;; check ranges on `in-vector', especially as a value
(test '() 'in-empty-vector (let ([v (in-vector '#())]) (for/list ([e v]) e)))
(test '() 'in-empty-vector (let ([v (in-vector '#() 0)]) (for/list ([e v]) e)))

View File

@ -2309,8 +2309,8 @@
(lambda () #'in-value)
(lambda (stx)
(syntax-case stx ()
[[(id) (_ expr)]
#'[(id) (:do-in ([(id) expr]) #t () #t () #t #f ())]]
[[<(id) (_ expr)]
#'[(id) (:do-in ([(id*) expr]) #t () #t ([(id) id*]) #t #f ())]]
[_ #f])))
(define-sequence-syntax *in-producer