%-----------------------------------------------------------------------------%
% Copyright (C) 1997-1999 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      : purity.m
% Authors   : pets (Peter Schachte)
% 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 module also does two final parts of type analysis:
%	- it resolves predicate overloading
%	  (perhaps it ought to also resolve function overloading,
%	  converting unifications that are function calls into
%	  HLDS call instructions, but currently that is done
%	  in polymorphism.m)
%	- it checks for unbound type variables and if there are any,
%	  it reports an error (or a warning, binding them to the type `void');
%	  similarly it checks for unsatisfied type class constraints.
%
%  These actions cannot be done until after type inference is complete,
%  so they need to be a separate "post-typecheck pass"; they are done
%  here in combination with the purity-analysis pass for efficiency reasons.
%
%  We also do elimination of double-negation in this pass.
%  It needs to be done somewhere after quantification analysis and
%  before mode analysis, and this is convenient place to do it.
%
%
%  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's declared impure, or
%	2a. It's 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
%  reodered relative to any other goals in a given conjunction; ie, 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.
%
%  XXX The current implementation doesn't handle impure functions.  The main
%      reason is that handling of nested functions is likely to get pretty 
%      confusing.  Because impure functions can't be reordered, the execution
%      order would have to be strictly innermost-first, left-to-right, and 
%      predicate arguments would always have to be evaluated before the
%      predicate call.  Implied modes are right out.  All in all, they just
%      won't be as natural as one might think at first.


:- module purity.
:- interface.

:- import_module prog_data, hlds_module, hlds_goal.
:- import_module io, bool.

% The purity type itself is defined in prog_data.m as follows:
% :- type purity	--->	pure
% 			;	(semipure)
% 			;	(impure).

%  Purity check a whole module.  Also do the post-typecheck stuff
%  described above, and eliminate double negations.
%  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 third 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(bool, module_info, bool, module_info, io__state, io__state).
:- mode puritycheck(in, in, out, out, di, uo) is det.

%  Sort of a "maximum" for impurity.
:- pred worst_purity(purity, purity, purity).
:- mode worst_purity(in, in, out) is det.

%  Compare two purities.
:- pred less_pure(purity, purity).
:- mode less_pure(in, in) is semidet.

%  Print out a purity name.
:- pred write_purity(purity, io__state, io__state).
:- mode write_purity(in, di, uo) is det.

%  Print out a purity prefix.
%  This works under the assumptions that all purity names but `pure' are prefix
%  Operators, and that we never need `pure' indicators/declarations.
:- pred write_purity_prefix(purity, io__state, io__state).
:- mode write_purity_prefix(in, di, uo) is det.

%  Get a purity name as a string.
:- pred purity_name(purity, string).
:- mode purity_name(in, out) is det.

%  Update a goal info to reflect the specified purity
:- pred add_goal_info_purity_feature(hlds_goal_info, purity, hlds_goal_info).
:- mode add_goal_info_purity_feature(in, in, out) is det.

%  Determine the purity of a goal from its hlds_goal_info.
:- pred infer_goal_info_purity(hlds_goal_info, purity).
:- mode infer_goal_info_purity(in, out) is det.

%  Check if a hlds_goal_info is for a pure goal
:- pred goal_info_is_pure(hlds_goal_info).
:- mode goal_info_is_pure(in) is semidet.

%  Check if a hlds_goal_info is for an impure goal.  Fails if the goal is
%  semipure, so this isn't the same as \+ goal_info_is_pure.
:- pred goal_info_is_impure(hlds_goal_info).
:- mode goal_info_is_impure(in) is semidet.

:- implementation.

:- import_module make_hlds, hlds_pred, prog_io_util.
:- import_module type_util, mode_util, code_util, prog_data, unify_proc.
:- import_module globals, options, mercury_to_mercury, hlds_out.
:- import_module passes_aux, typecheck, module_qual, clause_to_proc.
:- import_module inst_util, prog_out.
:- import_module post_typecheck.

:- import_module list, map, varset, term, string, require, std_util.
:- import_module assoc_list, bool, int, set.

%-----------------------------------------------------------------------------%
%				Public Predicates


puritycheck(FoundTypeError, HLDS0, PostTypecheckError, HLDS) -->
	globals__io_lookup_bool_option(statistics, Statistics),
	globals__io_lookup_bool_option(verbose, Verbose),
	io__stderr_stream(StdErr),
	io__set_output_stream(StdErr, OldStream),

	maybe_write_string(Verbose, "% Purity-checking clauses...\n"),
	check_preds_purity(FoundTypeError, HLDS0, PostTypecheckError, HLDS),
	maybe_report_stats(Statistics),

	io__set_output_stream(OldStream, _).


%  worst_purity/3 could be written more compactly, but this definition
%  guarantees us a determinism error if we add to type `purity'.  We also
%  define less_pure/2 in terms of worst_purity/3 rather than the other way
%  around for the same reason.

worst_purity(pure, pure, pure).
worst_purity(pure, (semipure), (semipure)).
worst_purity(pure, (impure), (impure)).
worst_purity((semipure), pure, (semipure)).
worst_purity((semipure), (semipure), (semipure)).
worst_purity((semipure), (impure), (impure)).
worst_purity((impure), pure, (impure)).
worst_purity((impure), (semipure), (impure)).
worst_purity((impure), (impure), (impure)).


less_pure(P1, P2) :-
	\+ worst_purity(P1, P2, P2).


add_goal_info_purity_feature(GoalInfo0, pure, GoalInfo) :-
	goal_info_remove_feature(GoalInfo0, (semipure), GoalInfo1),
	goal_info_remove_feature(GoalInfo1, (impure), GoalInfo).
add_goal_info_purity_feature(GoalInfo0, (semipure), GoalInfo) :-
	goal_info_add_feature(GoalInfo0, (semipure), GoalInfo).
add_goal_info_purity_feature(GoalInfo0, (impure), GoalInfo) :-
	goal_info_add_feature(GoalInfo0, (impure), GoalInfo).


infer_goal_info_purity(GoalInfo, Purity) :-
	(
	    goal_info_has_feature(GoalInfo, (impure)) ->
		Purity = (impure)
	;
	    goal_info_has_feature(GoalInfo, (semipure)) ->
		Purity = (semipure)
	;
		Purity = pure
	).


goal_info_is_pure(GoalInfo) :-
	\+ goal_info_has_feature(GoalInfo, (impure)),
	\+ goal_info_has_feature(GoalInfo, (semipure)).
	

goal_info_is_impure(GoalInfo) :-
	goal_info_has_feature(GoalInfo, (impure)).
	

% this works under the assumptions that all purity names but `pure' are prefix
% operators, and that we never need `pure' indicators/declarations.

write_purity_prefix(Purity) -->
	( { Purity = pure } ->
		[]
	;
		write_purity(Purity),
		io__write_string(" ")
	).

write_purity(Purity) -->
	{ purity_name(Purity, String) },
	io__write_string(String).

purity_name(pure, "pure").
purity_name((semipure), "semipure").
purity_name((impure), "impure").



%-----------------------------------------------------------------------------%
%	 Purity-check the code for all the predicates in a module

:- pred check_preds_purity(bool, module_info, bool, module_info,
			io__state, io__state).
:- mode check_preds_purity(in, in, out, out, di, uo) is det.

check_preds_purity(FoundTypeError, ModuleInfo0,
		PostTypecheckError, ModuleInfo) -->
	{ module_info_predids(ModuleInfo0, PredIds) },
	check_preds_purity_2(PredIds, FoundTypeError, ModuleInfo0,
		ModuleInfo1, 0, NumErrors, no, PostTypecheckError),
	{ module_info_num_errors(ModuleInfo1, Errs0) },
	{ Errs is Errs0 + NumErrors },
	{ module_info_set_num_errors(ModuleInfo1, Errs, ModuleInfo) }.


:- pred check_preds_purity_2(list(pred_id), bool, module_info, module_info,
			int, int, bool, bool, io__state, io__state).
:- mode check_preds_purity_2(in, in, in, out, in, out, in, out, di, uo) is det.

check_preds_purity_2([], _, ModuleInfo, ModuleInfo, NumErrors, NumErrors,
		PostTypecheckError, PostTypecheckError) --> [].
check_preds_purity_2([PredId | PredIds], FoundTypeError, ModuleInfo0,
		ModuleInfo, NumErrors0, NumErrors,
		PostTypecheckError0, PostTypecheckError) -->
	{ module_info_preds(ModuleInfo0, Preds0) },
	{ map__lookup(Preds0, PredId, PredInfo0) },
	(	
		{ pred_info_is_imported(PredInfo0)
		; pred_info_is_pseudo_imported(PredInfo0) }
	->
		post_typecheck__finish_imported_pred(ModuleInfo0, PredId,
				PredInfo0, PredInfo),
		{ NumErrors1 = NumErrors0 },
		{ PostTypecheckError1 = PostTypecheckError0 }
	;
		write_pred_progress_message("% Purity-checking ", PredId,
					    ModuleInfo0),
		%
		% Only report error messages for unbound type variables
		% if we didn't get any type errors already; this avoids
		% a lot of spurious diagnostics.
		%
		{ bool__not(FoundTypeError, ReportErrs) },
		post_typecheck__check_type_bindings(PredId, PredInfo0,
				ModuleInfo0, ReportErrs,
				PredInfo1, UnboundTypeErrsInThisPred),
		%
		% if there were any unsatisfied type class constraints,
		% then that can cause internal errors in polymorphism.m
		% if we try to continue, so we need to halt compilation
		% after this pass.
		%
		{ UnboundTypeErrsInThisPred \= 0 ->
			PostTypecheckError1 = yes
		;
			PostTypecheckError1 = PostTypecheckError0
		},
		puritycheck_pred(PredId, PredInfo1, PredInfo2, ModuleInfo0,
				PurityErrsInThisPred),
		post_typecheck__finish_pred(ModuleInfo0, PredId, PredInfo2,
				PredInfo),
		{ NumErrors1 is NumErrors0 + UnboundTypeErrsInThisPred
					   + PurityErrsInThisPred }
	),
	{ map__det_update(Preds0, PredId, PredInfo, Preds) },
	{ module_info_get_predicate_table(ModuleInfo0, PredTable0) },
	{ predicate_table_set_preds(PredTable0, Preds, PredTable) },
	{ module_info_set_predicate_table(ModuleInfo0, PredTable,
					  ModuleInfo1) },

	(
		{ pred_info_get_goal_type(PredInfo0, assertion) }
	->
		post_typecheck__finish_assertion(ModuleInfo1,
				PredId, ModuleInfo2)
	;
		{ ModuleInfo2 = ModuleInfo1 }
	),
	check_preds_purity_2(PredIds, FoundTypeError, ModuleInfo2, ModuleInfo,
				  NumErrors1, NumErrors,
				  PostTypecheckError1, PostTypecheckError).

	% Purity-check the code for single predicate, reporting any errors.

%-----------------------------------------------------------------------------%
%			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're 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, pred_info, pred_info, module_info, int,
		io__state, io__state).
