Skip to content

Commit 5913f24

Browse files
authored
Merge pull request #22943 from JuliaLang/jb/fix18650
RFC: fix #18650, parsing generator expressions containing macro calls
2 parents e1f8383 + b43ca9d commit 5913f24

File tree

3 files changed

+40
-19
lines changed

3 files changed

+40
-19
lines changed

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ This section lists changes that do not have deprecation warnings.
4242

4343
* Juxtaposing string literals (e.g. `"x"y`) is now a syntax error ([#20575]).
4444

45+
* Macro calls with `for` expressions are now parsed as generators inside
46+
function argument lists ([#18650]). Examples:
47+
48+
+ `sum(@inbounds a[i] for i = 1:n)` used to give a syntax error, but is now
49+
parsed as `sum(@inbounds(a[i]) for i = 1:n)`.
50+
51+
+ `sum(@m x for i = 1:n end)` used to parse the argument to `sum` as a 2-argument
52+
call to macro `@m`, but now parses it as a generator plus a syntax error
53+
for the dangling `end`.
54+
4555
* `@__DIR__` returns the current working directory rather than `nothing` when not run
4656
from a file ([#21759]).
4757

src/julia-parser.scm

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@
144144
(define range-colon-enabled #t)
145145
; in space-sensitive mode "x -y" is 2 expressions, not a subtraction
146146
(define space-sensitive #f)
147-
(define inside-vec #f)
147+
; seeing `for` stops parsing macro arguments and makes a generator
148+
(define for-generator #f)
148149
; treat 'end' like a normal symbol instead of a reserved word
149150
(define end-symbol #f)
150151
; treat newline like ordinary whitespace instead of as a potential separator
@@ -164,7 +165,7 @@
164165
`(with-bindings ((range-colon-enabled #t)
165166
(space-sensitive #f)
166167
(where-enabled #t)
167-
(inside-vec #f)
168+
(for-generator #f)
168169
(end-symbol #f)
169170
(whitespace-newline #f))
170171
,@body))
@@ -178,12 +179,6 @@
178179
(whitespace-newline #f))
179180
,@body))
180181

181-
(define-macro (with-inside-vec . body)
182-
`(with-bindings ((space-sensitive #t)
183-
(inside-vec #t)
184-
(whitespace-newline #f))
185-
,@body))
186-
187182
(define-macro (with-end-symbol . body)
188183
`(with-bindings ((end-symbol #t))
189184
,@body))
@@ -1129,7 +1124,7 @@
11291124
((#\( )
11301125
(if (ts:space? s) (disallowed-space ex t))
11311126
(take-token s)
1132-
(let ((c (let ((al (parse-arglist s #\) )))
1127+
(let ((c (let ((al (parse-call-arglist s #\) )))
11331128
(receive
11341129
(params args) (separate (lambda (x)
11351130
(and (pair? x)
@@ -1172,7 +1167,7 @@
11721167
(cond ((eqv? (peek-token s) #\()
11731168
(begin
11741169
(take-token s)
1175-
`(|.| ,ex (tuple ,@(parse-arglist s #\) )))))
1170+
`(|.| ,ex (tuple ,@(parse-call-arglist s #\) )))))
11761171
((eqv? (peek-token s) ':)
11771172
(begin
11781173
(take-token s)
@@ -1195,7 +1190,7 @@
11951190
((#\{ )
11961191
(if (ts:space? s) (disallowed-space ex t))
11971192
(take-token s)
1198-
(loop (list* 'curly ex (parse-arglist s #\} ))))
1193+
(loop (list* 'curly ex (parse-call-arglist s #\} ))))
11991194
((#\" #\`)
12001195
(if (and (or (symbol? ex) (valid-modref? ex))
12011196
(not (operator? ex))
@@ -1629,7 +1624,7 @@
16291624
(let loop ((exprs '()))
16301625
(if (or (closing-token? (peek-token s))
16311626
(newline? (peek-token s))
1632-
(and inside-vec (eq? (peek-token s) 'for)))
1627+
(and for-generator (eq? (peek-token s) 'for)))
16331628
(reverse! exprs)
16341629
(let ((e (parse-eq s)))
16351630
(case (peek-token s)
@@ -1645,13 +1640,20 @@
16451640
x))
16461641
lst))
16471642

1643+
;; like parse-arglist, but with `for` parsed as a generator
1644+
(define (parse-call-arglist s closer)
1645+
(with-bindings ((for-generator #t))
1646+
(parse-arglist s closer)))
1647+
16481648
;; handle function call argument list, or any comma-delimited list.
16491649
;; . an extra comma at the end is allowed
16501650
;; . expressions after a ; are enclosed in (parameters ...)
16511651
;; . an expression followed by ... becomes (... x)
16521652
(define (parse-arglist s closer)
1653-
(with-normal-ops
1654-
(with-whitespace-newline
1653+
(with-bindings ((range-colon-enabled #t)
1654+
(space-sensitive #f)
1655+
(where-enabled #t)
1656+
(whitespace-newline #t))
16551657
(let loop ((lst '()))
16561658
(let ((t (require-token s)))
16571659
(if (eqv? t closer)
@@ -1689,7 +1691,7 @@
16891691
(error (string "unexpected \"" c "\" in argument list")))
16901692
(else
16911693
(error (string "missing comma or " closer
1692-
" in argument list"))))))))))))
1694+
" in argument list")))))))))))
16931695

16941696
(define (parse-vect s first closer)
16951697
(let loop ((lst '())
@@ -1709,7 +1711,7 @@
17091711
((#\;)
17101712
(if (eqv? (require-token s) closer)
17111713
(loop lst nxt)
1712-
(let ((params (parse-arglist s closer)))
1714+
(let ((params (parse-call-arglist s closer)))
17131715
`(vcat ,@params ,@(reverse lst) ,nxt))))
17141716
((#\] #\})
17151717
(error (string "unexpected \"" t "\"")))
@@ -1792,8 +1794,11 @@
17921794
(error (string "expected space before \"" t "\""))))
17931795

17941796
(define (parse-cat s closer last-end-symbol)
1795-
(with-normal-ops
1796-
(with-inside-vec
1797+
(with-bindings ((range-colon-enabled #t)
1798+
(space-sensitive #t)
1799+
(where-enabled #t)
1800+
(whitespace-newline #f)
1801+
(for-generator #t))
17971802
(if (eqv? (require-token s) closer)
17981803
(begin (take-token s)
17991804
'())
@@ -1811,7 +1816,7 @@
18111816
(parse-vect s first closer)
18121817
(parse-matrix s first closer #t last-end-symbol)))
18131818
(else
1814-
(parse-matrix s first closer #f last-end-symbol))))))))
1819+
(parse-matrix s first closer #f last-end-symbol)))))))
18151820

18161821
(define (kw-to-= e) (if (kwarg? e) (cons '= (cdr e)) e))
18171822
(define (=-to-kw e) (if (assignment? e) (cons 'kw (cdr e)) e))

test/parse.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,3 +1284,9 @@ end
12841284
@test parse("(::A)") == Expr(Symbol("::"), :A)
12851285
@test_throws ParseError parse("(::, 1)")
12861286
@test_throws ParseError parse("(1, ::)")
1287+
1288+
# issue #18650
1289+
let ex = parse("maximum(@elapsed sleep(1) for k = 1:10)")
1290+
@test isa(ex, Expr) && ex.head === :call && ex.args[2].head === :generator &&
1291+
ex.args[2].args[1].head === :macrocall
1292+
end

0 commit comments

Comments
 (0)