Next: , Previous: (dir), Up: (dir)

Guile-Lint

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).



Next: , Previous: Top, Up: Top

1 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.

1.1 Copying Conditions

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.


Next: , Previous: Introduction, Up: Top

2 Installing

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.

--prefix
Guile-Lint can be installed under any tree, the install directory is included in the guile-lint script and added to %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.
Documentation formats
The source for this manual is guile-lint.texi, in Texinfo format (see Texinfo). Info format guile-lint.info is included in the distribution, which can be read with Emacs (see Other Help Commands) or a stand-alone info reader such as the Texinfo one (see GNU Info).

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).


Next: , Previous: Installing, Up: Top

3 Invocation

Guile-Lint is run as

     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,

-e expr
--eval expr
Evaluate some extra lint setup code (see Extending).
-h
--help
Print a summary of the command line options.
-i
--ignore-case
Read code case-insensitively, ie. using the Guile reader “case-insensitive” option (see Reader options).
-k
--keywords
Read keywords in :foo style, as well as #:foo, per the Guile reader “keywords” option (see Reader options).
-l filename
--load filename
Load a file with extra lint setup code (see Extending).
-L dirname
--path dirname
Add a directory to the front of %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.
--r5rs
Check using just R5RS standard bindings, not Guile extensions.
--slib
Check a program which uses slib (see SLIB). This option is for a program designed to be run by the slib startup script. If a Guile (use-modules (ice-9 slib)) is used then --slib is not needed.
--slib-module
Check an slib module (see SLIB). The difference between an slib program and an slib module is that unused top-level bindings provoke warnings for a program, but for a module they're assumed to be part of the exported interface.
--use-srfi=n,n,...
Preload the given SRFI modules (see SRFI Support Modules). This is like the --use-srfi option which Guile itself accepts (see Invoking Guile).
--version
Print the Guile-Lint version number.


Next: , Previous: Invocation, Up: Top

4 Checks

Bindings
Unbound variables or unknown functions in expressions are detected. This is done by walking through the code and looking for references to variables not bound by any 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.

Top-level expressions
Expressions at the top-level are treated as immediately evaluated, so all variables in them must be satisfied at that point.

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.

Special forms
Standard special forms like 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.

Modules
Modules imported with 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 required 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.)

Errors
Errors are printed in GNU style ‘file:line:column:’ like
          /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.

Functions
Argument counts in calls are checked, both for standard functions and for application functions (if they're defined in straightforward ways).

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.

Pure functions
A “pure” function is one which has no side effects, its only purpose is to produce a return value (either from parameters or from global variables etc). Guile-Lint warns when the return value from a call to such a function is not used, since a call like that is pointless.
Macros
Application macros defined in straightforward ways are recognised, and are evaluated to perform their transformations. This is done for 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.)

Conditional code
Code which does tests with 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.


Next: , Previous: Checks, Up: Top

5 Hints

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.


Next: , Previous: Hints, Up: Top

6 Extending

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.


Previous: Extending, Up: Top

Concept Index