Add compiler warning for possibly unbound variables.

* module/language/tree-il/analyze.scm (<toplevel-info>): New record
  type.
  (env-module, report-possibly-unbound-variables): New procedures.

* module/language/tree-il/compile-glil.scm (%warning-passes): Add
  `unbound-variable'.

* module/system/base/message.scm (%warning-types): Likewise.

* test-suite/tests/tree-il.test (read-and-compile, %opts-w-unbound):
  New.
  ("warnings")["unbound variable"]: New test prefix.
This commit is contained in:
Ludovic Courtès 2009-10-06 23:39:56 +02:00
commit f67ddf9dbf
4 changed files with 171 additions and 3 deletions

View file

@ -24,8 +24,11 @@
#:use-module (system base syntax)
#:use-module (system base message)
#:use-module (language tree-il)
#:use-module ((system base compile)
#:select (current-compilation-environment))
#:export (analyze-lexicals
report-unused-variables))
report-unused-variables
report-possibly-unbound-variables))
;; Allocation is the process of assigning storage locations for lexical
;; variables. A lexical variable has a distinct "address", or storage
@ -615,3 +618,98 @@
(make-binding-info '() '() '())
tree)
tree)
;;;
;;; Unbound variable analysis.
;;;
;; <toplevel-info> records are used during tree traversal in search of
;; possibly unbound variable. They contain a list of references to
;; potentially unbound top-level variables, a list of the top-level defines
;; that have been encountered, and a "location stack" (see above).
(define-record-type <toplevel-info>
(make-toplevel-info refs defs locs)
toplevel-info?
(refs toplevel-info-refs) ;; ((VARIABLE-NAME . LOCATION) ...)
(defs toplevel-info-defs) ;; (VARIABLE-NAME ...)
(locs toplevel-info-locs)) ;; (LOCATION ...)
(define (env-module e)
"Return the module corresponding to E."
;; XXX: This is a bit of a hack since since representation of compile-time
;; environments is hidden in `(language scheme compile-tree-il)'.
(cond ((pair? e) (car e))
((module? e) e)
(else (current-compilation-environment))))
;; TODO: Combine with `report-unused-variables' so we don't traverse the tree
;; once for each warning type.
(define (report-possibly-unbound-variables tree env)
"Return possibly unbound variables in TREE. Return TREE."
(define toplevel
(let ((env (env-module env)))
(tree-il-fold (lambda (x info)
;; X is a leaf: extend INFO's refs accordingly.
(let ((refs (toplevel-info-refs info))
(defs (toplevel-info-defs info))
(locs (toplevel-info-locs info)))
(define (bound? name)
(or (and (module? env)
(module-variable env name))
(memq name defs)))
(record-case x
((<toplevel-ref> name src)
(if (bound? name)
info
(let ((src (or src (find pair? locs))))
(make-toplevel-info (alist-cons name src refs)
defs
locs))))
(else info))))
(lambda (x info)
;; Going down into X.
(let* ((refs (toplevel-info-refs info))
(defs (toplevel-info-defs info))
(src (tree-il-src x))
(locs (cons src (toplevel-info-locs info))))
(define (bound? name)
(or (and (module? env)
(module-variable env name))
(memq name defs)))
(record-case x
((<toplevel-set> name src)
(if (bound? name)
(make-toplevel-info refs defs locs)
(let ((src (find pair? locs)))
(make-toplevel-info (alist-cons name src refs)
defs
locs))))
((<toplevel-define> name)
(make-toplevel-info (alist-delete name refs eq?)
(cons name defs)
locs))
(else
(make-toplevel-info refs defs locs)))))
(lambda (x info)
;; Leaving X's scope.
(let ((refs (toplevel-info-refs info))
(defs (toplevel-info-defs info))
(locs (toplevel-info-locs info)))
(make-toplevel-info refs defs (cdr locs))))
(make-toplevel-info '() '() '())
tree)))
(for-each (lambda (name+loc)
(let ((name (car name+loc))
(loc (cdr name+loc)))
(warning 'unbound-variable loc name)))
(reverse (toplevel-info-refs toplevel)))
tree)

View file

@ -46,7 +46,8 @@
(define *comp-module* (make-fluid))
(define %warning-passes
`((unused-variable . ,report-unused-variables)))
`((unused-variable . ,report-unused-variables)
(unbound-variable . ,report-possibly-unbound-variables)))
(define (compile-glil x e opts)
(define warnings

View file

@ -79,6 +79,12 @@
"report unused variables"
,(lambda (port loc name)
(format port "~A: warning: unused variable `~A'~%"
loc name)))
(unbound-variable
"report possibly unbound variables"
,(lambda (port loc name)
(format port "~A: warning: possibly unbound variable `~A'~%"
loc name))))))
(define (lookup-warning-type name)