:- mode puritycheck_pred(in, in, out, in, out, di, uo) is det.

puritycheck_pred(PredId, PredInfo0, PredInfo, ModuleInfo, NumErrors) -->
	{ pred_info_get_purity(PredInfo0, DeclPurity) } ,
	{ pred_info_get_promised_pure(PredInfo0, Promised) },
	( { pred_info_get_goal_type(PredInfo0, pragmas) } ->
		{ WorstPurity = (impure) },
		{ Purity = pure },
		{ PredInfo = PredInfo0 },
		{ NumErrors0 = 0 }
	;   
		{ pred_info_clauses_info(PredInfo0, ClausesInfo0) },
		{ clauses_info_clauses(ClausesInfo0, Clauses0) },
		compute_purity(Clauses0, Clauses, PredInfo0, ModuleInfo,
				pure, Purity, 0, NumErrors0),
		{ clauses_info_set_clauses(ClausesInfo0, Clauses,
				ClausesInfo) },
		{ pred_info_set_clauses_info(PredInfo0, ClausesInfo,
				PredInfo) },
		{ WorstPurity = Purity }
	),
	( { DeclPurity \= pure, Promised = yes } ->
		{ NumErrors is NumErrors0 + 1 },
		error_inconsistent_promise(ModuleInfo, PredInfo, PredId,
					  DeclPurity)
	; { less_pure(DeclPurity, WorstPurity) } ->
		{ NumErrors = NumErrors0 },
		warn_exaggerated_impurity_decl(ModuleInfo, PredInfo, PredId,
					     DeclPurity, WorstPurity)
	; { less_pure(Purity, DeclPurity), Promised = no } ->
		{ NumErrors is NumErrors0 + 1 },
		error_inferred_impure(ModuleInfo, PredInfo, PredId, Purity)
	; { Purity = pure, Promised = yes } ->
		{ NumErrors = NumErrors0 },
		warn_unnecessary_promise_pure(ModuleInfo, PredInfo, PredId)
	;
		{ NumErrors = NumErrors0 }
	).


