-- static partial checkings for Lua. -- -- This program checks some metalua or plain lua source code for most common -- mistakes. Its design focuses on the ability to check plain lua code: there is -- no need to load any extension in the module. -- -- The current checkings include: -- -- * Listing all free variables, and make sure they are declared. -- * For free vars known as modules, check that indexings in them are also -- declared. -- * When the type of something is known, do some basic type checkings. These -- checkings are by no means exhaustive; however, when a parameter function -- is constant or statically declared, it's checked. --[[ Type grammar: t ::= | t and t | t or t | function (t, ...) return t, ... end | { (k=t)... } | table(t, t) | string | number | integer | boolean | userdata | nil | multi(t, ...) | _ --]] match function get_type | `Number{...} -> return +{number} | `String{...} -> return +{string} | `True|`False -> return +{boolean} | `Nil -> return +{nil} | `Dots -> return +{_} | `Stat{_,v} -> return get_type(v) | `Paren{t} -> return get_one_type(t) | `Call{f, ...} -> local ftype = get_type(f) match ftype with | `Function{ _, {`Return{result}} } -> return get_type(result) | `Function{ _, {`Return{...} == results} } -> local r2 = +{ multi() } for r in ivalues(results) table.insert(r2, get_type(r)) end return r2 | `And{...} -> return +{_} -- not implemented | `Or{ a, b } -> match get_one_type(a), get_one_type(b) with | `Function{...}==f1, `Function{...}==f2 -> return `Op{ 'or', get_type(`Call{f1}), get_type(`Call{f2})} | `Function{...}==f, _ | _, `Function{...}==f -> return get_type(`Call{f}) | _ -> return +{_} end | `Invoke{o, m, ... } == x -> return get_type(`Call{`Index{o, m}}) | `Op{...}==o -> return get_op_type(o) | `Table{...}==t -> local r = `Table{ } for x in ivalues(t) do match x with | `Pair{ `String{...}==k, v } -> table.insert(r, `Pair{k, get_one_type(v)}) | t -> table.insert(r, get_one_type(t)) end end return r | `Function{...}==f -> | `Id{v} -> | `Index{t, k} -> match get_one_type(t), get_one_type(k) with | `Call{`Id 'table', tk, tv }, _ -> return tv | `Table{...}==tt, `Id 'string' -> local types_rt = require 'extension.types' function check_function(f, term) match get_type(term) with | `Function{ params, {`Return{...} == results}}, args -> | `And{ a, b }, args -> check_function(a, args) check_function(b, args) | `Or{ a, b }, args -> if not pcall(check_function, a, args) then check_function(b, args) end | `Id '_' -> -- pass | _ -> error ("Call to a non-function") end end function check_index(a, b, term) match get_type(term) with | `Table{} match function cfg.id.up | `Call{ f, ... } == x -> check_function (f, x) | `Index{ a, b } == x -> check_index (a, b, x) end -- List free vars cfg.id.