Skip to content

Commit 3672686

Browse files
authored
Analysis rework (#488)
1 parent 7174238 commit 3672686

File tree

3 files changed

+142
-102
lines changed

3 files changed

+142
-102
lines changed

default-recommendations/for-loop-shortcuts.rkt

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
resyntax/private/identifier-naming
2525
resyntax/private/logger
2626
resyntax/private/syntax-traversal
27-
syntax/parse)
27+
syntax/parse
28+
syntax/parse/define)
2829

2930

3031
;@----------------------------------------------------------------------------------------------------
@@ -408,22 +409,25 @@ return just that result."
408409
[else
409410
loop-body:expr ...
410411
(cons loop-element:expr (loop2:id (:rest-by-any-name vs3:id)))]))
411-
#:when (free-identifier=? #'loop #'loop2)
412-
#:when (free-identifier=? #'vs #'vs2)
413-
#:when (free-identifier=? #'vs #'vs3)
414-
#:when (not
415-
(for/or ([body-stx (in-list (cons #'loop-element (attribute loop-body)))])
416-
(syntax-find-first body-stx
417-
(~and (~var usage (expression-directly-enclosing (attribute vs)))
418-
(~not (:first-by-any-name _))))))
412+
#:when (log-resyntax-rule-condition (free-identifier=? #'loop #'loop2))
413+
#:when (log-resyntax-rule-condition (free-identifier=? #'vs #'vs2))
414+
#:when (log-resyntax-rule-condition (free-identifier=? #'vs #'vs3))
415+
#:when (log-resyntax-rule-condition
416+
(not
417+
(for/or ([body-stx (in-list (cons #'loop-element (attribute loop-body)))])
418+
(syntax-find-first body-stx
419+
(~and (~var usage (expression-directly-enclosing (attribute vs)))
420+
(~not (:first-by-any-name _)))))))
419421
#:cut
420422

421423
#:with element-id (depluralize-id #'vs)
422424

423425
#:with (modified-result-element modified-body ...)
424426
(for/list ([body-stx (cons #'loop-element (attribute loop-body))])
425427
(syntax-traverse body-stx
426-
[(:first-by-any-name vs-usage:id) #:when (free-identifier=? #'vs-usage #'vs) #'element-id]))
428+
[(:first-by-any-name vs-usage:id)
429+
#:when (free-identifier=? (attribute vs-usage) (attribute vs))
430+
(attribute element-id)]))
427431

428432
(for/list ([element-id (in-list init-list)])
429433
modified-body ...
@@ -451,7 +455,7 @@ return just that result."
451455
#:with modified-element-condition
452456
(syntax-traverse (attribute element-condition)
453457
[(:first-by-any-name vs-usage:id)
454-
#:when (free-identifier=? (attribute vs) (attribute vs-usage))
458+
#:when (free-identifier=? (attribute vs-usage) (attribute vs))
455459
(attribute element-id)])
456460

457461
(for/and ([element-id (in-list init-list)])
@@ -479,12 +483,11 @@ return just that result."
479483
#:with modified-element-condition
480484
(syntax-traverse (attribute element-condition)
481485
[(:first-by-any-name vs-usage:id)
482-
#:when (free-identifier=? (attribute vs) (attribute vs-usage))
486+
#:when (log-resyntax-rule-condition (free-identifier=? (attribute vs) (attribute vs-usage)))
483487
(attribute element-id)])
484488

485489
(for/or ([element-id (in-list init-list)])
486490
modified-element-condition))
487-
488491

489492

490493
(define-refactoring-rule named-let-loop-to-for/first-in-vector
@@ -496,11 +499,11 @@ return just that result."
496499
(if condition:expr
497500
true-branch:expr
498501
(loop2:id (~or (add1 i4:id) (+ i4:id 1) (+ 1 i4:id)))))))
499-
#:when (and (free-identifier=? #'loop1 #'loop2)
500-
(free-identifier=? #'i1 #'i2)
501-
(free-identifier=? #'i1 #'i3)
502-
(free-identifier=? #'i1 #'i4)
503-
(free-identifier=? #'vec1 #'vec2))
502+
#:when (and (log-resyntax-rule-condition (free-identifier=? #'loop1 #'loop2))
503+
(log-resyntax-rule-condition (free-identifier=? #'i1 #'i2))
504+
(log-resyntax-rule-condition (free-identifier=? #'i1 #'i3))
505+
(log-resyntax-rule-condition (free-identifier=? #'i1 #'i4))
506+
(log-resyntax-rule-condition (free-identifier=? #'vec1 #'vec2)))
504507
(for/first ([x (in-vector vec1)] #:when condition)
505508
true-branch))
506509

private/source.rkt

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,28 @@
3333

3434

3535
(require guard
36-
racket/file
37-
racket/hash
3836
racket/match
3937
racket/path
4038
racket/port
39+
racket/stream
4140
rebellion/base/comparator
4241
rebellion/base/immutable-string
42+
rebellion/base/option
4343
rebellion/base/range
4444
rebellion/collection/list
4545
rebellion/collection/range-set
46+
rebellion/collection/sorted-map
4647
rebellion/collection/vector/builder
4748
rebellion/streaming/reducer
4849
rebellion/streaming/transducer
4950
rebellion/type/record
5051
resyntax/private/fully-expanded-syntax
5152
resyntax/private/linemap
5253
resyntax/private/logger
54+
resyntax/private/syntax-movement
5355
resyntax/private/syntax-neighbors
5456
resyntax/private/syntax-path
57+
resyntax/private/syntax-traversal
5558
syntax/id-table
5659
syntax/modread
5760
syntax/parse)
@@ -137,30 +140,10 @@
137140
(define program-source-name (syntax-source program-stx))
138141
(define current-expand-observe (dynamic-require ''#%expobs 'current-expand-observe))
139142
(define original-visits (make-vector-builder))
140-
(define expanded-originals-by-path (make-hash))
141-
142-
(define (add-all-original-subforms! stx)
143-
(when (resyntax-should-analyze-syntax? stx #:as-visit? #false)
144-
(hash-set! expanded-originals-by-path (syntax-original-path stx) stx))
145-
(syntax-parse stx
146-
[(subform ...) (for-each add-all-original-subforms! (attribute subform))]
147-
[(subform ...+ . tail-form)
148-
(for-each add-all-original-subforms! (attribute subform))
149-
(add-all-original-subforms! #'tail-form)]
150-
[_ (void)]))
151-
152-
(define (syntax-original-and-from-source? stx)
153-
(and (syntax-original? stx)
154-
;; Some macros are able to bend hygiene and syntax properties in such a way that they
155-
;; introduce syntax objects into the program that are syntax-original?, but from a
156-
;; different file than the one being expanded. So in addition to checking for
157-
;; originality, we also check that they come from the same source as the main program
158-
;; syntax object. The (open ...) clause of the define-signature macro bends hygiene
159-
;; in this way, and is what originally motivated the addition of this check.
160-
(equal? (syntax-source stx) program-source-name)))
143+
(define most-recent-visits-by-original-path (make-hash))
161144

162145
(define/guard (resyntax-should-analyze-syntax? stx #:as-visit? [as-visit? #true])
163-
(guard (syntax-original-and-from-source? stx) #:else #false)
146+
(guard (syntax-original-and-from-source? stx program-source-name) #:else #false)
164147
(guard as-visit? #:else #true)
165148
(define stx-lines (syntax-line-range stx #:linemap code-linemap))
166149
(define overlaps? (range-set-overlaps? lines stx-lines))
@@ -178,8 +161,12 @@
178161
(define/match (observe-event! sig val)
179162
[('visit (? syntax? visited))
180163
(when (resyntax-should-analyze-syntax? visited)
181-
(vector-builder-add original-visits visited)
182-
(add-all-original-subforms! visited))]
164+
(vector-builder-add original-visits visited))
165+
(for ([visit-subform (in-stream (syntax-search-everything visited))]
166+
#:when (and (resyntax-should-analyze-syntax? visit-subform #:as-visit? #false)
167+
(syntax-has-original-path? visit-subform)))
168+
(define path (syntax-original-path visit-subform))
169+
(hash-set! most-recent-visits-by-original-path path visit-subform))]
183170
[(_ _) (void)])
184171

185172
(define output-port (open-output-string))
@@ -195,39 +182,54 @@
195182
(namespace-require/expansion-time (extract-module-require-spec expanded))
196183

197184
(define output (get-output-string output-port))
185+
(define movement-table (syntax-movement-table expanded))
198186
(define binding-table (fully-expanded-syntax-binding-table expanded))
199187
(define original-binding-table-by-path
200188
(for*/fold ([table (hash)])
201189
([phase-table (in-hash-values binding-table)]
202190
[(id uses) (in-free-id-table phase-table)]
203-
#:when (syntax-original-and-from-source? id)
191+
#:when (syntax-original-and-from-source? id program-source-name)
204192
[use (in-list uses)])
205193
(hash-update table (syntax-original-path id) (λ (previous) (cons use previous)) '())))
194+
195+
(define expanded-with-properties
196+
(syntax-traverse expanded
197+
[id:id
198+
#:do [(define path (syntax-original-path (attribute id)))]
199+
#:when path
200+
(define usages (hash-ref original-binding-table-by-path path '()))
201+
(syntax-property this-syntax 'identifier-usages usages)]
202+
#:parent-context-modifier values
203+
#:parent-srcloc-modifier values
204+
#:parent-props-modifier values))
206205

207-
(add-all-original-subforms! expanded)
208-
209-
(define/guard (add-usages stx)
210-
(guard (identifier? stx) #:else stx)
211-
(define usages (hash-ref original-binding-table-by-path (syntax-original-path stx) '()))
212-
(syntax-property stx 'identifier-usages usages))
213-
214-
(define (enrich stx)
215-
(define new-context
216-
(add-usages
217-
(or (hash-ref expanded-originals-by-path (syntax-original-path stx) #false) stx)))
218-
(syntax-parse stx
219-
[(subform ...)
220-
(datum->syntax new-context
221-
(map enrich (attribute subform))
222-
new-context
223-
new-context)]
224-
[(subform ...+ . tail-form)
225-
(datum->syntax new-context
226-
(append (map enrich (attribute subform)) (enrich #'tail-form))
227-
new-context
228-
new-context)]
229-
[_ new-context]))
230-
206+
(define (enrich stx #:skip-root? [skip-root? #false])
207+
(syntax-traverse stx
208+
#:skip-root? skip-root?
209+
[child
210+
#:do [(define child-stx (attribute child))
211+
(define orig-path (syntax-original-path child-stx))]
212+
#:when (and orig-path (sorted-map-contains-key? movement-table orig-path))
213+
#:do [(define expansion
214+
(transduce (sorted-map-get movement-table orig-path)
215+
(mapping (λ (p) (syntax-ref expanded-with-properties p)))
216+
(filtering syntax-original?)
217+
#:into into-first))]
218+
#:when (present? expansion)
219+
(match-define (present expanded-child) expansion)
220+
(log-resyntax-debug "enriching ~a with scopes and properties from expansion" child-stx)
221+
(enrich (datum->syntax expanded-child (syntax-e child-stx) child-stx expanded-child)
222+
#:skip-root? #true)]
223+
[child
224+
#:do [(define child-stx (attribute child))
225+
(define orig-path (syntax-original-path child-stx))]
226+
#:when (and orig-path (hash-has-key? most-recent-visits-by-original-path orig-path))
227+
#:do [(define visit (hash-ref most-recent-visits-by-original-path orig-path))]
228+
(log-resyntax-debug "enriching ~a with scopes from visit" child-stx)
229+
(enrich (datum->syntax visit (syntax-e child-stx) child-stx child-stx) #:skip-root? #true)]
230+
#:parent-context-modifier values
231+
#:parent-srcloc-modifier values
232+
#:parent-props-modifier values))
231233

232234
(define visited
233235
(transduce (build-vector original-visits)
@@ -254,6 +256,17 @@
254256
#:namespace ns)))
255257

256258

259+
(define (syntax-original-and-from-source? stx source-name)
260+
(and (syntax-original? stx)
261+
;; Some macros are able to bend hygiene and syntax properties in such a way that they
262+
;; introduce syntax objects into the program that are syntax-original?, but from a
263+
;; different file than the one being expanded. So in addition to checking for
264+
;; originality, we also check that they come from the same source as the main program
265+
;; syntax object. The (open ...) clause of the define-signature macro bends hygiene
266+
;; in this way, and is what originally motivated the addition of this check.
267+
(equal? (syntax-source stx) source-name)))
268+
269+
257270
(define (extract-module-require-spec mod-stx)
258271
(syntax-parse mod-stx
259272
[(_ name _ . _) `',(syntax-e #'name)]))

private/syntax-traversal.rkt

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414

1515
(require (for-syntax racket/base
1616
resyntax/private/more-syntax-parse-classes)
17+
racket/match
1718
racket/sequence
1819
racket/stream
1920
syntax/parse
2021
syntax/parse/define)
2122

2223

2324
(module+ test
24-
(require rackunit
25+
(require racket/syntax-srcloc
26+
rackunit
2527
(submod "..")))
2628

2729

@@ -130,43 +132,64 @@
130132
(check-false (syntax-find-first stx #:literals (cons) (cons _ _ _)))))
131133

132134

135+
(define (perform-syntax-traversal stx modifier
136+
#:skip-root? [skip-root? #false]
137+
#:parent-context-modifier [context-modifier* #false]
138+
#:parent-srcloc-modifier [srcloc-modifier (λ (_) #'here)]
139+
#:parent-props-modifier [props-modifier (λ (_) #false)])
140+
(define context-modifier
141+
(or context-modifier*
142+
(let ([scope (make-syntax-introducer)])
143+
(λ (stx) (scope stx 'add)))))
144+
(let loop ([stx stx] [root? #true])
145+
(define should-skip-because-root? (and skip-root? root?))
146+
(define modified-stx (and (not should-skip-because-root?) (modifier stx)))
147+
(match modified-stx
148+
[(== stx) stx]
149+
[(? syntax?) modified-stx]
150+
[#false
151+
(syntax-parse stx
152+
[(child ...)
153+
(define traversed-children
154+
(for/list ([child-stx (in-list (attribute child))])
155+
(loop child-stx #false)))
156+
(define all-same?
157+
(for/and ([traversed-child (in-list traversed-children)]
158+
[child-stx (in-list (attribute child))])
159+
(equal? traversed-child child-stx)))
160+
(if all-same?
161+
stx
162+
(datum->syntax (context-modifier stx)
163+
traversed-children
164+
(srcloc-modifier stx)
165+
(props-modifier stx)))]
166+
[_ stx])])))
167+
168+
133169
(define-syntax-parse-rule
134170
(syntax-traverse (~var stx-expr (expr/c #'syntax?))
135-
(~optional (~seq #:skip-root? skip-root?) #:defaults ([skip-root? #'#false]))
171+
(~optional (~seq #:skip-root? skip-root?))
136172
option:syntax-parse-option ...
137-
[clause-pattern directive:syntax-parse-pattern-directive ... clause-body:expr ...+] ...)
138-
(let ([skip-root-id skip-root?])
139-
(define-syntax-class traversal-case
140-
#:attributes (traversed)
141-
(~@ . option) ...
142-
(pattern clause-pattern (~@ . directive) ...
143-
#:attr traversed (let () clause-body ...)) ...)
144-
(let loop ([stx stx-expr.c] [root? #true])
173+
[clause-pattern directive:syntax-parse-pattern-directive ... clause-body:expr ...+] ...
174+
(~optional (~seq #:parent-context-modifier context-modifier:expr))
175+
(~optional (~seq #:parent-srcloc-modifier srcloc-modifier:expr))
176+
(~optional (~seq #:parent-props-modifier props-modifier:expr)))
177+
145178

146-
(define (rewrap-datum datum)
147-
(datum->syntax stx datum stx stx))
179+
(let ()
148180

181+
(define (traversal-case stx)
149182
(syntax-parse stx
183+
(~@ . option) ...
184+
[clause-pattern (~@ . directive) ... clause-body ...]
185+
...
186+
[_ #false]))
150187

151-
[child
152-
#:when (not (and skip-root-id root?))
153-
#:with (~var matched traversal-case) (attribute child)
154-
(define case-scope (make-syntax-introducer))
155-
(case-scope (attribute matched.traversed) 'add)]
156-
157-
[(part (... ...))
158-
#:cut
159-
(rewrap-datum
160-
(for/list ([child (in-list (attribute part))])
161-
(loop child #false)))]
162-
[(part (... ...+) . tail-part)
163-
#:cut
164-
(define traversed-children
165-
(for/list ([child (in-list (attribute part))])
166-
(loop child #false)))
167-
(define traversed-tail (loop #'tail-part #false))
168-
(rewrap-datum (append traversed-children traversed-tail))]
169-
[_ stx]))))
188+
(perform-syntax-traversal stx-expr.c traversal-case
189+
(~? (~@ #:skip-root? skip-root?))
190+
(~? (~@ #:parent-context-modifier context-modifier))
191+
(~? (~@ #:parent-srcloc-modifier srcloc-modifier))
192+
(~? (~@ #:parent-props-modifier props-modifier)))))
170193

171194

172195
(module+ test
@@ -222,10 +245,11 @@
222245
(syntax-traverse stx
223246
[(_ id:id) (attribute id)]))
224247
(check-equal? (syntax->datum traversed-stx) '(1 2 b 3 4))
225-
(check-true (syntax-original? traversed-stx))
248+
(check-false (syntax-original? traversed-stx))
249+
(check-not-equal? (syntax-srcloc traversed-stx) (syntax-srcloc stx))
226250
(define/syntax-parse (1* 2* b* 3* 4*) traversed-stx)
227251
(check-true (syntax-original? #'1*))
228252
(check-true (syntax-original? #'2*))
229-
(check-false (syntax-original? #'b*))
253+
(check-true (syntax-original? #'b*))
230254
(check-true (syntax-original? #'3*))
231255
(check-true (syntax-original? #'4*))))

0 commit comments

Comments
 (0)