% Infer the purity of a single (non-pragma c_code) predicate

:- pred compute_purity(list(clause), list(clause), pred_info, module_info,
	purity, purity, int, int, io__state, io__state).
:- mode compute_purity(in, out, in, in, in, out, in, out, di, uo) is det.

compute_purity([], [], _, _, Purity, Purity, NumErrors, NumErrors) -->
	[].
compute_purity([Clause0|Clauses0], [Clause|Clauses], PredInfo, ModuleInfo,
		Purity0, Purity, NumErrors0, NumErrors) -->
	{ Clause0 = clause(Ids, Body0 - Info0, Context) },
	compute_expr_purity(Body0, Body, Info0, PredInfo, ModuleInfo,
			    no, Bodypurity, NumErrors0, NumErrors1),
	{ add_goal_info_purity_feature(Info0, Bodypurity, Info) },
	{ worst_purity(Purity0, Bodypurity, Purity1) },
	{ Clause = clause(Ids, Body - Info, Context) },
	compute_purity(Clauses0, Clauses, PredInfo, ModuleInfo,
		       Purity1, Purity, NumErrors1, NumErrors).

:- pred compute_expr_purity(hlds_goal_expr, hlds_goal_expr, hlds_goal_info,
	pred_info, module_info, bool, purity, int, int, io__state, io__state).
