mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-29 19:30:36 +02:00
New warnings: -Wuse-before-definition, -Wnon-idempotent-definition
* module/ice-9/boot-9.scm (%auto-compilation-options): Add use-before-definition and non-idempotent-definition. * module/language/tree-il/analyze.scm (<use-before-def-info>): New analysis info. (make-use-before-definition-analysis): New function. (goops-toplevel-definition): Move down. (unbound-variable-analysis, macro-use-before-definition): Remove, as they are subsumed by use-before-def. There are some deprecated bindings though. (make-analyzer): Rework to allow for use-before-def analysis to handle multiple * module/system/base/message.scm (%warning-types): Add handlers for the new warning types. * test-suite/tests/tree-il.test: Add tests. * doc/ref/api-evaluation.texi (Compilation): Update.
This commit is contained in:
parent
64c89458e6
commit
9d5978a756
5 changed files with 418 additions and 171 deletions
|
@ -1,7 +1,7 @@
|
|||
@c -*-texinfo-*-
|
||||
@c This is part of the GNU Guile Reference Manual.
|
||||
@c Copyright (C) 1996, 1997, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009,
|
||||
@c 2010, 2011, 2012, 2013, 2014, 2020 Free Software Foundation, Inc.
|
||||
@c 2010, 2011, 2012, 2013, 2014, 2020, 2021 Free Software Foundation, Inc.
|
||||
@c See the file guile.texi for copying conditions.
|
||||
|
||||
@node Read/Load/Eval/Compile
|
||||
|
@ -666,13 +666,9 @@ For example, to compile R6RS code, you might want to pass @command{-x
|
|||
@item -W @var{warning}
|
||||
@itemx --warn=@var{warning}
|
||||
@cindex warnings, compiler
|
||||
Emit warnings of type @var{warning}; use @code{--warn=help} for a list
|
||||
of available warnings and their description. Currently recognized
|
||||
warnings include @code{unused-variable}, @code{unused-toplevel},
|
||||
@code{shadowed-toplevel}, @code{unbound-variable},
|
||||
@code{macro-use-before-definition},
|
||||
@code{arity-mismatch}, @code{format},
|
||||
@code{duplicate-case-datum}, and @code{bad-case-datum}.
|
||||
Enable specific warning passes; use @code{-Whelp} for a list of
|
||||
available options. The default is @code{-W1}, which enables a number of
|
||||
common warnings. Pass @code{-W0} to disable all warnings.
|
||||
|
||||
@item -O @var{opt}
|
||||
@itemx --optimize=@var{opt}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
;;; -*- mode: scheme; coding: utf-8; -*-
|
||||
|
||||
;;;; Copyright (C) 1995-2014, 2016-2020 Free Software Foundation, Inc.
|
||||
;;;; Copyright (C) 1995-2014, 2016-2021 Free Software Foundation, Inc.
|
||||
;;;;
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -4200,9 +4200,9 @@ but it fails to load."
|
|||
|
||||
(define %auto-compilation-options
|
||||
;; Default `compile-file' option when auto-compiling.
|
||||
'(#:warnings (unbound-variable shadowed-toplevel
|
||||
macro-use-before-definition arity-mismatch
|
||||
format duplicate-case-datum bad-case-datum)))
|
||||
'(#:warnings (shadowed-toplevel use-before-definition arity-mismatch
|
||||
format duplicate-case-datum bad-case-datum
|
||||
non-idempotent-definition)))
|
||||
|
||||
(define* (load-in-vicinity dir file-name #:optional reader)
|
||||
"Load source file FILE-NAME in vicinity of directory DIR. Use a
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
;;; TREE-IL -> GLIL compiler
|
||||
;;; Diagnostic warnings for Tree-IL
|
||||
|
||||
;; Copyright (C) 2001,2008-2014,2016,2018-2020 Free Software Foundation, Inc.
|
||||
;; Copyright (C) 2001,2008-2014,2016,2018-2021 Free Software Foundation, Inc.
|
||||
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -34,8 +34,7 @@
|
|||
unused-variable-analysis
|
||||
unused-toplevel-analysis
|
||||
shadowed-toplevel-analysis
|
||||
unbound-variable-analysis
|
||||
macro-use-before-definition-analysis
|
||||
make-use-before-definition-analysis
|
||||
arity-analysis
|
||||
format-analysis
|
||||
make-analyzer))
|
||||
|
@ -368,155 +367,300 @@ given `tree-il' element."
|
|||
|
||||
|
||||
;;;
|
||||
;;; Unbound variable analysis.
|
||||
;;; Use before definition analysis.
|
||||
;;;
|
||||
;;; This analysis collects all definitions of top-level variables, and
|
||||
;;; references to top-level variables. As it visits the term, it tries
|
||||
;;; to match uses to the definition that corresponds to that program
|
||||
;;; point. For example, in this sample program:
|
||||
;;;
|
||||
;;; (define a 42)
|
||||
;;; (define b a)
|
||||
;;;
|
||||
;;; The analysis will be able to know that the definition of "a"
|
||||
;;; referred to when defining "b" is 42.
|
||||
;;;
|
||||
;;; In many cases this definition is conservative. For example, in this
|
||||
;;; code:
|
||||
;;;
|
||||
;;; (define a 42)
|
||||
;;; (define b (lambda () a))
|
||||
;;;
|
||||
;;; We don't necessarily know that the "a" in the lambda is 42, as a
|
||||
;;; further top-level definition could provide a different value.
|
||||
;;; However, we do know that "a" is bound, unlike in this code:
|
||||
;;;
|
||||
;;; (define b (lambda () a))
|
||||
;;;
|
||||
;;; Here we should issue a warning if no import provides an "a" binding.
|
||||
;;;
|
||||
;;; Use-before-def analysis also issues specialized warnings for some
|
||||
;;; less common errors. One relates specifically to macro use before
|
||||
;;; definition. If a compilation unit defines a macro and has some uses
|
||||
;;; of the macro, usually the uses will be expanded out by the
|
||||
;;; macro-expander. If there is any reference to a macro as a value,
|
||||
;;; that usually indicates a bug in the user's program. Like in this
|
||||
;;; program:
|
||||
;;;
|
||||
;;; (define (a) (b))
|
||||
;;; (define-syntax-rule (b) 42)
|
||||
;;;
|
||||
;;; If this program is expanded one top-level expression at a time,
|
||||
;;; which is Guile's default compilation mode, the expander will assume
|
||||
;;; that the reference to (b) is a call to a top-level procedure, only
|
||||
;;; to find out it's a macro later on. Use-before-def analysis can warn
|
||||
;;; for this case.
|
||||
;;;
|
||||
;;; Similarly, if a compilation unit uses an imported binding, then
|
||||
;;; provides a local definition for the binding, this may cause problems
|
||||
;;; if the module is re-loaded. Consider:
|
||||
;;;
|
||||
;;; (define-module (foo))
|
||||
;;; (define a +)
|
||||
;;; (define + -)
|
||||
;;;
|
||||
;;; In this fragment, we see the intention of the programmer is to
|
||||
;;; locally redefine `+', but to preserve the previous definition in
|
||||
;;; `a'.
|
||||
;;;
|
||||
;;; However, if the module is loaded twice, `a' will be bound not to the
|
||||
;;; `(guile)' binding of `+', but rather to `-'. This is because each
|
||||
;;; module has a single global instance, and the first definition
|
||||
;;; already bound `+' to `-'. Use-before-def analysis can detect this
|
||||
;;; situation as well.
|
||||
;;;
|
||||
|
||||
;; <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, and a list of the top-level
|
||||
;; defines that have been encountered.
|
||||
(define-record-type <toplevel-info>
|
||||
(make-toplevel-info refs defs)
|
||||
toplevel-info?
|
||||
(refs toplevel-info-refs) ;; ((VARIABLE-NAME . LOCATION) ...)
|
||||
(defs toplevel-info-defs)) ;; (VARIABLE-NAME ...)
|
||||
;;; <use-before-def-info> records are used during tree traversal in
|
||||
;;; search of possible uses of values before they are defined. They
|
||||
;;; contain a list of references to top-level variables, and a list of
|
||||
;;; the top-level definitions that have been encountered. Any definition
|
||||
;;; which is a macro should in theory be expanded out already; if that's
|
||||
;;; not the case, the program likely has a bug.
|
||||
(define-record-type <use-before-def-info>
|
||||
(make-use-before-def-info depth uses defs)
|
||||
use-before-def-info?
|
||||
;; LOCAL-DEF := #(MACRO? DEPTH LOCATION)
|
||||
;; DEF := LOCAL-DEF ; Defined in compilation unit already at use.
|
||||
;; | import ; Def provided by imported module.
|
||||
;; | unknown-module ; Module at use site not known.
|
||||
;; | unknown-declarative ; Defined, but def not within compilation unit.
|
||||
;; | unknown-imperative ; Same as above, but in non-declarative module.
|
||||
;; | unbound ; No top-level definition known at use
|
||||
;; USE := #(MOD-NAME VAR-NAME DEPTH DEF LOCATION)
|
||||
(depth use-before-def-info-depth) ;; Zero if definitely evaluated
|
||||
(uses use-before-def-info-uses) ;; List of USE
|
||||
(defs use-before-def-info-defs)) ;; Vhash of ((MOD . NAME) . LOCAL-DEF)
|
||||
|
||||
(define (goops-toplevel-definition proc args env)
|
||||
;; If call of PROC to ARGS is a GOOPS top-level definition, return
|
||||
;; the name of the variable being defined; otherwise return #f. This
|
||||
;; assumes knowledge of the current implementation of `define-class' et al.
|
||||
(define (toplevel-define-arg args)
|
||||
(match args
|
||||
((($ <const> _ (and (? symbol?) exp)) _)
|
||||
exp)
|
||||
(_ #f)))
|
||||
|
||||
(match proc
|
||||
(($ <module-ref> _ '(oop goops) 'toplevel-define! #f)
|
||||
(toplevel-define-arg args))
|
||||
(($ <toplevel-ref> _ _ 'toplevel-define!)
|
||||
;; This may be the result of expanding one of the GOOPS macros within
|
||||
;; `oop/goops.scm'.
|
||||
(and (eq? env (resolve-module '(oop goops)))
|
||||
(toplevel-define-arg args)))
|
||||
;; If call of PROC to ARGS is a GOOPS top-level definition, return the
|
||||
;; name of the variable being defined; otherwise return #f. This
|
||||
;; assumes knowledge of the current implementation of `define-class'
|
||||
;; et al.
|
||||
(match (cons proc args)
|
||||
((($ <module-ref> _ '(oop goops) 'toplevel-define! #f)
|
||||
($ <const> _ (? symbol? name))
|
||||
exp)
|
||||
;; We don't know the precise module in which we are defining the
|
||||
;; variable :/ Guess that it's in `env'.
|
||||
(vector (module-name env) name exp))
|
||||
((($ <toplevel-ref> _ '(oop goops) 'toplevel-define!)
|
||||
($ <const> _ (? symbol? name))
|
||||
exp)
|
||||
(vector '(oop goops) name exp))
|
||||
(_ #f)))
|
||||
|
||||
(define unbound-variable-analysis
|
||||
(define* (make-use-before-definition-analysis #:key (warning-level 0)
|
||||
(enabled-warnings '()))
|
||||
;; Report possibly unbound variables in the given tree.
|
||||
(make-tree-analysis
|
||||
(lambda (x info env locs)
|
||||
;; Going down into X.
|
||||
(let* ((refs (toplevel-info-refs info))
|
||||
(defs (toplevel-info-defs info))
|
||||
(src (tree-il-src x)))
|
||||
(define (bound? name)
|
||||
(or (and (module? env)
|
||||
(module-variable env name))
|
||||
(vhash-assq name defs)))
|
||||
(define (enabled-for-level? level) (<= level warning-level))
|
||||
(define-syntax-rule (define-warning enabled
|
||||
#:level level #:name warning-name)
|
||||
(define enabled
|
||||
(or (enabled-for-level? level)
|
||||
(memq 'warning-name enabled-warnings))))
|
||||
(define-warning use-before-definition-enabled
|
||||
#:level 1 #:name use-before-definition)
|
||||
(define-warning unbound-variable-enabled
|
||||
#:level 1 #:name unbound-variable)
|
||||
(define-warning macro-use-before-definition-enabled
|
||||
#:level 1 #:name macro-use-before-definition)
|
||||
(define-warning non-idempotent-definition-enabled
|
||||
#:level 1 #:name non-idempotent-definition)
|
||||
(define (resolve mod name defs)
|
||||
(match (vhash-assoc (cons mod name) defs)
|
||||
((_ . local-def)
|
||||
;; Top-level def present in this compilation unit, before this
|
||||
;; use.
|
||||
local-def)
|
||||
(#f
|
||||
(let ((mod (and mod (resolve-module mod #f #:ensure #f))))
|
||||
(cond
|
||||
((not mod)
|
||||
;; We don't know the module with respect to which this var
|
||||
;; is being resolved.
|
||||
'unknown-module)
|
||||
((module-local-variable mod name)
|
||||
;; The variable is locally bound in the module, but not by
|
||||
;; any definition in the compilation unit; perhaps by load
|
||||
;; or load-extension or something.
|
||||
(if (module-declarative? mod)
|
||||
'unknown-declarative
|
||||
'unknown-imperative))
|
||||
((module-variable mod name)
|
||||
;; The variable is an import. At the time of use, the
|
||||
;; name is bound to the import.
|
||||
'import)
|
||||
(else
|
||||
;; Variable unbound in the module.
|
||||
'unbound))))))
|
||||
|
||||
(record-case x
|
||||
((<toplevel-ref> name src)
|
||||
(if (bound? name)
|
||||
info
|
||||
(let ((src (or src (find pair? locs))))
|
||||
(make-toplevel-info (vhash-consq name src refs)
|
||||
defs))))
|
||||
((<toplevel-set> name src)
|
||||
(if (bound? name)
|
||||
(make-toplevel-info refs defs)
|
||||
(let ((src (find pair? locs)))
|
||||
(make-toplevel-info (vhash-consq name src refs)
|
||||
defs))))
|
||||
((<toplevel-define> name)
|
||||
(make-toplevel-info (vhash-delq name refs)
|
||||
(vhash-consq name #t defs)))
|
||||
(and
|
||||
(or use-before-definition-enabled
|
||||
unbound-variable-enabled
|
||||
macro-use-before-definition-enabled
|
||||
non-idempotent-definition-enabled)
|
||||
(make-tree-analysis
|
||||
(lambda (x info env locs)
|
||||
;; Going down into X.
|
||||
(define (make-use mod name depth def src)
|
||||
(vector mod name depth def src))
|
||||
(define (make-def is-macro? depth src)
|
||||
(vector is-macro? depth src))
|
||||
(define (nearest-loc src)
|
||||
(or src (find pair? locs)))
|
||||
(define (add-use mod name src)
|
||||
(match info
|
||||
(($ <use-before-def-info> depth uses defs)
|
||||
(let* ((def (resolve mod name defs))
|
||||
(use (make-use mod name depth def src)))
|
||||
(make-use-before-def-info depth (cons use uses) defs)))))
|
||||
(define (add-def mod name src is-macro?)
|
||||
(match info
|
||||
(($ <use-before-def-info> depth uses defs)
|
||||
(let ((def (make-def is-macro? depth src)))
|
||||
(make-use-before-def-info depth uses
|
||||
(vhash-cons (cons mod name) def
|
||||
defs))))))
|
||||
(define (macro? x)
|
||||
(match x
|
||||
(($ <primcall> _ 'make-syntax-transformer) #t)
|
||||
(_ #f)))
|
||||
(match x
|
||||
(($ <toplevel-ref> src mod name)
|
||||
(add-use mod name (nearest-loc src)))
|
||||
(($ <toplevel-set> src mod name)
|
||||
(add-use mod name (nearest-loc src)))
|
||||
(($ <toplevel-define> src mod name exp)
|
||||
(add-def mod name (nearest-loc src) (macro? exp)))
|
||||
(($ <call> src proc args)
|
||||
;; Check for a dynamic top-level definition, as is
|
||||
;; done by code expanded from GOOPS macros.
|
||||
(match (goops-toplevel-definition proc args env)
|
||||
(#f info)
|
||||
(#(mod name exp) (add-def mod name (nearest-loc src) (macro? exp)))))
|
||||
((or ($ <lambda>) ($ <conditional>))
|
||||
(match info
|
||||
(($ <use-before-def-info> depth uses defs)
|
||||
(make-use-before-def-info (1+ depth) uses defs))))
|
||||
(_ info)))
|
||||
|
||||
((<call> proc args)
|
||||
;; Check for a dynamic top-level definition, as is
|
||||
;; done by code expanded from GOOPS macros.
|
||||
(let ((name (goops-toplevel-definition proc args
|
||||
env)))
|
||||
(if (symbol? name)
|
||||
(make-toplevel-info (vhash-delq name refs)
|
||||
(vhash-consq name #t defs))
|
||||
(make-toplevel-info refs defs))))
|
||||
(else
|
||||
(make-toplevel-info refs defs)))))
|
||||
(lambda (x info env locs)
|
||||
;; Leaving X's scope.
|
||||
(match x
|
||||
((or ($ <lambda>) ($ <conditional>))
|
||||
(match info
|
||||
(($ <use-before-def-info> depth uses defs)
|
||||
(make-use-before-def-info (1- depth) uses defs))))
|
||||
(_ info)))
|
||||
|
||||
(lambda (x info env locs)
|
||||
;; Leaving X's scope.
|
||||
info)
|
||||
(lambda (info env)
|
||||
(define (compute-macros defs)
|
||||
(let ((macros (make-hash-table)))
|
||||
(vlist-for-each (match-lambda
|
||||
((mod+name . #(is-macro? depth src))
|
||||
(when is-macro?
|
||||
(hash-set! macros mod+name src))))
|
||||
defs)
|
||||
macros))
|
||||
;; Post-process the result.
|
||||
;; FIXME: What to do with defs at nonzero depth?
|
||||
(match info
|
||||
(($ <use-before-def-info> 0 uses defs)
|
||||
;; The way the traversal works is that we only add entries to
|
||||
;; `defs' as we go, corresponding to local bindings.
|
||||
;; Therefore the result of `resolve' can only go from being an
|
||||
;; import, unbound, or top-level definition to being a
|
||||
;; definition within the compilation unit. It can't go from
|
||||
;; e.g. being an import to being a top-level definition, for
|
||||
;; the purposes of our analysis, without the definition being
|
||||
;; local to the compilation unit.
|
||||
(let ((macros (compute-macros defs))
|
||||
(issued-unbound-warnings (make-hash-table)))
|
||||
(for-each
|
||||
(match-lambda
|
||||
(#(mod name use-depth def-at-use use-loc)
|
||||
(cond
|
||||
((and (hash-ref macros (cons mod name))
|
||||
macro-use-before-definition-enabled)
|
||||
;; Something bound to this name is a macro, probably
|
||||
;; later in the compilation unit. Probably the author
|
||||
;; made a mistake somewhere!
|
||||
(warning 'macro-use-before-definition use-loc name))
|
||||
(else
|
||||
(let ((def-at-end (resolve mod name defs)))
|
||||
(match (cons def-at-use def-at-end)
|
||||
(('import . 'import) #t)
|
||||
(('import . #(is-macro? def-depth def-loc))
|
||||
;; At use, the binding was an import, but later
|
||||
;; had a local definition. Warn as this could
|
||||
;; pose a hazard when reloading the module, as the
|
||||
;; initial binding wouldn't come from the import.
|
||||
;; If depth nonzero though, use might happen later
|
||||
;; as it might be in a lambda, so no warning in
|
||||
;; that case.
|
||||
(when (and non-idempotent-definition-enabled
|
||||
(zero? use-depth) (zero? def-depth))
|
||||
(warning 'non-idempotent-definition use-loc name)))
|
||||
(('unbound . 'unbound)
|
||||
;; No binding at all; probably an error at
|
||||
;; run-time, but we just warn at compile-time.
|
||||
(when unbound-variable-enabled
|
||||
(unless (hash-ref issued-unbound-warnings
|
||||
(cons mod name))
|
||||
(hash-set! issued-unbound-warnings (cons mod name) #t)
|
||||
(warning 'unbound-variable use-loc name))))
|
||||
(('unbound . _)
|
||||
;; If the depth at the use is 0, then the use
|
||||
;; definitely occurs before the definition.
|
||||
(when (and use-before-definition-enabled
|
||||
(zero? use-depth))
|
||||
(warning 'use-before-definition use-loc name)))
|
||||
(('unknown-module . _)
|
||||
;; Could issue a warning here that for whatever
|
||||
;; reason, we weren't able to reason about what
|
||||
;; module was current!
|
||||
#t)
|
||||
(('unknown-declarative . 'unknown-declarative)
|
||||
;; FIXME: Probably we should emit a warning as in
|
||||
;; a declarative module perhaps this should not
|
||||
;; happen.
|
||||
#t)
|
||||
(('unknown-declarative . _)
|
||||
;; Def later in compilation unit than use; no
|
||||
;; problem. Can occur when reloading declarative
|
||||
;; modules.
|
||||
#t)
|
||||
(('unknown-imperative . _)
|
||||
;; Def present and although not visible at the
|
||||
;; use, don't warn as use module is
|
||||
;; non-declarative.
|
||||
#t)
|
||||
(((? vector) . (? vector?))
|
||||
;; Def locally bound at use; no problem.
|
||||
#t)))))))
|
||||
(reverse uses))))))
|
||||
|
||||
(lambda (toplevel env)
|
||||
;; Post-process the result.
|
||||
(vlist-for-each (match-lambda
|
||||
((name . loc)
|
||||
(warning 'unbound-variable loc name)))
|
||||
(vlist-reverse (toplevel-info-refs toplevel))))
|
||||
|
||||
(make-toplevel-info vlist-null vlist-null)))
|
||||
|
||||
|
||||
;;;
|
||||
;;; Macro use-before-definition analysis.
|
||||
;;;
|
||||
|
||||
;; <macro-use-info> records are used during tree traversal in search of
|
||||
;; possibly uses of macros before they are defined. They contain a list
|
||||
;; of references to top-level variables, and a list of the top-level
|
||||
;; macro definitions that have been encountered. Any definition which
|
||||
;; is a macro should in theory be expanded out already; if that's not
|
||||
;; the case, the program likely has a bug.
|
||||
(define-record-type <macro-use-info>
|
||||
(make-macro-use-info uses defs)
|
||||
macro-use-info?
|
||||
(uses macro-use-info-uses) ;; ((VARIABLE-NAME . LOCATION) ...)
|
||||
(defs macro-use-info-defs)) ;; ((VARIABLE-NAME . LOCATION) ...)
|
||||
|
||||
(define macro-use-before-definition-analysis
|
||||
;; Report possibly unbound variables in the given tree.
|
||||
(make-tree-analysis
|
||||
(lambda (x info env locs)
|
||||
;; Going down into X.
|
||||
(define (nearest-loc src)
|
||||
(or src (find pair? locs)))
|
||||
(define (add-use name src)
|
||||
(match info
|
||||
(($ <macro-use-info> uses defs)
|
||||
(make-macro-use-info (vhash-consq name src uses) defs))))
|
||||
(define (add-def name src)
|
||||
(match info
|
||||
(($ <macro-use-info> uses defs)
|
||||
(make-macro-use-info uses (vhash-consq name src defs)))))
|
||||
(define (macro? x)
|
||||
(match x
|
||||
(($ <primcall> _ 'make-syntax-transformer) #t)
|
||||
(_ #f)))
|
||||
(match x
|
||||
(($ <toplevel-ref> src mod name)
|
||||
(add-use name (nearest-loc src)))
|
||||
(($ <toplevel-set> src mod name)
|
||||
(add-use name (nearest-loc src)))
|
||||
(($ <toplevel-define> src mod name (? macro?))
|
||||
(add-def name (nearest-loc src)))
|
||||
(_ info)))
|
||||
|
||||
(lambda (x info env locs)
|
||||
;; Leaving X's scope.
|
||||
info)
|
||||
|
||||
(lambda (info env)
|
||||
;; Post-process the result.
|
||||
(match info
|
||||
(($ <macro-use-info> uses defs)
|
||||
(vlist-for-each
|
||||
(match-lambda
|
||||
((name . use-loc)
|
||||
(when (vhash-assq name defs)
|
||||
(warning 'macro-use-before-definition use-loc name))))
|
||||
(vlist-reverse (macro-use-info-uses info))))))
|
||||
|
||||
(make-macro-use-info vlist-null vlist-null)))
|
||||
(make-use-before-def-info 0 '() vlist-null))))
|
||||
|
||||
|
||||
;;;
|
||||
|
@ -1088,22 +1232,59 @@ resort, return #t when EXP refers to the global variable SPECIAL-NAME."
|
|||
|
||||
#t))
|
||||
|
||||
(define %warning-passes
|
||||
`(#(unused-variable 3 ,unused-variable-analysis)
|
||||
#(unused-toplevel 2 ,unused-toplevel-analysis)
|
||||
#(shadowed-toplevel 2 ,shadowed-toplevel-analysis)
|
||||
#(unbound-variable 1 ,unbound-variable-analysis)
|
||||
#(macro-use-before-definition 1 ,macro-use-before-definition-analysis)
|
||||
#(arity-mismatch 1 ,arity-analysis)
|
||||
#(format 1 ,format-analysis)))
|
||||
(begin-deprecated
|
||||
(define-syntax unbound-variable-analysis
|
||||
(identifier-syntax
|
||||
(begin
|
||||
(issue-deprecation-warning
|
||||
"`unbound-variable-analysis' is deprecated. "
|
||||
"Use `make-use-before-definition-analysis' instead.")
|
||||
(make-use-before-definition-analysis
|
||||
#:enabled-warnings '(unbound-variable)))))
|
||||
(define-syntax macro-use-before-definition-analysis
|
||||
(identifier-syntax
|
||||
(begin
|
||||
(issue-deprecation-warning
|
||||
"`macro-use-before-definition-analysis' is deprecated. "
|
||||
"Use `make-use-before-definition-analysis' instead.")
|
||||
(make-use-before-definition-analysis
|
||||
#:enabled-warnings '(macro-use-before-definition)))))
|
||||
(export unbound-variable-analysis
|
||||
macro-use-before-definition-analysis))
|
||||
|
||||
(define-syntax-rule (define-analysis make-analysis
|
||||
#:level level #:kind kind #:analysis analysis)
|
||||
(define* (make-analysis #:key (warning-level 0) (enabled-warnings '()))
|
||||
(and (or (<= level warning-level)
|
||||
(memq 'kind enabled-warnings))
|
||||
analysis)))
|
||||
|
||||
(define-analysis make-unused-variable-analysis
|
||||
#:level 3 #:kind unused-variable #:analysis unused-variable-analysis)
|
||||
(define-analysis make-unused-toplevel-analysis
|
||||
#:level 2 #:kind unused-toplevel #:analysis unused-toplevel-analysis)
|
||||
(define-analysis make-shadowed-toplevel-analysis
|
||||
#:level 2 #:kind shadowed-toplevel #:analysis shadowed-toplevel-analysis)
|
||||
(define-analysis make-arity-analysis
|
||||
#:level 1 #:kind arity-mismatch #:analysis arity-analysis)
|
||||
(define-analysis make-format-analysis
|
||||
#:level 1 #:kind format #:analysis format-analysis)
|
||||
|
||||
(define (make-analyzer warning-level warnings)
|
||||
(define (enabled-for-level? level) (<= level warning-level))
|
||||
(let ((analyses (filter-map (match-lambda
|
||||
(#(kind level analysis)
|
||||
(and (or (enabled-for-level? level)
|
||||
(memq kind warnings))
|
||||
analysis)))
|
||||
%warning-passes)))
|
||||
(define-syntax compute-analyses
|
||||
(syntax-rules ()
|
||||
((_) '())
|
||||
((_ make-analysis . make-analysis*)
|
||||
(let ((tail (compute-analyses . make-analysis*)))
|
||||
(match (make-analysis #:warning-level warning-level
|
||||
#:enabled-warnings warnings)
|
||||
(#f tail)
|
||||
(analysis (cons analysis tail)))))))
|
||||
(let ((analyses (compute-analyses make-unused-variable-analysis
|
||||
make-unused-toplevel-analysis
|
||||
make-shadowed-toplevel-analysis
|
||||
make-arity-analysis
|
||||
make-format-analysis
|
||||
make-use-before-definition-analysis)))
|
||||
(lambda (exp env)
|
||||
(analyze-tree analyses exp env))))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
;;; User interface messages
|
||||
|
||||
;; Copyright (C) 2009-2012,2016,2018,2020 Free Software Foundation, Inc.
|
||||
;; Copyright (C) 2009-2012,2016,2018,2020-2021 Free Software Foundation, Inc.
|
||||
|
||||
;;; This library is free software; you can redistribute it and/or modify it
|
||||
;;; under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -127,6 +127,18 @@
|
|||
(emit port "~A: warning: macro `~A' used before definition~%"
|
||||
loc name)))
|
||||
|
||||
(use-before-definition
|
||||
"report uses of top-levels before they are defined"
|
||||
,(lambda (port loc name)
|
||||
(emit port "~A: warning: `~A' used before definition~%"
|
||||
loc name)))
|
||||
|
||||
(non-idempotent-definition
|
||||
"report names that can refer to imports on first load, but module definitions on second load"
|
||||
,(lambda (port loc name)
|
||||
(emit port "~A: warning: non-idempotent binding for `~A'. When first loaded, value for `~A` comes from imported binding, but later module-local definition overrides it; any module reload would capture module-local binding rather than import.~%"
|
||||
loc name name)))
|
||||
|
||||
(arity-mismatch
|
||||
"report procedure arity mismatches (wrong number of arguments)"
|
||||
,(lambda (port loc name certain?)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
;;;; tree-il.test --- test suite for compiling tree-il -*- scheme -*-
|
||||
;;;; Andy Wingo <wingo@pobox.com> --- May 2009
|
||||
;;;;
|
||||
;;;; Copyright (C) 2009-2014,2018-2020 Free Software Foundation, Inc.
|
||||
;;;; Copyright (C) 2009-2014,2018-2021 Free Software Foundation, Inc.
|
||||
;;;;
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -202,6 +202,12 @@
|
|||
(define %opts-w-unbound
|
||||
'(#:warnings (unbound-variable)))
|
||||
|
||||
(define %opts-w-use-before-definition
|
||||
'(#:warnings (use-before-definition)))
|
||||
|
||||
(define %opts-w-non-idempotent-definition
|
||||
'(#:warnings (non-idempotent-definition)))
|
||||
|
||||
(define %opts-w-arity
|
||||
'(#:warnings (arity-mismatch)))
|
||||
|
||||
|
@ -551,6 +557,58 @@
|
|||
#:env m
|
||||
#:opts %opts-w-unbound))))))))
|
||||
|
||||
(with-test-prefix "use-before-definition"
|
||||
(define-syntax-rule (pass-if-warnings expr pat test)
|
||||
(pass-if 'expr
|
||||
(match (call-with-warnings
|
||||
(lambda ()
|
||||
(compile 'expr #:to 'cps
|
||||
#:opts %opts-w-use-before-definition)))
|
||||
(pat test)
|
||||
(_ #f))))
|
||||
|
||||
(define-syntax-rule (pass-if-no-warnings expr)
|
||||
(pass-if-warnings expr () #t))
|
||||
|
||||
(pass-if-no-warnings
|
||||
(begin (define x +) x))
|
||||
(pass-if-warnings
|
||||
(begin x (define x +))
|
||||
(w) (number? (string-contains w "`x' used before definition")))
|
||||
(pass-if-warnings
|
||||
(begin (set! x 1) (define x +))
|
||||
(w) (number? (string-contains w "`x' used before definition")))
|
||||
(pass-if-no-warnings
|
||||
(begin (lambda () x) (define x +)))
|
||||
(pass-if-no-warnings
|
||||
(begin (if (defined? 'x) x) (define x +))))
|
||||
|
||||
(with-test-prefix "non-idempotent-definition"
|
||||
(define-syntax-rule (pass-if-warnings expr pat test)
|
||||
(pass-if 'expr
|
||||
(match (call-with-warnings
|
||||
(lambda ()
|
||||
(compile 'expr #:to 'cps
|
||||
#:opts %opts-w-non-idempotent-definition)))
|
||||
(pat test)
|
||||
(_ #f))))
|
||||
|
||||
(define-syntax-rule (pass-if-no-warnings expr)
|
||||
(pass-if-warnings expr () #t))
|
||||
|
||||
(pass-if-no-warnings
|
||||
(begin (define - +) (define y -)))
|
||||
(pass-if-warnings
|
||||
(begin - (define - +))
|
||||
(w) (number? (string-contains w "non-idempotent binding for `-'")))
|
||||
(pass-if-warnings
|
||||
(begin (define y -) (define - +))
|
||||
(w) (number? (string-contains w "non-idempotent binding for `-'")))
|
||||
(pass-if-no-warnings
|
||||
(begin (lambda () -) (define - +)))
|
||||
(pass-if-no-warnings
|
||||
(begin (if (defined? '-) -) (define - +))))
|
||||
|
||||
(with-test-prefix "arity mismatch"
|
||||
|
||||
(pass-if "quiet"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue