Files
mercury/compiler/find_module.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

285 lines
11 KiB
Mathematica

%-----------------------------------------------------------------------------e
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------e
% Copyright (C) 2014 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% This module defines predicates that find Mercury modules.
%
%---------------------------------------------------------------------------%
:- module parse_tree.find_module.
:- interface.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module mdbcomp.sym_name.
:- import_module io.
:- import_module list.
:- import_module maybe.
%---------------------------------------------------------------------------%
% search_for_module_source(Globals, Dirs, InterfaceDirs,
% ModuleName, FoundSourceFile, !IO):
%
% Look for the source for ModuleName in Dirs. If found, return the
% (relative or absolute) path name of the source file that contains
% the module.
%
% This will also search for files matching partially qualified versions
% of ModuleName, dropping qualifiers outermost to innermost, but only if
% a more qualified `.m' or `.int' file doesn't exist in InterfaceDirs.
% For example, module foo.bar.baz can be found in foo.bar.m, bar.baz.m
% or bar.m.
%
:- pred search_for_module_source(globals::in, list(dir_name)::in,
list(dir_name)::in, module_name::in,
maybe_error(file_name)::out, io::di, io::uo) is det.
% search_for_module_source_and_stream(Globals, Dirs, InterfaceDirs,
% ModuleName, FoundSourceFileNameAndStream, !IO):
%
% As search_for_module_source, but if we find the file, then return
% not just its path name, but also an open stream reading from it.
% Closing that stream is the caller's responsibility.
%
:- pred search_for_module_source_and_stream(globals::in, list(dir_name)::in,
list(dir_name)::in, module_name::in,
maybe_error(path_name_and_stream)::out, io::di, io::uo) is det.
% Read the first item from the given file to find the module name.
%
:- pred find_module_name(globals::in, file_name::in, maybe(module_name)::out,
io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module parse_tree.error_util.
:- import_module parse_tree.file_names.
:- import_module parse_tree.parse_module. % for peek_at_file
:- import_module dir.
:- import_module string.
%---------------------------------------------------------------------------%
search_for_module_source(Globals, Dirs, InterfaceDirs, ModuleName,
MaybeFileName, !IO) :-
search_for_module_source_and_stream(Globals, Dirs, InterfaceDirs,
ModuleName, MaybeFileNameAndStream, !IO),
(
MaybeFileNameAndStream =
ok(path_name_and_stream(SourceFileName, SourceStream)),
io.close_input(SourceStream, !IO),
MaybeFileName = ok(SourceFileName)
;
MaybeFileNameAndStream = error(Msg),
MaybeFileName = error(Msg)
).
search_for_module_source_and_stream(Globals, Dirs, InterfaceDirs, ModuleName,
MaybeFileNameAndStream, !IO) :-
search_for_module_source_dropping_qualifiers(Globals,
Dirs, ModuleName, MaybeFileNameAndStream0, !IO),
(
MaybeFileNameAndStream0 =
ok(path_name_and_stream(SourceFileName, SourceStream)),
( if
string.remove_suffix(dir.basename(SourceFileName),
".m", SourceFileBaseName),
file_name_to_module_name(SourceFileBaseName, SourceFileModuleName),
ModuleName \= SourceFileModuleName
then
% The module name doesn't match the file name. Return an error
% if there is a more qualified matching `.m' or `.int' file in
% the interface search path. This avoids having a file `read.m'
% in the current directory prevent the compiler from finding
% `bit_buffer.read.int' in the standard library.
% Note that we never need to read from this more qualified file.
search_for_module_source_dropping_qualifiers(Globals,
InterfaceDirs, ModuleName, MaybeIFaceFileNameAndStream, !IO),
(
MaybeIFaceFileNameAndStream =
ok(path_name_and_stream(_IFaceFileName, IFaceStream)),
io.close_input(IFaceStream, !IO)
;
MaybeIFaceFileNameAndStream = error(_)
),
( if
MaybeIFaceFileNameAndStream =
ok(path_name_and_stream(IFaceFileName, _IFaceStream)),
IFaceFileName \= SourceFileName,
string.remove_suffix(dir.basename(IFaceFileName), ".m",
IFaceFileBaseName),
file_name_to_module_name(IFaceFileBaseName,
IFaceFileModuleName),
partial_sym_name_matches_full(SourceFileModuleName,
IFaceFileModuleName)
then
io.close_input(SourceStream, !IO),
Error =
find_source_error(ModuleName, Dirs, yes(IFaceFileName)),
MaybeFileNameAndStream = error(Error)
else
module_name_to_file_name(Globals, do_not_create_dirs, ".int",
ModuleName, IntFile, !IO),
search_for_file_returning_dir(InterfaceDirs, IntFile,
MaybeIntDir, !IO),
( if
MaybeIntDir = ok(IntDir),
IntDir \= dir.this_directory
then
io.close_input(SourceStream, !IO),
Error = find_source_error(ModuleName, Dirs,
yes(IntDir/IntFile)),
MaybeFileNameAndStream = error(Error)
else
MaybeFileNameAndStream = MaybeFileNameAndStream0
)
)
else
MaybeFileNameAndStream = MaybeFileNameAndStream0
)
;
MaybeFileNameAndStream0 = error(_),
MaybeFileNameAndStream = MaybeFileNameAndStream0
).
%------------%
:- pred search_for_module_source_dropping_qualifiers(globals::in,
list(dir_name)::in, module_name::in,
maybe_error(path_name_and_stream)::out, io::di, io::uo) is det.
search_for_module_source_dropping_qualifiers(Globals, Dirs, ModuleName,
MaybeErrorFileNameAndStream, !IO) :-
search_for_module_source_dropping_qualifiers_loop(Globals, Dirs,
ModuleName, MaybeFileNameAndStream, !IO),
(
MaybeFileNameAndStream = yes(FileNameAndStream),
MaybeErrorFileNameAndStream = ok(FileNameAndStream)
;
MaybeFileNameAndStream = no,
Error = find_source_error(ModuleName, Dirs, no),
MaybeErrorFileNameAndStream = error(Error)
).
:- pred search_for_module_source_dropping_qualifiers_loop(globals::in,
list(dir_name)::in, module_name::in, maybe(path_name_and_stream)::out,
io::di, io::uo) is det.
search_for_module_source_dropping_qualifiers_loop(Globals, Dirs,
PartialModuleName0, MaybeFileNameAndStream, !IO) :-
module_name_to_file_name(Globals, do_not_create_dirs, ".m",
PartialModuleName0, FileName0, !IO),
search_for_file_and_stream(Dirs, FileName0, MaybeFileNameAndStream0, !IO),
(
MaybeFileNameAndStream0 = ok(FileNameAndStream0),
MaybeFileNameAndStream = yes(FileNameAndStream0)
;
MaybeFileNameAndStream0 = error(_),
( if
drop_outermost_qualifier(PartialModuleName0, PartialModuleName1)
then
search_for_module_source_dropping_qualifiers_loop(Globals, Dirs,
PartialModuleName1, MaybeFileNameAndStream, !IO)
else
MaybeFileNameAndStream = no
)
).
%------------%
:- pred drop_outermost_qualifier(module_name::in, module_name::out) is semidet.
drop_outermost_qualifier(SymName, DroppedQualifierSymName) :-
SymName = qualified(ParentModuleName, ChildName),
drop_outermost_qualifier_loop(ParentModuleName, ChildName,
DroppedQualifierSymName).
:- pred drop_outermost_qualifier_loop(module_name::in, string::in,
module_name::out) is det.
drop_outermost_qualifier_loop(ParentModuleName, ChildName,
DroppedQualifierSymName) :-
(
ParentModuleName = unqualified(_ParentName),
DroppedQualifierSymName = unqualified(ChildName)
;
ParentModuleName = qualified(GrandParentModuleName, ParentName),
drop_outermost_qualifier_loop(GrandParentModuleName, ParentName,
DroppedQualifierGrandParentModuleName),
DroppedQualifierSymName =
qualified(DroppedQualifierGrandParentModuleName, ChildName)
).
%------------%
:- func find_source_error(module_name, list(dir_name), maybe(file_name))
= string.
find_source_error(ModuleName, Dirs, MaybeBetterMatch) = Msg :-
ModuleNameStr = sym_name_to_string(ModuleName),
Msg0 = "cannot find source for module `" ++ ModuleNameStr ++
"' in directories " ++
string.join_list(", ",
map((func(Dir) = "`" ++ Dir ++ "'"), Dirs)),
(
MaybeBetterMatch = no,
Msg = Msg0
;
MaybeBetterMatch = yes(BetterMatchFile),
Msg = Msg0 ++ ", but found " ++ BetterMatchFile ++
" in interface search path"
).
%---------------------------------------------------------------------------%
find_module_name(Globals, FileName, MaybeModuleName, !IO) :-
io.open_input(FileName, OpenRes, !IO),
(
OpenRes = ok(FileStream),
( if string.remove_suffix(FileName, ".m", PartialFileName0) then
PartialFileName = PartialFileName0
else
PartialFileName = FileName
),
( if dir.basename(PartialFileName, BaseName0) then
BaseName = BaseName0
else
BaseName = ""
),
file_name_to_module_name(BaseName, DefaultModuleName),
peek_at_file(FileStream, DefaultModuleName, [], FileName, ModuleName,
Specs, !IO),
io.close_input(FileStream, !IO),
MaybeModuleName = yes(ModuleName),
% XXX We don't check whether ModuleName was actually read
% from the named file; it could just be DefaultModuleName.
% XXX _NumErrors
write_error_specs(Specs, Globals, 0, _NumWarnings, 0, _NumErrors, !IO)
;
OpenRes = error(Error),
ErrorMsg = io.error_message(Error),
io.progname_base("mercury_compile", Progname, !IO),
Pieces = [fixed(Progname), suffix(":"), words("error opening"),
quote(FileName), suffix(":"), words(ErrorMsg), suffix("."), nl],
Spec = error_spec(severity_error, phase_read_files,
[error_msg(no, treat_as_first, 0, [always(Pieces)])]),
% XXX _NumErrors
write_error_spec(Spec, Globals, 0, _NumWarnings, 0, _NumErrors, !IO),
MaybeModuleName = no
).
%---------------------------------------------------------------------------%
:- end_module parse_tree.find_module.
%---------------------------------------------------------------------------%