This manual describes how to install and use Guile-Lint, version 14.
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Kevin Ryde
Guile-Lint is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version (see Introduction).
Guile-Lint performs various syntactic and semantic checks on Guile programs and modules (see The Guile Reference Manual), aiming to pick up common mistakes. A file can be checked for example with
guile-lint myprog.scm
Guile-Lint is not a substitute for a test suite, but it's a good way to find possible problems in hard to test code, or code still under rapid development. The problems most often found are due to functions renamed or changed without all usages updated.
Caution: Some of the code under test may be executed, so Guile-Lint should only be run on trusted source.
Guile-Lint is Free Software, published under the terms of the GNU General Public License (version 3 or higher). The file COPYING contains the full license terms.
Guile-Lint is copyrighted and there are restrictions on its distribution and redistribution, but these restrictions are designed to permit everything a cooperating person would want to do. Essentially you can give or sell copies to anyone, with or without modifications, but you must allow everyone else to do the same and you must clearly identify any modifications.
Be aware there is no warranty whatsoever for Guile-Lint. This is described in full in the license terms.
Guile-Lint is designed for use with Guile 1.6.4 or higher. An Autoconf and Automake based configuration system is used (see Autoconf, and see GNU Automake). A basic build and install can be made as follows. This will install under ‘/usr/local’.
./configure make make install
The usual configure options and build targets are available. Run ‘./configure --help’ for a summary of the options.
%load-path
if not
already there. The tree doesn't have to be the same as Guile itself is using,
and the Guile version can be changed without rebuilding Guile-Lint.
The usual Automake targets are available to make PostScript guile-lint.ps, PDF guile-lint.pdf and DVI guile-lint.dvi. These will require various TeX and Texinfo tools. DocBook and XML can be generated by makeinfo too (see Options for makeinfo).
guile-lint [--options] filename...
Each given file is checked, and errors or warnings are printed to the standard output. The exit code is non-zero if there's any errors (warnings and hints are not errors).
guile-lint runs whichever guile is in the PATH environment variable. A different Guile can be forced by running as a -s script,
my-guile -s /usr/bin/guile-lint testfile.scm ...
This can also be used to add options to Guile, like --debug if something seems to be going wrong with guile-lint or the modules it loads.
guile-lint itself takes the following options,
:foo
style, as well as #:foo
, per the Guile
reader “keywords” option (see Reader options).
%load-path
(see Configuration Build and Installation).
This can be used to direct Guile-Lint to modules in a non-standard location
needed by the code under test.
slib
startup script. If a
Guile (use-modules (ice-9 slib))
is used then --slib is not
needed.
let
, define
, module import, etc.
Bindings created with let
and friends and top-level definitions, but
then never used are warned about. But unused formal parameters in
lambda
etc (which includes receive
and let-values
) don't
cause warnings, since they often exist only to satisfy a caller, without being
used.
Occasionally a binding is made in a let*
or similar just for a
side-effect, eg. a read
from a port to discard something. That sort of
thing is not detected, but hopefully it's rare.
But this is not applied recursively to the code within functions called. Perhaps this could be done in the future, but it's unlikely to be perfect, since it's easy for callbacks or similar to obscure some or all of what's actually executed.
Forms like (if (defined? 'foo) ...
and (if (not (defined? 'foo))
...
in immediate expressions are considered to be adapting to a particular
version of Guile or libraries. Guile-Lint follows this and only checks the
legs which are true for the running Guile.
let
and cond
have consistency checks
applied. For example the variable names in a let
must be all
different, or in cond
only the last clause can be an else
.
When a badly structured special form is encountered (and an error printed) its
contents are generally not checked further, so Guile-Lint should be re-run
once initial problems have been corrected.
use-modules
are loaded and so must be available
through %load-path. See the -L option for an easy way to add
directories there (see Invocation).
Modules which are imported but then don't have any bindings used are warned about. This is not good if a module is loaded merely for side-effects, but that should be rare, and it's probably not good style to have too much hidden magic like that.
SRFI modules can be loaded with use-modules
in the usual way, or
alternately with the ‘guile-lint --use-srfi=N,N,...’ option like Guile
itself. SRFI-55 require-extension
is recognised for loading SRFI
modules too, as long as Guile itself has that feature (which means version 1.8
and up, See SRFI-55 - Requiring Features.)
SLIB modules can be require
d in the usual way (see Require), as long as Guile-Lint is run with the --slib option. (The
intention is to also support Guile's (use-modules (ice-9 slib))
style,
but that module has had problems in recent versions of Guile, eg. 1.8.1.)
/home/gg/lint/misc/foo.scm:8:1: unbound symbol: blahblah
Emacs compilation-mode
recognises this (see Compilation Mode).
Messages for bad special forms are printed as they're encountered, but unbound variables are reported at the end because a later top-level define might satisfy a reference.
The location shown for an unbound variable is the start of the containing
S-expression (ie. parenthesized form). This is because that's all the Guile
reader provides, it doesn't record the location of every list element. Unless
an expression is very big this is normally enough.
For most standard functions, checks are applied to constant arguments. For
example format
strings, or the mode string for open-file
. Some
similar checks can be setup for application functions, see Hints.
For most standard functions taking a procedure as a parameter, that procedure
is checked against the arguments it will be called with. For example the
procedure given to for-each
should have one parameter for each iterated
list.
Argument types and possible return types are known for core functions and
various Guile modules, including Guile-Gtk (see Guile-Gtk). Guile-Lint can check an argument has the right type in various
cases, but by no means all cases. A wrong type is flagged when it's
definitely wrong. If there's several possible return values from a function,
then only when none of them will suit the place it's used is an error printed.
defmacro
,
define-macro
, and expressions like (define foo (procedure->macro
...))
, etc.
But macro definition takes place only in a the plain "guile-user
"
environment, so transformer code can only rely on standard functions, not
things defined by the rest of the application code under test. Often this is
enough.
Macro expansion takes place without a proper expansion environment (the "env" argument to the transformers), so unfortunately application macros and macros imported from other modules will only be able to perform fairly straightforward manipulations of the given expression.
Application syntax-rules
macros are not well supported (see The R5RS syntax-rules
System). There's some checking and an effort at expansion, but it's quite
primitive.
syntax-rules
macros imported from other modules are handled somewhat
better, they get executed to do their expansion, but the environment provided
is only the originating module, so probably only straightforward expansions
will work. (The subtle handling of bound and unbound literals is likely to be
wrong for instance.)
defined?
or checks function arity before
calling might result in false error reports. There's not much to be done
about that, since such tests are hard to identify. Hopefully it will be rare
in sensible programs, and for instance restricted to a block of compatibility
setups.
Certain hints can be included in application code to tell Guile-Lint to
perform extra checks. Currently this just allows applications to flag
functions which are like format
and should have format strings checked.
For example,
(define (my-error type severity fmt . args) ... stuff ... (apply format (current-error-port) fmt args)) ''(guile-lint simple-format my-error 2)
The (guile-lint simple-format ...)
form says my-error
is a
simple-format
style function, taking two arguments before the format
string. Because this form is a quote
it's ignored when the code
actually runs. The full set of hints available are
''(guile-lint format func) ''(guile-lint format func n) ''(guile-lint ice-9-format func) ''(guile-lint ice-9-format func n) ''(guile-lint simple-format func) ''(guile-lint simple-format func n)
func is flagged as working like format
. Optional n is the
number of fixed arguments preceding the format string in each call. For
instance this would be 1 for the standard format
functions. The
default is no preceding arguments (ie. N=0).
ice-9-format
means the full (ice-9 format)
function,
simple-format
is just the core simple-format
function, and plain
format
gets whichever is the current binding of the format
function, either simple-format
by default or ice-9
format
when that module has been used.
The -l or -e options allow extra code to be executed before
the checking commences. For instance new special forms can be registered with
lint-handler!
. However note that the programming interface for
lint-handler!
and other procedures is not guaranteed to remain in its
present form, so don't write too much using it.
For interest, there's two main problems with the current style. Firstly it's rather tedious to work through an “expr” with explicit tests and whatnot. Secondly running the handlers in one pass as the code is read tends to do them a bit too early for maximum checking. A better idea would be to collect up all the calls made and the possible values assigned to variables, and use that to deduce return types and parameter types. That sort of thing needs to be deferred until the end of reading since assignments can be made anywhere, and toplevel definitions can be made in any order.
%load-path
: Invocationdefine-macro
: Checksdefined?
: Checksdefmacro
: Checksformat
: Hintsprocedure->macro
: Checksprocedure->memoizing-macro
: Checksrequire-extension
: Checkssimple-format
: Hintssyntax-rules
: Checksuse-modules
: Checks