Files
mercury/compiler/purity.m
2018-10-03 10:21:33 +10:00

1665 lines
69 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1997-2012,2014 The University of Melbourne.
% Copyright (C) 2015 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.
%-----------------------------------------------------------------------------%
%
% File: purity.m
% Main authors: schachte (Peter Schachte, main author and designer of the
% purity system), trd (modifications for impure functions).
%
% Purpose: handle `impure' and `promise_pure' declarations; finish off
% type checking.
%
% The main purpose of this module is check the consistency of the `impure' and
% `promise_pure' (etc.) declarations, and to thus report error messages if the
% program is not "purity-correct". This includes treating procedures with
% different clauses for different modes as impure, unless promised pure.
%
% This module also does some tasks that are logically part of type analysis
% but must be done after type inference is complete:
%
% - resolution of predicate overloading
% - resolution of function symbol overloading
% (we call resolve_unify_functor.m to do this)
% - checking the types of the outer variables in atomic goals, and insertion
% of their conversions to and from the inner variables.
%
% These are the tasks controlled by the run_post_typecheck_tasks field
% in the purity_info. Please note that these are *separate* from the tasks
% done by post_typecheck.m.
%
% (You may also wish to see the comments in typecheck.m and post_typecheck.m.)
%
% We also eliminate double negations in this pass. It needs to be done
% somewhere after the initial quantification of procedure bodies and
% before mode analysis, and this is a convenient place to do it.
%
% This pass also converts calls to `private_builtin.unsafe_type_cast'
% into `generic_call(unsafe_cast, ...)' goals.
%
%-----------------------------------------------------------------------------%
%
% The aim of Mercury's purity system is to allow one to declare certain parts
% of one's program to be impure, thereby forbidding the compiler from making
% certain optimizations to that part of the code. Since one can often
% implement a perfectly pure predicate or function in terms of impure
% predicates and functions, one is also allowed to promise to the compiler
% that a predicate *is* pure, despite calling impure predicates and
% functions.
%
% To keep purity/impurity consistent, it is required that every impure
% predicate/function be declared so. A predicate is impure if:
%
% 1. It is declared impure, or
% 2a. It is not promised pure, and
% 2b. It calls some impure predicates or functions.
%
% A predicate or function is declared impure by preceding the `pred' or
% `func' in its declaration with `impure'. It is promised to be pure with a
%
% :- pragma promise_pure(Name/Arity).
%
% directive.
%
% Calls to impure predicates may not be optimized away. Neither may they be
% reordered relative to any other goals in a given conjunction; i.e., an impure
% goal cleaves a conjunction into the stuff before it and the stuff after it.
% Both of these groups may be reordered separately, but no goal from either
% group may move into the other. Similarly for disjunctions.
%
% Semipure goals are goals that are sensitive to the effects of impure goals.
% They may be reordered and optimized away just like pure goals, except that
% a semipure goal may behave differently after a call to an impure goal than
% before. This means that semipure (as well as impure) predicates must not
% be tabled. Further, duplicate semipure goals on different sides of an
% impure goal must not be optimized away. In the current implementation,
% we simply do not optimize away duplicate semipure (or impure) goals at all.
%
% A predicate either has no purity declaration and so is assumed pure,
% or is declared semipure or impure, or is promised to be pure despite calling
% semipure or impure predicates. This promise cannot be checked, so we must
% trust the programmer.
%
% See the language reference manual for more information on syntax and
% semantics.
%
% The current implementation now handles impure functions.
% They are limited to being used as part of an explicit unification
% with a purity indicator before the goal.
%
% impure X = some_impure_func(Arg1, Arg2, ...)
%
% This eliminates any need to define some order of evaluation of nested
% impure functions.
%
% Of course it also eliminates the benefits of using functions to cut down
% on the number of variables introduced. The main use of impure functions
% is to interface nicely with foreign language functions.
%
% Any non-variable arguments to the function are flattened into unification
% goals (see unravel_unifications in superhomogeneous.m) which are placed
% as pure goals before the function call itself.
%
% Wishlist:
% It would be nice to use impure functions in DCG goals as well as
% normal unifications.
%
% It might be nice to allow
% X = impure some_impure_fuc(Arg1, Arg2, ...)
% syntax as well. But there are advantages to having the impure or semipure
% annotation in a regular position (on the left hand side of a goal) too.
% If this is implemented, it should probably be handled in the parser, and
% turned into an impure unify item.
%
% It may also be nice to allow semipure function calls to occur inline
% (since ordering is not an issue for them).
%
% To do:
% Reconsider whether impure parallel conjuncts should be allowed.
%
%-----------------------------------------------------------------------------%
:- module check_hlds.purity.
:- 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.
%-----------------------------------------------------------------------------%
% Purity check a whole module. Also do the post-typecheck stuff described
% above, and eliminate double negations and calls to
% `private_builtin.unsafe_type_cast/2'. The first argument specifies
% whether there were any type errors (if so, we suppress some diagnostics
% in post_typecheck.m because they are usually spurious). The second
% argument specifies whether post_typecheck.m detected any errors that
% would cause problems for later passes (if so, we stop compilation after
% this pass).
%
:- pred puritycheck_module(module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
% Rerun purity checking on a procedure after an optimization pass has
% performed transformations which might affect the procedure's purity.
% repuritycheck_proc makes sure that the goal_infos contain the correct
% purity, and that the pred_info contains the promised_pure or
% promised_semipure markers which might be needed if a promised pure
% procedure was inlined into the procedure being checked.
%
:- pred repuritycheck_proc(module_info::in, pred_proc_id::in, pred_info::in,
pred_info::out) is det.
% Generate an error message for unifications marked impure/semipure
% that are not function calls (e.g. impure X = 4).
%
:- func impure_unification_expr_error(prog_context, purity) = error_spec.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.resolve_unify_functor.
:- import_module hlds.from_ground_term_util.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_clauses.
:- import_module hlds.hlds_error_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_rtti.
:- import_module hlds.instmap.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_table.
:- import_module hlds.quantification.
:- import_module hlds.vartypes.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.builtin_lib_types.
:- import_module parse_tree.parse_tree_out_term.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_mode.
:- import_module parse_tree.prog_out.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.set_of_var.
:- import_module assoc_list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module term.
:- import_module varset.
%-----------------------------------------------------------------------------%
puritycheck_module(!ModuleInfo, !Specs) :-
module_info_get_valid_pred_ids(!.ModuleInfo, PredIds),
maybe_puritycheck_preds(PredIds, !ModuleInfo, !Specs).
:- pred maybe_puritycheck_preds(list(pred_id)::in,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
maybe_puritycheck_preds([], !ModuleInfo, !Specs).
maybe_puritycheck_preds([PredId | PredIds], !ModuleInfo, !Specs) :-
module_info_pred_info(!.ModuleInfo, PredId, PredInfo0),
( if
( pred_info_is_imported(PredInfo0)
; pred_info_is_pseudo_imported(PredInfo0)
)
then
true
else
trace [io(!IO)] (
write_pred_progress_message("% Purity-checking ", PredId,
!.ModuleInfo, !IO)
),
puritycheck_pred(PredId, PredInfo0, PredInfo, !.ModuleInfo, !Specs),
module_info_set_pred_info(PredId, PredInfo, !ModuleInfo)
),
maybe_puritycheck_preds(PredIds, !ModuleInfo, !Specs).
%-----------------------------------------------------------------------------%
%
% Check purity of a single predicate.
%
% Purity checking is quite simple. Since impurity /must/ be declared, we can
% perform a single pass checking that the actual purity of each predicate
% matches the declared (or implied) purity. A predicate is just as pure as
% its least pure goal. While we are doing this, we attach a `feature' to each
% goal that is not pure, including non-atomic goals, indicating its purity.
% This information must be maintained by later compilation passes, at least
% until after the last pass that may perform transformations that would not
% be correct for impure code. As we check purity and attach impurity
% features, we also check that impure (semipure) atomic goals were marked in
% the source code as impure (semipure). At this stage in the computation,
% this is indicated by already having the appropriate goal feature. (During
% the translation from term to goal, calls have their purity attached to
% them, and in the translation from goal to hlds_goal, the attached purity is
% turned into the appropriate feature in the hlds_goal_info.)
:- pred puritycheck_pred(pred_id::in, pred_info::in, pred_info::out,
module_info::in, list(error_spec)::in, list(error_spec)::out) is det.
puritycheck_pred(PredId, !PredInfo, ModuleInfo, !Specs) :-
pred_info_get_purity(!.PredInfo, DeclaredPurity),
pred_info_get_promised_purity(!.PredInfo, MaybePromisedPurity),
some [!ClausesInfo] (
pred_info_get_clauses_info(!.PredInfo, !:ClausesInfo),
clauses_info_clauses(Clauses0, ItemNumbers, !ClausesInfo),
clauses_info_get_vartypes(!.ClausesInfo, VarTypes0),
clauses_info_get_varset(!.ClausesInfo, VarSet0),
PurityInfo0 = purity_info(ModuleInfo, run_post_typecheck_tasks,
do_not_need_to_requantify, have_not_converted_unify, !.PredInfo,
VarTypes0, VarSet0, []),
compute_purity_for_clauses(Clauses0, Clauses, !.PredInfo,
purity_pure, ActualPurity, PurityInfo0, PurityInfo),
PurityInfo = purity_info(_, _, _, _, !:PredInfo,
VarTypes, VarSet, GoalSpecs),
clauses_info_set_vartypes(VarTypes, !ClausesInfo),
clauses_info_set_varset(VarSet, !ClausesInfo),
set_clause_list(Clauses, ClausesRep),
clauses_info_set_clauses_rep(ClausesRep, ItemNumbers, !ClausesInfo),
pred_info_set_clauses_info(!.ClausesInfo, !PredInfo)
),
perform_pred_purity_checks(ModuleInfo, PredId, !.PredInfo,
ActualPurity, DeclaredPurity, MaybePromisedPurity, PredSpecs),
!:Specs = GoalSpecs ++ PredSpecs ++ !.Specs.
% Perform purity checking of the actual and declared purity,
% and check that promises are consistent.
%
% ActualPurity: The inferred purity of the pred.
% DeclaredPurity: The declared purity of the pred.
% MaybePromisedPurity: Did we promise this pred as pure or semipure?
%
:- pred perform_pred_purity_checks(module_info::in, pred_id::in, pred_info::in,
purity::in, purity::in, maybe(purity)::in, list(error_spec)::out) is det.
perform_pred_purity_checks(ModuleInfo, PredId, PredInfo,
ActualPurity, DeclaredPurity, MaybePromisedPurity, !:Specs) :-
!:Specs = [],
(
MaybePromisedPurity = no
;
MaybePromisedPurity = yes(PromisedPurity),
% The declared purity must match any promises.
( if DeclaredPurity = PromisedPurity then
true
else
InconsistentPromiseSpec = error_inconsistent_purity_promise(
ModuleInfo, PredInfo, PredId, DeclaredPurity),
!:Specs = [InconsistentPromiseSpec | !.Specs]
),
% You shouldn't promise pure unnecessarily. However, there is no point
% in warning about compiler generated predicates.
( if
ActualPurity = PromisedPurity,
pred_info_get_origin(PredInfo, Origin),
not (
Origin = origin_transformed(_, _, _)
;
Origin = origin_created(_)
)
then
UnnecessaryPromiseSpec = warn_unnecessary_purity_promise(
ModuleInfo, PredInfo, PredId, PromisedPurity),
!:Specs = [UnnecessaryPromiseSpec | !.Specs]
else
true
)
),
% The purity should match the declaration.
ComparisonResult = compare_purity(ActualPurity, DeclaredPurity),
(
ComparisonResult = (=)
;
ComparisonResult = (<),
(
MaybePromisedPurity = yes(_PromisedPurity),
% The promise is intended to tell the compiler that the purity
% of the procedure body is not ActualPurity, but _PromisedPurity.
%
% If _PromisedPurity = DeclaredPurity, then this means that
% ComparisonResult should really be (=).
%
% If _PromisedPurity \= DeclaredPurity, then we have already
% generated an error message (see InconsistentPromiseSpec).
true
;
MaybePromisedPurity = no,
NotPureEnoughSpec = error_not_pure_enough(ModuleInfo, PredInfo,
PredId, ActualPurity),
!:Specs = [NotPureEnoughSpec | !.Specs]
)
;
ComparisonResult = (>),
% We don't warn about exaggerated impurity decls in class methods
% or instance methods --- it just means that the predicate provided
% as an implementation was more pure than necessary.
%
% We don't warn about exaggerated impurity decls in foreign language
% code -- this is just because we assume they are pure (XXX we do not
% do so anymore), but you can declare them to be impure.
%
% We don't warn about exaggerated impurity declarations for "stub"
% procedures, i.e. procedures which originally had no clauses.
pred_info_get_markers(PredInfo, Markers),
pred_info_get_goal_type(PredInfo, GoalType),
( if
( GoalType = goal_type_foreign
; GoalType = goal_type_clause_and_foreign
; check_marker(Markers, marker_class_method)
; check_marker(Markers, marker_class_instance_method)
; check_marker(Markers, marker_stub)
)
then
true
else
TooPureSpec = warn_pred_body_too_pure(ModuleInfo,
PredInfo, PredId, ActualPurity, DeclaredPurity),
!:Specs = [TooPureSpec | !.Specs]
)
).
:- func compare_purity(purity, purity) = comparison_result.
compare_purity(purity_pure, purity_pure) = (=).
compare_purity(purity_pure, purity_semipure) = (>).
compare_purity(purity_pure, purity_impure) = (>).
compare_purity(purity_semipure, purity_pure) = (<).
compare_purity(purity_semipure, purity_semipure) = (=).
compare_purity(purity_semipure, purity_impure) = (>).
compare_purity(purity_impure, purity_pure) = (<).
compare_purity(purity_impure, purity_semipure) = (<).
compare_purity(purity_impure, purity_impure) = (=).
%-----------------------------------------------------------------------------%
repuritycheck_proc(ModuleInfo, proc(_PredId, ProcId), !PredInfo) :-
pred_info_get_proc_table(!.PredInfo, Procs0),
map.lookup(Procs0, ProcId, ProcInfo0),
proc_info_get_goal(ProcInfo0, Goal0),
proc_info_get_vartypes(ProcInfo0, VarTypes0),
proc_info_get_varset(ProcInfo0, VarSet0),
PurityInfo0 = purity_info(ModuleInfo, do_not_run_post_typecheck_tasks,
do_not_need_to_requantify, have_not_converted_unify, !.PredInfo,
VarTypes0, VarSet0, []),
compute_goal_purity(Goal0, Goal, Bodypurity, _, PurityInfo0, PurityInfo),
PurityInfo = purity_info(_, _, NeedToRequantify, _, !:PredInfo,
VarTypes, VarSet, _),
proc_info_set_goal(Goal, ProcInfo0, ProcInfo1),
proc_info_set_vartypes(VarTypes, ProcInfo1, ProcInfo2),
proc_info_set_varset(VarSet, ProcInfo2, ProcInfo3),
(
NeedToRequantify = need_to_requantify,
requantify_proc_general(ordinary_nonlocals_maybe_lambda,
ProcInfo3, ProcInfo)
;
NeedToRequantify = do_not_need_to_requantify,
ProcInfo = ProcInfo3
),
map.det_update(ProcId, ProcInfo, Procs0, Procs),
pred_info_set_proc_table(Procs, !PredInfo),
% A predicate should never become less pure after inlining, so update
% any promises in the pred_info if the purity of the goal worsened
% (for example if a promised pure predicate was inlined).
pred_info_get_purity(!.PredInfo, OldPurity),
pred_info_get_markers(!.PredInfo, Markers0),
( if
less_pure(Bodypurity, OldPurity)
then
(
OldPurity = purity_pure,
remove_marker(marker_promised_semipure, Markers0, Markers1),
add_marker(marker_promised_pure, Markers1, Markers)
;
OldPurity = purity_semipure,
add_marker(marker_promised_semipure, Markers0, Markers)
;
OldPurity = purity_impure,
Markers = Markers0
),
pred_info_set_markers(Markers, !PredInfo)
else if
less_pure(OldPurity, Bodypurity),
[_] = pred_info_procids(!.PredInfo)
then
% If there is only one procedure, update the purity in the pred_info
% if the purity improved.
%
% XXX Storing the purity in the pred_info is the wrong thing to do,
% because optimizations can make some procedures more pure than others.
(
Bodypurity = purity_pure,
remove_marker(marker_is_impure, Markers0, Markers1),
remove_marker(marker_is_semipure, Markers1, Markers)
;
Bodypurity = purity_semipure,
remove_marker(marker_is_impure, Markers0, Markers1),
add_marker(marker_is_semipure, Markers1, Markers)
;
Bodypurity = purity_impure,
Markers = Markers0
),
pred_info_set_markers(Markers, !PredInfo)
else
true
).
%-----------------------------------------------------------------------------%
% Infer the purity of a single (non-foreign_proc) predicate.
%
:- pred compute_purity_for_clauses(list(clause)::in, list(clause)::out,
pred_info::in, purity::in, purity::out,
purity_info::in, purity_info::out) is det.
compute_purity_for_clauses([], [], _, !Purity, !Info).
compute_purity_for_clauses([Clause0 | Clauses0], [Clause | Clauses], PredInfo,
!Purity, !Info) :-
compute_purity_for_clause(Clause0, Clause, PredInfo, ClausePurity, !Info),
!:Purity = worst_purity(!.Purity, ClausePurity),
compute_purity_for_clauses(Clauses0, Clauses, PredInfo, !Purity, !Info).
% Infer the purity of a single clause.
%
:- pred compute_purity_for_clause(clause::in, clause::out, pred_info::in,
purity::out, purity_info::in, purity_info::out) is det.
compute_purity_for_clause(Clause0, Clause, PredInfo, Purity, !Info) :-
Goal0 = Clause0 ^ clause_body,
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
!Info ^ pi_requant := do_not_need_to_requantify,
compute_expr_purity(GoalExpr0, GoalExpr1, GoalInfo0, BodyPurity0, _,
!Info),
% If this clause doesn't apply to all modes of this procedure,
% i.e. the procedure has different clauses for different modes,
% then we must treat it as impure, unless either the compiler or
% the programmer has promised that the clauses are semantically equivalent.
%
% The default impurity of foreign_proc procedures is handled when
% processing the foreign_proc goal -- they are not counted as impure
% here simply because they have different clauses for different modes.
ApplicableProcIds = Clause0 ^ clause_applicable_procs,
( if
(
(
ApplicableProcIds = all_modes
% Clauses that apply in all modes pose no purity problem.
;
ApplicableProcIds = selected_modes(ClauseProcIds),
list.sort(ClauseProcIds, SortedClauseProcIds),
AllProcIds = pred_info_procids(PredInfo),
% Clauses that apply in some modes pose a purity problem
% only if the *some* modes are not actually *all* the modes.
% (The list returned by pred_info_procids is always sorted.)
SortedClauseProcIds = AllProcIds
;
( ApplicableProcIds = unify_in_in_modes
; ApplicableProcIds = unify_non_in_in_modes
)
% Clauses that have these ApplicableProcIds are all created
% by the compiler, and it creates them pure by construction.
)
;
pred_info_get_markers(PredInfo, Markers),
check_marker(Markers, marker_promised_equivalent_clauses)
;
pred_info_get_goal_type(PredInfo, GoalType),
GoalType = goal_type_foreign
)
then
ClausePurity = purity_pure
else
ClausePurity = purity_impure
),
Purity = worst_purity(BodyPurity0, ClausePurity),
goal_info_set_purity(Purity, GoalInfo0, GoalInfo1),
Goal1 = hlds_goal(GoalExpr1, GoalInfo1),
NeedToRequantify = !.Info ^ pi_requant,
(
NeedToRequantify = need_to_requantify,
pred_info_get_clauses_info(PredInfo, ClausesInfo),
clauses_info_get_headvar_list(ClausesInfo, HeadVars),
VarTypes1 = !.Info ^ pi_vartypes,
VarSet1 = !.Info ^ pi_varset,
% The RTTI varmaps here are just a dummy value, because the real ones
% are not introduced until polymorphism.
rtti_varmaps_init(EmptyRttiVarmaps),
implicitly_quantify_clause_body_general(
ordinary_nonlocals_maybe_lambda,
HeadVars, _Warnings, Goal1, Goal,
VarSet1, VarSet, VarTypes1, VarTypes, EmptyRttiVarmaps, _),
!Info ^ pi_vartypes := VarTypes,
!Info ^ pi_varset := VarSet
;
NeedToRequantify = do_not_need_to_requantify,
Goal = Goal1
),
Clause = Clause0 ^ clause_body := Goal.
%-----------------------------------------------------------------------------%
:- pred compute_goal_purity(hlds_goal::in, hlds_goal::out, purity::out,
contains_trace_goal::out, purity_info::in, purity_info::out) is det.
compute_goal_purity(Goal0, Goal, Purity, ContainsTrace, !Info) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
compute_expr_purity(GoalExpr0, GoalExpr, GoalInfo0, Purity, ContainsTrace,
!Info),
update_purity_ct_in_goal_info(Purity, ContainsTrace, GoalInfo0, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred update_purity_ct_in_goal_info(purity::in, contains_trace_goal::in,
hlds_goal_info::in, hlds_goal_info::out) is det.
update_purity_ct_in_goal_info(Purity, ContainsTrace, !GoalInfo) :-
goal_info_set_purity(Purity, !GoalInfo),
(
ContainsTrace = contains_trace_goal,
goal_info_add_feature(feature_contains_trace, !GoalInfo)
;
ContainsTrace = contains_no_trace_goal,
goal_info_remove_feature(feature_contains_trace, !GoalInfo)
).
% Compute the purity of a list of hlds_goals. Since the purity of a
% disjunction is computed the same way as the purity of a conjunction,
% we use the same code for both
%
% NOTE: Please update Mercury.options if this predicate is moved to another
% module. It must be compiled with --optimize-constructor-last-call.
%
:- pred compute_goals_purity(list(hlds_goal)::in, list(hlds_goal)::out,
purity::in, purity::out, contains_trace_goal::in, contains_trace_goal::out,
purity_info::in, purity_info::out) is det.
compute_goals_purity([], [], !Purity, !ContainsTrace, !Info).
compute_goals_purity([HeadGoal0 | TailGoals0], Goals, !Purity, !ContainsTrace,
!Info) :-
compute_goal_purity(HeadGoal0, HeadGoal, GoalPurity, GoalContainsTrace,
!Info),
!:Purity = worst_purity(GoalPurity, !.Purity),
!:ContainsTrace = worst_contains_trace(GoalContainsTrace, !.ContainsTrace),
compute_goals_purity(TailGoals0, TailGoals, !Purity, !ContainsTrace,
!Info),
Goals = [HeadGoal | TailGoals]. % lcmc
:- pred compute_cases_purity(list(case)::in, list(case)::out,
purity::in, purity::out, contains_trace_goal::in, contains_trace_goal::out,
purity_info::in, purity_info::out) is det.
compute_cases_purity([], [], !Purity, !ContainsTrace, !Info).
compute_cases_purity([Case0 | Cases0], [Case | Cases], !Purity, !ContainsTrace,
!Info) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
compute_goal_purity(Goal0, Goal, GoalPurity, GoalContainsTrace, !Info),
Case = case(MainConsId, OtherConsIds, Goal),
!:Purity = worst_purity(GoalPurity, !.Purity),
!:ContainsTrace = worst_contains_trace(GoalContainsTrace, !.ContainsTrace),
compute_cases_purity(Cases0, Cases, !Purity, !ContainsTrace, !Info).
:- pred compute_parallel_goals_purity(list(hlds_goal)::in,
list(hlds_goal)::out, purity::in, purity::out, contains_trace_goal::in,
contains_trace_goal::out, purity_info::in, purity_info::out) is det.
compute_parallel_goals_purity([], [], !Purity, !ContainsTrace, !Info).
compute_parallel_goals_purity([Goal0 | Goals0], [Goal | Goals], !Purity,
!ContainsTrace, !Info) :-
compute_goal_purity(Goal0, Goal, GoalPurity, GoalContainsTrace, !Info),
(
( GoalPurity = purity_pure
; GoalPurity = purity_semipure
)
;
GoalPurity = purity_impure,
Goal0 = hlds_goal(_, GoalInfo0),
Context = goal_info_get_context(GoalInfo0),
Spec = impure_parallel_conjunct_error(Context, GoalPurity),
purity_info_add_message(Spec, !Info)
),
!:Purity = worst_purity(GoalPurity, !.Purity),
!:ContainsTrace = worst_contains_trace(GoalContainsTrace, !.ContainsTrace),
compute_parallel_goals_purity(Goals0, Goals, !Purity, !ContainsTrace,
!Info).
%-----------------------------------------------------------------------------%
:- pred compute_expr_purity(hlds_goal_expr::in, hlds_goal_expr::out,
hlds_goal_info::in, purity::out, contains_trace_goal::out,
purity_info::in, purity_info::out) is det.
compute_expr_purity(GoalExpr0, GoalExpr, GoalInfo, Purity, ContainsTrace,
!Info) :-
(
GoalExpr0 = conj(ConjType, Goals0),
(
ConjType = plain_conj,
compute_goals_purity(Goals0, Goals, purity_pure, Purity,
contains_no_trace_goal, ContainsTrace, !Info)
;
ConjType = parallel_conj,
compute_parallel_goals_purity(Goals0, Goals, purity_pure, Purity,
contains_no_trace_goal, ContainsTrace, !Info)
),
GoalExpr = conj(ConjType, Goals)
;
GoalExpr0 = plain_call(_, _, _, _, _, _),
compute_plain_call_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info)
;
GoalExpr0 = generic_call(GenericCall0, _ArgVars, _Modes0,
_MaybeArgRegs, _Det),
GoalExpr = GoalExpr0,
(
GenericCall0 = higher_order(_, Purity, _, _)
;
GenericCall0 = class_method(_, _, _, _),
Purity = purity_pure % XXX this is wrong!
;
( GenericCall0 = cast(_)
; GenericCall0 = event_call(_)
),
Purity = purity_pure
),
ContainsTrace = contains_no_trace_goal
;
GoalExpr0 = switch(Var, Canfail, Cases0),
compute_cases_purity(Cases0, Cases, purity_pure, Purity,
contains_no_trace_goal, ContainsTrace, !Info),
GoalExpr = switch(Var, Canfail, Cases)
;
GoalExpr0 = unify(_, _, _, _, _),
compute_unify_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info)
;
GoalExpr0 = disj(Goals0),
compute_goals_purity(Goals0, Goals, purity_pure, Purity,
contains_no_trace_goal, ContainsTrace, !Info),
GoalExpr = disj(Goals)
;
GoalExpr0 = negation(Goal0),
% Eliminate double negation.
negate_goal(Goal0, GoalInfo, NotGoal0),
( if NotGoal0 = hlds_goal(negation(Goal1), _) then
compute_goal_purity(Goal1, Goal, Purity, ContainsTrace, !Info),
GoalExpr = negation(Goal)
else
compute_goal_purity(NotGoal0, NotGoal1, Purity, ContainsTrace,
!Info),
NotGoal1 = hlds_goal(GoalExpr, _)
)
;
GoalExpr0 = scope(_, _),
compute_scope_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info)
;
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
compute_goal_purity(Cond0, Cond, Purity1, ContainsTrace1, !Info),
compute_goal_purity(Then0, Then, Purity2, ContainsTrace2, !Info),
compute_goal_purity(Else0, Else, Purity3, ContainsTrace3, !Info),
worst_purity(Purity1, Purity2) = Purity12,
worst_purity(Purity12, Purity3) = Purity,
( if
( ContainsTrace1 = contains_trace_goal
; ContainsTrace2 = contains_trace_goal
; ContainsTrace3 = contains_trace_goal
)
then
ContainsTrace = contains_trace_goal
else
ContainsTrace = contains_no_trace_goal
),
GoalExpr = if_then_else(Vars, Cond, Then, Else)
;
GoalExpr0 = call_foreign_proc(Attributes, _, _, _, _, _, _),
Purity = get_purity(Attributes),
ContainsTrace = contains_no_trace_goal,
GoalExpr = GoalExpr0
;
GoalExpr0 = shorthand(_),
compute_shorthand_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info)
).
%-----------------------------------------------------------------------------%
%
% Auxiliary procedures for handling plain calls.
%
:- pred compute_plain_call_expr_purity(
hlds_goal_expr::in(goal_expr_plain_call), hlds_goal_expr::out,
hlds_goal_info::in, purity::out, contains_trace_goal::out,
purity_info::in, purity_info::out) is det.
compute_plain_call_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info) :-
GoalExpr0 = plain_call(PredId0, ProcId, ArgVars, Status,
MaybeUnifyContext, SymName0),
RunPostTypecheck = !.Info ^ pi_run_post_typecheck,
PredInfo = !.Info ^ pi_pred_info,
ModuleInfo = !.Info ^ pi_module_info,
CallContext = goal_info_get_context(GoalInfo),
(
RunPostTypecheck = run_post_typecheck_tasks,
finally_resolve_pred_overloading(ModuleInfo, PredInfo,
PredId0, SymName0, ArgVars, CallContext, PredId, SymName),
( if
% Convert any calls to private_builtin.unsafe_type_cast
% into unsafe_type_cast generic calls.
SymName = qualified(mercury_private_builtin_module,
"unsafe_type_cast"),
ArgVars = [InputArg, OutputArg]
then
GoalExpr = generic_call(cast(unsafe_type_cast),
[InputArg, OutputArg], [in_mode, out_mode],
arg_reg_types_unset, detism_det)
else
GoalExpr = plain_call(PredId, ProcId, ArgVars, Status,
MaybeUnifyContext, SymName)
)
;
RunPostTypecheck = do_not_run_post_typecheck_tasks,
PredId = PredId0,
GoalExpr = GoalExpr0
),
DeclaredPurity = goal_info_get_purity(GoalInfo),
perform_goal_purity_checks(CallContext, PredId,
DeclaredPurity, ActualPurity, !Info),
Purity = ActualPurity,
ContainsTrace = contains_no_trace_goal.
% Handle any unresolved overloading for a predicate call.
%
:- pred finally_resolve_pred_overloading(module_info::in, pred_info::in,
pred_id::in, sym_name::in, list(prog_var)::in, prog_context::in,
pred_id::out, sym_name::out) is det.
finally_resolve_pred_overloading(ModuleInfo, CallerPredInfo,
PredId0, PredName0, Args0, Context, PredId, PredName) :-
% In the case of a call to an overloaded predicate, typecheck.m
% does not figure out the correct pred_id. We must do that here.
( if PredId0 = invalid_pred_id then
pred_info_get_typevarset(CallerPredInfo, TVarSet),
pred_info_get_exist_quant_tvars(CallerPredInfo, ExistQVars),
pred_info_get_external_type_params(CallerPredInfo, ExternalTypeParams),
pred_info_get_markers(CallerPredInfo, Markers),
pred_info_get_clauses_info(CallerPredInfo, ClausesInfo),
clauses_info_get_vartypes(ClausesInfo, VarTypes),
lookup_var_types(VarTypes, Args0, ArgTypes),
resolve_pred_overloading(ModuleInfo, Markers, TVarSet, ExistQVars,
ArgTypes, ExternalTypeParams, Context, PredName0, PredName, PredId)
else
PredId = PredId0,
module_info_pred_info(ModuleInfo, PredId, PredInfo),
PredModule = pred_info_module(PredInfo),
PredBaseName = pred_info_name(PredInfo),
PredName = qualified(PredModule, PredBaseName)
).
% Perform purity checking of the actual and declared purity,
% and check that promises are consistent.
%
% ActualPurity: The inferred purity of the goal
% DeclaredPurity: The declared purity of the goal
%
:- pred perform_goal_purity_checks(prog_context::in, pred_id::in, purity::in,
purity::out, purity_info::in, purity_info::out) is det.
perform_goal_purity_checks(Context, PredId, DeclaredPurity, ActualPurity,
!Info) :-
ModuleInfo = !.Info ^ pi_module_info,
PredInfo = !.Info ^ pi_pred_info,
module_info_pred_info(ModuleInfo, PredId, CalleePredInfo),
pred_info_get_purity(CalleePredInfo, ActualPurity),
( if
% The purity of the callee should match the
% purity declared at the call.
ActualPurity = DeclaredPurity
then
true
else if
% Don't require purity annotations on calls in compiler-generated code.
is_unify_index_or_compare_pred(PredInfo)
then
true
else if
less_pure(ActualPurity, DeclaredPurity)
then
Spec = error_missing_body_impurity_decl(ModuleInfo, PredId, Context),
purity_info_add_message(Spec, !Info)
else if
% We don't warn about exaggerated impurity decls in class methods
% or instance methods --- it just means that the predicate provided
% as an implementation was more pure than necessary.
% Don't warn about exaggerated impurity decls in compiler-generated
% mutable predicates either.
pred_info_get_markers(PredInfo, Markers),
( check_marker(Markers, marker_class_method)
; check_marker(Markers, marker_class_instance_method)
; check_marker(Markers, marker_mutable_access_pred)
)
then
true
else
Spec = warn_unnecessary_body_impurity_decl(ModuleInfo, PredId,
Context, DeclaredPurity),
purity_info_add_message(Spec, !Info)
).
%-----------------------------------------------------------------------------%
%
% Auxiliary procedures for handling unifications.
%
:- pred compute_unify_expr_purity(hlds_goal_expr::in(goal_expr_unify),
hlds_goal_expr::out, hlds_goal_info::in, purity::out,
contains_trace_goal::out, purity_info::in, purity_info::out) is det.
compute_unify_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info) :-
GoalExpr0 = unify(LHSVar, RHS0, Mode, Unification, UnifyContext),
(
RHS0 = rhs_lambda_goal(LambdaPurity, Groundness, PredOrFunc,
EvalMethod, LambdaNonLocals, LambdaQuantVars,
LambdaModes, LambdaDetism, LambdaGoal0),
LambdaGoal0 = hlds_goal(LambdaGoalExpr0, LambdaGoalInfo0),
compute_expr_purity(LambdaGoalExpr0, LambdaGoalExpr,
LambdaGoalInfo0, GoalPurity, _, !Info),
LambdaGoal = hlds_goal(LambdaGoalExpr, LambdaGoalInfo0),
RHS = rhs_lambda_goal(LambdaPurity, Groundness, PredOrFunc,
EvalMethod, LambdaNonLocals, LambdaQuantVars,
LambdaModes, LambdaDetism, LambdaGoal),
check_closure_purity(GoalInfo, LambdaPurity, GoalPurity,
ClosureSpecs),
purity_info_add_messages(ClosureSpecs, !Info),
GoalExpr = unify(LHSVar, RHS, Mode, Unification, UnifyContext),
% The unification itself is always pure,
% even if the lambda expression body is impure.
DeclaredPurity = goal_info_get_purity(GoalInfo),
(
( DeclaredPurity = purity_impure
; DeclaredPurity = purity_semipure
),
Context = goal_info_get_context(GoalInfo),
Spec = impure_unification_expr_error(Context, DeclaredPurity),
purity_info_add_message(Spec, !Info)
;
DeclaredPurity = purity_pure
),
ActualPurity = purity_pure,
ContainsTrace = contains_no_trace_goal
;
RHS0 = rhs_functor(ConsId, _, Args),
RunPostTypecheck = !.Info ^ pi_run_post_typecheck,
(
RunPostTypecheck = run_post_typecheck_tasks,
ModuleInfo = !.Info ^ pi_module_info,
PredInfo0 = !.Info ^ pi_pred_info,
VarTypes0 = !.Info ^ pi_vartypes,
VarSet0 = !.Info ^ pi_varset,
resolve_unify_functor(ModuleInfo, LHSVar, ConsId, Args, Mode,
Unification, UnifyContext, GoalInfo, PredInfo0, PredInfo,
VarSet0, VarSet, VarTypes0, VarTypes, Goal1, IsPlainUnify),
!Info ^ pi_vartypes := VarTypes,
!Info ^ pi_varset := VarSet,
!Info ^ pi_pred_info := PredInfo,
(
IsPlainUnify = is_plain_unify
;
IsPlainUnify = is_not_plain_unify,
!Info ^ pi_converted_unify := have_converted_unify
;
IsPlainUnify = is_unknown_ref(Spec),
purity_info_add_message(Spec, !Info)
)
;
RunPostTypecheck = do_not_run_post_typecheck_tasks,
Goal1 = hlds_goal(GoalExpr0, GoalInfo)
),
( if Goal1 = hlds_goal(unify(_, _, _, _, _), _) then
check_var_functor_unify_purity(!.Info, GoalInfo,
LHSVar, ConsId, Args, UnifySpecs),
purity_info_add_messages(UnifySpecs, !Info),
ActualPurity = purity_pure,
ContainsTrace = contains_no_trace_goal,
Goal = Goal1
else
compute_goal_purity(Goal1, Goal, ActualPurity, ContainsTrace,
!Info)
),
Goal = hlds_goal(GoalExpr, _)
;
RHS0 = rhs_var(_),
GoalExpr = GoalExpr0,
ActualPurity = purity_pure,
ContainsTrace = contains_no_trace_goal
),
Purity = ActualPurity.
:- pred check_var_functor_unify_purity(purity_info::in, hlds_goal_info::in,
prog_var::in, cons_id::in, list(prog_var)::in, list(error_spec)::out)
is det.
check_var_functor_unify_purity(Info, GoalInfo, Var, ConsId, Args, Specs) :-
% If the unification involves a higher order RHS, check that
% the purity of the ConsId matches the purity of the variable's type.
VarTypes = Info ^ pi_vartypes,
lookup_var_type(VarTypes, Var, TypeOfVar),
PredInfo = Info ^ pi_pred_info,
pred_info_get_markers(PredInfo, CallerMarkers),
Context = goal_info_get_context(GoalInfo),
( if
ConsId = cons(PName, _, _),
type_is_higher_order_details(TypeOfVar, TypePurity, PredOrFunc,
_EvalMethod, VarArgTypes)
then
pred_info_get_typevarset(PredInfo, TVarSet),
pred_info_get_exist_quant_tvars(PredInfo, ExistQTVars),
pred_info_get_external_type_params(PredInfo, ExternalTypeParams),
lookup_var_types(VarTypes, Args, ArgTypes0),
list.append(ArgTypes0, VarArgTypes, PredArgTypes),
ModuleInfo = Info ^ pi_module_info,
( if
get_pred_id_by_types(calls_are_fully_qualified(CallerMarkers),
PName, PredOrFunc, TVarSet, ExistQTVars, PredArgTypes,
ExternalTypeParams, ModuleInfo, Context, CalleePredId)
then
module_info_pred_info(ModuleInfo, CalleePredId, CalleePredInfo),
pred_info_get_purity(CalleePredInfo, CalleePurity),
check_closure_purity(GoalInfo, TypePurity, CalleePurity,
ClosureSpecs)
else
% If we can't find the type of the function, it is because
% typecheck couldn't give it one. Typechecking gives an error
% in this case, we just keep silent.
ClosureSpecs = []
)
else
% No closure; no specs.
ClosureSpecs = []
),
% The unification itself is always pure,
% even if it is a unification with an impure higher-order term.
% Check for a bogus purity annotation on the unification.
DeclaredPurity = goal_info_get_purity(GoalInfo),
(
( DeclaredPurity = purity_semipure
; DeclaredPurity = purity_impure
),
% Don't warn about bogus purity annotations in compiler-generated
% mutable predicates.
( if check_marker(CallerMarkers, marker_mutable_access_pred) then
Specs = ClosureSpecs
else
Spec = impure_unification_expr_error(Context, DeclaredPurity),
Specs = [Spec | ClosureSpecs]
)
;
DeclaredPurity = purity_pure,
Specs = ClosureSpecs
).
:- pred check_closure_purity(hlds_goal_info::in, purity::in, purity::in,
list(error_spec)::out) is det.
check_closure_purity(GoalInfo, DeclaredPurity, ActualPurity, Specs) :-
( if less_pure(ActualPurity, DeclaredPurity) then
Context = goal_info_get_context(GoalInfo),
Spec = report_error_closure_purity(Context,
DeclaredPurity, ActualPurity),
Specs = [Spec]
else
% We don't bother to warn if the DeclaredPurity is less pure than the
% ActualPurity; that would lead to too many spurious warnings.
Specs = []
).
%-----------------------------------------------------------------------------%
%
% Auxiliary procedures for handling scopes (mostly from_ground_term scopes).
%
:- pred compute_scope_expr_purity(hlds_goal_expr::in(goal_expr_scope),
hlds_goal_expr::out, hlds_goal_info::in, purity::out,
contains_trace_goal::out, purity_info::in, purity_info::out) is det.
compute_scope_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info) :-
GoalExpr0 = scope(Reason0, SubGoal0),
(
Reason0 = promise_purity(PromisedPurity),
compute_goal_purity(SubGoal0, SubGoal, _, ContainsTrace, !Info),
GoalExpr = scope(Reason0, SubGoal),
Purity = PromisedPurity
;
Reason0 = from_ground_term(TermVar, Kind0),
(
( Kind0 = from_ground_term_initial
; Kind0 = from_ground_term_construct
),
SubGoal0 = hlds_goal(SubGoalExpr0, SubGoalInfo0),
( if SubGoalExpr0 = conj(plain_conj, SubGoals0Prime) then
SubGoals0 = SubGoals0Prime
else
unexpected($pred,
"from_ground_term_initial goal is not plain conj")
),
PostTypeCheck = !.Info ^ pi_run_post_typecheck,
(
PostTypeCheck = run_post_typecheck_tasks,
compute_goal_purity_in_fgt_ptc(SubGoals0,
[], RevMarkedSubGoals, purity_pure, Purity,
contains_no_trace_goal, ContainsTrace, !Info,
fgt_invariants_kept, Invariants),
(
Invariants = fgt_invariants_kept,
list.map(project_kept_goal,
RevMarkedSubGoals, RevSubGoals),
list.reverse(RevSubGoals, SubGoals),
SubGoalExpr = conj(plain_conj, SubGoals),
update_purity_ct_in_goal_info(Purity, ContainsTrace,
SubGoalInfo0, SubGoalInfo),
SubGoal = hlds_goal(SubGoalExpr, SubGoalInfo),
GoalExpr = scope(Reason0, SubGoal)
;
Invariants = fgt_invariants_broken,
(
Kind0 = from_ground_term_initial,
ConstructOrderMarkedSubGoals = RevMarkedSubGoals,
Order = deconstruct_top_down
;
Kind0 = from_ground_term_construct,
list.reverse(RevMarkedSubGoals,
ConstructOrderMarkedSubGoals),
Order = construct_bottom_up
),
introduce_partial_fgt_scopes(GoalInfo, SubGoalInfo0,
ConstructOrderMarkedSubGoals, Order, SubGoal),
% Delete the scope wrapper around SubGoal0.
SubGoal = hlds_goal(GoalExpr, _)
)
;
PostTypeCheck = do_not_run_post_typecheck_tasks,
GoalExpr = GoalExpr0,
compute_goal_purity_in_fgt_no_ptc(SubGoals0, !.Info,
[], Specs),
purity_info_add_messages(Specs, !Info),
Purity = purity_pure,
ContainsTrace = contains_no_trace_goal
)
;
( Kind0 = from_ground_term_deconstruct
; Kind0 = from_ground_term_other
),
!Info ^ pi_converted_unify := have_not_converted_unify,
compute_goal_purity(SubGoal0, SubGoal, Purity, ContainsTrace,
!Info),
HaveConvertedUnify = !.Info ^ pi_converted_unify,
(
HaveConvertedUnify = have_not_converted_unify,
GoalExpr = scope(Reason0, SubGoal)
;
HaveConvertedUnify = have_converted_unify,
% We could delete the scope. However, there may be
% some compiler passes than could benefit from it,
% and I expect we will get here rarely enough that
% what we do here does not matter all that much.
Reason = from_ground_term(TermVar, from_ground_term_other),
GoalExpr = scope(Reason, SubGoal)
)
)
;
( Reason0 = disable_warnings(_, _)
; Reason0 = promise_solutions(_, _)
; Reason0 = require_detism(_)
; Reason0 = require_complete_switch(_)
; Reason0 = require_switch_arms_detism(_, _)
; Reason0 = commit(_)
; Reason0 = barrier(_)
; Reason0 = exist_quant(_)
),
compute_goal_purity(SubGoal0, SubGoal, Purity, ContainsTrace, !Info),
GoalExpr = scope(Reason0, SubGoal)
;
Reason0 = trace_goal(_, _, _, _, _),
compute_goal_purity(SubGoal0, SubGoal, _SubPurity, _, !Info),
GoalExpr = scope(Reason0, SubGoal),
Purity = purity_pure,
ContainsTrace = contains_trace_goal
;
% Purity checking happens before the introduction of loop control
% scopes.
Reason0 = loop_control(_, _, _),
unexpected($pred, "loop_control")
).
:- pred compute_goal_purity_in_fgt_ptc(list(hlds_goal)::in,
list(fgt_marked_goal)::in, list(fgt_marked_goal)::out,
purity::in, purity::out, contains_trace_goal::in, contains_trace_goal::out,
purity_info::in, purity_info::out,
fgt_invariants_status::in, fgt_invariants_status::out) is det.
compute_goal_purity_in_fgt_ptc([], !RevMarkedSubGoals,
!Purity, !ContainsTrace, !Info, !Invariants).
compute_goal_purity_in_fgt_ptc([Goal0 | Goals0], !RevMarkedSubGoals,
!Purity, !ContainsTrace, !Info, !Invariants) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
( if
GoalExpr0 = unify(XVarPrime, Y, ModePrime, UnificationPrime,
UnifyContextPrime),
Y = rhs_functor(ConsIdPrime, _, YVarsPrime)
then
XVar = XVarPrime,
Mode = ModePrime,
Unification = UnificationPrime,
UnifyContext = UnifyContextPrime,
ConsId = ConsIdPrime,
YVars = YVarsPrime
else
unexpected($pred,
"from_ground_term_initial conjunct is not functor unify")
),
ModuleInfo = !.Info ^ pi_module_info,
PredInfo0 = !.Info ^ pi_pred_info,
VarTypes0 = !.Info ^ pi_vartypes,
VarSet0 = !.Info ^ pi_varset,
resolve_unify_functor(ModuleInfo, XVar, ConsId, YVars, Mode,
Unification, UnifyContext, GoalInfo0, PredInfo0, PredInfo,
VarSet0, VarSet, VarTypes0, VarTypes, Goal1, IsPlainUnify),
Goal1 = hlds_goal(GoalExpr1, GoalInfo1),
(
IsPlainUnify = is_plain_unify,
trace [compiletime(flag("purity_fgt_sanity_tests"))] (
( if GoalExpr1 = unify(_, _, _, _, _) then
true
else
unexpected($pred, "is_plain_unify goal is not unify")
),
expect(unify(PredInfo0, PredInfo), $pred, "PredInfo != PredInfo"),
expect(unify(VarSet0, VarSet), $pred, "VarSet != VarSet"),
expect(unify(VarTypes0, VarTypes), $pred, "VarTypes != VarTypes")
),
check_var_functor_unify_purity(!.Info, GoalInfo0, XVar, ConsId, YVars,
UnifySpecs),
purity_info_add_messages(UnifySpecs, !Info),
% !Purity and !ContainsTrace are unchanged.
update_purity_ct_in_goal_info(purity_pure, contains_no_trace_goal,
GoalInfo1, GoalInfo),
Goal = hlds_goal(GoalExpr1, GoalInfo),
% Goal may be different from Goal0 (e.g. it may have the cons_id
% module qualified), but if resolve_unify_functor returned
% is_plain_unify, then the change does not invalidate
% the invariants of from_ground_term_{initial,construct} scopes.
MarkedSubGoal = fgt_kept_goal(Goal, XVar, YVars)
% !Invariants is unchanged.
;
IsPlainUnify = is_not_plain_unify,
!Info ^ pi_vartypes := VarTypes,
!Info ^ pi_varset := VarSet,
!Info ^ pi_pred_info := PredInfo,
( if GoalExpr1 = unify(_, _, _, _, _) then
check_var_functor_unify_purity(!.Info, GoalInfo0,
XVar, ConsId, YVars, UnifySpecs),
purity_info_add_messages(UnifySpecs, !Info),
update_purity_ct_in_goal_info(purity_pure, contains_no_trace_goal,
GoalInfo1, GoalInfo),
Goal = hlds_goal(GoalExpr1, GoalInfo)
% !Purity and !ContainsTrace are unchanged.
else
compute_goal_purity(Goal1, Goal, GoalPurity, GoalContainsTrace,
!Info),
!:Purity = worst_purity(GoalPurity, !.Purity),
!:ContainsTrace = worst_contains_trace(GoalContainsTrace,
!.ContainsTrace)
),
MarkedSubGoal = fgt_broken_goal(Goal, XVar, YVars),
!:Invariants = fgt_invariants_broken
;
IsPlainUnify = is_unknown_ref(Spec),
purity_info_add_message(Spec, !Info),
MarkedSubGoal = fgt_broken_goal(Goal1, XVar, YVars),
!:Invariants = fgt_invariants_broken
),
!:RevMarkedSubGoals = [MarkedSubGoal | !.RevMarkedSubGoals],
compute_goal_purity_in_fgt_ptc(Goals0, !RevMarkedSubGoals,
!Purity, !ContainsTrace, !Info, !Invariants).
:- pred compute_goal_purity_in_fgt_no_ptc(list(hlds_goal)::in,
purity_info::in, list(error_spec)::in, list(error_spec)::out) is det.
compute_goal_purity_in_fgt_no_ptc([], _, !Specs).
compute_goal_purity_in_fgt_no_ptc([Goal0 | Goals0], Info, !Specs) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
( if
GoalExpr0 = unify(XVarPrime, Y, _, _, _),
Y = rhs_functor(ConsIdPrime, _, YVarsPrime)
then
XVar = XVarPrime,
ConsId = ConsIdPrime,
YVars = YVarsPrime
else
unexpected($pred,
"from_ground_term_initial conjunct is not functor unify")
),
check_var_functor_unify_purity(Info, GoalInfo0, XVar, ConsId, YVars,
UnifySpecs),
!:Specs = UnifySpecs ++ !.Specs,
compute_goal_purity_in_fgt_no_ptc(Goals0, Info, !Specs).
%-----------------------------------------------------------------------------%
%
% Auxiliary procedures for handling shorthand goals (mostly atomic goals).
%
:- pred compute_shorthand_expr_purity(hlds_goal_expr::in(goal_expr_shorthand),
hlds_goal_expr::out, hlds_goal_info::in, purity::out,
contains_trace_goal::out, purity_info::in, purity_info::out) is det.
compute_shorthand_expr_purity(GoalExpr0, GoalExpr, GoalInfo,
Purity, ContainsTrace, !Info) :-
GoalExpr0 = shorthand(ShortHand0),
(
ShortHand0 = atomic_goal(GoalType, Outer, Inner, MaybeOutputVars,
MainGoal0, OrElseGoals0, OrElseInners),
RunPostTypecheck = !.Info ^ pi_run_post_typecheck,
(
RunPostTypecheck = run_post_typecheck_tasks,
VarSet = !.Info ^ pi_varset,
VarTypes = !.Info ^ pi_vartypes,
Outer = atomic_interface_vars(OuterDI, OuterUO),
Context = goal_info_get_context(GoalInfo),
check_outer_var_type(Context, VarTypes, VarSet, OuterDI,
OuterDIType, OuterDITypeSpecs),
check_outer_var_type(Context, VarTypes, VarSet, OuterUO,
OuterUOType, OuterUOTypeSpecs),
( if OuterDIType = OuterUOType then
OuterMismatchSpecs = []
else
OuterMismatchSpecs = [mismatched_outer_var_types(Context)]
),
OuterTypeSpecs = OuterDITypeSpecs ++ OuterUOTypeSpecs ++
OuterMismatchSpecs,
(
OuterTypeSpecs = [_ | _],
list.foldl(purity_info_add_message, OuterTypeSpecs, !Info),
MainGoal1 = MainGoal0,
OrElseGoals1 = OrElseGoals0
;
OuterTypeSpecs = [],
AtomicGoalsAndInners = assoc_list.from_corresponding_lists(
[MainGoal0 | OrElseGoals0], [Inner | OrElseInners]),
list.map_foldl(wrap_inner_outer_goals(Outer),
AtomicGoalsAndInners, AllAtomicGoals1, !Info),
(
AllAtomicGoals1 = [MainGoal1 | OrElseGoals1]
;
AllAtomicGoals1 = [],
unexpected($pred, "AllAtomicGoals1 = []")
),
!Info ^ pi_requant := need_to_requantify
)
;
RunPostTypecheck = do_not_run_post_typecheck_tasks,
MainGoal1 = MainGoal0,
OrElseGoals1 = OrElseGoals0
),
compute_goal_purity(MainGoal1, MainGoal, Purity1, ContainsTrace1,
!Info),
compute_goals_purity(OrElseGoals1, OrElseGoals,
purity_pure, Purity2, contains_no_trace_goal, ContainsTrace2,
!Info),
Purity = worst_purity(Purity1, Purity2),
( if
( ContainsTrace1 = contains_trace_goal
; ContainsTrace2 = contains_trace_goal
)
then
ContainsTrace = contains_trace_goal
else
ContainsTrace = contains_no_trace_goal
),
ShortHand = atomic_goal(GoalType, Outer, Inner, MaybeOutputVars,
MainGoal, OrElseGoals, OrElseInners),
GoalExpr = shorthand(ShortHand)
;
ShortHand0 = try_goal(MaybeIO, ResultVar, SubGoal0),
compute_goal_purity(SubGoal0, SubGoal, Purity, ContainsTrace,
!Info),
ShortHand = try_goal(MaybeIO, ResultVar, SubGoal),
GoalExpr = shorthand(ShortHand)
;
ShortHand0 = bi_implication(_, _),
% These should have been expanded out by now.
unexpected($pred, "bi_implication")
).
:- pred check_outer_var_type(prog_context::in, vartypes::in, prog_varset::in,
prog_var::in, mer_type::out, list(error_spec)::out) is det.
check_outer_var_type(Context, VarTypes, VarSet, Var, VarType, Specs) :-
lookup_var_type(VarTypes, Var, VarType),
( if
( VarType = io_state_type
; VarType = stm_atomic_type
)
then
Specs = []
else
Spec = bad_outer_var_type_error(Context, VarSet, Var),
Specs = [Spec]
).
:- pred wrap_inner_outer_goals(atomic_interface_vars::in,
pair(hlds_goal, atomic_interface_vars)::in, hlds_goal::out,
purity_info::in, purity_info::out) is det.
wrap_inner_outer_goals(Outer, Goal0 - Inner, Goal, !Info) :-
Goal0 = hlds_goal(_, GoalInfo0),
NonLocals0 = goal_info_get_nonlocals(GoalInfo0),
Context = goal_info_get_context(GoalInfo0),
Outer = atomic_interface_vars(OuterDI, OuterUO),
Inner = atomic_interface_vars(InnerDI, InnerUO),
% Generate the STM outer_to_inner and inner_to_outer goals.
OuterToInnerPred = "stm_from_outer_to_inner",
InnerToOuterPred = "stm_from_inner_to_outer",
ModuleInfo = !.Info ^ pi_module_info,
Clobbered = ground(clobbered, none_or_default_func),
Unique = ground(unique, none_or_default_func),
generate_simple_call(ModuleInfo, mercury_stm_builtin_module,
OuterToInnerPred, pf_predicate, only_mode,
detism_det, purity_pure, [OuterDI, InnerDI], [],
instmap_delta_from_assoc_list([OuterDI - Clobbered, InnerDI - Unique]),
Context, OuterToInnerGoal),
generate_simple_call(ModuleInfo, mercury_stm_builtin_module,
InnerToOuterPred, pf_predicate, only_mode,
detism_det, purity_pure, [InnerUO, OuterUO], [],
instmap_delta_from_assoc_list([InnerUO - Clobbered, OuterUO - Unique]),
Context, InnerToOuterGoal),
WrapExpr = conj(plain_conj, [OuterToInnerGoal, Goal0, InnerToOuterGoal]),
% After the addition of OuterToInnerGoal and InnerToOuterGoal,
% OuterDI and OuterUO will definitely be used by the code inside the new
% goal, and *should* be used by code outside the goal. However, even if
% they are not, the nonlocals set is allowed to overapproximate.
set_of_var.insert_list([OuterDI, OuterUO], NonLocals0, NonLocals),
goal_info_set_nonlocals(NonLocals, GoalInfo0, GoalInfo1),
goal_info_add_feature(feature_contains_stm_inner_outer, GoalInfo1,
GoalInfo),
Goal = hlds_goal(WrapExpr, GoalInfo).
%-----------------------------------------------------------------------------%
%
% This part of the module is for generating error messages.
%
:- func pred_context(module_info, pred_info, pred_id) = list(format_component).
pred_context(ModuleInfo, _PredInfo, PredId) = Pieces :-
PredPieces = describe_one_pred_name(ModuleInfo, should_not_module_qualify,
PredId),
Pieces = [words("In")] ++ PredPieces ++ [suffix(":"), nl].
:- func error_inconsistent_purity_promise(module_info, pred_info, pred_id,
purity) = error_spec.
error_inconsistent_purity_promise(ModuleInfo, PredInfo, PredId, Purity)
= Spec :-
pred_info_get_context(PredInfo, Context),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
PredOrFuncStr = pred_or_func_to_full_str(PredOrFunc),
purity_name(Purity, PurityName),
PredContextPieces = pred_context(ModuleInfo, PredInfo, PredId),
MainPieces = PredContextPieces ++
[words("error: declared"), fixed(PurityName),
words("but promised pure."), nl],
VerbosePieces = [words("A pure"), fixed(PredOrFuncStr),
words("that invokes impure or semipure code"),
words("should be promised pure and should have"),
words("no impurity declaration."), nl],
Msg = simple_msg(Context,
[always(MainPieces), verbose_only(verbose_always, VerbosePieces)]),
Spec = error_spec(severity_error, phase_purity_check, [Msg]).
:- func warn_pred_body_too_pure(module_info, pred_info, pred_id,
purity, purity) = error_spec.
warn_pred_body_too_pure(ModuleInfo, PredInfo, PredId,
ActualPurity, DeclaredPurity) = Spec :-
pred_info_get_context(PredInfo, Context),
PredContextPieces = pred_context(ModuleInfo, PredInfo, PredId),
purity_name(DeclaredPurity, DeclaredPurityName),
purity_name(ActualPurity, ActualPurityName),
Pieces = PredContextPieces ++
[words("warning: declared"), fixed(DeclaredPurityName),
words("but actually"), fixed(ActualPurityName), suffix("."), nl],
Msg = simple_msg(Context, [always(Pieces)]),
Spec = error_spec(severity_warning, phase_purity_check, [Msg]).
:- func warn_unnecessary_purity_promise(module_info, pred_info, pred_id,
purity) = error_spec.
warn_unnecessary_purity_promise(ModuleInfo, PredInfo, PredId, PromisedPurity)
= Spec :-
pred_info_get_context(PredInfo, Context),
PredContextPieces = pred_context(ModuleInfo, PredInfo, PredId),
(
PromisedPurity = purity_pure,
Pragma = "promise_pure",
CodeStr = "impure or semipure"
;
PromisedPurity = purity_semipure,
Pragma = "promise_semipure",
CodeStr = "impure"
;
PromisedPurity = purity_impure,
unexpected($pred, "promise_impure")
),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
MainPieces = [words("warning: unnecessary"), quote(Pragma),
words("pragma."), nl],
VerbosePieces = [words("This"), p_or_f(PredOrFunc),
words("does not invoke any"), fixed(CodeStr), words("code,"),
words("so there is no need for a"), quote(Pragma), words("pragma."),
nl],
Msg = simple_msg(Context,
[always(PredContextPieces), always(MainPieces),
verbose_only(verbose_always, VerbosePieces)]),
Spec = error_spec(severity_warning, phase_purity_check, [Msg]).
:- func error_not_pure_enough(module_info, pred_info, pred_id, purity)
= error_spec.
error_not_pure_enough(ModuleInfo, PredInfo, PredId, Purity) = Spec :-
pred_info_get_context(PredInfo, Context),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
PredOrFuncStr = pred_or_func_to_full_str(PredOrFunc),
PredContextPieces = pred_context(ModuleInfo, PredInfo, PredId),
pred_info_get_purity(PredInfo, DeclaredPurity),
purity_name(Purity, PurityName),
purity_name(DeclaredPurity, DeclaredPurityName),
Pieces1 = [words("purity error:"), fixed(PredOrFuncStr),
words("is"), fixed(PurityName), suffix("."), nl],
( if is_unify_index_or_compare_pred(PredInfo) then
Pieces2 = [words("It must be pure.")]
else
Pieces2 = [words("It must be declared"), quote(PurityName),
words("or promised"), fixed(DeclaredPurityName), suffix("."), nl]
),
Msg = simple_msg(Context,
[always(PredContextPieces), always(Pieces1), always(Pieces2)]),
Spec = error_spec(severity_error, phase_purity_check, [Msg]).
:- func error_missing_body_impurity_decl(module_info, pred_id, prog_context)
= error_spec.
error_missing_body_impurity_decl(ModuleInfo, PredId, Context) = Spec :-
module_info_pred_info(ModuleInfo, PredId, PredInfo),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
pred_info_get_purity(PredInfo, Purity),
purity_name(Purity, PurityName),
PredPieces = describe_one_pred_name(ModuleInfo, should_module_qualify,
PredId),
Pieces1 = [words("In call to "), fixed(PurityName)] ++
PredPieces ++ [suffix(":"), nl],
(
PredOrFunc = pf_predicate,
Pieces2 = [words("purity error: call must be preceded by"),
quote(PurityName), words("indicator."), nl]
;
PredOrFunc = pf_function,
Pieces2 = [words("purity error: call must be in"),
words("an explicit unification which is preceded by"),
quote(PurityName), words("indicator."), nl]
),
Msg = simple_msg(Context, [always(Pieces1), always(Pieces2)]),
Spec = error_spec(severity_error, phase_purity_check, [Msg]).
:- func warn_unnecessary_body_impurity_decl(module_info, pred_id, prog_context,
purity) = error_spec.
warn_unnecessary_body_impurity_decl(ModuleInfo, PredId, Context,
DeclaredPurity) = Spec :-
module_info_pred_info(ModuleInfo, PredId, PredInfo),
pred_info_get_purity(PredInfo, ActualPurity),
purity_name(DeclaredPurity, DeclaredPurityName),
purity_name(ActualPurity, ActualPurityName),
PredPieces = describe_one_pred_name(ModuleInfo, should_module_qualify,
PredId),
Pieces1 = [words("In call to")] ++ PredPieces ++ [suffix(":"), nl,
words("warning: unnecessary"), quote(DeclaredPurityName),
words("indicator."), nl],
(
ActualPurity = purity_pure,
Pieces2 = [words("No purity indicator is necessary."), nl]
;
( ActualPurity = purity_impure
; ActualPurity = purity_semipure
),
Pieces2 = [words("A purity indicator of"), quote(ActualPurityName),
words("is sufficient."), nl]
),
Msg = simple_msg(Context, [always(Pieces1), always(Pieces2)]),
Spec = error_spec(severity_warning, phase_purity_check, [Msg]).
:- func report_error_closure_purity(prog_context, purity, purity) = error_spec.
report_error_closure_purity(Context, _DeclaredPurity, ActualPurity) = Spec :-
purity_name(ActualPurity, ActualPurityName),
Pieces = [words("Purity error in closure: closure body is"),
fixed(ActualPurityName), suffix(","),
words("but closure was not declared"),
fixed(ActualPurityName), suffix("."), nl],
Msg = simple_msg(Context, [always(Pieces)]),
Spec = error_spec(severity_error, phase_purity_check, [Msg]).
impure_unification_expr_error(Context, Purity) = Spec :-
purity_name(Purity, PurityName),
Pieces = [words("Purity error: unification with expression"),
words("was declared"), fixed(PurityName), suffix(","),
words("but expression was not a function call."), nl],
Msg = simple_msg(Context, [always(Pieces)]),
Spec = error_spec(severity_error, phase_purity_check, [Msg]).
:- func impure_parallel_conjunct_error(prog_context, purity) = error_spec.
impure_parallel_conjunct_error(Context, Purity) = Spec :-
purity_name(Purity, PurityName),
Pieces = [words("Purity error: parallel conjunct is"),
fixed(PurityName), suffix(","),
words("but parallel conjuncts must be pure or semipure."), nl],
Msg = simple_msg(Context, [always(Pieces)]),
Spec = error_spec(severity_error, phase_purity_check, [Msg]).
:- func bad_outer_var_type_error(prog_context, prog_varset, prog_var)
= error_spec.
bad_outer_var_type_error(Context, VarSet, Var) = Spec :-
Pieces = [words("The type of outer variable"),
fixed(mercury_var_to_name_only(VarSet, Var)),
words("must be either io.state or stm_builtin.stm."), nl],
Msg = simple_msg(Context, [always(Pieces)]),
Spec = error_spec(severity_error, phase_type_check, [Msg]).
:- func mismatched_outer_var_types(prog_context) = error_spec.
mismatched_outer_var_types(Context) = Spec :-
Pieces = [words("The types of the two outer variables differ."), nl],
Msg = simple_msg(Context, [always(Pieces)]),
Spec = error_spec(severity_error, phase_type_check, [Msg]).
%-----------------------------------------------------------------------------%
:- type run_post_typecheck_tasks
---> run_post_typecheck_tasks
; do_not_run_post_typecheck_tasks.
:- type converted_unify
---> have_not_converted_unify
; have_converted_unify.
:- type purity_info
---> purity_info(
% Fields not changed by purity checking.
pi_module_info :: module_info,
pi_run_post_typecheck :: run_post_typecheck_tasks,
% Fields which may be changed.
pi_requant :: need_to_requantify,
pi_converted_unify :: converted_unify,
pi_pred_info :: pred_info,
pi_vartypes :: vartypes,
pi_varset :: prog_varset,
pi_messages :: list(error_spec)
).
:- pred purity_info_add_message(error_spec::in,
purity_info::in, purity_info::out) is det.
purity_info_add_message(Spec, !Info) :-
Msgs0 = !.Info ^ pi_messages,
Msgs = [Spec | Msgs0],
!Info ^ pi_messages := Msgs.
:- pred purity_info_add_messages(list(error_spec)::in,
purity_info::in, purity_info::out) is det.
purity_info_add_messages(Specs, !Info) :-
(
Specs = []
;
Specs = [_ | _],
Msgs0 = !.Info ^ pi_messages,
Msgs = Specs ++ Msgs0,
!Info ^ pi_messages := Msgs
).
%-----------------------------------------------------------------------------%
:- end_module check_hlds.purity.
%-----------------------------------------------------------------------------%