:- mode compute_expr_purity(in, out, in, in, in, in, out, in, out, di, uo)
	is det.

compute_expr_purity(conj(Goals0), conj(Goals), _, PredInfo, ModuleInfo,
		InClosure, Purity, NumErrors0, NumErrors) -->
	compute_goals_purity(Goals0, Goals, PredInfo, ModuleInfo,
			     InClosure, pure, Purity, NumErrors0, NumErrors).
compute_expr_purity(par_conj(Goals0, SM), par_conj(Goals, SM), _, PredInfo,
		ModuleInfo, InClosure, Purity, NumErrors0, NumErrors) -->
	compute_goals_purity(Goals0, Goals, PredInfo, ModuleInfo,
			     InClosure, pure, Purity, NumErrors0, NumErrors).
compute_expr_purity(call(PredId0,ProcId,Vars,BIState,UContext,Name0),
		call(PredId,ProcId,Vars,BIState,UContext,Name), GoalInfo,
		PredInfo, ModuleInfo, InClosure, ActualPurity,
		NumErrors0, NumErrors) -->
	{ post_typecheck__resolve_pred_overloading(PredId0, Vars, PredInfo,
		ModuleInfo, Name0, Name, PredId) },
	{ module_info_preds(ModuleInfo, Preds) },
	{ map__lookup(Preds, PredId, CalleePredInfo) },
	{ pred_info_get_purity(CalleePredInfo, ActualPurity) },
	{ infer_goal_info_purity(GoalInfo, DeclaredPurity) },
	{ goal_info_get_context(GoalInfo, CallContext) },
	( { code_util__compiler_generated(PredInfo) } ->
		% Don't require purity annotations on calls in
		% compiler-generated code
		{ NumErrors = NumErrors0 }
	; { ActualPurity = DeclaredPurity } ->
		{ NumErrors = NumErrors0 }
	; { InClosure = yes } ->
		% Don't report purity errors inside closures:  the whole
		% closure is an error if it's not pure
		{ NumErrors = NumErrors0 }
	; { less_pure(ActualPurity, DeclaredPurity) } ->
		error_missing_body_impurity_decl(ModuleInfo, CalleePredInfo,
						 PredId, CallContext,
						 ActualPurity),
		{ NumErrors is NumErrors0 + 1 }
	;
		warn_unnecessary_body_impurity_decl(ModuleInfo, CalleePredInfo,
						    PredId, CallContext,
						    ActualPurity,
						    DeclaredPurity),
		{ NumErrors = NumErrors0 }
	).
