Skip to content

Commit 2460d2e

Browse files
Copilotjackfirthgithub-actions[bot]
authored
Ignore out-of-syntax properties returned by analyzers (#707)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: jackfirth <[email protected]> Co-authored-by: Jacqueline Firth <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 90130c1 commit 2460d2e

File tree

2 files changed

+161
-1
lines changed

2 files changed

+161
-1
lines changed

private/analysis.rkt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@
145145
(λ (analyzer)
146146
(syntax-property-bundle-entries
147147
(expansion-analyze analyzer expanded))))
148+
(filtering
149+
(λ (prop-entry)
150+
(match-define (syntax-property-entry path key _value) prop-entry)
151+
(define valid? (syntax-contains-path? expanded path))
152+
(unless valid?
153+
(log-resyntax-warning
154+
"ignoring property with out-of-syntax path returned by analyzer~n path: ~a~n property key: ~a"
155+
path
156+
key))
157+
valid?))
148158
#:into into-syntax-property-bundle))
149159

150160
(define expansion-analyzer-props-adjusted-for-visits
@@ -260,4 +270,40 @@
260270
#:analyzers (list identifier-usage-analyzer
261271
ignored-result-values-analyzer
262272
variable-mutability-analyzer)))
263-
(check-true (source-code-analysis? analysis-default))))
273+
(check-true (source-code-analysis? analysis-default)))
274+
275+
(test-case "source-analyze filters out properties with invalid paths"
276+
;; Create a test analyzer that returns properties with both valid and invalid paths
277+
(define test-source (string-source "#lang racket/base (define x 1)"))
278+
279+
(define bad-analyzer
280+
(make-expansion-analyzer
281+
#:name 'bad-analyzer
282+
(λ (expanded)
283+
(syntax-property-bundle
284+
;; Valid path - the root
285+
(syntax-property-entry empty-syntax-path 'valid-prop #true)
286+
;; Invalid path - way out of bounds
287+
(syntax-property-entry (syntax-path (list 999)) 'invalid-prop #true)
288+
;; Another invalid path
289+
(syntax-property-entry (syntax-path (list 0 999)) 'another-invalid-prop #true)))))
290+
291+
;; Run analysis with the bad analyzer - should not crash
292+
(define analysis (source-analyze test-source #:analyzers (list bad-analyzer)))
293+
294+
;; Check that the analysis completed successfully
295+
(check-true (source-code-analysis? analysis))
296+
297+
;; Check that the valid property is present in the result
298+
(define props (source-code-analysis-added-syntax-properties analysis))
299+
(check-true (syntax-property-bundle? props))
300+
301+
;; The valid property at the root should be present
302+
(define root-props (syntax-property-bundle-get-immediate-properties props empty-syntax-path))
303+
(check-equal? (hash-ref root-props 'valid-prop #false) #true)
304+
305+
;; The invalid properties should NOT be present
306+
;; Check that path /999 is not in the bundle
307+
(define path-999-props
308+
(syntax-property-bundle-get-immediate-properties props (syntax-path (list 999))))
309+
(check-true (hash-empty? path-999-props))))

private/syntax-path.rkt

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
[syntax-path->string (-> syntax-path? immutable-string?)]
2424
[string->syntax-path (-> string? syntax-path?)]
2525
[syntax-ref (-> syntax? syntax-path? syntax?)]
26+
[syntax-contains-path? (-> syntax? syntax-path? boolean?)]
2627
[syntax-set (-> syntax? syntax-path? syntax? syntax?)]
2728
[syntax-remove-splice
2829
(-> syntax? (and/c proper-syntax-path? nonempty-syntax-path?) exact-nonnegative-integer? syntax?)]
@@ -564,6 +565,119 @@
564565
(check-regexp-match #rx"path is inconsistent" (exn-message thrown)))))
565566

566567

568+
(define (syntax-contains-path? init-stx path)
569+
(let/ec return
570+
(for/fold ([stx init-stx])
571+
([element (in-treelist (syntax-path-elements path))])
572+
(define unwrapped (syntax-e stx))
573+
(cond
574+
; Handle improper lists - flatten them so the tail is treated as the last element
575+
[(and (pair? unwrapped) (not (list? unwrapped)))
576+
(define flattened (flatten-improper-list unwrapped))
577+
(unless (< element (length flattened))
578+
(return #false))
579+
(list-ref flattened element)]
580+
; Handle proper lists
581+
[(list? unwrapped)
582+
(unless (< element (length unwrapped))
583+
(return #false))
584+
(list-ref unwrapped element)]
585+
; Handle vectors
586+
[(vector? unwrapped)
587+
(unless (< element (vector-length unwrapped))
588+
(return #false))
589+
(vector-ref unwrapped element)]
590+
; Handle boxes - treat as single-element list
591+
[(box? unwrapped)
592+
(unless (zero? element)
593+
(return #false))
594+
(unbox unwrapped)]
595+
; Handle prefab structs - treat as list of fields
596+
[(prefab-struct? unwrapped)
597+
(define fields (struct->list unwrapped))
598+
(unless (< element (length fields))
599+
(return #false))
600+
(list-ref fields element)]
601+
; Hashes are unsupported
602+
[(hash? unwrapped)
603+
(return #false)]
604+
; Other datums don't have children
605+
[else
606+
(return #false)]))
607+
; Check if result would be a non-syntax component
608+
(define result-stx
609+
(for/fold ([stx init-stx])
610+
([element (in-treelist (syntax-path-elements path))])
611+
(define unwrapped (syntax-e stx))
612+
(cond
613+
[(and (pair? unwrapped) (not (list? unwrapped)))
614+
(list-ref (flatten-improper-list unwrapped) element)]
615+
[(list? unwrapped)
616+
(list-ref unwrapped element)]
617+
[(vector? unwrapped)
618+
(vector-ref unwrapped element)]
619+
[(box? unwrapped)
620+
(unbox unwrapped)]
621+
[(prefab-struct? unwrapped)
622+
(list-ref (struct->list unwrapped) element)]
623+
[else stx])))
624+
(not (or (pair? result-stx) (empty? result-stx)))))
625+
626+
627+
(module+ test
628+
(test-case "syntax-contains-path?"
629+
630+
(test-case "empty path on any syntax"
631+
(define stx #'a)
632+
(check-true (syntax-contains-path? stx empty-syntax-path)))
633+
634+
(test-case "valid path in list"
635+
(define stx #'(a b c))
636+
(check-true (syntax-contains-path? stx (syntax-path (list 0))))
637+
(check-true (syntax-contains-path? stx (syntax-path (list 1))))
638+
(check-true (syntax-contains-path? stx (syntax-path (list 2)))))
639+
640+
(test-case "invalid path in list - out of bounds"
641+
(define stx #'(a b c))
642+
(check-false (syntax-contains-path? stx (syntax-path (list 3))))
643+
(check-false (syntax-contains-path? stx (syntax-path (list 10)))))
644+
645+
(test-case "valid nested path"
646+
(define stx #'(a (b c) d))
647+
(check-true (syntax-contains-path? stx (syntax-path (list 1 0))))
648+
(check-true (syntax-contains-path? stx (syntax-path (list 1 1)))))
649+
650+
(test-case "invalid nested path"
651+
(define stx #'(a (b c) d))
652+
(check-false (syntax-contains-path? stx (syntax-path (list 1 2))))
653+
(check-false (syntax-contains-path? stx (syntax-path (list 3 0)))))
654+
655+
(test-case "valid path in vector"
656+
(define stx #'#[a b c])
657+
(check-true (syntax-contains-path? stx (syntax-path (list 0))))
658+
(check-true (syntax-contains-path? stx (syntax-path (list 1)))))
659+
660+
(test-case "invalid path in vector"
661+
(define stx #'#[a b c])
662+
(check-false (syntax-contains-path? stx (syntax-path (list 5)))))
663+
664+
(test-case "valid path in box"
665+
(define stx #'#&a)
666+
(check-true (syntax-contains-path? stx (syntax-path (list 0)))))
667+
668+
(test-case "invalid path in box"
669+
(define stx #'#&a)
670+
(check-false (syntax-contains-path? stx (syntax-path (list 1)))))
671+
672+
(test-case "path on non-compound datum"
673+
(define stx #'atom)
674+
(check-false (syntax-contains-path? stx (syntax-path (list 0)))))
675+
676+
(test-case "path referring to non-syntax component"
677+
; This test ensures we catch the error when syntax-ref finds a non-syntax component
678+
(define stx #'(a b c))
679+
; If we try to drill down into 'a', we should fail because atoms don't have children
680+
(check-false (syntax-contains-path? stx (syntax-path (list 0 0)))))))
567681

568682

569683

0 commit comments

Comments
 (0)