Module typecheck

Gradual type checking for Lua functions.

The behaviour of the functions in this module are controlled by the value of the global _DEBUG. Not setting _DEBUG prior to loading this module is equivalent to having _DEBUG = true.

The first line of Lua code in production quality applications that value execution speed over rigorous function type checking should be:

_DEBUG = false

Alternatively, if your project also uses Lua stdlib, this modules still respects the std.debug_init _DEBUG table:

require "std.debug_init"._DEBUG = false

or:

require "std.debug_init"._DEBUG = { argcheck = false }

This mitigates almost all of the overhead of type checking with the functions from this module.

Functions

argcheck (name, i, expected, actual[, level=2]) Check the type of an argument against expected types.
argerror (name, i[, extramsg[, level=1]]) Raise a bad argument error.
argscheck (decl, inner) Wrap a function definition with argument type and arity checking.
check (expected, actual) Checks the type of actual against the expected typespec
extramsg_mismatch (expected, actual[, index]) Format a type mismatch error.
extramsg_toomany (bad, expected, actual) Format a too many things error.
parsetypes (types) Compact permutation list into a list of valid types at each argument.
resulterror (name, i[, extramsg[, level=1]]) Raise a bad result error.
typesplit (either) Split a typespec string into a table of normalized type names.

Metamethods

__index (name) Lazy loading of typecheck modules.


Functions

argcheck (name, i, expected, actual[, level=2])
Check the type of an argument against expected types. Equivalent to luaL_argcheck in the Lua C API.

Call argerror if there is a type mismatch.

Argument actual must match one of the types from in expected, each of which can be the name of a primitive Lua type, a stdlib object type, or one of the special options below:

#table    accept any non-empty table
any       accept any non-nil argument type
callable  accept a function or a functor
file      accept an open file object
func      accept a function
function  accept a function
functor   accept an object with a __call metamethod
int       accept an integer valued number
list      accept a table where all keys are a contiguous 1-based integer range
#list     accept any non-empty list
object    accept any std.Object derived type
:foo      accept only the exact string ":foo", works for any :-prefixed string

The :foo format allows for type-checking of self-documenting boolean-like constant string parameters predicated on nil versus :option instead of false versus true. Or you could support both:

argcheck ("table.copy", 2, "boolean|:nometa|nil", nometa)

A very common pattern is to have a list of possible types including "nil" when the argument is optional. Rather than writing long-hand as above, prepend a question mark to the list of types and omit the explicit "nil" entry:

argcheck ("table.copy", 2, "?boolean|:nometa", predicate)

Normally, you should not need to use the level parameter, as the default is to blame the caller of the function using argcheck in error messages; which is almost certainly what you want.

Parameters:

  • name string function to blame in error message
  • i int argument number to blame in error message
  • expected string specification for acceptable argument types
  • actual argument passed
  • level int call stack level to blame for the error (default 2)

Usage:

    local function case (with, branches)
      argcheck ("std.functional.case", 2, "#table", branches)
      ...
argerror (name, i[, extramsg[, level=1]])
Raise a bad argument error. Equivalent to luaL_argerror in the Lua C API. This function does not return. The level argument behaves just like the core error function.

Parameters:

  • name string function to callout in error message
  • i int argument number
  • extramsg string additional text to append to message inside parentheses (optional)
  • level int call stack level to blame for the error (default 1)

See also:

Usage:

    local function slurp (file)
      local h, err = input_handle (file)
      if h == nil then argerror ("std.io.slurp", 1, err, 2) end
      ...
argscheck (decl, inner)

Wrap a function definition with argument type and arity checking. In addition to checking that each argument type matches the corresponding element in the types table with argcheck, if the final element of types ends with an ellipsis, remaining unchecked arguments are checked against that type:

 format = argscheck ("string.format (string, ?any...)", string.format)

A colon in the function name indicates that the argument type list does not have a type for self:

 format = argscheck ("string:format (?any...)", string.format)

If an argument can be omitted entirely, then put its type specification in square brackets:

 insert = argscheck ("table.insert (table, [int], ?any)", table.insert)

Similarly return types can be checked with the same list syntax as arguments:

 len = argscheck ("string.len (string) => int", string.len)

Additionally, variant return type lists can be listed like this:

 open = argscheck ("io.open (string, ?string) => file or nil, string",
                   io.open)

Parameters:

  • decl string function type declaration string
  • inner func function to wrap with argument checking

Usage:

    local case = argscheck ("std.functional.case (?any, #table) => [any...]",
      function (with, branches)
        ...
    end)
    
    -- Alternatively, as an annotation:
    local case = argscheck "std.functional.case (?any, #table) => [any...]" ..
    function (with, branches)
      ...
    end
check (expected, actual)
Checks the type of actual against the expected typespec

Parameters:

  • expected string expected typespec
  • actual object being typechecked

Returns:

    bool true, if actual matches expected

Or

  1. nil
  2. string an extramsg_mismatch format error message, otherwise

Usage:

    --> stdin:2: string or number expected, got empty table
    assert (check ("string|number", {}))
extramsg_mismatch (expected, actual[, index])
Format a type mismatch error.

Parameters:

  • expected string a pipe delimited list of matchable types
  • actual the actual argument to match with
  • index number erroring container element index (optional)

Returns:

    string formatted extramsg for this mismatch for argerror

See also:

Usage:

    if fmt ~= nil and type (fmt) ~= "string" then
      argerror ("format", 1, extramsg_mismatch ("?string", fmt))
    end
extramsg_toomany (bad, expected, actual)
Format a too many things error.

Parameters:

  • bad string the thing there are too many of
  • expected int maximum number of bad things expected
  • actual int actual number of bad things that triggered the error

See also:

Usage:

    if select ("#", ...) > 7 then
      argerror ("sevenses", 8, extramsg_toomany ("argument", 7, select ("#", ...)))
    end
parsetypes (types)
Compact permutation list into a list of valid types at each argument. Eliminate bracketed types by combining all valid types at each position for all permutations of typelist.

Parameters:

  • types list a normalized list of type names

Returns:

    list valid types for each positional parameter
resulterror (name, i[, extramsg[, level=1]])
Raise a bad result error. Like argerror for bad results. This function does not return. The level argument behaves just like the core error function.

Parameters:

  • name string function to callout in error message
  • i int result number
  • extramsg string additional text to append to message inside parentheses (optional)
  • level int call stack level to blame for the error (default 1)

Usage:

    local function slurp (file)
      ...
      if type (result) ~= "string" then resulterror ("std.io.slurp", 1, err, 2) end
typesplit (either)
Split a typespec string into a table of normalized type names.

Parameters:

  • either string or table "?bool|:nometa" or {"boolean", ":nometa"}

Returns:

    table a new list with duplicates removed and leading "?"s replaced by a "nil" element

Metamethods

__index (name)
Lazy loading of typecheck modules. Don't load everything on initial startup, wait until first attempt to access a submodule, and then load it on demand.

Parameters:

Returns:

    table or nil the submodule that was loaded to satisfy the missing name, otherwise nil if nothing was found

Usage:

    local version = require 'typecheck'.version
generated by LDoc 1.4.6 Last updated 2017-07-07 20:13:21