Files
mercury/compiler/read_modules.m
Zoltan Somogyi d29183e5fb Improve error messages about unexpected module names.
When you get this message, the error may be in the module name that it
reports to be wrong, but it may be in the places that set the compiler's
expectations of what the name of the module should be. This latter is
very likely the case when one moves a module of the Mercury compiler
from one package to another. In such cases, the problems are the old modules
that continue to refer to the renamed module by its old name.

This diff includes in the error message the identities of the modules
that refer to the old name; these are the modules that establish the
expectation that is not met.

compiler/deps_map.m:
    When tracing references from module A to module B.C (either because
    A imports B.C, or because A = B and A includes C), record A as a source
    of the expectation that any file that contains module C will have
    B.C as module C's fully qualified name. Since a module is usually imported
    by more than one other module, there may be several sources of such
    expectations.

compiler/parse_module.m:
    Require callers of the functions that read in modules from files
    to specify the contexts of the places that establish the expectation
    of the module's fully qualified name.

    When the expectation is not met, include the contexts in the error message.

compiler/read_modules.m:
    Pass those contexts through to parse_module.m.

compiler/find_module.m:
compiler/make.module_dep_file.m:
compiler/mercury_compile_main.m:
compiler/modules.m:
compiler/recompilation.check.m:
    Conform to the changes above.

tests/invalid/bad_module_name.err_exp:
    Expect the updated error message.
2017-12-05 10:51:26 +11:00