compute_expr_purity(generic_call(GenericCall0, Args, Modes0, Det),
		generic_call(GenericCall, Args, Modes, Det),
		GoalInfo, PredInfo, ModuleInfo, _InClosure, Purity,
		NumErrors, NumErrors) -->
	{ Purity = pure },
	(
		{ GenericCall0 = higher_order(_, _, _) },
		{ GenericCall = GenericCall0 },
		{ Modes = Modes0 }
	;
		{ GenericCall0 = class_method(_, _, _, _) },
		{ GenericCall = GenericCall0 },
		{ Modes = Modes0 }
	;
		{ GenericCall0 = aditi_builtin(Builtin0, CallId0) },
		{ goal_info_get_context(GoalInfo, Context) },
		post_typecheck__finish_aditi_builtin(ModuleInfo, PredInfo,
			Args, Context, Builtin0, Builtin,
			CallId0, CallId, Modes),
		{ GenericCall = aditi_builtin(Builtin, CallId) }
	).
compute_expr_purity(switch(Var,Canfail,Cases0,Storemap),
		switch(Var,Canfail,Cases,Storemap), _, PredInfo,
		ModuleInfo, InClosure, Purity, NumErrors0, NumErrors) -->
	compute_cases_purity(Cases0, Cases, PredInfo, ModuleInfo,
			     InClosure, pure, Purity, NumErrors0, NumErrors).
compute_expr_purity(Unif0, Unif, GoalInfo, PredInfo, ModuleInfo, _,
		pure, NumErrors0, NumErrors) -->
	{ Unif0 = unify(A,RHS0,C,D,E) },
	{ Unif  = unify(A,RHS,C,D,E) },
	(
		{ RHS0 = lambda_goal(F, EvalMethod, FixModes, H, Vars,
			Modes0, K, Goal0 - Info0) }
	->
		{ RHS = lambda_goal(F, EvalMethod, modes_are_ok, H, Vars,
			Modes, K, Goal - Info0) },
		compute_expr_purity(Goal0, Goal, Info0, PredInfo, ModuleInfo,
				    yes, Purity, NumErrors0, NumErrors1),
		error_if_closure_impure(GoalInfo, Purity,
					NumErrors1, NumErrors),
		{
			FixModes = modes_are_ok,
			Modes = Modes0
		;
			FixModes = modes_need_fixing,
			(
				EvalMethod = normal,
				error(
	"compute_expr_purity: modes need fixing for normal lambda_goal")
			;
				EvalMethod = (aditi_top_down),
				% `aditi_top_down' predicates can't call
				% database predicates, so their `aditi__state'
				% arguments must have mode `unused'.
				% The `aditi__state's are passed even
				% though they are not used so that the
				% arguments of the closure and the
				% base relation being updated match.
				unused_mode(StateMode)
			;
				EvalMethod = (aditi_bottom_up),
				% Make sure `aditi_bottom_up' expressions have
				% a `ui' mode for their aditi_state.
				StateMode = aditi_ui_mode
			),
			pred_info_clauses_info(PredInfo, ClausesInfo),
			clauses_info_vartypes(ClausesInfo, VarTypes),
			map__apply_to_list(Vars, VarTypes, LambdaVarTypes),
			fix_aditi_state_modes(StateMode, LambdaVarTypes,
				Modes0, Modes)
		}
	;
		{ RHS = RHS0 },
		{ NumErrors = NumErrors0 }
	).
compute_expr_purity(disj(Goals0,Store), disj(Goals,Store), _, PredInfo,
		ModuleInfo, InClosure, Purity, NumErrors0, NumErrors) -->
	compute_goals_purity(Goals0, Goals, PredInfo, ModuleInfo,
			     InClosure, pure, Purity, NumErrors0, NumErrors).
