Plain source file: base/Modules/CheckModules.lhs (2005-03-04)

CheckModules is imported by: PPModules, WorkModule.

Error Detection


In the previous section we described how to compute the in-scope and export relations of mutually recursive modules. The algorithm produces a result even for modules containing errors. We now examine what properties need to be satisfied by correct solutions, and how we can detect ``bad'' solutions.

> module CheckModules(ModSysErr(..),chkModule) where
> import Relations
> import NamesEntities
> import ModSysAST
> --import Modules
> import List (partition,nub)
> import Maybe(isNothing)
> import Sets

Even though this specification aims for clarity rather than efficiency or usability, we believe that it is important not only to detect invalid solutions, but also to say why they are invalid. The data type ModSysErr classifies the different kinds of problems which might occur.

> data ModSysErr
>   = UndefinedModuleAlias ModName
>   | UndefinedExport QName
>   | UndefinedSubExport QName Name
>   | AmbiguousExport Name [Entity]
>   | MissingModule ModName
>   | UndefinedImport ModName Name
>   | UndefinedSubImport ModName Name Name
>   deriving Show

The meanings of the individual errors are as follows:

%We concentrate on checking a single module and, as a result we do not detect %errors such as an import from an unknown module. This check could be %done before the module system analysis is performed, either by an external %tool, or by a separate phase in the compilation pipeline. %The validity of an in-scope relation depends on how the names in a module %are used. For example, ambiguous names are allowed, %as long as they are not used anywhere in the program. Because we have %abstracted over such uses (by representing them as just entities) we %do not check the validity of in-scope relations.

In this section we preset functions to validate the import and export specifications of a module. The task of the function chkModule is to ensure that a module is valid from the point of view of the module system. To achieve this we need to check: (1) that the module interface is unambiguous; (2) that all referenced modules are present, and if so, (a) that each import declaration is valid; (b) that the export specification is valid. If some referenced modules are missing, we report that, but skip the remaining checks, since they might produce bogus error messages.

> chkModule ::
>   (ModName -> Maybe (Rel Name Entity)) ->
>   Rel QName Entity ->
>   Module ->
>     [ModSysErr]
> chkModule expsOf inscp mod
>   = chkAmbigExps mod_exports
>     ++ if null missingModules
>        then chkExpSpec inscp mod
>              ++ [err | (imp,Just exps) <- impSources,
>                        err <- chkImport exps imp]
>        else map MissingModule missingModules
>   where
>   Just mod_exports = expsOf (modName mod)
>   missingModules =
>     nub [impSource imp|(imp,Nothing)<-impSources]
>   impSources =
>     [(imp,expsOf (impSource imp))|imp<-modImports mod]

The parameter expsOf is a function, which maps module names to their export relations. The parameter inscp is the in-scope relation of the module we are checking. The result of chkModule is a list of errors detected in the module.

The export specification and the import declarations are checked by separate functions. chkModule provides the necessary information to each function, and collects their results in a single list of errors.

A module should not contain ambiguities in its interface. It is however possible---in fact quite common---to have the same name refer to a type constructor and a value constructor. As we previously discussed, this is not considered to be an ambiguity as we may determine from the context which one is meant.

> chkAmbigExps :: Rel Name Entity -> [ModSysErr]
> chkAmbigExps exps = concatMap isAmbig
>                              (setToList (dom exps))
>   where
>   isAmbig n =
>     let (values,types) = partition isValue (applyRel exps n)
>     in ambig n values ++ ambig n types
>   ambig n ents@(_:_:_)    = [AmbiguousExport n ents]
>   ambig n _               = []

The function chkAmbigExps detects ambiguities in the export relation of a module (exps). For each name in the domain of exps, we use applyRel to compute the list of entities it may refer to. The function isAmbig detects any ambiguities in this list, considering names in different namespaces separately. The function isValue identifies the entities occupying the value namespace.

We have already encountered some similarity between import declaration and export specifications. We exploit this again, by using the same function chkEntSpec to ensure that entries in export and import lists are defined. The parameters are essentially the same as in the mEntSpec function of the previous section, but we shall briefly describe them again. The boolean isHiding tells us if we are in the special case of hiding imports. The two functions errUndef and errUndefSub are new, and are needed so that we can report different errors for the import and export cases. Finally, we have the specification we are checking, and the relation modeling either the exports of the source module, or the symbol table of the current module.

> chkEntSpec :: (Ord j, ToSimple j) =>
>   Bool ->                             -- is it a hiding import?
>   (j -> ModSysErr) ->                 -- report error
>   (j -> Name -> ModSysErr) ->         -- report error
>   EntSpec j ->                        -- the specification
>   Rel j Entity  ->                    -- the relation to check
>   (j->[Entity]) ->                    -- the relation to check
>     [ModSysErr]                       -- detected errors
> chkEntSpec isHiding errUndef errUndefSub
>            (Ent x subspec) rel relfun =
>   case xents of
>     []   -> [errUndef x]
>     ents -> concatMap chk ents
>   where
>   xents = filter consider (relfun x)
>   chk ent =
>     case subspec of
>       Just (Subs subs) ->
>         map (errUndefSub x)
>             (filter (not . (`elementOf` subsInScope)) subs)
>         where
>         subsInScope =
>           mapSet toSimple
>             $ dom
>             $ restrictRng (`elementOf` owns ent) rel
>       _ -> []
>   consider
>     | isHiding && isNothing subspec = const True
>     | otherwise                     = not . isCon

Despite the large number of arguments, the function is quite simple. We lookup what the name in the specification (x) may refer to, and if nothing was found we report an error. In case it was defined we check the subordinate list in two steps. First we compute the names of subordinate entities of ent which are also in rel (subsInScope). Then we make sure that all listed subordinates are in subsInScope. We do not consider ambiguities in chkEntSpec, as this is the task of the function chkAmbigExps. The predicate consider has the same role as in mEntSpec.

We now describe how to check an export specification. It may be either implicit or explicit. Implicit specifications are always correct. For an explicit specification we need to check all entries in the exports list. For entries of the form module M we need to ensure that M is a valid alias in this module. An alias is valid, if it is either introduced by an import declaration, or is the name of the current module. For other entries we need to check that the entities they refer to are defined by using the generic chkEntSpec.

> chkExpSpec :: Rel QName Entity -> Module -> [ModSysErr]
> chkExpSpec inscp mod =
>     case modExpList mod of
>       Nothing   -> []
>       Just exps -> concatMap chk exps
>   where
>   aliases = modName mod : impAs `map` modImports mod
>   chk (ModuleExp x)
>     | x `elem` aliases = []
>     | otherwise        = [UndefinedModuleAlias x]
>   chk (EntExp spec) = chkEntSpec False
>                UndefinedExport UndefinedSubExport
>                spec inscp (applyRel inscp)

The remaining check we have is the validity of import declarations. The process is quite similar to the checks of the export specification and we have already done all the hard work in chkEntSpec. The function chkImport just uses chkEntSpec to ensure the correctness of the entries in the specification list of the import.

> chkImport :: Rel Name Entity -> Import -> [ModSysErr]
> chkImport exps imp = concatMap chk (impList imp)
>   where
>   src      = impSource imp
>   chk spec =
>     chkEntSpec (impHiding imp)
>       (UndefinedImport src) (UndefinedSubImport src)
>       spec exps (applyRel exps)


(HTML for this module was generated on 2009-01-04. About the conversion tool.)