Coalesce tree traversals made for warnings.
[bpt/guile.git] / module / language / tree-il / analyze.scm
CommitLineData
cf10678f
AW
1;;; TREE-IL -> GLIL compiler
2
3;; Copyright (C) 2001,2008,2009 Free Software Foundation, Inc.
4
53befeb7
NJ
5;;;; This library is free software; you can redistribute it and/or
6;;;; modify it under the terms of the GNU Lesser General Public
7;;;; License as published by the Free Software Foundation; either
8;;;; version 3 of the License, or (at your option) any later version.
9;;;;
10;;;; This library is distributed in the hope that it will be useful,
11;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13;;;; Lesser General Public License for more details.
14;;;;
15;;;; You should have received a copy of the GNU Lesser General Public
16;;;; License along with this library; if not, write to the Free Software
17;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
cf10678f
AW
18
19;;; Code:
20
21(define-module (language tree-il analyze)
66d3e9a3 22 #:use-module (srfi srfi-1)
4b856371 23 #:use-module (srfi srfi-9)
cf10678f 24 #:use-module (system base syntax)
4b856371 25 #:use-module (system base message)
cf10678f 26 #:use-module (language tree-il)
4b856371 27 #:export (analyze-lexicals
48b1db75
LC
28 analyze-tree
29 unused-variable-analysis
30 unbound-variable-analysis))
cf10678f 31
66d3e9a3
AW
32;; Allocation is the process of assigning storage locations for lexical
33;; variables. A lexical variable has a distinct "address", or storage
34;; location, for each procedure in which it is referenced.
35;;
36;; A variable is "local", i.e., allocated on the stack, if it is
37;; referenced from within the procedure that defined it. Otherwise it is
38;; a "closure" variable. For example:
39;;
40;; (lambda (a) a) ; a will be local
41;; `a' is local to the procedure.
42;;
43;; (lambda (a) (lambda () a))
44;; `a' is local to the outer procedure, but a closure variable with
45;; respect to the inner procedure.
46;;
47;; If a variable is ever assigned, it needs to be heap-allocated
48;; ("boxed"). This is so that closures and continuations capture the
49;; variable's identity, not just one of the values it may have over the
50;; course of program execution. If the variable is never assigned, there
51;; is no distinction between value and identity, so closing over its
52;; identity (whether through closures or continuations) can make a copy
53;; of its value instead.
54;;
55;; Local variables are stored on the stack within a procedure's call
56;; frame. Their index into the stack is determined from their linear
57;; postion within a procedure's binding path:
cf10678f
AW
58;; (let (0 1)
59;; (let (2 3) ...)
60;; (let (2) ...))
61;; (let (2 3 4) ...))
62;; etc.
63;;
5af166bd
AW
64;; This algorithm has the problem that variables are only allocated
65;; indices at the end of the binding path. If variables bound early in
66;; the path are not used in later portions of the path, their indices
67;; will not be recycled. This problem is particularly egregious in the
68;; expansion of `or':
69;;
70;; (or x y z)
71;; -> (let ((a x)) (if a a (let ((b y)) (if b b z))))
72;;
73;; As you can see, the `a' binding is only used in the ephemeral `then'
74;; clause of the first `if', but its index would be reserved for the
75;; whole of the `or' expansion. So we have a hack for this specific
76;; case. A proper solution would be some sort of liveness analysis, and
77;; not our linear allocation algorithm.
78;;
66d3e9a3
AW
79;; Closure variables are captured when a closure is created, and stored
80;; in a vector. Each closure variable has a unique index into that
81;; vector.
82;;
9059993f
AW
83;; There is one more complication. Procedures bound by <fix> may, in
84;; some cases, be rendered inline to their parent procedure. That is to
85;; say,
86;;
87;; (letrec ((lp (lambda () (lp)))) (lp))
88;; => (fix ((lp (lambda () (lp)))) (lp))
89;; => goto FIX-BODY; LP: goto LP; FIX-BODY: goto LP;
90;; ^ jump over the loop ^ the fixpoint lp ^ starting off the loop
91;;
92;; The upshot is that we don't have to allocate any space for the `lp'
93;; closure at all, as it can be rendered inline as a loop. So there is
94;; another kind of allocation, "label allocation", in which the
95;; procedure is simply a label, placed at the start of the lambda body.
96;; The label is the gensym under which the lambda expression is bound.
97;;
98;; The analyzer checks to see that the label is called with the correct
99;; number of arguments. Calls to labels compile to rename + goto.
100;; Lambda, the ultimate goto!
101;;
66d3e9a3
AW
102;;
103;; The return value of `analyze-lexicals' is a hash table, the
104;; "allocation".
105;;
106;; The allocation maps gensyms -- recall that each lexically bound
107;; variable has a unique gensym -- to storage locations ("addresses").
108;; Since one gensym may have many storage locations, if it is referenced
109;; in many procedures, it is a two-level map.
110;;
111;; The allocation also stored information on how many local variables
9059993f
AW
112;; need to be allocated for each procedure, lexicals that have been
113;; translated into labels, and information on what free variables to
114;; capture from its lexical parent procedure.
66d3e9a3 115;;
8a4ca0ea
AW
116;; In addition, we have a conflation: while we're traversing the code,
117;; recording information to pass to the compiler, we take the
118;; opportunity to generate labels for each lambda-case clause, so that
119;; generated code can skip argument checks at runtime if they match at
120;; compile-time.
121;;
66d3e9a3
AW
122;; That is:
123;;
124;; sym -> {lambda -> address}
8a4ca0ea
AW
125;; lambda -> (labels . free-locs)
126;; lambda-case -> (gensym . nlocs)
66d3e9a3 127;;
9059993f 128;; address ::= (local? boxed? . index)
8a4ca0ea 129;; labels ::= ((sym . lambda) ...)
66d3e9a3
AW
130;; free-locs ::= ((sym0 . address0) (sym1 . address1) ...)
131;; free variable addresses are relative to parent proc.
132
133(define (make-hashq k v)
134 (let ((res (make-hash-table)))
135 (hashq-set! res k v)
136 res))
cf10678f
AW
137
138(define (analyze-lexicals x)
66d3e9a3
AW
139 ;; bound-vars: lambda -> (sym ...)
140 ;; all identifiers bound within a lambda
9059993f 141 (define bound-vars (make-hash-table))
66d3e9a3
AW
142 ;; free-vars: lambda -> (sym ...)
143 ;; all identifiers referenced in a lambda, but not bound
144 ;; NB, this includes identifiers referenced by contained lambdas
9059993f 145 (define free-vars (make-hash-table))
66d3e9a3
AW
146 ;; assigned: sym -> #t
147 ;; variables that are assigned
d97b69d9 148 (define assigned (make-hash-table))
5af166bd 149 ;; refcounts: sym -> count
66d3e9a3 150 ;; allows us to detect the or-expansion in O(1) time
9059993f 151 (define refcounts (make-hash-table))
8a4ca0ea 152 ;; labels: sym -> lambda
9059993f 153 ;; for determining if fixed-point procedures can be rendered as
8a4ca0ea 154 ;; labels.
9059993f
AW
155 (define labels (make-hash-table))
156
66d3e9a3 157 ;; returns variables referenced in expr
d97b69d9
AW
158 (define (analyze! x proc labels-in-proc tail? tail-call-args)
159 (define (step y) (analyze! y proc labels-in-proc #f #f))
160 (define (step-tail y) (analyze! y proc labels-in-proc tail? #f))
161 (define (step-tail-call y args) (analyze! y proc labels-in-proc #f
162 (and tail? args)))
163 (define (recur/labels x new-proc labels)
164 (analyze! x new-proc (append labels labels-in-proc) #t #f))
165 (define (recur x new-proc) (analyze! x new-proc '() tail? #f))
cf10678f
AW
166 (record-case x
167 ((<application> proc args)
d97b69d9
AW
168 (apply lset-union eq? (step-tail-call proc args)
169 (map step args)))
cf10678f
AW
170
171 ((<conditional> test then else)
d97b69d9 172 (lset-union eq? (step test) (step-tail then) (step-tail else)))
cf10678f 173
e5f5113c 174 ((<lexical-ref> gensym)
5af166bd 175 (hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0)))
d97b69d9
AW
176 (if (not (and tail-call-args
177 (memq gensym labels-in-proc)
8a4ca0ea
AW
178 (let ((p (hashq-ref labels gensym)))
179 (and p
180 (let lp ((c (lambda-body p)))
181 (and c (lambda-case? c)
182 (or
183 ;; for now prohibit optional &
184 ;; keyword arguments; can relax this
185 ;; restriction later
186 (and (= (length (lambda-case-req c))
187 (length tail-call-args))
188 (not (lambda-case-opt c))
189 (not (lambda-case-kw c))
190 (not (lambda-case-rest c))
191 (not (lambda-case-predicate c)))
192 (lp (lambda-case-else c)))))))))
d97b69d9 193 (hashq-set! labels gensym #f))
66d3e9a3 194 (list gensym))
cf10678f 195
e5f5113c 196 ((<lexical-set> gensym exp)
66d3e9a3 197 (hashq-set! assigned gensym #t)
d97b69d9 198 (hashq-set! labels gensym #f)
66d3e9a3 199 (lset-adjoin eq? (step exp) gensym))
cf10678f 200
e5f5113c 201 ((<module-set> exp)
cf10678f
AW
202 (step exp))
203
e5f5113c 204 ((<toplevel-set> exp)
cf10678f
AW
205 (step exp))
206
e5f5113c 207 ((<toplevel-define> exp)
cf10678f
AW
208 (step exp))
209
210 ((<sequence> exps)
d97b69d9
AW
211 (let lp ((exps exps) (ret '()))
212 (cond ((null? exps) '())
213 ((null? (cdr exps))
214 (lset-union eq? ret (step-tail (car exps))))
215 (else
216 (lp (cdr exps) (lset-union eq? ret (step (car exps))))))))
cf10678f 217
8a4ca0ea
AW
218 ((<lambda> body)
219 ;; order is important here
220 (hashq-set! bound-vars x '())
221 (let ((free (recur body x)))
222 (hashq-set! bound-vars x (reverse! (hashq-ref bound-vars x)))
223 (hashq-set! free-vars x free)
224 free))
225
b0c8c187 226 ((<lambda-case> opt kw inits vars predicate body else)
8a4ca0ea
AW
227 (hashq-set! bound-vars proc
228 (append (reverse vars) (hashq-ref bound-vars proc)))
229 (lset-union
230 eq?
231 (lset-difference eq?
b0c8c187
AW
232 (lset-union eq?
233 (apply lset-union eq? (map step inits))
234 (if predicate (step predicate) '())
8a4ca0ea
AW
235 (step-tail body))
236 vars)
237 (if else (step-tail else) '())))
66d3e9a3 238
f4aa8d53 239 ((<let> vars vals body)
66d3e9a3
AW
240 (hashq-set! bound-vars proc
241 (append (reverse vars) (hashq-ref bound-vars proc)))
242 (lset-difference eq?
d97b69d9 243 (apply lset-union eq? (step-tail body) (map step vals))
66d3e9a3 244 vars))
cf10678f 245
f4aa8d53 246 ((<letrec> vars vals body)
66d3e9a3
AW
247 (hashq-set! bound-vars proc
248 (append (reverse vars) (hashq-ref bound-vars proc)))
249 (for-each (lambda (sym) (hashq-set! assigned sym #t)) vars)
250 (lset-difference eq?
d97b69d9 251 (apply lset-union eq? (step-tail body) (map step vals))
66d3e9a3
AW
252 vars))
253
c21c89b1 254 ((<fix> vars vals body)
d97b69d9 255 ;; Try to allocate these procedures as labels.
8a4ca0ea 256 (for-each (lambda (sym val) (hashq-set! labels sym val))
d97b69d9 257 vars vals)
c21c89b1
AW
258 (hashq-set! bound-vars proc
259 (append (reverse vars) (hashq-ref bound-vars proc)))
d97b69d9
AW
260 ;; Step into subexpressions.
261 (let* ((var-refs
262 (map
263 ;; Since we're trying to label-allocate the lambda,
264 ;; pretend it's not a closure, and just recurse into its
265 ;; body directly. (Otherwise, recursing on a closure
266 ;; that references one of the fix's bound vars would
267 ;; prevent label allocation.)
268 (lambda (x)
269 (record-case x
8a4ca0ea
AW
270 ((<lambda> body)
271 ;; just like the closure case, except here we use
272 ;; recur/labels instead of recur
273 (hashq-set! bound-vars x '())
274 (let ((free (recur/labels body x vars)))
275 (hashq-set! bound-vars x (reverse! (hashq-ref bound-vars x)))
276 (hashq-set! free-vars x free)
277 free))))
d97b69d9
AW
278 vals))
279 (vars-with-refs (map cons vars var-refs))
280 (body-refs (recur/labels body proc vars)))
281 (define (delabel-dependents! sym)
282 (let ((refs (assq-ref vars-with-refs sym)))
283 (if refs
284 (for-each (lambda (sym)
285 (if (hashq-ref labels sym)
286 (begin
287 (hashq-set! labels sym #f)
288 (delabel-dependents! sym))))
289 refs))))
290 ;; Stepping into the lambdas and the body might have made some
291 ;; procedures not label-allocatable -- which might have
292 ;; knock-on effects. For example:
293 ;; (fix ((a (lambda () (b)))
294 ;; (b (lambda () a)))
295 ;; (a))
296 ;; As far as `a' is concerned, both `a' and `b' are
297 ;; label-allocatable. But `b' references `a' not in a proc-tail
298 ;; position, which makes `a' not label-allocatable. The
299 ;; knock-on effect is that, when back-propagating this
300 ;; information to `a', `b' will also become not
301 ;; label-allocatable, as it is referenced within `a', which is
302 ;; allocated as a closure. This is a transitive relationship.
303 (for-each (lambda (sym)
304 (if (not (hashq-ref labels sym))
305 (delabel-dependents! sym)))
306 vars)
307 ;; Now lift bound variables with label-allocated lambdas to the
308 ;; parent procedure.
309 (for-each
310 (lambda (sym val)
311 (if (hashq-ref labels sym)
312 ;; Remove traces of the label-bound lambda. The free
313 ;; vars will propagate up via the return val.
314 (begin
315 (hashq-set! bound-vars proc
316 (append (hashq-ref bound-vars val)
317 (hashq-ref bound-vars proc)))
318 (hashq-remove! bound-vars val)
319 (hashq-remove! free-vars val))))
320 vars vals)
321 (lset-difference eq?
322 (apply lset-union eq? body-refs var-refs)
323 vars)))
c21c89b1 324
8a4ca0ea
AW
325 ((<let-values> exp body)
326 (lset-union eq? (step exp) (step body)))
66d3e9a3
AW
327
328 (else '())))
329
9059993f
AW
330 ;; allocation: sym -> {lambda -> address}
331 ;; lambda -> (nlocs labels . free-locs)
332 (define allocation (make-hash-table))
333
66d3e9a3
AW
334 (define (allocate! x proc n)
335 (define (recur y) (allocate! y proc n))
336 (record-case x
337 ((<application> proc args)
338 (apply max (recur proc) (map recur args)))
cf10678f 339
66d3e9a3
AW
340 ((<conditional> test then else)
341 (max (recur test) (recur then) (recur else)))
cf10678f 342
e5f5113c 343 ((<lexical-set> exp)
66d3e9a3
AW
344 (recur exp))
345
e5f5113c 346 ((<module-set> exp)
66d3e9a3
AW
347 (recur exp))
348
e5f5113c 349 ((<toplevel-set> exp)
66d3e9a3
AW
350 (recur exp))
351
e5f5113c 352 ((<toplevel-define> exp)
66d3e9a3
AW
353 (recur exp))
354
355 ((<sequence> exps)
356 (apply max (map recur exps)))
357
8a4ca0ea 358 ((<lambda> body)
66d3e9a3
AW
359 ;; allocate closure vars in order
360 (let lp ((c (hashq-ref free-vars x)) (n 0))
361 (if (pair? c)
362 (begin
363 (hashq-set! (hashq-ref allocation (car c))
364 x
365 `(#f ,(hashq-ref assigned (car c)) . ,n))
366 (lp (cdr c) (1+ n)))))
367
8a4ca0ea 368 (let ((nlocs (allocate! body x 0))
66d3e9a3
AW
369 (free-addresses
370 (map (lambda (v)
371 (hashq-ref (hashq-ref allocation v) proc))
9059993f
AW
372 (hashq-ref free-vars x)))
373 (labels (filter cdr
374 (map (lambda (sym)
375 (cons sym (hashq-ref labels sym)))
376 (hashq-ref bound-vars x)))))
66d3e9a3 377 ;; set procedure allocations
8a4ca0ea 378 (hashq-set! allocation x (cons labels free-addresses)))
66d3e9a3 379 n)
cf10678f 380
b0c8c187 381 ((<lambda-case> opt kw inits vars predicate body else)
8a4ca0ea
AW
382 (max
383 (let lp ((vars vars) (n n))
384 (if (null? vars)
b0c8c187
AW
385 (let ((nlocs (apply
386 max
387 (if predicate (allocate! predicate body n) n)
388 (allocate! body proc n)
389 ;; inits not logically at the end, but they
390 ;; are the list...
391 (map (lambda (x) (allocate! x body n)) inits))))
8a4ca0ea
AW
392 ;; label and nlocs for the case
393 (hashq-set! allocation x (cons (gensym ":LCASE") nlocs))
394 nlocs)
395 (begin
396 (hashq-set! allocation (car vars)
397 (make-hashq
398 proc `(#t ,(hashq-ref assigned (car vars)) . ,n)))
399 (lp (cdr vars) (1+ n)))))
400 (if else (allocate! else proc n) n)))
401
66d3e9a3
AW
402 ((<let> vars vals body)
403 (let ((nmax (apply max (map recur vals))))
404 (cond
405 ;; the `or' hack
406 ((and (conditional? body)
407 (= (length vars) 1)
408 (let ((v (car vars)))
409 (and (not (hashq-ref assigned v))
410 (= (hashq-ref refcounts v 0) 2)
411 (lexical-ref? (conditional-test body))
412 (eq? (lexical-ref-gensym (conditional-test body)) v)
413 (lexical-ref? (conditional-then body))
414 (eq? (lexical-ref-gensym (conditional-then body)) v))))
415 (hashq-set! allocation (car vars)
416 (make-hashq proc `(#t #f . ,n)))
417 ;; the 1+ for this var
418 (max nmax (1+ n) (allocate! (conditional-else body) proc n)))
419 (else
420 (let lp ((vars vars) (n n))
421 (if (null? vars)
422 (max nmax (allocate! body proc n))
423 (let ((v (car vars)))
cf10678f
AW
424 (hashq-set!
425 allocation v
66d3e9a3
AW
426 (make-hashq proc
427 `(#t ,(hashq-ref assigned v) . ,n)))
428 (lp (cdr vars) (1+ n)))))))))
429
430 ((<letrec> vars vals body)
431 (let lp ((vars vars) (n n))
432 (if (null? vars)
433 (let ((nmax (apply max
434 (map (lambda (x)
435 (allocate! x proc n))
436 vals))))
437 (max nmax (allocate! body proc n)))
438 (let ((v (car vars)))
439 (hashq-set!
440 allocation v
441 (make-hashq proc
442 `(#t ,(hashq-ref assigned v) . ,n)))
443 (lp (cdr vars) (1+ n))))))
cf10678f 444
c21c89b1 445 ((<fix> vars vals body)
d97b69d9
AW
446 (let lp ((in vars) (n n))
447 (if (null? in)
448 (let lp ((vars vars) (vals vals) (nmax n))
449 (cond
450 ((null? vars)
451 (max nmax (allocate! body proc n)))
452 ((hashq-ref labels (car vars))
8a4ca0ea 453 ;; allocate lambda body inline to proc
d97b69d9
AW
454 (lp (cdr vars)
455 (cdr vals)
456 (record-case (car vals)
8a4ca0ea
AW
457 ((<lambda> body)
458 (max nmax (allocate! body proc n))))))
d97b69d9
AW
459 (else
460 ;; allocate closure
461 (lp (cdr vars)
462 (cdr vals)
463 (max nmax (allocate! (car vals) proc n))))))
464
465 (let ((v (car in)))
466 (cond
467 ((hashq-ref assigned v)
468 (error "fixpoint procedures may not be assigned" x))
469 ((hashq-ref labels v)
470 ;; no binding, it's a label
471 (lp (cdr in) n))
472 (else
473 ;; allocate closure binding
474 (hashq-set! allocation v (make-hashq proc `(#t #f . ,n)))
475 (lp (cdr in) (1+ n))))))))
c21c89b1 476
8a4ca0ea
AW
477 ((<let-values> exp body)
478 (max (recur exp) (recur body)))
66d3e9a3
AW
479
480 (else n)))
cf10678f 481
d97b69d9 482 (analyze! x #f '() #t #f)
66d3e9a3 483 (allocate! x #f 0)
cf10678f
AW
484
485 allocation)
4b856371
LC
486
487\f
48b1db75
LC
488;;;
489;;; Tree analyses for warnings.
490;;;
491
492(define-record-type <tree-analysis>
493 (make-tree-analysis leaf down up post init)
494 tree-analysis?
495 (leaf tree-analysis-leaf) ;; (lambda (x result env) ...)
496 (down tree-analysis-down) ;; (lambda (x result env) ...)
497 (up tree-analysis-up) ;; (lambda (x result env) ...)
498 (post tree-analysis-post) ;; (lambda (result env) ...)
499 (init tree-analysis-init)) ;; arbitrary value
500
501(define (analyze-tree analyses tree env)
502 "Run all tree analyses listed in ANALYSES on TREE for ENV, using
503`tree-il-fold'. Return TREE."
504 (define (traverse proc)
505 (lambda (x results)
506 (map (lambda (analysis result)
507 ((proc analysis) x result env))
508 analyses
509 results)))
510
511 (let ((results
512 (tree-il-fold (traverse tree-analysis-leaf)
513 (traverse tree-analysis-down)
514 (traverse tree-analysis-up)
515 (map tree-analysis-init analyses)
516 tree)))
517
518 (for-each (lambda (analysis result)
519 ((tree-analysis-post analysis) result env))
520 analyses
521 results))
522
523 tree)
524
525\f
4b856371
LC
526;;;
527;;; Unused variable analysis.
528;;;
529
530;; <binding-info> records are used during tree traversals in
531;; `report-unused-variables'. They contain a list of the local vars
532;; currently in scope, a list of locals vars that have been referenced, and a
533;; "location stack" (the stack of `tree-il-src' values for each parent tree).
534(define-record-type <binding-info>
535 (make-binding-info vars refs locs)
536 binding-info?
537 (vars binding-info-vars) ;; ((GENSYM NAME LOCATION) ...)
538 (refs binding-info-refs) ;; (GENSYM ...)
539 (locs binding-info-locs)) ;; (LOCATION ...)
540
48b1db75
LC
541(define unused-variable-analysis
542 ;; Report about unused variables in TREE.
543
544 (make-tree-analysis
545 (lambda (x info env)
546 ;; X is a leaf: extend INFO's refs accordingly.
547 (let ((refs (binding-info-refs info))
548 (vars (binding-info-vars info))
549 (locs (binding-info-locs info)))
550 (record-case x
551 ((<lexical-ref> gensym)
552 (make-binding-info vars (cons gensym refs) locs))
553 (else info))))
554
555 (lambda (x info env)
556 ;; Going down into X: extend INFO's variable list
557 ;; accordingly.
558 (let ((refs (binding-info-refs info))
559 (vars (binding-info-vars info))
560 (locs (binding-info-locs info))
561 (src (tree-il-src x)))
562 (define (extend inner-vars inner-names)
563 (append (map (lambda (var name)
564 (list var name src))
565 inner-vars
566 inner-names)
567 vars))
568 (record-case x
569 ((<lexical-set> gensym)
570 (make-binding-info vars (cons gensym refs)
571 (cons src locs)))
572 ((<lambda-case> req opt inits rest kw vars)
573 ;; FIXME keywords.
574 (let ((names `(,@req
575 ,@(map car (or opt '()))
576 ,@(if rest (list rest) '())
577 ,@(if kw (map cadr (cdr kw)) '()))))
578 (make-binding-info (extend vars names) refs
579 (cons src locs))))
580 ((<let> vars names)
581 (make-binding-info (extend vars names) refs
582 (cons src locs)))
583 ((<letrec> vars names)
584 (make-binding-info (extend vars names) refs
585 (cons src locs)))
586 ((<fix> vars names)
587 (make-binding-info (extend vars names) refs
588 (cons src locs)))
589 (else info))))
590
591 (lambda (x info env)
592 ;; Leaving X's scope: shrink INFO's variable list
593 ;; accordingly and reported unused nested variables.
594 (let ((refs (binding-info-refs info))
595 (vars (binding-info-vars info))
596 (locs (binding-info-locs info)))
597 (define (shrink inner-vars refs)
598 (for-each (lambda (var)
599 (let ((gensym (car var)))
600 ;; Don't report lambda parameters as
601 ;; unused.
602 (if (and (not (memq gensym refs))
603 (not (and (lambda-case? x)
604 (memq gensym
605 inner-vars))))
606 (let ((name (cadr var))
607 ;; We can get approximate
608 ;; source location by going up
609 ;; the LOCS location stack.
610 (loc (or (caddr var)
611 (find pair? locs))))
612 (warning 'unused-variable loc name)))))
613 (filter (lambda (var)
614 (memq (car var) inner-vars))
615 vars))
616 (fold alist-delete vars inner-vars))
617
618 ;; For simplicity, we leave REFS untouched, i.e., with
619 ;; names of variables that are now going out of scope.
620 ;; It doesn't hurt as these are unique names, it just
621 ;; makes REFS unnecessarily fat.
622 (record-case x
623 ((<lambda-case> vars)
624 (make-binding-info (shrink vars refs) refs
625 (cdr locs)))
626 ((<let> vars)
627 (make-binding-info (shrink vars refs) refs
628 (cdr locs)))
629 ((<letrec> vars)
630 (make-binding-info (shrink vars refs) refs
631 (cdr locs)))
632 ((<fix> vars)
633 (make-binding-info (shrink vars refs) refs
634 (cdr locs)))
635 (else info))))
636
637 (lambda (result env) #t)
638 (make-binding-info '() '() '())))
f67ddf9d
LC
639
640\f
641;;;
642;;; Unbound variable analysis.
643;;;
644
645;; <toplevel-info> records are used during tree traversal in search of
646;; possibly unbound variable. They contain a list of references to
647;; potentially unbound top-level variables, a list of the top-level defines
648;; that have been encountered, and a "location stack" (see above).
649(define-record-type <toplevel-info>
650 (make-toplevel-info refs defs locs)
651 toplevel-info?
652 (refs toplevel-info-refs) ;; ((VARIABLE-NAME . LOCATION) ...)
653 (defs toplevel-info-defs) ;; (VARIABLE-NAME ...)
654 (locs toplevel-info-locs)) ;; (LOCATION ...)
655
6bb891dc 656(define (goops-toplevel-definition proc args env)
b6d2306d
LC
657 ;; If application of PROC to ARGS is a GOOPS top-level definition, return
658 ;; the name of the variable being defined; otherwise return #f. This
659 ;; assumes knowledge of the current implementation of `define-class' et al.
6bb891dc
LC
660 (define (toplevel-define-arg args)
661 (and (pair? args) (pair? (cdr args)) (null? (cddr args))
662 (record-case (car args)
663 ((<const> exp)
664 (and (symbol? exp) exp))
665 (else #f))))
666
b6d2306d
LC
667 (record-case proc
668 ((<module-ref> mod public? name)
669 (and (equal? mod '(oop goops))
670 (not public?)
671 (eq? name 'toplevel-define!)
6bb891dc
LC
672 (toplevel-define-arg args)))
673 ((<toplevel-ref> name)
674 ;; This may be the result of expanding one of the GOOPS macros within
675 ;; `oop/goops.scm'.
676 (and (eq? name 'toplevel-define!)
677 (eq? env (resolve-module '(oop goops)))
678 (toplevel-define-arg args)))
b6d2306d
LC
679 (else #f)))
680
48b1db75
LC
681(define unbound-variable-analysis
682 ;; Return possibly unbound variables in TREE.
683 (make-tree-analysis
684 (lambda (x info env)
685 ;; X is a leaf: extend INFO's refs accordingly.
686 (let ((refs (toplevel-info-refs info))
687 (defs (toplevel-info-defs info))
688 (locs (toplevel-info-locs info)))
689 (define (bound? name)
690 (or (and (module? env)
691 (module-variable env name))
692 (memq name defs)))
693
694 (record-case x
695 ((<toplevel-ref> name src)
696 (if (bound? name)
697 info
698 (let ((src (or src (find pair? locs))))
699 (make-toplevel-info (alist-cons name src refs)
700 defs
701 locs))))
702 (else info))))
703
704 (lambda (x info env)
705 ;; Going down into X.
706 (let* ((refs (toplevel-info-refs info))
707 (defs (toplevel-info-defs info))
708 (src (tree-il-src x))
709 (locs (cons src (toplevel-info-locs info))))
710 (define (bound? name)
711 (or (and (module? env)
712 (module-variable env name))
713 (memq name defs)))
714
715 (record-case x
716 ((<toplevel-set> name src)
717 (if (bound? name)
718 (make-toplevel-info refs defs locs)
719 (let ((src (find pair? locs)))
720 (make-toplevel-info (alist-cons name src refs)
721 defs
722 locs))))
723 ((<toplevel-define> name)
724 (make-toplevel-info (alist-delete name refs eq?)
725 (cons name defs)
726 locs))
727
728 ((<application> proc args)
729 ;; Check for a dynamic top-level definition, as is
730 ;; done by code expanded from GOOPS macros.
731 (let ((name (goops-toplevel-definition proc args
732 env)))
733 (if (symbol? name)
734 (make-toplevel-info (alist-delete name refs
735 eq?)
736 (cons name defs)
737 locs)
738 (make-toplevel-info refs defs locs))))
739 (else
740 (make-toplevel-info refs defs locs)))))
741
742 (lambda (x info env)
743 ;; Leaving X's scope.
744 (let ((refs (toplevel-info-refs info))
745 (defs (toplevel-info-defs info))
746 (locs (toplevel-info-locs info)))
747 (make-toplevel-info refs defs (cdr locs))))
748
749 (lambda (toplevel env)
750 ;; Post-process the result.
751 (for-each (lambda (name+loc)
752 (let ((name (car name+loc))
753 (loc (cdr name+loc)))
754 (warning 'unbound-variable loc name)))
755 (reverse (toplevel-info-refs toplevel))))
756
757 (make-toplevel-info '() '() '())))