compute_expr_purity(not(Goal0), NotGoal, GoalInfo0, PredInfo, ModuleInfo,
		InClosure, Purity, NumErrors0, NumErrors) -->
	%
	% eliminate double negation
	%
	{ negate_goal(Goal0, GoalInfo0, NotGoal0) },
	( { NotGoal0 = not(Goal1) - _GoalInfo1 } ->
		compute_goal_purity(Goal1, Goal, PredInfo, ModuleInfo, 
				    InClosure, Purity, NumErrors0, NumErrors),
		{ NotGoal = not(Goal) }
	;
		compute_goal_purity(NotGoal0, NotGoal1, PredInfo, ModuleInfo, 
				    InClosure, Purity, NumErrors0, NumErrors),
		{ NotGoal1 = NotGoal - _ }
	).
compute_expr_purity(some(Vars, CanRemove, Goal0), some(Vars, CanRemove, Goal),
		_, PredInfo, ModuleInfo, InClosure, Purity,
		NumErrors0, NumErrors) -->
	compute_goal_purity(Goal0, Goal, PredInfo, ModuleInfo, 
			    InClosure, Purity, NumErrors0, NumErrors).
compute_expr_purity(if_then_else(Vars,Goali0,Goalt0,Goale0,Store),
		if_then_else(Vars,Goali,Goalt,Goale,Store), _, PredInfo,
		ModuleInfo, InClosure, Purity, NumErrors0, NumErrors) -->
	compute_goal_purity(Goali0, Goali, PredInfo, ModuleInfo,
			    InClosure, Purity1, NumErrors0, NumErrors1),
	compute_goal_purity(Goalt0, Goalt, PredInfo, ModuleInfo,
			    InClosure, Purity2, NumErrors1, NumErrors2),
	compute_goal_purity(Goale0, Goale, PredInfo, ModuleInfo,
			    InClosure, Purity3, NumErrors2, NumErrors),
	{ worst_purity(Purity1, Purity2, Purity12) },
	{ worst_purity(Purity12, Purity3, Purity) }.
compute_expr_purity(Ccode, Ccode, _, _, ModuleInfo, _, Purity,
		NumErrors, NumErrors) -->
	{ Ccode = pragma_c_code(_,PredId,_,_,_,_,_) },
	{ module_info_preds(ModuleInfo, Preds) },
	{ map__lookup(Preds, PredId, PredInfo) },
	{ pred_info_get_purity(PredInfo, Purity) }.
compute_expr_purity(bi_implication(_, _), _, _, _, _, _, _, _, _) -->
	% these should have been expanded out by now
	{ error("compute_expr_purity: unexpected bi_implication") }.

:- pred compute_goal_purity(hlds_goal, hlds_goal, pred_info,
	module_info, bool, purity, int, int, io__state, io__state).
:- mode compute_goal_purity(in, out, in, in, in, out, in, out, di, uo) is det.

