mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-10 11:23:15 +00:00
Estimated hours taken: 100
Branches: main
Make definitions of abstract types available when generating
code for importing modules. This is necessary for the .NET
back-end, and for `:- pragma export' on the C back-end.
compiler/prog_data.m:
compiler/modules.m:
compiler/make.dependencies.m:
compiler/recompilation.version.m:
Handle implementation sections in interface files.
There is a new pseudo-declaration `abstract_imported'
which is applied to items from the implementation
section of an interface file. `abstract_imported'
items may not be used in the error checking passes
for the curent module.
compiler/equiv_type_hlds.m:
compiler/notes/compiler_design.html:
New file.
Go over the HLDS expanding all types fully after
semantic checking has been run.
compiler/mercury_compile.m:
Add the new pass.
Don't write the `.opt' file if there are any errors.
compiler/instmap.m:
Add a predicate instmap_delta_map_foldl to apply
a procedure to all insts in an instmap.
compiler/equiv_type.m:
Export predicates for use by equiv_type_hlds.m
Reorder arguments so state variables and higher-order
programming can be used.
compiler/prog_data.m:
compiler/prog_io_pragma.m:
compiler/make_hlds.m:
compiler/mercury_to_mercury.m:
Handle `:- pragma foreign_type' as a form of type
declaration rather than a pragma.
compiler/hlds_data.m:
compiler/*.m:
Add a field to the type_info_cell_constructor cons_id
to identify the type_ctor, which is needed by
equiv_type_hlds.m.
compiler/module_qual.m:
Donn't allow items from the implementation section of
interface files to match items in the current module.
compiler/*.m:
tests/*/*.m:
Add missing imports which only became apparent with
the bug fixes above.
Remove unnecessary imports which only became apparent with
the bug fixes above.
tests/hard_coded/Mmakefile:
tests/hard_coded/export_test2.{m,exp}:
Test case.
tests/invalid/Mmakefile:
tests/invalid/missing_interface_import2.{m,err_exp}:
Test case.
1565 lines
48 KiB
Mathematica
1565 lines
48 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2002-2003 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: recompilation_check.m
|
|
% Main author: stayl
|
|
%
|
|
% Check whether a module should be recompiled.
|
|
%-----------------------------------------------------------------------------%
|
|
:- module recompilation__check.
|
|
|
|
:- interface.
|
|
|
|
:- import_module parse_tree__modules.
|
|
:- import_module parse_tree__prog_data.
|
|
:- import_module parse_tree__prog_io.
|
|
|
|
:- import_module list, io.
|
|
|
|
:- type modules_to_recompile
|
|
---> (all)
|
|
; some(list(module_name))
|
|
.
|
|
|
|
:- type find_target_file_names ==
|
|
pred(module_name, list(file_name), io__state, io__state).
|
|
:- inst find_target_file_names ==
|
|
(pred(in, out, di, uo) is det).
|
|
|
|
:- type find_timestamp_file_names ==
|
|
pred(module_name, list(file_name), io__state, io__state).
|
|
:- inst find_timestamp_file_names ==
|
|
(pred(in, out, di, uo) is det).
|
|
|
|
% recompilation__check__should_recompile(ModuleName, FindTargetFiles,
|
|
% FindTimestampFiles, ModulesToRecompile, ReadModules)
|
|
%
|
|
% Process the `.used' files for the given module and all its
|
|
% inline sub-modules to find out which modules need to be recompiled.
|
|
% `FindTargetFiles' takes a module name and returns a list of
|
|
% file names which need to be up-to-date to avoid recompilation.
|
|
% `FindTimestampFiles' takes a module name and returns a list of
|
|
% file names which should be touched if the module does not need
|
|
% to be recompiled.
|
|
% `ReadModules' is the list of interface files read during
|
|
% recompilation checking, returned to avoid rereading them
|
|
% if recompilation is required.
|
|
:- pred recompilation__check__should_recompile(module_name::in,
|
|
find_target_file_names::in(find_target_file_names),
|
|
find_timestamp_file_names::in(find_timestamp_file_names),
|
|
modules_to_recompile::out, read_modules::out,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds__error_util.
|
|
:- import_module hlds__hlds_data. % for type field_access_type
|
|
:- import_module hlds__hlds_pred. % for field_access_function_name,
|
|
% type pred_id.
|
|
:- import_module libs__globals.
|
|
:- import_module libs__options.
|
|
:- import_module libs__timestamp.
|
|
:- import_module parse_tree__prog_io_util.
|
|
:- import_module parse_tree__prog_out.
|
|
:- import_module parse_tree__prog_util.
|
|
:- import_module recompilation__usage.
|
|
:- import_module recompilation__version.
|
|
|
|
:- import_module assoc_list, bool, exception, int, map, parser, require.
|
|
:- import_module set, std_util, string, term, term_io.
|
|
|
|
recompilation__check__should_recompile(ModuleName, FindTargetFiles,
|
|
FindTimestampFiles, Info ^ modules_to_recompile,
|
|
Info ^ read_modules) -->
|
|
globals__io_lookup_bool_option(find_all_recompilation_reasons,
|
|
FindAll),
|
|
{ Info0 = recompilation_check_info(ModuleName, no, [], map__init,
|
|
init_item_id_set(map__init, map__init, map__init),
|
|
set__init, some([]), FindAll, []) },
|
|
recompilation__check__should_recompile_2(no, FindTargetFiles,
|
|
FindTimestampFiles, ModuleName, Info0, Info).
|
|
|
|
:- pred recompilation__check__should_recompile_2(bool::in,
|
|
find_target_file_names::in(find_target_file_names),
|
|
find_timestamp_file_names::in(find_timestamp_file_names),
|
|
module_name::in, recompilation_check_info::in,
|
|
recompilation_check_info::out, io__state::di, io__state::uo) is det.
|
|
|
|
recompilation__check__should_recompile_2(IsSubModule, FindTargetFiles,
|
|
FindTimestampFiles, ModuleName, Info0, Info) -->
|
|
{ Info1 = (Info0 ^ module_name := ModuleName)
|
|
^ sub_modules := [] },
|
|
module_name_to_file_name(ModuleName, ".used", no, UsageFileName),
|
|
io__open_input(UsageFileName, MaybeVersionStream),
|
|
(
|
|
{ MaybeVersionStream = ok(VersionStream0) },
|
|
io__set_input_stream(VersionStream0, OldInputStream),
|
|
|
|
promise_only_solution_io(
|
|
(pred(R::out, di, uo) is cc_multi -->
|
|
try_io(
|
|
(pred(Info2::out, di, uo) is det -->
|
|
recompilation__check__should_recompile_3(
|
|
IsSubModule, FindTargetFiles,
|
|
Info1, Info2)
|
|
), R)
|
|
),
|
|
Result),
|
|
(
|
|
{ Result = succeeded(Info3) },
|
|
{ Reasons = Info3 ^ recompilation_reasons }
|
|
;
|
|
{ Result = failed },
|
|
{ error("recompilation__check__should_recompile_2") }
|
|
;
|
|
{ Result = exception(Exception) },
|
|
{ univ_to_type(Exception, RecompileException0) ->
|
|
RecompileException = RecompileException0
|
|
;
|
|
rethrow(Result)
|
|
},
|
|
{ RecompileException =
|
|
recompile_exception(Reason, Info3) },
|
|
{ Reasons = [Reason] }
|
|
),
|
|
|
|
( { Reasons = [] } ->
|
|
FindTimestampFiles(ModuleName, TimestampFiles),
|
|
write_recompilation_message(
|
|
(pred(di, uo) is det -->
|
|
io__write_string("Not recompiling module "),
|
|
prog_out__write_sym_name(ModuleName),
|
|
io__write_string(".\n")
|
|
)),
|
|
list__foldl(touch_datestamp, TimestampFiles),
|
|
{ Info4 = Info3 }
|
|
;
|
|
{ add_module_to_recompile(ModuleName, Info3, Info4) },
|
|
write_recompilation_message(
|
|
(pred(di, uo) is det -->
|
|
list__foldl(
|
|
write_recompile_reason(ModuleName),
|
|
list__reverse(Reasons))
|
|
))
|
|
),
|
|
io__set_input_stream(OldInputStream, VersionStream),
|
|
io__close_input(VersionStream),
|
|
|
|
( { (all) = Info4 ^ modules_to_recompile } ->
|
|
{ Info = Info4 }
|
|
;
|
|
{ Info5 = Info4 ^ is_inline_sub_module := yes },
|
|
list__foldl2(
|
|
recompilation__check__should_recompile_2(yes,
|
|
FindTargetFiles, FindTimestampFiles),
|
|
Info5 ^ sub_modules, Info5, Info)
|
|
)
|
|
;
|
|
{ MaybeVersionStream = error(_) },
|
|
write_recompilation_message(
|
|
(pred(di, uo) is det -->
|
|
{ Reason = file_error(UsageFileName,
|
|
"file `" ++ UsageFileName ++ "' not found.") },
|
|
write_recompile_reason(ModuleName, Reason)
|
|
)),
|
|
{ Info = Info1 ^ modules_to_recompile := (all) }
|
|
).
|
|
|
|
:- pred recompilation__check__should_recompile_3(bool::in,
|
|
find_target_file_names::in(find_target_file_names),
|
|
recompilation_check_info::in, recompilation_check_info::out,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
recompilation__check__should_recompile_3(IsSubModule, FindTargetFiles,
|
|
Info0, Info) -->
|
|
|
|
%
|
|
% WARNING: any exceptions thrown before the sub_modules
|
|
% field is set in the recompilation_check_info must set
|
|
% the modules_to_recompile field to `all', or else
|
|
% the nested sub-modules will not be checked and necessary
|
|
% recompilations may be missed.
|
|
%
|
|
|
|
%
|
|
% Check that the format of the usage file is the current format.
|
|
%
|
|
read_term_check_for_error_or_eof(Info0, "usage file version number",
|
|
VersionNumberTerm),
|
|
(
|
|
{ VersionNumberTerm = term__functor(term__atom(","),
|
|
[UsageFileVersionNumberTerm,
|
|
VersionNumbersVersionNumberTerm], _) },
|
|
{ UsageFileVersionNumberTerm =
|
|
term__functor(
|
|
term__integer(usage_file_version_number),
|
|
_, _) },
|
|
{ VersionNumbersVersionNumberTerm =
|
|
term__functor(
|
|
term__integer(version_numbers_version_number),
|
|
_, _) }
|
|
->
|
|
[]
|
|
;
|
|
io__input_stream_name(UsageFileName),
|
|
{ throw_syntax_error(
|
|
file_error(UsageFileName,
|
|
"invalid usage file version number in file `"
|
|
++ UsageFileName ++ "'."),
|
|
Info0) }
|
|
),
|
|
|
|
%
|
|
% Find the timestamp of the module the last time it was compiled.
|
|
%
|
|
read_term_check_for_error_or_eof(Info0, "module timestamp",
|
|
TimestampTerm),
|
|
{ parse_module_timestamp(Info0, TimestampTerm, _, ModuleTimestamp) },
|
|
{ ModuleTimestamp = module_timestamp(_, RecordedTimestamp, _) },
|
|
|
|
( { IsSubModule = yes } ->
|
|
% For inline sub-modules we don't need to check
|
|
% the module timestamp because we've already checked
|
|
% the timestamp for the parent module.
|
|
{ Info3 = Info0 }
|
|
;
|
|
%
|
|
% If the module has changed, recompile.
|
|
%
|
|
{ ModuleName = Info0 ^ module_name },
|
|
read_mod_if_changed(ModuleName, ".m", "Reading module",
|
|
yes, RecordedTimestamp, Items, Error,
|
|
FileName, MaybeNewTimestamp),
|
|
{
|
|
MaybeNewTimestamp = yes(NewTimestamp),
|
|
NewTimestamp \= RecordedTimestamp
|
|
->
|
|
record_read_file(ModuleName,
|
|
ModuleTimestamp ^ timestamp := NewTimestamp,
|
|
Items, Error, FileName, Info0, Info1),
|
|
Info2 = Info1 ^ modules_to_recompile := (all),
|
|
record_recompilation_reason(module_changed(FileName),
|
|
Info2, Info3)
|
|
;
|
|
( Error \= no_module_errors
|
|
; MaybeNewTimestamp = no
|
|
)
|
|
->
|
|
throw_syntax_error(
|
|
file_error(FileName,
|
|
"error reading file `"
|
|
++ FileName ++ "'."),
|
|
Info0)
|
|
;
|
|
Info3 = Info0
|
|
}
|
|
),
|
|
|
|
%
|
|
% Find out whether this module has any inline sub-modules.
|
|
%
|
|
read_term_check_for_error_or_eof(Info3, "inline sub-modules",
|
|
SubModulesTerm),
|
|
{
|
|
SubModulesTerm = term__functor(term__atom("sub_modules"),
|
|
SubModuleTerms, _),
|
|
list__map(
|
|
(pred(Term::in, SubModule::out) is semidet :-
|
|
sym_name_and_args(Term, SubModule, [])
|
|
),
|
|
SubModuleTerms, SubModules)
|
|
->
|
|
Info4 = Info3 ^ sub_modules := SubModules
|
|
;
|
|
Reason1 = syntax_error(get_term_context(SubModulesTerm),
|
|
"error in sub_modules term"),
|
|
throw_syntax_error(Reason1, Info3)
|
|
},
|
|
|
|
%
|
|
% Check whether the output files are present and up-to-date.
|
|
%
|
|
FindTargetFiles(Info4 ^ module_name, TargetFiles),
|
|
list__foldl2(
|
|
(pred(TargetFile::in, RInfo0::in, RInfo::out, di, uo) is det -->
|
|
io__file_modification_time(TargetFile, TargetModTimeResult),
|
|
{
|
|
TargetModTimeResult = ok(TargetModTime),
|
|
compare(TargetModTimeCompare,
|
|
time_t_to_timestamp(TargetModTime),
|
|
RecordedTimestamp),
|
|
TargetModTimeCompare = (>)
|
|
->
|
|
RInfo = RInfo0
|
|
;
|
|
Reason2 = output_file_not_up_to_date(TargetFile),
|
|
record_recompilation_reason(Reason2, RInfo0, RInfo)
|
|
}
|
|
), TargetFiles, Info4, Info5),
|
|
|
|
%
|
|
% Read in the used items, used for checking for
|
|
% ambiguities with new items.
|
|
%
|
|
read_term_check_for_error_or_eof(Info5, "used items",
|
|
UsedItemsTerm),
|
|
{ parse_used_items(Info5, UsedItemsTerm, UsedItems) },
|
|
{ Info6 = Info5 ^ used_items := UsedItems },
|
|
|
|
read_term_check_for_error_or_eof(Info6, "used classes",
|
|
UsedClassesTerm),
|
|
{
|
|
UsedClassesTerm = term__functor(term__atom("used_classes"),
|
|
UsedClassTerms, _),
|
|
list__map(
|
|
(pred(Term::in, UsedClass::out) is semidet :-
|
|
parse_name_and_arity(Term,
|
|
ClassName, ClassArity),
|
|
UsedClass = ClassName - ClassArity
|
|
), UsedClassTerms, UsedClasses)
|
|
->
|
|
Info7 = Info6 ^ used_typeclasses :=
|
|
set__list_to_set(UsedClasses)
|
|
;
|
|
Reason3 = syntax_error(get_term_context(UsedClassesTerm),
|
|
"error in used_typeclasses term"),
|
|
throw_syntax_error(Reason3, Info6)
|
|
},
|
|
check_imported_modules(Info7, Info).
|
|
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred parse_module_timestamp(recompilation_check_info::in, term::in,
|
|
module_name::out, module_timestamp::out) is det.
|
|
|
|
parse_module_timestamp(Info, Term, ModuleName, ModuleTimestamp) :-
|
|
conjunction_to_list(Term, Args),
|
|
(
|
|
Args = [ModuleNameTerm, SuffixTerm,
|
|
TimestampTerm | MaybeOtherTerms],
|
|
sym_name_and_args(ModuleNameTerm, ModuleName0, []),
|
|
SuffixTerm = term__functor(term__string(Suffix), [], _),
|
|
Timestamp = term_to_timestamp(TimestampTerm),
|
|
(
|
|
MaybeOtherTerms = [term__functor(term__atom("used"),
|
|
[], _)],
|
|
NeedQualifier = must_be_qualified
|
|
;
|
|
MaybeOtherTerms = [],
|
|
NeedQualifier = may_be_unqualified
|
|
)
|
|
->
|
|
ModuleName = ModuleName0,
|
|
ModuleTimestamp = module_timestamp(Suffix,
|
|
Timestamp, NeedQualifier)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in module timestamp"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred parse_used_items(recompilation_check_info::in,
|
|
term::in, resolved_used_items::out) is det.
|
|
|
|
parse_used_items(Info, Term, UsedItems) :-
|
|
( Term = term__functor(term__atom("used_items"), UsedItemTerms, _) ->
|
|
list__foldl(parse_used_item_set(Info), UsedItemTerms,
|
|
init_item_id_set(map__init, map__init, map__init),
|
|
UsedItems)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in used items"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_used_item_set(recompilation_check_info::in, term::in,
|
|
resolved_used_items::in, resolved_used_items::out) is det.
|
|
|
|
parse_used_item_set(Info, Term, UsedItems0, UsedItems) :-
|
|
(
|
|
Term = term__functor(term__atom(ItemTypeStr), ItemTerms, _),
|
|
string_to_item_type(ItemTypeStr, ItemType)
|
|
->
|
|
( is_simple_item_type(ItemType) ->
|
|
list__foldl(parse_simple_item(Info),
|
|
ItemTerms, map__init, SimpleItems),
|
|
UsedItems = update_simple_item_set(UsedItems0,
|
|
ItemType, SimpleItems)
|
|
; is_pred_or_func_item_type(ItemType) ->
|
|
list__foldl(parse_pred_or_func_item(Info),
|
|
ItemTerms, map__init, PredOrFuncItems),
|
|
UsedItems = update_pred_or_func_set(UsedItems0,
|
|
ItemType, PredOrFuncItems)
|
|
; ItemType = functor ->
|
|
list__foldl(parse_functor_item(Info),
|
|
ItemTerms, map__init, CtorItems),
|
|
UsedItems = UsedItems0 ^ functors := CtorItems
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
string__append(
|
|
"error in used items: unknown item type :",
|
|
ItemTypeStr)),
|
|
throw_syntax_error(Reason, Info)
|
|
)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in used items"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_simple_item(recompilation_check_info::in, term::in,
|
|
simple_item_set::in, simple_item_set::out) is det.
|
|
|
|
parse_simple_item(Info, Term, Set0, Set) :-
|
|
(
|
|
Term = term__functor(term__atom("-"),
|
|
[NameArityTerm, MatchesTerm], _),
|
|
parse_name_and_arity(NameArityTerm, SymName, Arity)
|
|
->
|
|
unqualify_name(SymName, Name),
|
|
conjunction_to_list(MatchesTerm, MatchTermList),
|
|
list__foldl(parse_simple_item_match(Info),
|
|
MatchTermList, map__init, Matches),
|
|
map__det_insert(Set0, Name - Arity, Matches, Set)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in simple items"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_simple_item_match(recompilation_check_info::in, term::in,
|
|
map(module_qualifier, module_name)::in,
|
|
map(module_qualifier, module_name)::out) is det.
|
|
|
|
parse_simple_item_match(Info, Term, Items0, Items) :-
|
|
(
|
|
(
|
|
Term = term__functor(term__atom("=>"),
|
|
[QualifierTerm, ModuleNameTerm], _)
|
|
->
|
|
sym_name_and_args(QualifierTerm, Qualifier, []),
|
|
sym_name_and_args(ModuleNameTerm, ModuleName, [])
|
|
;
|
|
sym_name_and_args(Term, ModuleName, []),
|
|
Qualifier = ModuleName
|
|
)
|
|
->
|
|
map__det_insert(Items0, Qualifier, ModuleName, Items)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in simple item match"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_pred_or_func_item(recompilation_check_info::in,
|
|
term::in, resolved_pred_or_func_set::in,
|
|
resolved_pred_or_func_set::out) is det.
|
|
|
|
parse_pred_or_func_item(Info, Term, Set0, Set) :-
|
|
parse_resolved_item_set(Info, parse_pred_or_func_item_match,
|
|
Term, Set0, Set).
|
|
|
|
:- pred parse_pred_or_func_item_match(recompilation_check_info::in, term::in,
|
|
resolved_pred_or_func_map::in, resolved_pred_or_func_map::out) is det.
|
|
|
|
parse_pred_or_func_item_match(Info, Term, Items0, Items) :-
|
|
PredId = invalid_pred_id,
|
|
(
|
|
(
|
|
Term = term__functor(term__atom("=>"),
|
|
[QualifierTerm, MatchesTerm], _)
|
|
->
|
|
sym_name_and_args(QualifierTerm, Qualifier, []),
|
|
conjunction_to_list(MatchesTerm, MatchesList),
|
|
list__map(
|
|
(pred(MatchTerm::in, Match::out) is semidet :-
|
|
sym_name_and_args(MatchTerm, MatchName, []),
|
|
Match = PredId - MatchName
|
|
),
|
|
MatchesList, Matches)
|
|
;
|
|
sym_name_and_args(Term, Qualifier, []),
|
|
Matches = [PredId - Qualifier]
|
|
)
|
|
->
|
|
map__det_insert(Items0, Qualifier, set__list_to_set(Matches),
|
|
Items)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in pred or func match"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_functor_item(recompilation_check_info::in, term::in,
|
|
resolved_functor_set::in, resolved_functor_set::out) is det.
|
|
|
|
parse_functor_item(Info, Term, Set0, Set) :-
|
|
parse_resolved_item_set(Info, parse_functor_matches, Term, Set0, Set).
|
|
|
|
:- pred parse_functor_matches(recompilation_check_info::in, term::in,
|
|
resolved_functor_map::in, resolved_functor_map::out) is det.
|
|
|
|
parse_functor_matches(Info, Term, Map0, Map) :-
|
|
(
|
|
Term = term__functor(term__atom("=>"),
|
|
[QualifierTerm, MatchesTerm], _),
|
|
sym_name_and_args(QualifierTerm, Qualifier, [])
|
|
->
|
|
conjunction_to_list(MatchesTerm, MatchesList),
|
|
list__map(parse_resolved_functor(Info),
|
|
MatchesList, Matches),
|
|
map__det_insert(Map0, Qualifier,
|
|
set__list_to_set(Matches), Map)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in functor match"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_resolved_functor(recompilation_check_info::in, term::in,
|
|
resolved_functor::out) is det.
|
|
|
|
parse_resolved_functor(Info, Term, Ctor) :-
|
|
(
|
|
Term = term__functor(term__atom(PredOrFuncStr),
|
|
[ModuleTerm, ArityTerm], _),
|
|
( PredOrFuncStr = "predicate", PredOrFunc = predicate
|
|
; PredOrFuncStr = "function", PredOrFunc = function
|
|
),
|
|
sym_name_and_args(ModuleTerm, ModuleName, []),
|
|
ArityTerm = term__functor(term__integer(Arity), [], _)
|
|
->
|
|
PredId = invalid_pred_id,
|
|
Ctor = pred_or_func(PredId, ModuleName, PredOrFunc, Arity)
|
|
;
|
|
Term = term__functor(term__atom("ctor"), [NameArityTerm], _),
|
|
parse_name_and_arity(NameArityTerm, TypeName, TypeArity)
|
|
->
|
|
Ctor = constructor(TypeName - TypeArity)
|
|
;
|
|
Term = term__functor(term__atom("field"),
|
|
[TypeNameArityTerm, ConsNameArityTerm], _),
|
|
parse_name_and_arity(TypeNameArityTerm, TypeName, TypeArity),
|
|
parse_name_and_arity(ConsNameArityTerm, ConsName, ConsArity)
|
|
->
|
|
Ctor = field(TypeName - TypeArity, ConsName - ConsArity)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in functor match"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- type parse_resolved_item_matches(T) ==
|
|
pred(recompilation_check_info, term, resolved_item_map(T),
|
|
resolved_item_map(T)).
|
|
:- inst parse_resolved_item_matches == (pred(in, in, in, out) is det).
|
|
|
|
:- pred parse_resolved_item_set(recompilation_check_info::in,
|
|
parse_resolved_item_matches(T)::in(parse_resolved_item_matches),
|
|
term::in, resolved_item_set(T)::in, resolved_item_set(T)::out) is det.
|
|
|
|
parse_resolved_item_set(Info, ParseMatches, Term, Set0, Set) :-
|
|
(
|
|
Term = term__functor(term__atom("-"),
|
|
[NameTerm, MatchesTerm], _),
|
|
NameTerm = term__functor(term__atom(Name), [], _)
|
|
->
|
|
conjunction_to_list(MatchesTerm, MatchTermList),
|
|
list__map(
|
|
parse_resolved_item_arity_matches(Info, ParseMatches),
|
|
MatchTermList, Matches),
|
|
map__det_insert(Set0, Name, Matches, Set)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in resolved item matches"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
:- pred parse_resolved_item_arity_matches(recompilation_check_info::in,
|
|
parse_resolved_item_matches(T)::in(parse_resolved_item_matches),
|
|
term::in, pair(arity, resolved_item_map(T))::out) is det.
|
|
|
|
parse_resolved_item_arity_matches(Info, ParseMatches,
|
|
Term, Arity - MatchMap) :-
|
|
(
|
|
Term = term__functor(term__atom("-"),
|
|
[ArityTerm, MatchesTerm], _),
|
|
ArityTerm = term__functor(term__integer(Arity0), [], _),
|
|
conjunction_to_list(MatchesTerm, MatchTermList)
|
|
->
|
|
Arity = Arity0,
|
|
list__foldl(
|
|
(pred(MatchTerm::in, Map0::in, Map::out) is det :-
|
|
ParseMatches(Info, MatchTerm, Map0, Map)
|
|
),
|
|
MatchTermList, map__init, MatchMap)
|
|
;
|
|
Reason = syntax_error(get_term_context(Term),
|
|
"error in resolved item matches"),
|
|
throw_syntax_error(Reason, Info)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
%
|
|
% Check whether the interface file read for a module
|
|
% in the last compilation has changed, and if so whether
|
|
% the items have changed in a way which should cause
|
|
% a recompilation.
|
|
%
|
|
:- pred check_imported_modules(recompilation_check_info::in,
|
|
recompilation_check_info::out, io__state::di, io__state::uo) is det.
|
|
|
|
check_imported_modules(Info0, Info) -->
|
|
parser__read_term(TermResult),
|
|
(
|
|
{ TermResult = term(_, Term) },
|
|
( { Term = term__functor(term__atom("done"), [], _) } ->
|
|
{ Info = Info0 }
|
|
;
|
|
check_imported_module(Term, Info0, Info1),
|
|
check_imported_modules(Info1, Info)
|
|
)
|
|
;
|
|
{ TermResult = error(Message, Line) },
|
|
io__input_stream_name(FileName),
|
|
{ Reason = syntax_error(term__context(FileName, Line),
|
|
Message) },
|
|
{ throw_syntax_error(Reason, Info0) }
|
|
;
|
|
{ TermResult = eof },
|
|
%
|
|
% There should always be an item `done.' at the end of
|
|
% the list of modules to check. This is used to make
|
|
% sure that the writing of the `.used' file was not
|
|
% interrupted.
|
|
%
|
|
io__input_stream_name(FileName),
|
|
io__get_line_number(Line),
|
|
{ Reason = syntax_error(term__context(FileName, Line),
|
|
"unexpected end of file") },
|
|
{ throw_syntax_error(Reason, Info0) }
|
|
).
|
|
|
|
:- pred check_imported_module(term::in, recompilation_check_info::in,
|
|
recompilation_check_info::out, io__state::di, io__state::uo) is det.
|
|
|
|
check_imported_module(Term, Info0, Info) -->
|
|
{
|
|
Term = term__functor(term__atom("=>"),
|
|
[TimestampTerm0, UsedItemsTerm0], _)
|
|
->
|
|
TimestampTerm = TimestampTerm0,
|
|
MaybeUsedItemsTerm = yes(UsedItemsTerm0)
|
|
;
|
|
TimestampTerm = Term,
|
|
MaybeUsedItemsTerm = no
|
|
},
|
|
{ parse_module_timestamp(Info0, TimestampTerm,
|
|
ImportedModuleName, ModuleTimestamp) },
|
|
|
|
{ ModuleTimestamp = module_timestamp(Suffix,
|
|
RecordedTimestamp, NeedQualifier) },
|
|
(
|
|
%
|
|
% If we're checking a sub-module, don't re-read
|
|
% interface files read for other modules checked
|
|
% during this compilation.
|
|
%
|
|
{ Info0 ^ is_inline_sub_module = yes },
|
|
{ find_read_module(Info0 ^ read_modules, ImportedModuleName,
|
|
Suffix, yes, Items0, MaybeNewTimestamp0,
|
|
Error0, FileName0) }
|
|
->
|
|
{ Items = Items0 },
|
|
{ MaybeNewTimestamp = MaybeNewTimestamp0 },
|
|
{ Error = Error0 },
|
|
{ FileName = FileName0 },
|
|
{ Recorded = bool__yes }
|
|
;
|
|
{ Recorded = bool__no },
|
|
read_mod_if_changed(ImportedModuleName, Suffix,
|
|
"Reading interface file for module",
|
|
yes, RecordedTimestamp, Items, Error,
|
|
FileName, MaybeNewTimestamp)
|
|
),
|
|
{
|
|
MaybeNewTimestamp = yes(NewTimestamp),
|
|
NewTimestamp \= RecordedTimestamp,
|
|
Error = no_module_errors
|
|
->
|
|
( Recorded = no ->
|
|
record_read_file(ImportedModuleName,
|
|
ModuleTimestamp ^ timestamp := NewTimestamp,
|
|
Items, Error, FileName, Info0, Info1)
|
|
;
|
|
Info1 = Info0
|
|
),
|
|
(
|
|
MaybeUsedItemsTerm = yes(UsedItemsTerm),
|
|
Items = [InterfaceItem, VersionNumberItem
|
|
| OtherItems],
|
|
InterfaceItem = module_defn(_, interface) - _,
|
|
VersionNumberItem = module_defn(_,
|
|
version_numbers(_, VersionNumbers)) - _
|
|
->
|
|
check_module_used_items(ImportedModuleName,
|
|
NeedQualifier, RecordedTimestamp,
|
|
UsedItemsTerm, VersionNumbers,
|
|
OtherItems, Info1, Info)
|
|
;
|
|
record_recompilation_reason(module_changed(FileName),
|
|
Info1, Info)
|
|
)
|
|
;
|
|
Error \= no_module_errors
|
|
->
|
|
throw_syntax_error(
|
|
file_error(FileName,
|
|
"error reading file `" ++ FileName ++ "'."),
|
|
Info0)
|
|
;
|
|
Info = Info0
|
|
}.
|
|
|
|
:- pred check_module_used_items(module_name::in, need_qualifier::in,
|
|
timestamp::in, term::in, version_numbers::in, item_list::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_module_used_items(ModuleName, NeedQualifier, OldTimestamp,
|
|
UsedItemsTerm, NewVersionNumbers,
|
|
Items) -->
|
|
|
|
{ recompilation__version__parse_version_numbers(UsedItemsTerm,
|
|
UsedItemsResult) },
|
|
=(Info0),
|
|
{
|
|
UsedItemsResult = ok(UsedVersionNumbers)
|
|
;
|
|
UsedItemsResult = error(Msg, ErrorTerm),
|
|
Reason = syntax_error(get_term_context(ErrorTerm), Msg),
|
|
throw_syntax_error(Reason, Info0)
|
|
},
|
|
|
|
{ UsedVersionNumbers = version_numbers(UsedItemVersionNumbers,
|
|
UsedInstanceVersionNumbers) },
|
|
{ NewVersionNumbers = version_numbers(NewItemVersionNumbers,
|
|
NewInstanceVersionNumbers) },
|
|
|
|
%
|
|
% Check whether any of the items which were used have changed.
|
|
%
|
|
list__foldl(
|
|
check_item_version_numbers(ModuleName,
|
|
UsedItemVersionNumbers, NewItemVersionNumbers),
|
|
[(type), type_body, (inst), (mode), (typeclass),
|
|
predicate, function]),
|
|
|
|
%
|
|
% Check whether added or modified items could cause name
|
|
% resolution ambiguities with items which were used.
|
|
%
|
|
list__foldl(
|
|
check_for_ambiguities(NeedQualifier,
|
|
OldTimestamp, UsedItemVersionNumbers),
|
|
Items),
|
|
|
|
%
|
|
% Check whether any instances of used typeclasses have been
|
|
% added, removed or changed.
|
|
%
|
|
check_instance_version_numbers(ModuleName, UsedInstanceVersionNumbers,
|
|
NewInstanceVersionNumbers),
|
|
|
|
%
|
|
% Check for new instances for used typeclasses.
|
|
%
|
|
{ ModuleInstances = set__sorted_list_to_set(
|
|
map__sorted_keys(NewInstanceVersionNumbers)) },
|
|
{ UsedInstances = set__sorted_list_to_set(
|
|
map__sorted_keys(UsedInstanceVersionNumbers)) },
|
|
|
|
UsedClasses =^ used_typeclasses,
|
|
{ set__difference(set__intersect(UsedClasses, ModuleInstances),
|
|
UsedInstances, AddedInstances) },
|
|
( { [AddedInstance | _] = set__to_sorted_list(AddedInstances) } ->
|
|
{ Reason1 = changed_or_added_instance(ModuleName,
|
|
AddedInstance) },
|
|
record_recompilation_reason(Reason1)
|
|
;
|
|
[]
|
|
).
|
|
|
|
:- func make_item_id(module_name, item_type, pair(string, arity)) = item_id.
|
|
|
|
make_item_id(Module, ItemType, Name - Arity) =
|
|
item_id(ItemType, qualified(Module, Name) - Arity).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred check_item_version_numbers(module_name::in, item_version_numbers::in,
|
|
item_version_numbers::in, item_type::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_item_version_numbers(ModuleName, UsedVersionNumbers,
|
|
NewVersionNumbers, ItemType) -->
|
|
{ NewItemTypeVersionNumbers = extract_ids(NewVersionNumbers,
|
|
ItemType) },
|
|
map__foldl(
|
|
(pred(NameArity::in, UsedVersionNumber::in, in, out) is det -->
|
|
(
|
|
{ map__search(NewItemTypeVersionNumbers,
|
|
NameArity, NewVersionNumber) }
|
|
->
|
|
( { NewVersionNumber = UsedVersionNumber } ->
|
|
[]
|
|
;
|
|
{ Reason = changed_item(
|
|
make_item_id(ModuleName, ItemType,
|
|
NameArity)) },
|
|
record_recompilation_reason(Reason)
|
|
)
|
|
;
|
|
{ Reason = removed_item(make_item_id(ModuleName,
|
|
ItemType, NameArity)) },
|
|
record_recompilation_reason(Reason)
|
|
)
|
|
),
|
|
extract_ids(UsedVersionNumbers, ItemType)).
|
|
|
|
:- pred check_instance_version_numbers(module_name::in,
|
|
instance_version_numbers::in, instance_version_numbers::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_instance_version_numbers(ModuleName, UsedInstanceVersionNumbers,
|
|
NewInstanceVersionNumbers) -->
|
|
map__foldl(
|
|
(pred(ClassId::in, UsedVersionNumber::in, in, out) is det -->
|
|
(
|
|
{ map__search(NewInstanceVersionNumbers,
|
|
ClassId, NewVersionNumber) }
|
|
->
|
|
( { UsedVersionNumber = NewVersionNumber } ->
|
|
[]
|
|
;
|
|
{ Reason = changed_or_added_instance(
|
|
ModuleName, ClassId) },
|
|
record_recompilation_reason(Reason)
|
|
)
|
|
;
|
|
{ Reason = removed_instance(ModuleName, ClassId) },
|
|
record_recompilation_reason(Reason)
|
|
)
|
|
), UsedInstanceVersionNumbers).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
%
|
|
% For each item which has changed since the last time
|
|
% we read the interface file, check whether it introduces
|
|
% ambiguities with items which were used when the current
|
|
% module was last compiled.
|
|
%
|
|
:- pred check_for_ambiguities(need_qualifier::in, timestamp::in,
|
|
item_version_numbers::in, item_and_context::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_for_ambiguities(_, _, _, clause(_, _, _, _, _) - _) -->
|
|
{ error("check_for_ambiguities: clause") }.
|
|
check_for_ambiguities(NeedQualifier, OldTimestamp, VersionNumbers,
|
|
type_defn(_, Name, Params, Body, _) - _) -->
|
|
{ Arity = list__length(Params) },
|
|
check_for_simple_item_ambiguity(NeedQualifier, OldTimestamp,
|
|
VersionNumbers, (type), Name, Arity, NeedsCheck),
|
|
( { NeedsCheck = yes } ->
|
|
check_type_defn_ambiguity_with_functor(NeedQualifier,
|
|
Name - Arity, Body)
|
|
;
|
|
[]
|
|
).
|
|
check_for_ambiguities(NeedQualifier, OldTimestamp, VersionNumbers,
|
|
inst_defn(_, Name, Params, _, _) - _) -->
|
|
check_for_simple_item_ambiguity(NeedQualifier, OldTimestamp,
|
|
VersionNumbers, (inst), Name, list__length(Params), _).
|
|
check_for_ambiguities(NeedQualifier, OldTimestamp, VersionNumbers,
|
|
mode_defn(_, Name, Params, _, _) - _) -->
|
|
check_for_simple_item_ambiguity(NeedQualifier, OldTimestamp,
|
|
VersionNumbers, (mode), Name, list__length(Params), _).
|
|
check_for_ambiguities(NeedQualifier, OldTimestamp, VersionNumbers,
|
|
typeclass(_, Name, Params, Interface, _) - _) -->
|
|
check_for_simple_item_ambiguity(NeedQualifier, OldTimestamp,
|
|
VersionNumbers, (typeclass), Name, list__length(Params),
|
|
NeedsCheck),
|
|
( { NeedsCheck = yes, Interface = concrete(Methods) } ->
|
|
list__foldl(
|
|
(pred(ClassMethod::in, in, out) is det -->
|
|
(
|
|
{ ClassMethod = pred_or_func(_, _, _,
|
|
PredOrFunc, MethodName, MethodArgs,
|
|
MethodWithType, _, _, _, _, _, _) },
|
|
check_for_pred_or_func_item_ambiguity(yes,
|
|
NeedQualifier, OldTimestamp,
|
|
VersionNumbers, PredOrFunc,
|
|
MethodName, MethodArgs, MethodWithType)
|
|
;
|
|
{ ClassMethod = pred_or_func_mode(_, _, _, _,
|
|
_, _, _, _) }
|
|
)
|
|
),
|
|
Methods)
|
|
;
|
|
[]
|
|
).
|
|
check_for_ambiguities(NeedQualifier, OldTimestamp, VersionNumbers,
|
|
pred_or_func(_, _, _, PredOrFunc, Name, Args,
|
|
WithType, _, _, _, _, _) - _)
|
|
-->
|
|
check_for_pred_or_func_item_ambiguity(no, NeedQualifier, OldTimestamp,
|
|
VersionNumbers, PredOrFunc, Name, Args, WithType).
|
|
check_for_ambiguities(_, _, _,
|
|
pred_or_func_mode(_, _, _, _, _, _, _) - _) --> [].
|
|
check_for_ambiguities(_, _, _, pragma(_) - _) --> [].
|
|
check_for_ambiguities(_, _, _, promise(_, _, _, _) - _) --> [].
|
|
check_for_ambiguities(_, _, _, module_defn(_, _) - _) --> [].
|
|
check_for_ambiguities(_, _, _, instance(_, _, _, _, _, _) - _) --> [].
|
|
check_for_ambiguities(_, _, _, nothing(_) - _) --> [].
|
|
|
|
:- pred item_is_new_or_changed(timestamp::in, item_version_numbers::in,
|
|
item_type::in, sym_name::in, arity::in) is semidet.
|
|
|
|
item_is_new_or_changed(UsedFileTimestamp, UsedVersionNumbers,
|
|
ItemType, SymName, Arity) :-
|
|
unqualify_name(SymName, Name),
|
|
(
|
|
map__search(extract_ids(UsedVersionNumbers, ItemType),
|
|
Name - Arity, UsedVersionNumber)
|
|
->
|
|
% XXX This assumes that version numbers are timestamps.
|
|
compare((>), UsedVersionNumber, UsedFileTimestamp)
|
|
;
|
|
true
|
|
).
|
|
|
|
:- pred check_for_simple_item_ambiguity(need_qualifier::in, timestamp::in,
|
|
item_version_numbers::in, item_type::in(simple_item), sym_name::in,
|
|
arity::in, bool::out, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
check_for_simple_item_ambiguity(NeedQualifier, UsedFileTimestamp,
|
|
VersionNumbers, ItemType, SymName, Arity, NeedsCheck) -->
|
|
(
|
|
{ item_is_new_or_changed(UsedFileTimestamp, VersionNumbers,
|
|
ItemType, SymName, Arity) }
|
|
->
|
|
{ NeedsCheck = yes },
|
|
UsedItems =^ used_items,
|
|
{ UsedItemMap = extract_simple_item_set(UsedItems, ItemType) },
|
|
{ unqualify_name(SymName, Name) },
|
|
(
|
|
{ map__search(UsedItemMap, Name - Arity,
|
|
MatchingQualifiers) }
|
|
->
|
|
map__foldl(
|
|
check_for_simple_item_ambiguity_2(
|
|
ItemType, NeedQualifier,
|
|
SymName, Arity),
|
|
MatchingQualifiers)
|
|
;
|
|
[]
|
|
)
|
|
;
|
|
{ NeedsCheck = no }
|
|
).
|
|
|
|
:- pred check_for_simple_item_ambiguity_2(item_type::in, need_qualifier::in,
|
|
sym_name::in, arity::in, module_qualifier::in, module_name::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_for_simple_item_ambiguity_2(ItemType, NeedQualifier,
|
|
SymName, Arity, OldModuleQualifier, OldMatchingModuleName) -->
|
|
{ unqualify_name(SymName, Name) },
|
|
(
|
|
% XXX This is a bit conservative in the
|
|
% case of partially qualified names but that
|
|
% hopefully won't come up too often.
|
|
{ NeedQualifier = must_be_qualified },
|
|
{ OldModuleQualifier = unqualified("") }
|
|
->
|
|
[]
|
|
;
|
|
{ QualifiedName = module_qualify_name(OldModuleQualifier,
|
|
Name) },
|
|
{ match_sym_name(QualifiedName, SymName) },
|
|
\+ { SymName = qualified(OldMatchingModuleName, _) }
|
|
->
|
|
{ OldMatchingName = qualified(OldMatchingModuleName, Name) },
|
|
{ Reason = item_ambiguity(item_id(ItemType, SymName - Arity),
|
|
[item_id(ItemType, OldMatchingName - Arity)]
|
|
) },
|
|
record_recompilation_reason(Reason)
|
|
;
|
|
[]
|
|
).
|
|
|
|
:- pred check_for_pred_or_func_item_ambiguity(bool::in, need_qualifier::in,
|
|
timestamp::in, item_version_numbers::in, pred_or_func::in,
|
|
sym_name::in, list(type_and_mode)::in, maybe(type)::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_for_pred_or_func_item_ambiguity(NeedsCheck, NeedQualifier, OldTimestamp,
|
|
VersionNumbers, PredOrFunc, SymName, Args, WithType) -->
|
|
{
|
|
WithType = no,
|
|
adjust_func_arity(PredOrFunc, Arity, list__length(Args))
|
|
;
|
|
WithType = yes(_),
|
|
Arity = list__length(Args)
|
|
},
|
|
{ ItemType = pred_or_func_to_item_type(PredOrFunc) },
|
|
(
|
|
{ NeedsCheck = yes
|
|
; item_is_new_or_changed(OldTimestamp, VersionNumbers,
|
|
ItemType, SymName, Arity)
|
|
}
|
|
->
|
|
UsedItems =^ used_items,
|
|
{ UsedItemMap = extract_pred_or_func_set(UsedItems,
|
|
ItemType) },
|
|
{ unqualify_name(SymName, Name) },
|
|
( { map__search(UsedItemMap, Name, MatchingArityList) } ->
|
|
list__foldl(
|
|
(pred((MatchArity - MatchingQualifiers)::in,
|
|
in, out) is det -->
|
|
(
|
|
{
|
|
WithType = yes(_),
|
|
MatchArity >= Arity
|
|
;
|
|
WithType = no,
|
|
MatchArity = Arity
|
|
}
|
|
->
|
|
map__foldl(
|
|
check_for_pred_or_func_item_ambiguity_2(
|
|
ItemType, NeedQualifier,
|
|
SymName, MatchArity),
|
|
MatchingQualifiers)
|
|
;
|
|
[]
|
|
)
|
|
), MatchingArityList)
|
|
;
|
|
[]
|
|
),
|
|
|
|
{ PredId = invalid_pred_id },
|
|
( { SymName = qualified(ModuleName, _) } ->
|
|
{
|
|
WithType = yes(_),
|
|
% We don't know the actual arity.
|
|
AritiesToMatch = any
|
|
;
|
|
WithType = no,
|
|
AritiesToMatch = less_than_or_equal(Arity)
|
|
},
|
|
check_functor_ambiguities(NeedQualifier,
|
|
SymName, AritiesToMatch,
|
|
pred_or_func(PredId, ModuleName,
|
|
PredOrFunc, Arity))
|
|
;
|
|
{ error(
|
|
"check_for_pred_or_func_item_ambiguity: unqualified predicate name") }
|
|
)
|
|
;
|
|
[]
|
|
).
|
|
|
|
:- pred check_for_pred_or_func_item_ambiguity_2(item_type::in,
|
|
need_qualifier::in, sym_name::in, arity::in, module_qualifier::in,
|
|
set(pair(pred_id, module_name))::in, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
check_for_pred_or_func_item_ambiguity_2(ItemType, NeedQualifier,
|
|
SymName, Arity, OldModuleQualifier, OldMatchingModuleNames) -->
|
|
{ unqualify_name(SymName, Name) },
|
|
(
|
|
% XXX This is a bit conservative in the
|
|
% case of partially qualified names but that
|
|
% hopefully won't come up too often.
|
|
{ NeedQualifier = must_be_qualified },
|
|
{ OldModuleQualifier = unqualified("") }
|
|
->
|
|
[]
|
|
;
|
|
{ QualifiedName = module_qualify_name(OldModuleQualifier,
|
|
Name) },
|
|
{ match_sym_name(QualifiedName, SymName) },
|
|
\+ {
|
|
SymName = qualified(PredModuleName, _),
|
|
set__member(_ - PredModuleName,
|
|
OldMatchingModuleNames)
|
|
}
|
|
->
|
|
{ AmbiguousDecls = list__map(
|
|
(func(_ - OldMatchingModule) = Item :-
|
|
OldMatchingName = qualified(OldMatchingModule, Name),
|
|
Item = item_id(ItemType, OldMatchingName - Arity)
|
|
),
|
|
set__to_sorted_list(OldMatchingModuleNames)) },
|
|
{ Reason = item_ambiguity(item_id(ItemType, SymName - Arity),
|
|
AmbiguousDecls
|
|
) },
|
|
record_recompilation_reason(Reason)
|
|
;
|
|
[]
|
|
).
|
|
|
|
%
|
|
% Go over the constructors for a type which has changed
|
|
% and check whether any of them could create an ambiguity
|
|
% with functors used during the last compilation.
|
|
%
|
|
:- pred check_type_defn_ambiguity_with_functor(need_qualifier::in,
|
|
type_ctor::in, type_defn::in, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
check_type_defn_ambiguity_with_functor(_, _, abstract_type(_)) --> [].
|
|
check_type_defn_ambiguity_with_functor(_, _, eqv_type(_)) --> [].
|
|
check_type_defn_ambiguity_with_functor(NeedQualifier,
|
|
TypeCtor, du_type(Ctors, _, _)) -->
|
|
list__foldl(check_functor_ambiguities(NeedQualifier, TypeCtor),
|
|
Ctors).
|
|
check_type_defn_ambiguity_with_functor(_, _, foreign_type(_, _)) --> [].
|
|
|
|
:- pred check_functor_ambiguities(need_qualifier::in, type_ctor::in,
|
|
constructor::in, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
check_functor_ambiguities(NeedQualifier, TypeCtor,
|
|
ctor(_, _, Name, Args)) -->
|
|
{ ResolvedCtor = constructor(TypeCtor) },
|
|
{ Arity = list__length(Args) },
|
|
check_functor_ambiguities(NeedQualifier, Name, exact(Arity),
|
|
ResolvedCtor),
|
|
list__foldl(
|
|
check_field_ambiguities(NeedQualifier,
|
|
field(TypeCtor, Name - Arity)),
|
|
Args).
|
|
|
|
:- pred check_field_ambiguities(need_qualifier::in, resolved_functor::in,
|
|
constructor_arg::in, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
check_field_ambiguities(_, _, no - _) --> [].
|
|
check_field_ambiguities(NeedQualifier, ResolvedCtor, yes(FieldName) - _) -->
|
|
%
|
|
% XXX The arities to match below will need to change if we ever
|
|
% allow taking the address of field access functions.
|
|
%
|
|
{ field_access_function_name(get, FieldName, ExtractFuncName) },
|
|
check_functor_ambiguities(NeedQualifier, ExtractFuncName,
|
|
exact(1), ResolvedCtor),
|
|
{ field_access_function_name(set, FieldName, UpdateFuncName) },
|
|
check_functor_ambiguities(NeedQualifier, UpdateFuncName,
|
|
exact(2), ResolvedCtor).
|
|
|
|
%
|
|
% Predicates and functions used as functors can match
|
|
% any arity less than or equal to the predicate or function's
|
|
% arity.
|
|
%
|
|
:- type functor_match_arity
|
|
---> exact(arity)
|
|
; less_than_or_equal(arity)
|
|
; any
|
|
.
|
|
|
|
:- pred check_functor_ambiguities(need_qualifier::in, sym_name::in,
|
|
functor_match_arity::in, resolved_functor::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_functor_ambiguities(NeedQualifier, Name, MatchArity, ResolvedCtor) -->
|
|
UsedItems =^ used_items,
|
|
{ unqualify_name(Name, UnqualName) },
|
|
{ UsedCtors = UsedItems ^ functors },
|
|
( { map__search(UsedCtors, UnqualName, UsedCtorAL) } ->
|
|
check_functor_ambiguities_2(NeedQualifier, Name, MatchArity,
|
|
ResolvedCtor, UsedCtorAL)
|
|
;
|
|
[]
|
|
).
|
|
|
|
:- pred check_functor_ambiguities_2(need_qualifier::in, sym_name::in,
|
|
functor_match_arity::in, resolved_functor::in,
|
|
assoc_list(arity, resolved_functor_map)::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_functor_ambiguities_2(_, _, _, _, []) --> [].
|
|
check_functor_ambiguities_2(NeedQualifier, Name, MatchArity,
|
|
ResolvedCtor, [Arity - UsedCtorMap | UsedCtorAL]) -->
|
|
(
|
|
{ MatchArity = exact(ArityToMatch) },
|
|
{ ArityToMatch = Arity ->
|
|
Check = bool__yes,
|
|
Continue = bool__no
|
|
;
|
|
Check = no,
|
|
( Arity < ArityToMatch ->
|
|
Continue = yes
|
|
;
|
|
Continue = no
|
|
)
|
|
}
|
|
;
|
|
{ MatchArity = less_than_or_equal(ArityToMatch) },
|
|
{ Arity =< ArityToMatch ->
|
|
Check = yes,
|
|
Continue = yes
|
|
;
|
|
Check = no,
|
|
Continue = no
|
|
}
|
|
;
|
|
{ MatchArity = any },
|
|
{ Check = yes },
|
|
{ Continue = yes }
|
|
),
|
|
( { Check = yes } ->
|
|
map__foldl(
|
|
check_functor_ambiguity(NeedQualifier,
|
|
Name, Arity, ResolvedCtor),
|
|
UsedCtorMap)
|
|
;
|
|
[]
|
|
),
|
|
( { Continue = yes } ->
|
|
check_functor_ambiguities_2(NeedQualifier, Name, MatchArity,
|
|
ResolvedCtor, UsedCtorAL)
|
|
;
|
|
[]
|
|
).
|
|
|
|
:- pred check_functor_ambiguity(need_qualifier::in,
|
|
sym_name::in, arity::in, resolved_functor::in,
|
|
module_qualifier::in, set(resolved_functor)::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
check_functor_ambiguity(NeedQualifier, SymName, Arity, ResolvedCtor,
|
|
OldModuleQualifier, OldResolvedCtors) -->
|
|
(
|
|
% XXX This is a bit conservative in the
|
|
% case of partially qualified names but that
|
|
% hopefully won't come up too often.
|
|
{ NeedQualifier = must_be_qualified },
|
|
{ OldModuleQualifier = unqualified("") }
|
|
->
|
|
[]
|
|
;
|
|
{ unqualify_name(SymName, Name) },
|
|
{ OldName = module_qualify_name(OldModuleQualifier, Name) },
|
|
{ match_sym_name(OldName, SymName) },
|
|
\+ { set__member(ResolvedCtor, OldResolvedCtors) }
|
|
->
|
|
{ Reason = functor_ambiguity(
|
|
module_qualify_name(OldModuleQualifier, Name),
|
|
Arity,
|
|
ResolvedCtor,
|
|
set__to_sorted_list(OldResolvedCtors)
|
|
) },
|
|
record_recompilation_reason(Reason)
|
|
;
|
|
[]
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type recompilation_check_info
|
|
---> recompilation_check_info(
|
|
module_name :: module_name,
|
|
is_inline_sub_module :: bool,
|
|
sub_modules :: list(module_name),
|
|
read_modules :: read_modules,
|
|
used_items :: resolved_used_items,
|
|
used_typeclasses :: set(item_name),
|
|
modules_to_recompile :: modules_to_recompile,
|
|
collect_all_reasons :: bool,
|
|
recompilation_reasons :: list(recompile_reason)
|
|
).
|
|
|
|
:- type recompile_exception
|
|
---> recompile_exception(
|
|
recompile_reason,
|
|
recompilation_check_info
|
|
).
|
|
|
|
:- type recompile_reason
|
|
---> file_error(
|
|
file_name,
|
|
string
|
|
)
|
|
|
|
; output_file_not_up_to_date(
|
|
file_name
|
|
)
|
|
|
|
; syntax_error(
|
|
term__context,
|
|
string
|
|
)
|
|
|
|
; module_changed(
|
|
file_name
|
|
)
|
|
|
|
; item_ambiguity(
|
|
item_id, % new item.
|
|
list(item_id) % ambiguous declarations.
|
|
)
|
|
|
|
; functor_ambiguity(
|
|
sym_name,
|
|
arity,
|
|
resolved_functor, % new item.
|
|
list(resolved_functor)
|
|
% ambiguous declarations.
|
|
)
|
|
|
|
; changed_item(
|
|
item_id
|
|
)
|
|
|
|
; removed_item(
|
|
item_id
|
|
)
|
|
|
|
; changed_or_added_instance(
|
|
module_name,
|
|
item_name % class name
|
|
)
|
|
|
|
; removed_instance(
|
|
module_name,
|
|
item_name % class name
|
|
)
|
|
.
|
|
|
|
:- pred add_module_to_recompile(module_name::in, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
add_module_to_recompile(Module, Info0, Info) :-
|
|
ModulesToRecompile0 = Info0 ^ modules_to_recompile,
|
|
(
|
|
ModulesToRecompile0 = (all),
|
|
Info = Info0
|
|
;
|
|
ModulesToRecompile0 = some(Modules0),
|
|
Info = Info0 ^ modules_to_recompile :=
|
|
some([Module | Modules0])
|
|
).
|
|
|
|
:- pred record_read_file(module_name::in, module_timestamp::in, item_list::in,
|
|
module_error::in, file_name::in, recompilation_check_info::in,
|
|
recompilation_check_info::out) is det.
|
|
|
|
record_read_file(ModuleName, ModuleTimestamp, Items, Error, FileName) -->
|
|
Imports0 =^ read_modules,
|
|
{ map__set(Imports0, ModuleName - ModuleTimestamp ^ suffix,
|
|
read_module(ModuleTimestamp, Items, Error, FileName),
|
|
Imports) },
|
|
^ read_modules := Imports.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred write_recompilation_message(pred(io__state, io__state),
|
|
io__state, io__state).
|
|
:- mode write_recompilation_message(pred(di, uo) is det, di, uo) is det.
|
|
|
|
write_recompilation_message(P) -->
|
|
globals__io_lookup_bool_option(verbose_recompilation, Verbose),
|
|
( { Verbose = yes } ->
|
|
P
|
|
;
|
|
[]
|
|
).
|
|
|
|
:- pred write_recompile_reason(module_name::in, recompile_reason::in,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
write_recompile_reason(ModuleName, Reason) -->
|
|
{ recompile_reason_message(Reason, MaybeContext, ErrorPieces0) },
|
|
{ ErrorPieces =
|
|
[words("Recompiling module"),
|
|
words(string__append(describe_sym_name(ModuleName), ":")),
|
|
nl
|
|
| ErrorPieces0] },
|
|
write_error_pieces_maybe_with_context(MaybeContext, 0, ErrorPieces).
|
|
|
|
:- pred recompile_reason_message(recompile_reason::in, maybe(context)::out,
|
|
list(format_component)::out) is det.
|
|
|
|
recompile_reason_message(file_error(_FileName, Msg), no, [words(Msg)]).
|
|
recompile_reason_message(output_file_not_up_to_date(FileName), no,
|
|
[words("output file"), words(FileName),
|
|
words("is not up to date.")]).
|
|
recompile_reason_message(syntax_error(Context, Msg), yes(Context),
|
|
[words(Msg)]).
|
|
recompile_reason_message(module_changed(FileName), no,
|
|
[words("file"), words("`" ++ FileName ++ "'"),
|
|
words("has changed.")]).
|
|
recompile_reason_message(item_ambiguity(Item, AmbiguousItems), no, Pieces) :-
|
|
AmbiguousItemPieces = component_lists_to_pieces(
|
|
list__map(describe_item, AmbiguousItems)),
|
|
Pieces = append_punctuation(
|
|
list__condense([
|
|
[words("addition of ") | describe_item(Item)],
|
|
[words("could cause an ambiguity with")],
|
|
AmbiguousItemPieces]),
|
|
'.').
|
|
recompile_reason_message(functor_ambiguity(SymName, Arity,
|
|
Functor, AmbiguousFunctors), no, Pieces) :-
|
|
FunctorPieces = describe_functor(SymName, Arity, Functor),
|
|
AmbiguousFunctorPieces = component_lists_to_pieces(
|
|
list__map(describe_functor(SymName, Arity),
|
|
AmbiguousFunctors)),
|
|
Pieces = append_punctuation(
|
|
list__condense([
|
|
[words("addition of ") | FunctorPieces],
|
|
[words("could cause an ambiguity with")],
|
|
AmbiguousFunctorPieces]),
|
|
'.').
|
|
recompile_reason_message(changed_item(Item), no,
|
|
list__append(describe_item(Item), [words("was modified.")])).
|
|
recompile_reason_message(removed_item(Item), no,
|
|
list__append(describe_item(Item), [words("was removed.")])).
|
|
recompile_reason_message(
|
|
changed_or_added_instance(ModuleName, ClassName - ClassArity),
|
|
no,
|
|
[
|
|
words("an instance for class"),
|
|
words(describe_sym_name_and_arity(ClassName / ClassArity)),
|
|
words("in module"),
|
|
words(describe_sym_name(ModuleName)),
|
|
words("was added or modified.")
|
|
]).
|
|
recompile_reason_message(removed_instance(ModuleName, ClassName - ClassArity),
|
|
no,
|
|
[
|
|
words("an instance for class "),
|
|
words(describe_sym_name_and_arity(ClassName / ClassArity)),
|
|
words("in module"),
|
|
words(describe_sym_name(ModuleName)),
|
|
words("was removed.")
|
|
]).
|
|
|
|
:- func describe_item(item_id) = list(format_component).
|
|
|
|
describe_item(item_id(ItemType0, SymName - Arity)) = Pieces :-
|
|
( body_item(ItemType0, ItemType1) ->
|
|
ItemType = ItemType1,
|
|
BodyWords = "body of "
|
|
;
|
|
ItemType = ItemType0,
|
|
BodyWords = ""
|
|
),
|
|
string_to_item_type(ItemTypeStr, ItemType),
|
|
Pieces = [
|
|
words(string__append(BodyWords, ItemTypeStr)),
|
|
words(describe_sym_name_and_arity(SymName / Arity))
|
|
].
|
|
|
|
:- pred body_item(item_type::in, item_type::out) is semidet.
|
|
|
|
body_item(type_body, (type)).
|
|
|
|
:- func describe_functor(sym_name, arity, resolved_functor) =
|
|
list(format_component).
|
|
|
|
describe_functor(SymName, _Arity,
|
|
pred_or_func(_, ModuleName, PredOrFunc, PredArity)) =
|
|
[words(ItemTypeStr), SymNameAndArityPiece] :-
|
|
string_to_item_type(ItemTypeStr,
|
|
pred_or_func_to_item_type(PredOrFunc)),
|
|
unqualify_name(SymName, UnqualName),
|
|
SymNameAndArityPiece = words(describe_sym_name_and_arity(
|
|
qualified(ModuleName, UnqualName) / PredArity)).
|
|
describe_functor(SymName, Arity, constructor(TypeName - TypeArity)) =
|
|
[words("constructor"),
|
|
words(describe_sym_name_and_arity(SymName / Arity)),
|
|
words("of type"),
|
|
words(describe_sym_name_and_arity(TypeName / TypeArity))
|
|
].
|
|
describe_functor(SymName, Arity,
|
|
field(TypeName - TypeArity, ConsName - ConsArity)) =
|
|
[words("field access function"),
|
|
words(describe_sym_name_and_arity(SymName / Arity)),
|
|
words("for constructor"),
|
|
words(describe_sym_name_and_arity(ConsName / ConsArity)),
|
|
words("of type"),
|
|
words(describe_sym_name_and_arity(TypeName / TypeArity))
|
|
].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred read_term_check_for_error_or_eof(recompilation_check_info::in,
|
|
string::in, term::out, io__state::di, io__state::uo) is det.
|
|
|
|
read_term_check_for_error_or_eof(Info, Item, Term) -->
|
|
parser__read_term(TermResult),
|
|
(
|
|
{ TermResult = term(_, Term) }
|
|
;
|
|
{ TermResult = error(Message, Line) },
|
|
io__input_stream_name(FileName),
|
|
{ Reason = syntax_error(term__context(FileName, Line),
|
|
Message) },
|
|
{ throw_syntax_error(Reason, Info) }
|
|
;
|
|
{ TermResult = eof },
|
|
io__input_stream_name(FileName),
|
|
io__get_line_number(Line),
|
|
{ Reason = syntax_error(term__context(FileName, Line),
|
|
string__append_list(
|
|
["unexpected end of file, expected ",
|
|
Item, "."])) },
|
|
{ throw_syntax_error(Reason, Info) }
|
|
).
|
|
|
|
:- func get_term_context(term) = term__context.
|
|
|
|
get_term_context(Term) =
|
|
( Term = term__functor(_, _, Context) ->
|
|
Context
|
|
;
|
|
term__context_init
|
|
).
|
|
|
|
:- pred record_recompilation_reason(recompile_reason::in,
|
|
recompilation_check_info::in, recompilation_check_info::out) is det.
|
|
|
|
record_recompilation_reason(Reason, Info0, Info) :-
|
|
( Info0 ^ collect_all_reasons = yes ->
|
|
Info = Info0 ^ recompilation_reasons :=
|
|
[Reason | Info0 ^ recompilation_reasons]
|
|
;
|
|
throw(recompile_exception(Reason, Info0))
|
|
).
|
|
|
|
:- pred throw_syntax_error(recompile_reason::in,
|
|
recompilation_check_info::in) is erroneous.
|
|
|
|
throw_syntax_error(Reason, Info) :-
|
|
% If there were syntax errors in a `.used' file written during
|
|
% a compilation, all outputs of that compilation are slightly
|
|
% suspect, so it's worth entirely redoing the compilation.
|
|
RecompileInfo = Info ^ modules_to_recompile := (all),
|
|
throw(recompile_exception(Reason, RecompileInfo)).
|
|
|
|
%-----------------------------------------------------------------------------%
|