546 lines
23 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1996-2009, 2011 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% File: read_modules.m.
%
%-----------------------------------------------------------------------------%
:- module parse_tree.read_modules.
:- interface.
:- import_module libs.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module libs.timestamp.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.error_util.
:- import_module parse_tree.file_kind.
:- import_module parse_tree.file_names.
:- import_module parse_tree.module_imports.
:- import_module parse_tree.parse_error.
:- import_module parse_tree.prog_item.
:- import_module io.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module term.
%-----------------------------------------------------------------------------%
%
% After we have read in Mercury code from a source file, interface file or
% optimization file, we record the parse tree we get from it, so we can
% avoid having to read it again.
% XXX ITEM_LIST We seem to sometimes re-read it anyway. Fix this.
%
% Since we use different types to represent the parse trees of source,
% interface and optimization files, we use three maps, one for each
% parse tree type. Each map maps a key, which consists of a module name
% and the kind of a file (e.g. .int0 vs .int2 for interface files)
% to the parse tree we got for that file.
%
% XXX ITEM_LIST The code that reads in optimization files does not
% record its results in hrmm_opt. I (zs) don't know whether that is
% a bug (leading to duplicate reads of optimization files) or a feature
% (keeping files that are by construction read exactly once out of a map
% where they won't be needed again).
%
:- type have_read_module_maps
---> have_read_module_maps(
hrmm_src :: have_read_module_src_map,
hrmm_int :: have_read_module_int_map,
hrmm_opt :: have_read_module_opt_map
).
:- type have_read_module_src_map ==
have_read_module_map(src_file_kind, parse_tree_src).
:- type have_read_module_int_map ==
have_read_module_map(int_file_kind, parse_tree_int).
:- type have_read_module_opt_map ==
have_read_module_map(opt_file_kind, parse_tree_opt).
:- type have_read_module_map(FK, PT) ==
map(have_read_module_key(FK), have_read_module(PT)).
:- type have_read_module_key(FK)
---> have_read_module_key(module_name, FK).
:- type have_read_module(PT)
---> have_read_module(
file_name,
module_timestamp,
PT,
list(error_spec),
read_module_errors
).
%-----------------------------------------------------------------------------%
:- type maybe_ignore_errors
---> ignore_errors
; do_not_ignore_errors.
% read_module_src(Globals, Descr, IgnoreErrors, Search,
% ModuleName, FileName, ReadModuleAndTimestamps, MaybeTimestamp,
% ParseTreeSrc, Specs, Errors, !IO):
%
% Given a module name, read in and parse the source code of that file,
% printing progress messages along the way if the verbosity level
% calls for that.
%
% If ModuleName is a nested module, then try searching for different
% filenames: for modules such as `foo.bar.baz.m', search first for
% `foo.bar.baz.m', then `bar.baz.m', then `baz.m'. If Search is do_search,
% search all directories given by the option search_directories for the
% module; otherwise, search for those filenames only in the current
% directory. Return in FileName the actual source file name found
% (excluding the directory part). If the actual module name
% (as determined by the `:- module' declaration) does not match
% the specified module name, then report an error message,
% but record the *expected* module name in the parse tree,
% not the one we actually found. This is because most parts
% of the compiler (including deps_map.m and make.module_dep_file.m)
% rely on the invariant which says that if Errors does not contain
% any fatal errors, then the returned ParseTreeSrc contains the
% module with the expected name. Invocations of the compiler
% that don't specify --make or request dependency map don't really
% care which module name we return here; they will work either way,
% the only difference being whether the names of the files they generate
% are based on the expected or the actual module name.
%
% N.B. This reads a module given the MODULE name. If you want to read
% a module given the FILE name, use `read_module_src_from_file'.
%
% If ReadModuleAndTimestamps is always_read_module(dont_return_timestamp),
% return `no' in MaybeTimestamp.
%
% If ReadModuleAndTimestamps is always_read_module(do_return_timestamp),
% attempt to return the modification time of the file in MaybeTimestamp.
%
% If ReadModuleAndTimestamps is dont_read_module_if_match(OldTimeStamp),
% then
%
% - if the timestamp of that file is exactly OldTimestamp, then
% don't read the file, but return OldTimestamp as the file's timestamp,
% alongside a dummy parse tree; while
% - if the timestamp of that file differs from OldTimestamp (virtually
% always because it is newer), then read the module from the file
% as usual, parse and return its contents as usual, and also return
% its actual timestamp.
%
% If the file could not be read, MaybeTimestamp will be `no'.
%
:- pred read_module_src(globals::in, string::in,
maybe_ignore_errors::in, maybe_search::in,
module_name::in, list(term.context)::in, file_name::out,
read_module_and_timestamps::in, maybe(timestamp)::out,
parse_tree_src::out, list(error_spec)::out, read_module_errors::out,
io::di, io::uo) is det.
% read_module_int(Globals, Descr, IgnoreErrors, Search,
% ModuleName, IntFileKind, FileName, ReturnTimestamp, MaybeTimestamp,
% ParseTreeInt, Specs, Errors, !IO):
%
% Given a module name, and the identity of one of its interface files,
% (.int0, .int, .int2 or .int3), read in and parse the contents of that
% interface file, printing progress messages along the way if the
% verbosity level calls for that.
%
% The meanings of the arguments are pretty much the same as for
% read_module_src, but while the names of the files that contain source
% files may not be fully module qualified, the names of interface files
% are always fully module qualified, so read_module_int does not search
% for the right filename. It knows what filename it looks for; the only
% search it does, if Search is do_search, is to decide which directory
% among the search directories contains the file with that filename.
%
:- pred read_module_int(globals::in, string::in,
maybe_ignore_errors::in, maybe_search::in,
module_name::in, int_file_kind::in, file_name::out,
read_module_and_timestamps::in, maybe(timestamp)::out,
parse_tree_int::out, list(error_spec)::out, read_module_errors::out,
io::di, io::uo) is det.
% read_module_src_from_file(Globals, SourceFileName, Descr, Search,
% ReadModuleAndTimestamps, MaybeTimestamp,
% ParseTreeSrc, Specs, Errors, !IO):
%
% Does pretty much the same job as read_module_src, but its job is
% to read the module stored in a specified file (SourceFileName),
% discovering the name of the module stored there by reading the file,
% as opposed to looking for the file containing a module with a specified
% name. It does not search for the right filename (that is SourceFileName),
% but if Search is do_search, it does search for that filename in the
% specified directories.
%
% The rest of the argument list has the same meaning as in read_module_src.
%
:- pred read_module_src_from_file(globals::in, file_name::in,
string::in, maybe_search::in,
read_module_and_timestamps::in,maybe(timestamp)::out,
parse_tree_src::out, list(error_spec)::out, read_module_errors::out,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
% maybe_read_module_int(Globals, HaveReadModuleMap, Descr, Search,
% ModuleName, IntFileKind, FileName, ReturnTimestamp, MaybeTimestamp,
% ParseTreeInt, Specs, Errors, !IO):
%
% If HaveReadModuleMap contains the already-read contents of the
% IntFileKind interface file for ModuleName, then return the information
% stored in HaveReadModuleMap for that file. If it is not there,
% read that interface file using read_module_int, regardless of its
% timestamp.
%
:- pred maybe_read_module_int(globals::in, have_read_module_int_map::in,
string::in, maybe_search::in, module_name::in, int_file_kind::in,
file_name::out, maybe_return_timestamp::in, maybe(timestamp)::out,
parse_tree_int::out, list(error_spec)::out, read_module_errors::out,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
% find_read_module_src(HaveReadModuleMap, ModuleName,
% ReturnTimestamp, FileName, MaybeTimestamp, ParseTree, Specs, Errors):
% find_read_module_int(HaveReadModuleMap, ModuleName, IntFileKind,
% ReturnTimestamp, FileName, MaybeTimestamp, ParseTree, Specs, Errors):
%
% Check whether HaveReadModuleMap contains the already-read contents
% of the specified source file or interface file. If it does, return
% its contents. If it does not, fail.
%
:- pred find_read_module_src(have_read_module_src_map::in, module_name::in,
maybe_return_timestamp::in, file_name::out, maybe(timestamp)::out,
parse_tree_src::out, list(error_spec)::out, read_module_errors::out)
is semidet.
:- pred find_read_module_int(have_read_module_int_map::in, module_name::in,
int_file_kind::in, maybe_return_timestamp::in,
file_name::out, maybe(timestamp)::out,
parse_tree_int::out, list(error_spec)::out, read_module_errors::out)
is semidet.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module libs.options.
:- import_module parse_tree.parse_module.
:- import_module parse_tree.find_module.
:- import_module bool.
:- import_module cord.
:- import_module dir.
:- import_module set.
:- import_module string.
%-----------------------------------------------------------------------------%
read_module_src(Globals, Descr, IgnoreErrors, Search,
ModuleName, ExpectationContexts, FileName,
ReadModuleAndTimestamps, MaybeTimestamp,
ParseTreeSrc, Specs, Errors, !IO) :-
read_module_begin(Globals, Descr, Search, ModuleName, fk_src,
FileName0, VeryVerbose, InterfaceSearchDirs, SearchDirs, !IO),
% For `.m' files we need to deal with the case where the module name
% does not match the file name, or where a partial match occurs
% in the current directory but the full match occurs in a search directory.
search_for_module_source_and_stream(Globals, SearchDirs,
InterfaceSearchDirs, ModuleName, MaybeFileNameAndStream, !IO),
actually_read_module_src(Globals, ModuleName, ExpectationContexts,
MaybeFileNameAndStream, ReadModuleAndTimestamps, MaybeTimestampRes,
ParseTreeSrc0, ModuleSpecs, Errors, !IO),
ParseTreeSrc0 = parse_tree_src(_ActualModuleName, ActualModuleNameContext,
ComponentsCord),
% If ModuleName = ActualModuleName, this obviously does the right thing.
% If ModuleName != ActualModuleName, then we must include ModuleName
% in ParseTreeSrc (see the comment above), and including recording
% ActualModuleNameContext as its context shouldn't mislead anyone
% who reads the error spec about the unexpected module name,
% which should be in Specs.
ParseTreeSrc = parse_tree_src(ModuleName, ActualModuleNameContext,
ComponentsCord),
IsEmpty = (if cord.is_empty(ComponentsCord) then yes else no),
read_module_end(Globals, IgnoreErrors, VeryVerbose,
MaybeFileNameAndStream, FileName0, FileName,
MaybeTimestampRes, MaybeTimestamp,
IsEmpty, ModuleSpecs, Specs, Errors, !IO).
read_module_int(Globals, Descr, IgnoreErrors, Search, ModuleName, IntFileKind,
FileName, ReadModuleAndTimestamps, MaybeTimestamp,
ParseTreeInt, Specs, Errors, !IO) :-
read_module_begin(Globals, Descr, Search, ModuleName, fk_int(IntFileKind),
FileName0, VeryVerbose, _InterfaceSearchDirs, SearchDirs, !IO),
search_for_file_and_stream(SearchDirs, FileName0,
MaybeFileNameAndStream, !IO),
actually_read_module_int(IntFileKind, Globals, ModuleName, [],
MaybeFileNameAndStream, ReadModuleAndTimestamps, MaybeTimestampRes,
ParseTreeInt, ModuleSpecs, Errors, !IO),
ParseTreeInt = parse_tree_int(_ActualModuleName, _IntFileKind,
_ActualModuleNameContext, _MaybeVersionNumbers,
IntIncls, ImpIncls, IntAvails, ImpAvails, IntItems, ImplItems),
( if
IntIncls = [],
ImpIncls = [],
IntAvails = [],
IntItems = [],
ImpAvails = [],
ImplItems = []
then
IsEmpty = yes
else
IsEmpty = no
),
read_module_end(Globals, IgnoreErrors, VeryVerbose,
MaybeFileNameAndStream, FileName0, FileName,
MaybeTimestampRes, MaybeTimestamp,
IsEmpty, ModuleSpecs, Specs, Errors, !IO).
read_module_src_from_file(Globals, FileName, Descr, Search,
ReadModuleAndTimestamps, MaybeTimestamp,
ParseTreeSrc, Specs, Errors, !IO) :-
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
maybe_write_string(VeryVerbose, "% ", !IO),
maybe_write_string(VeryVerbose, Descr, !IO),
maybe_write_string(VeryVerbose, " `", !IO),
maybe_write_string(VeryVerbose, FileName, !IO),
maybe_write_string(VeryVerbose, "'... ", !IO),
maybe_flush_output(VeryVerbose, !IO),
FullFileName = FileName ++ ".m",
( if dir.basename(FileName, BaseFileNamePrime) then
BaseFileName = BaseFileNamePrime
else
BaseFileName = ""
),
file_name_to_module_name(BaseFileName, DefaultModuleName),
(
Search = do_search,
globals.lookup_accumulating_option(Globals, search_directories,
SearchDirs)
;
Search = do_not_search,
SearchDirs = [dir.this_directory]
),
search_for_file_and_stream(SearchDirs, FullFileName,
MaybeFileNameAndStream, !IO),
actually_read_module_src(Globals, DefaultModuleName, [],
MaybeFileNameAndStream, ReadModuleAndTimestamps, MaybeTimestampRes,
ParseTreeSrc, Specs0, Errors, !IO),
check_timestamp(Globals, FullFileName, MaybeTimestampRes, MaybeTimestamp,
!IO),
handle_any_read_module_errors(Globals, VeryVerbose, Errors,
Specs0, Specs, !IO).
%-----------------------------------------------------------------------------%
:- pred read_module_begin(globals::in, string::in,
maybe_search::in, module_name::in, file_kind::in, file_name::out,
bool::out, list(string)::out, list(string)::out, io::di, io::uo) is det.
read_module_begin(Globals, Descr, Search, ModuleName, FileKind,
FileName0, VeryVerbose, InterfaceSearchDirs, SearchDirs, !IO) :-
Extension = file_kind_to_extension(FileKind),
(
Search = do_search,
module_name_to_search_file_name(Globals, Extension,
ModuleName, FileName0, !IO)
;
Search = do_not_search,
module_name_to_file_name(Globals, do_not_create_dirs, Extension,
ModuleName, FileName0, !IO)
),
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
Msg = "% " ++ Descr ++ " `" ++ FileName0 ++ "'... ",
maybe_write_string(VeryVerbose, Msg, !IO),
maybe_flush_output(VeryVerbose, !IO),
globals.lookup_accumulating_option(Globals, search_directories,
InterfaceSearchDirs),
(
Search = do_search,
SearchDirs = InterfaceSearchDirs
;
Search = do_not_search,
SearchDirs = [dir.this_directory]
).
:- pred read_module_end(globals::in, maybe_ignore_errors::in, bool::in,
maybe_error(path_name_and_stream)::in, file_name::in, file_name::out,
maybe(io.res(timestamp))::in, maybe(timestamp)::out, bool::in,
list(error_spec)::in, list(error_spec)::out, read_module_errors::in,
io::di, io::uo) is det.
read_module_end(Globals, IgnoreErrors, VeryVerbose,
MaybeFileNameAndStream, FileName0, FileName,
MaybeTimestampRes, MaybeTimestamp, IsEmpty,
ModuleSpecs, Specs, Errors, !IO) :-
(
MaybeFileNameAndStream = ok(path_name_and_stream(FileName, _))
;
MaybeFileNameAndStream = error(_),
FileName = FileName0
),
check_timestamp(Globals, FileName0, MaybeTimestampRes, MaybeTimestamp,
!IO),
(
IgnoreErrors = ignore_errors,
( if
set.contains(Errors, rme_could_not_open_file),
% I (zs) think the test of IsEmpty is redundant, and could be
% buggy as well (since a file containing just ":- module x"
% would now yield an empty item list), but better safe than sorry.
IsEmpty = yes
then
maybe_write_string(VeryVerbose, "not found.\n", !IO),
Specs = []
else
maybe_write_string(VeryVerbose, "done.\n", !IO),
Specs = ModuleSpecs
)
;
IgnoreErrors = do_not_ignore_errors,
handle_any_read_module_errors(Globals, VeryVerbose, Errors,
ModuleSpecs, Specs, !IO)
).
:- pred handle_any_read_module_errors(globals::in, bool::in,
read_module_errors::in, list(error_spec)::in, list(error_spec)::out,
io::di, io::uo) is det.
handle_any_read_module_errors(Globals, VeryVerbose, Errors, !Specs, !IO) :-
( if set.is_empty(Errors) then
maybe_write_string(VeryVerbose, "successful parse.\n", !IO)
else
set.intersect(Errors, fatal_read_module_errors, FatalErrors),
( if set.is_empty(FatalErrors) then
maybe_write_string(VeryVerbose, "parse error(s).\n", !IO)
else
maybe_write_string(VeryVerbose, "fatal error(s).\n", !IO)
),
maybe_write_out_errors_no_module(VeryVerbose, Globals, !Specs, !IO),
io.set_exit_status(1, !IO)
).
%-----------------------------------------------------------------------------%
maybe_read_module_int(Globals, HaveReadModuleMap, Descr, Search,
ModuleName, IntFileKind, FileName, ReturnTimestamp, MaybeTimestamp,
ParseTreeInt, Specs, Errors, !IO) :-
( if
find_read_module_int(HaveReadModuleMap, ModuleName, IntFileKind,
ReturnTimestamp, FileNamePrime, MaybeTimestampPrime,
ParseTreeIntPrime, SpecsPrime, ErrorsPrime)
then
FileName = FileNamePrime,
MaybeTimestamp = MaybeTimestampPrime,
ParseTreeInt = ParseTreeIntPrime,
Specs = SpecsPrime,
Errors = ErrorsPrime
else
read_module_int(Globals, Descr, do_not_ignore_errors, Search,
ModuleName, IntFileKind, FileName,
always_read_module(ReturnTimestamp), MaybeTimestamp,
ParseTreeInt, Specs, Errors, !IO)
).
%-----------------------------------------------------------------------------%
find_read_module_src(HaveReadModuleMap, ModuleName, ReturnTimestamp,
FileName, MaybeTimestamp, ParseTreeSrc, Specs, Errors) :-
Key = have_read_module_key(ModuleName, sfk_src),
map.search(HaveReadModuleMap, Key, HaveReadModule),
HaveReadModule = have_read_module(FileName, ModuleTimestamp,
ParseTreeSrc, Specs, Errors),
(
ReturnTimestamp = do_return_timestamp,
ModuleTimestamp = module_timestamp(_, Timestamp, _),
MaybeTimestamp = yes(Timestamp)
;
ReturnTimestamp = dont_return_timestamp,
MaybeTimestamp = no
).
find_read_module_int(HaveReadModuleMap, ModuleName, IntFileKind,
ReturnTimestamp, FileName, MaybeTimestamp,
ParseTreeInt, Specs, Errors) :-
Key = have_read_module_key(ModuleName, IntFileKind),
map.search(HaveReadModuleMap, Key, HaveReadModule),
HaveReadModule = have_read_module(FileName, ModuleTimestamp,
ParseTreeInt, Specs, Errors),
(
ReturnTimestamp = do_return_timestamp,
ModuleTimestamp = module_timestamp(_, Timestamp, _),
MaybeTimestamp = yes(Timestamp)
;
ReturnTimestamp = dont_return_timestamp,
MaybeTimestamp = no
).
%-----------------------------------------------------------------------------%
:- pred check_timestamp(globals::in, file_name::in,
maybe(io.res(timestamp))::in, maybe(timestamp)::out,
io::di, io::uo) is det.
check_timestamp(Globals, FileName, MaybeTimestampRes, MaybeTimestamp, !IO) :-
(
MaybeTimestampRes = yes(ok(Timestamp)),
MaybeTimestamp = yes(Timestamp)
;
MaybeTimestampRes = yes(error(IOError)),
MaybeTimestamp = no,
globals.lookup_bool_option(Globals, smart_recompilation,
SmartRecompilation),
% Should we print the warning if smart recompilation has already been
% disabled by an earlier error? At the moment, we do.
(
SmartRecompilation = yes,
report_modification_time_warning(Globals, FileName, IOError, !IO)
;
SmartRecompilation = no
)
;
MaybeTimestampRes = no,
MaybeTimestamp = no
).
:- pred report_modification_time_warning(globals::in, file_name::in,
io.error::in, io::di, io::uo) is det.
report_modification_time_warning(Globals, SourceFileName, Error, !IO) :-
io_set_disable_smart_recompilation(yes, !IO),
io_set_disable_generate_item_version_numbers(yes, !IO),
globals.lookup_bool_option(Globals, warn_smart_recompilation, Warn),
(
Warn = yes,
io.write_string("Warning: cannot find modification time for ", !IO),
io.write_string(SourceFileName, !IO),
io.write_string(":\n", !IO),
io.error_message(Error, Msg),
io.write_string(" ", !IO),
io.write_string(Msg, !IO),
io.write_string(".\n", !IO),
io.write_string(" Smart recompilation will not work.\n", !IO),
globals.lookup_bool_option(Globals, halt_at_warn, HaltAtWarn),
(
HaltAtWarn = yes,
io.set_exit_status(1, !IO)
;
HaltAtWarn = no
)
;
Warn = no
).
%-----------------------------------------------------------------------------%
:- end_module parse_tree.read_modules.
%-----------------------------------------------------------------------------%