Files
mercury/compiler/term_constr_errors.m
2018-04-07 18:25:43 +10:00

322 lines
12 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2002, 2005-2007, 2010-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: term_constr_errors.m.
% Main author: juliensf.
%
%-----------------------------------------------------------------------------%
:- module transform_hlds.term_constr_errors.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module parse_tree.
:- import_module parse_tree.error_util.
:- import_module parse_tree.prog_data.
:- import_module list.
%-----------------------------------------------------------------------------%
%
% Termination 2 Errors
%
% The termination errors are all in reference to possible non-termination.
% While it is possible for pass 1 to go amiss the worst that will happen
% (barring an abnormal abort) is that the size of the arguments will be
% unconstrained.
:- type term2_error_kind
---> imported_pred
% Termination could not be proved because it depends upon
% information from another module and that information is not
% available.
; can_loop_proc_called(pred_proc_id, pred_proc_id)
% Termination could not be proved because the procedure called
% another procedure that may not terminate.
; cond_not_satisfied
% Termination could not be proved because no set of decreasing
% argument could be found.
; horder_call
% Termination could not be proved because the procedure makes
% higher-order calls.
; does_not_term_pragma(pred_id)
% Termination could not be proved because the procedure was marked
% with a `does_not_terminate' pragma.
; foreign_proc_called(pred_proc_id).
% Termination depends upon the properties of a piece of foreign
% code that cannot be established as terminating.
:- type term2_error
---> term2_error(prog_context, term2_error_kind).
%-----------------------------------------------------------------------------%
:- pred maybe_report_term2_errors(module_info::in, scc::in,
list(term2_error)::in,
list(error_spec)::in, list(error_spec)::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.hlds_error_util.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module transform_hlds.term_util.
:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module maybe.
:- import_module require.
:- import_module set.
:- import_module string.
%-----------------------------------------------------------------------------%
maybe_report_term2_errors(ModuleInfo, SCC, Errors, !Specs) :-
decide_what_term2_errors_to_report(ModuleInfo, SCC, Errors,
MaybeErrorsToReport),
(
MaybeErrorsToReport = no
;
MaybeErrorsToReport = yes(ErrorsToReport),
report_term2_errors(ModuleInfo, SCC, ErrorsToReport, !Specs)
).
:- pred decide_what_term2_errors_to_report(module_info::in,
scc::in, list(term2_error)::in, maybe(list(term2_error))::out) is det.
decide_what_term2_errors_to_report(ModuleInfo, SCC, Errors,
MaybeErrorsToReport) :-
% NOTE The code of this predicate follows the same logic as the
% code of decide_what_term_errors_to_report in termination.m.
% Although there are differences in the data types they operate on
% and the options they consult, any changes here probably require
% corresponding changes there as well.
% The logic of this code is documented in comments in
% decide_what_term_errors_to_report.
module_info_get_globals(ModuleInfo, Globals),
globals.lookup_bool_option(Globals, check_termination2, NormalErrors),
globals.lookup_bool_option(Globals, verbose_check_termination2,
VerboseErrors),
( if
IsCheckTerm =
( pred(PPId::in) is semidet :-
module_info_pred_proc_info(ModuleInfo, PPId, PredInfo, _),
not pred_info_is_imported(PredInfo),
pred_info_get_markers(PredInfo, Markers),
check_marker(Markers, marker_check_termination)
),
set.filter(IsCheckTerm, SCC, CheckTermPPIds),
set.is_non_empty(CheckTermPPIds)
then
MaybeErrorsToReport = yes(Errors)
else if
IsNonImported =
( pred(PPId::in) is semidet :-
module_info_pred_proc_info(ModuleInfo, PPId, PredInfo, _),
not pred_info_is_imported(PredInfo)
),
set.filter(IsNonImported, SCC, NonImportedPPIds),
set.is_non_empty(NonImportedPPIds)
then
(
VerboseErrors = yes,
MaybeErrorsToReport = yes(Errors)
;
VerboseErrors = no,
(
NormalErrors = yes,
IsDirect =
( pred(Error::in) is semidet :-
Error = term2_error(_, ErrorKind),
term2_error_kind_is_direct(ErrorKind) = yes
),
list.filter(IsDirect, Errors, DirectErrors),
(
DirectErrors = [],
MaybeErrorsToReport = yes(Errors)
;
DirectErrors = [_ | _],
MaybeErrorsToReport = yes(DirectErrors)
)
;
NormalErrors = no,
MaybeErrorsToReport = no
)
)
else
MaybeErrorsToReport = no
).
%-----------------------------------------------------------------------------%
:- pred report_term2_errors(module_info::in, scc::in,
list(term2_error)::in, list(error_spec)::in, list(error_spec)::out) is det.
report_term2_errors(ModuleInfo, SCC, Errors, !Specs) :-
get_context_from_scc(ModuleInfo, SCC, Context),
( if set.is_singleton(SCC, PPId) then
Pieces0 = [words("Termination of")],
ProcName = describe_one_proc_name(ModuleInfo,
should_module_qualify, PPId),
Pieces1 = Pieces0 ++ ProcName,
Single = yes(PPId)
else
Pieces0 = [words("Termination of the"),
words("mutually recursive procedures")],
ProcNames = describe_several_proc_names(ModuleInfo,
should_module_qualify, set.to_sorted_list(SCC)),
Pieces1 = Pieces0 ++ ProcNames,
Single = no
),
(
Errors = [],
Pieces2 = [words("not proven, for unknown reason(s).")],
ReasonMsgsCord = cord.init
;
Errors = [Error],
Pieces2 = [words("not proven for the following reason:")],
describe_term2_error(ModuleInfo, Single, no, Error,
cord.init, ReasonMsgsCord)
;
Errors = [_, _ | _],
Pieces2 = [words("not proven for the following reasons:")],
describe_term2_errors(ModuleInfo, Single, 1, Errors,
cord.init, ReasonMsgsCord)
),
ReasonMsgs = cord.list(ReasonMsgsCord),
Msgs = [simple_msg(Context, [always(Pieces1 ++ Pieces2)]) | ReasonMsgs],
Spec = error_spec(severity_warning, phase_termination_analysis, Msgs),
!:Specs = [Spec | !.Specs].
:- pred describe_term2_errors(module_info::in, maybe(pred_proc_id)::in,
int::in, list(term2_error)::in,
cord(error_msg)::in, cord(error_msg)::out) is det.
describe_term2_errors(_, _, _, [], !ReasonMsgsCord).
describe_term2_errors(ModuleInfo, Single, ErrNum0, [Error | Errors],
!ReasonMsgsCord) :-
describe_term2_error(ModuleInfo, Single, yes(ErrNum0), Error,
!ReasonMsgsCord),
describe_term2_errors(ModuleInfo, Single, ErrNum0 + 1, Errors,
!ReasonMsgsCord).
:- pred describe_term2_error(module_info::in, maybe(pred_proc_id)::in,
maybe(int)::in, term2_error::in,
cord(error_msg)::in, cord(error_msg)::out) is det.
describe_term2_error(ModuleInfo, Single, MaybeErrorNum, Error,
!ReasonMsgsCord) :-
Error = term2_error(Context, ErrorKind),
term2_error_kind_description(ModuleInfo, Single, ErrorKind, Pieces0),
(
MaybeErrorNum = yes(N),
string.int_to_string(N, Nstr),
Preamble = "Reason " ++ Nstr ++ ":",
Pieces = [fixed(Preamble) | Pieces0]
;
MaybeErrorNum = no,
Pieces = Pieces0
),
ReasonMsg = error_msg(yes(Context), treat_as_first, 0, [always(Pieces)]),
!:ReasonMsgsCord = cord.snoc(!.ReasonMsgsCord, ReasonMsg).
:- pred term2_error_kind_description(module_info::in, maybe(pred_proc_id)::in,
term2_error_kind::in, list(format_component)::out) is det.
term2_error_kind_description(ModuleInfo, Single, Error, Pieces) :-
(
Error = cond_not_satisfied,
Pieces = [words("The termination condition"),
words("is not satisfiable."), nl]
;
Error = imported_pred,
Pieces = [words("It contains one or more"),
words("predicates and/or functions"),
words("imported from another module."), nl]
;
Error = can_loop_proc_called(CallerPPId, CalleePPId),
(
Single = yes(PPId),
expect(unify(PPId, CallerPPId), $pred, "caller outside this SCC"),
Piece1 = [words("It")]
;
Single = no,
ProcName = describe_one_proc_name(ModuleInfo,
should_module_qualify, CallerPPId),
Piece1 = ProcName
),
Piece2 = words("calls"),
CalleePiece = describe_one_proc_name(ModuleInfo,
should_module_qualify, CalleePPId),
Pieces3 = [words("which could not be proven to terminate."), nl],
Pieces = Piece1 ++ [Piece2] ++ CalleePiece ++ Pieces3
;
Error = horder_call,
Pieces = [words("It contains a higher-order call."), nl]
;
Error = does_not_term_pragma(PredId),
Pieces1 = [words("There is a"), pragma_decl("does_not_terminate"),
words("declaration for")],
(
Single = yes(PPId),
PPId = proc(SCCPredId, _),
expect(unify(PredId, SCCPredId), $pred,
"does not terminate pragma outside this SCC"),
Pieces2 = [words("it."), nl]
;
Single = no,
PredDesc = describe_one_pred_name(ModuleInfo,
should_module_qualify, PredId),
Pieces2 = PredDesc ++ [suffix("."), nl]
),
Pieces = Pieces1 ++ Pieces2
;
Error = foreign_proc_called(PPId),
Name = describe_one_proc_name(ModuleInfo, should_module_qualify, PPId),
Pieces = [words("There is a call the foreign procedure")] ++
Name ++ [words("which is not known to terminate."), nl]
).
%-----------------------------------------------------------------------------%
:- func term2_error_kind_is_direct(term2_error_kind) = bool.
term2_error_kind_is_direct(ErrorKind) = IsDirect :-
(
( ErrorKind = cond_not_satisfied
; ErrorKind = foreign_proc_called(_)
),
IsDirect = yes
;
( ErrorKind = imported_pred
; ErrorKind = horder_call
; ErrorKind = does_not_term_pragma(_)
; ErrorKind = can_loop_proc_called(_, _)
),
IsDirect = no
).
%-----------------------------------------------------------------------------%
:- end_module transform_hlds.term_constr_errors.
%-----------------------------------------------------------------------------%