compute_goal_purity(Goal0 - GoalInfo0, Goal - GoalInfo, PredInfo, ModuleInfo,
		InClosure, Purity, NumErrors0, NumErrors) -->
	compute_expr_purity(Goal0, Goal, GoalInfo0, PredInfo, ModuleInfo,
			    InClosure, Purity, NumErrors0, NumErrors),
	{ add_goal_info_purity_feature(GoalInfo0, Purity, 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

:- pred compute_goals_purity(list(hlds_goal), list(hlds_goal), pred_info,
	module_info, bool, purity, purity, int, int, io__state, io__state).
:- mode compute_goals_purity(in, out, in, in, in, in, out, in, out, di, uo)
	is det.

compute_goals_purity([], [], _, _, _, Purity, Purity, NumErrors, NumErrors) -->
	[].
compute_goals_purity([Goal0|Goals0], [Goal|Goals], PredInfo, ModuleInfo,
		InClosure, Purity0, Purity, NumErrors0, NumErrors) -->
	compute_goal_purity(Goal0, Goal, PredInfo, ModuleInfo, 
			    InClosure, Purity1, NumErrors0, NumErrors1),
	{ worst_purity(Purity0, Purity1, Purity2) },
	compute_goals_purity(Goals0, Goals, PredInfo, ModuleInfo, InClosure,
			     Purity2, Purity, NumErrors1, NumErrors).



:- pred compute_cases_purity(list(case), list(case), pred_info, module_info,
	bool, purity, purity, int, int, io__state, io__state).
:- mode compute_cases_purity(in, out, in, in, in, in, out, in, out, di, uo)
	is det.

compute_cases_purity([], [], _, _, _, Purity, Purity, NumErrors, NumErrors) -->
	[].
compute_cases_purity([case(Ctor,Goal0)|Goals0], [case(Ctor,Goal)|Goals],
		PredInfo, ModuleInfo, InClosure, Purity0, Purity,
		NumErrors0, NumErrors) -->
	compute_goal_purity(Goal0, Goal, PredInfo, ModuleInfo, 
			    InClosure, Purity1, NumErrors0, NumErrors1),
	{ worst_purity(Purity0, Purity1, Purity2) },
	compute_cases_purity(Goals0, Goals, PredInfo, ModuleInfo, InClosure,
			     Purity2, Purity, NumErrors1, NumErrors).

	% Make sure lambda expressions introduced by the compiler
	% have the correct mode for their `aditi__state' arguments.
:- pred fix_aditi_state_modes((mode), list(type), list(mode), list(mode)).
:- mode fix_aditi_state_modes(in, in, in, out) is det.

fix_aditi_state_modes(_, [], [], []).
fix_aditi_state_modes(_, [_|_], [], []) :-
	error("purity:fix_aditi_state_modes").
fix_aditi_state_modes(_, [], [_|_], []) :-
	error("purity:fix_aditi_state_modes").
fix_aditi_state_modes(AditiStateMode, [Type | Types],
		[ArgMode0 | Modes0], [ArgMode | Modes]) :-
	( type_is_aditi_state(Type) ->
		ArgMode = AditiStateMode
	;
		ArgMode = ArgMode0
	),
	fix_aditi_state_modes(AditiStateMode, Types, Modes0, Modes).

%-----------------------------------------------------------------------------%
%				Print error messages


:- pred error_inconsistent_promise(module_info, pred_info, pred_id, purity,
				  io__state, io__state).
:- mode error_inconsistent_promise(in, in, in, in, di, uo) is det.

error_inconsistent_promise(ModuleInfo, PredInfo, PredId, Purity) -->
	{ pred_info_context(PredInfo, Context) },
	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
	prog_out__write_context(Context),
	report_warning("  warning: declared `"),
	write_purity(Purity),
	io__write_string("' but promised pure.\n"),
	globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
	( { VerboseErrors = yes } ->
		{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
		prog_out__write_context(Context),
		io__write_string("  A pure "),
		hlds_out__write_pred_or_func(PredOrFunc),
		io__write_string(" that invokes impure or semipure code should\n"),
		prog_out__write_context(Context),
		io__write_string(
		    "  be promised pure and should have no impurity declaration.\n"
		)
	;
		[]
	).


:- pred warn_exaggerated_impurity_decl(module_info, pred_info, pred_id,
				       purity, purity, io__state, io__state).
:- mode warn_exaggerated_impurity_decl(in, in, in, in, in, di, uo) is det.

warn_exaggerated_impurity_decl(ModuleInfo, PredInfo, PredId,
		DeclPurity, AcutalPurity) -->
	{ pred_info_context(PredInfo, Context) },
	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
	prog_out__write_context(Context),
	report_warning("  warning: declared `"),
	write_purity(DeclPurity),
	io__write_string("' but actually "),
	write_purity(AcutalPurity),
	io__write_string(".\n").

:- pred warn_unnecessary_promise_pure(module_info, pred_info, pred_id,
				  io__state, io__state).
:- mode warn_unnecessary_promise_pure(in, in, in, di, uo) is det.

warn_unnecessary_promise_pure(ModuleInfo, PredInfo, PredId) -->
	{ pred_info_context(PredInfo, Context) },
	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
	prog_out__write_context(Context),
	report_warning("  warning: unnecessary `promise_pure' pragma.\n"),
	globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
	( { VerboseErrors = yes } ->
		prog_out__write_context(Context),
		{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
		io__write_string("  This "),
		hlds_out__write_pred_or_func(PredOrFunc),
		io__write_string(
		    " does not invoke any impure or semipure code,\n"
		),
		prog_out__write_context(Context),
		io__write_string(
		    "  so there is no need for a `promise_pure' pragma.\n"
		)
	;
		[]
	).


:- pred error_inferred_impure(module_info, pred_info, pred_id, purity,
	io__state, io__state).
:- mode error_inferred_impure(in, in, in, in, di, uo) is det.

error_inferred_impure(ModuleInfo, PredInfo, PredId, Purity) -->
	{ pred_info_context(PredInfo, Context) },
	{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
	prog_out__write_context(Context),
	io__write_string("  error: "),
	hlds_out__write_pred_or_func(PredOrFunc),
	io__write_string(" is "),
	write_purity(Purity),
	io__write_string(".\n"),
	prog_out__write_context(Context),
	( { code_util__compiler_generated(PredInfo) } ->
		io__write_string("  It must be pure.\n")
	;
		io__write_string("  It must be declared `"),
		write_purity(Purity),
		io__write_string("' or promised pure.\n")
	).


:- pred error_missing_body_impurity_decl(module_info, pred_info, pred_id,
				  prog_context, purity, io__state, io__state).
:- mode error_missing_body_impurity_decl(in, in, in, in, in, di, uo) is det.

error_missing_body_impurity_decl(ModuleInfo, _, PredId, Context,
		Purity) -->
	prog_out__write_context(Context),
	io__write_string("In call to "),
	write_purity(Purity),
	io__write_string(" "),
	hlds_out__write_pred_id(ModuleInfo, PredId),
	io__write_string(":\n"),
	prog_out__write_context(Context),
	io__write_string("  error: call must be preceded by `"),
	write_purity(Purity),
	io__write_string("' indicator.\n").


:- pred warn_unnecessary_body_impurity_decl(module_info, pred_info, pred_id,
	prog_context, purity, purity, io__state, io__state).
:- mode warn_unnecessary_body_impurity_decl(in, in, in, in, in, in, di, uo)
	is det.

warn_unnecessary_body_impurity_decl(ModuleInfo, _, PredId, Context,
		ActualPurity, DeclaredPurity) -->
	prog_out__write_context(Context),
	io__write_string("In call to "),
	hlds_out__write_pred_id(ModuleInfo, PredId),
	io__write_string(":\n"),
	prog_out__write_context(Context),
	io__write_string("  warning: unnecessary `"),
	write_purity(DeclaredPurity),
	io__write_string("' indicator.\n"),
	prog_out__write_context(Context),
	( { ActualPurity = pure } ->
		io__write_string("  No purity indicator is necessary.\n")
	;
		io__write_string("  A purity indicator of `"),
		write_purity(ActualPurity),
		io__write_string("' is sufficient.\n")
	).
	

:- pred error_if_closure_impure(hlds_goal_info, purity, int, int,
	io__state, io__state).
:- mode error_if_closure_impure(in, in, in, out, di, uo) is det.

error_if_closure_impure(GoalInfo, Purity, NumErrors0, NumErrors) -->
	( { Purity = pure } ->
		{ NumErrors = NumErrors0 }
	;
		{ NumErrors is NumErrors0 + 1 },
		{ goal_info_get_context(GoalInfo, Context) },
		prog_out__write_context(Context),
		io__write_string("Error in closure: closure is "),
		write_purity(Purity),
		io__write_string(".\n"),
		globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
		( { VerboseErrors = yes } ->
			prog_out__write_context(Context),
			io__write_string("  All closures must be pure.\n")
		;   
			[]
		)
	).


:- pred write_context_and_pred_id(module_info, pred_info, pred_id,
				  io__state, io__state).
:- mode write_context_and_pred_id(in, in, in, di, uo) is det.

write_context_and_pred_id(ModuleInfo, PredInfo, PredId) -->
	{ pred_info_context(PredInfo, Context) },
	prog_out__write_context(Context),
	io__write_string("In "),
	hlds_out__write_pred_id(ModuleInfo, PredId),
	io__write_string(":\n").


%-----------------------------------------------------------------------------%
