Skip to content

Commit 9cbe058

Browse files
authored
Fix some syntax path bugs (#493)
1 parent a18ea8a commit 9cbe058

File tree

1 file changed

+117
-10
lines changed

1 file changed

+117
-10
lines changed

private/syntax-path.rkt

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,108 @@
163163
[(list _ _) #false]))
164164

165165

166-
(define (syntax-ref stx path)
167-
(for/fold ([stx stx])
168-
([element (in-treelist (syntax-path-elements path))])
169-
(match element
170-
[(? exact-nonnegative-integer? i) (list-ref (syntax-e stx) i)]
171-
[(tail-syntax i) (drop (syntax-e stx) i)]
172-
[(vector-element-syntax i) (vector-ref (syntax-e stx) i)]
173-
[(== box-element-syntax) (unbox (syntax-e stx))]
174-
[(hash-value-syntax key) (hash-ref (syntax-e stx) key)]
175-
[(prefab-field-syntax i) (prefab-struct-ref (syntax-e stx) i)])))
166+
(define (syntax-ref init-stx path)
167+
(define result
168+
(for/fold ([stx init-stx])
169+
([element (in-treelist (syntax-path-elements path))])
170+
(define unwrapped
171+
; It's only *not* syntax in the case where `tail-syntax` was used to pick out a trailing
172+
; list of subforms of a form. These sorts of syntax objects get created by #%app macro
173+
; insertion, which is how I discovered this check was necessary.
174+
(if (syntax? stx)
175+
(syntax-e stx)
176+
stx))
177+
(match element
178+
[(? exact-nonnegative-integer? i)
179+
(unless (possibly-improper-list-of-minimum-size? unwrapped (add1 i))
180+
(raise-arguments-error 'syntax-ref
181+
"syntax path is inconsistent with the syntax's shape"
182+
"syntax" init-stx
183+
"path" path
184+
"malformed subform" stx
185+
"path element" element))
186+
(list-ref unwrapped i)]
187+
[(tail-syntax i) (drop unwrapped i)]
188+
[(vector-element-syntax i) (vector-ref unwrapped i)]
189+
[(== box-element-syntax) (unbox unwrapped)]
190+
[(hash-value-syntax key) (hash-ref unwrapped key)]
191+
[(prefab-field-syntax i) (prefab-struct-ref unwrapped i)])))
192+
(when (or (pair? result) (empty? result))
193+
(raise-arguments-error 'syntax-ref
194+
"syntax path refers to a non-syntax component"
195+
"syntax" init-stx
196+
"path" path
197+
"component" result))
198+
result)
199+
200+
201+
(module+ test
202+
(test-case "syntax-ref"
203+
204+
(test-case "empty path"
205+
(define stx #'a)
206+
(define actual (syntax-ref stx empty-syntax-path))
207+
(check-equal? actual stx))
208+
209+
(test-case "list element path"
210+
(define stx #'(a b c))
211+
(define actual (syntax-ref stx (syntax-path (list 1))))
212+
(check-equal? (syntax->datum actual) 'b))
213+
214+
(test-case "tail syntax path"
215+
(define stx #'(a . (b c)))
216+
(define actual (syntax-ref stx (syntax-path (list (tail-syntax 1)))))
217+
(check-equal? (syntax->datum actual) '(b c)))
218+
219+
(test-case "tail syntax path of flat syntax list"
220+
(define stx #'(a b c))
221+
(define thrown
222+
(with-handlers ([(λ (_) #true) values])
223+
(syntax-ref stx (syntax-path (list (tail-syntax 1))))
224+
#false))
225+
(check-pred exn:fail:contract? thrown)
226+
(check-regexp-match #rx"syntax-ref:" (exn-message thrown))
227+
(check-regexp-match #rx"path refers to a non-syntax component" (exn-message thrown)))
228+
229+
(test-case "vector element path"
230+
(define stx #'#[a b c])
231+
(define actual (syntax-ref stx (syntax-path (list (vector-element-syntax 1)))))
232+
(check-equal? (syntax->datum actual) 'b))
233+
234+
(test-case "box element path"
235+
(define stx #'#&a)
236+
(define actual (syntax-ref stx (syntax-path (list box-element-syntax))))
237+
(check-equal? (syntax->datum actual) 'a))
238+
239+
(test-case "hash value path"
240+
(define stx #'#hash((a . 1) (b . 2) (c . 3)))
241+
(define actual (syntax-ref stx (syntax-path (list (hash-value-syntax 'b)))))
242+
(check-equal? (syntax->datum actual) 2))
243+
244+
(test-case "nested list path"
245+
(define stx #'(a b c (m (FOO x y z) n)))
246+
(define actual (syntax-ref stx (syntax-path (list 3 1 0))))
247+
(check-equal? (syntax->datum actual) 'FOO))
248+
249+
(test-case "list element after tail syntax path"
250+
(define stx #'(a b . (c FOO e)))
251+
(define actual (syntax-ref stx (syntax-path (list (tail-syntax 2) 1))))
252+
(check-equal? (syntax->datum actual) 'FOO))
253+
254+
(test-case "list element after tail syntax path in flat syntax"
255+
(define stx #'(a b c FOO e))
256+
(define actual (syntax-ref stx (syntax-path (list (tail-syntax 2) 1))))
257+
(check-equal? (syntax->datum actual) 'FOO))
258+
259+
(test-case "list element on syntax that's too short"
260+
(define stx #'(a b c))
261+
(define thrown
262+
(with-handlers ([(λ (_) #true) values])
263+
(syntax-ref stx (syntax-path (list 10)))
264+
#false))
265+
(check-pred exn:fail:contract? thrown)
266+
(check-regexp-match #rx"syntax-ref:" (exn-message thrown))
267+
(check-regexp-match #rx"path is inconsistent" (exn-message thrown)))))
176268

177269

178270
(define (syntax-label-paths stx property-name)
@@ -292,3 +384,18 @@
292384
(define syntax-path<=>
293385
(comparator-map (lexicographic-comparator syntax-path-element<=>) syntax-path-elements
294386
#:name 'syntax-path<=>))
387+
388+
389+
(define (possibly-improper-list-of-minimum-size? v size)
390+
(or (zero? size)
391+
(and (pair? v) (possibly-improper-list-of-minimum-size? (cdr v) (sub1 size)))))
392+
393+
394+
(module+ test
395+
(test-case "possibly-improper-list-of-minimum-size?"
396+
(check-true (possibly-improper-list-of-minimum-size? '(a b c d e) 2))
397+
(check-false (possibly-improper-list-of-minimum-size? '(a b c d e) 8))
398+
(check-true (possibly-improper-list-of-minimum-size? '(a b c d . e) 2))
399+
(check-false (possibly-improper-list-of-minimum-size? '(a b c d . e) 8))
400+
(check-true (possibly-improper-list-of-minimum-size? 'a 0))
401+
(check-false (possibly-improper-list-of-minimum-size? 'a 1))))

0 commit comments

Comments
 (0)